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 collapse_matches: bool,
1073 autoindent_mode: Option<AutoindentMode>,
1074 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1075 input_enabled: bool,
1076 use_modal_editing: bool,
1077 read_only: bool,
1078 leader_id: Option<CollaboratorId>,
1079 remote_id: Option<ViewId>,
1080 pub hover_state: HoverState,
1081 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1082 gutter_hovered: bool,
1083 hovered_link_state: Option<HoveredLinkState>,
1084 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1085 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1086 active_edit_prediction: Option<EditPredictionState>,
1087 /// Used to prevent flickering as the user types while the menu is open
1088 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1089 edit_prediction_settings: EditPredictionSettings,
1090 edit_predictions_hidden_for_vim_mode: bool,
1091 show_edit_predictions_override: Option<bool>,
1092 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1093 edit_prediction_preview: EditPredictionPreview,
1094 edit_prediction_indent_conflict: bool,
1095 edit_prediction_requires_modifier_in_indent_conflict: bool,
1096 next_inlay_id: usize,
1097 next_color_inlay_id: usize,
1098 _subscriptions: Vec<Subscription>,
1099 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1100 gutter_dimensions: GutterDimensions,
1101 style: Option<EditorStyle>,
1102 text_style_refinement: Option<TextStyleRefinement>,
1103 next_editor_action_id: EditorActionId,
1104 editor_actions: Rc<
1105 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1106 >,
1107 use_autoclose: bool,
1108 use_auto_surround: bool,
1109 auto_replace_emoji_shortcode: bool,
1110 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1111 show_git_blame_gutter: bool,
1112 show_git_blame_inline: bool,
1113 show_git_blame_inline_delay_task: Option<Task<()>>,
1114 git_blame_inline_enabled: bool,
1115 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1116 serialize_dirty_buffers: bool,
1117 show_selection_menu: Option<bool>,
1118 blame: Option<Entity<GitBlame>>,
1119 blame_subscription: Option<Subscription>,
1120 custom_context_menu: Option<
1121 Box<
1122 dyn 'static
1123 + Fn(
1124 &mut Self,
1125 DisplayPoint,
1126 &mut Window,
1127 &mut Context<Self>,
1128 ) -> Option<Entity<ui::ContextMenu>>,
1129 >,
1130 >,
1131 last_bounds: Option<Bounds<Pixels>>,
1132 last_position_map: Option<Rc<PositionMap>>,
1133 expect_bounds_change: Option<Bounds<Pixels>>,
1134 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1135 tasks_update_task: Option<Task<()>>,
1136 breakpoint_store: Option<Entity<BreakpointStore>>,
1137 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1138 hovered_diff_hunk_row: Option<DisplayRow>,
1139 pull_diagnostics_task: Task<()>,
1140 in_project_search: bool,
1141 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1142 breadcrumb_header: Option<String>,
1143 focused_block: Option<FocusedBlock>,
1144 next_scroll_position: NextScrollCursorCenterTopBottom,
1145 addons: HashMap<TypeId, Box<dyn Addon>>,
1146 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1147 load_diff_task: Option<Shared<Task<()>>>,
1148 /// Whether we are temporarily displaying a diff other than git's
1149 temporary_diff_override: bool,
1150 selection_mark_mode: bool,
1151 toggle_fold_multiple_buffers: Task<()>,
1152 _scroll_cursor_center_top_bottom_task: Task<()>,
1153 serialize_selections: Task<()>,
1154 serialize_folds: Task<()>,
1155 mouse_cursor_hidden: bool,
1156 minimap: Option<Entity<Self>>,
1157 hide_mouse_mode: HideMouseMode,
1158 pub change_list: ChangeList,
1159 inline_value_cache: InlineValueCache,
1160 selection_drag_state: SelectionDragState,
1161 colors: Option<LspColorData>,
1162 post_scroll_update: Task<()>,
1163 refresh_colors_task: Task<()>,
1164 inlay_hints: Option<LspInlayHintData>,
1165 folding_newlines: Task<()>,
1166 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1167}
1168
1169fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1170 if debounce_ms > 0 {
1171 Some(Duration::from_millis(debounce_ms))
1172 } else {
1173 None
1174 }
1175}
1176
1177#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1178enum NextScrollCursorCenterTopBottom {
1179 #[default]
1180 Center,
1181 Top,
1182 Bottom,
1183}
1184
1185impl NextScrollCursorCenterTopBottom {
1186 fn next(&self) -> Self {
1187 match self {
1188 Self::Center => Self::Top,
1189 Self::Top => Self::Bottom,
1190 Self::Bottom => Self::Center,
1191 }
1192 }
1193}
1194
1195#[derive(Clone)]
1196pub struct EditorSnapshot {
1197 pub mode: EditorMode,
1198 show_gutter: bool,
1199 show_line_numbers: Option<bool>,
1200 show_git_diff_gutter: Option<bool>,
1201 show_code_actions: Option<bool>,
1202 show_runnables: Option<bool>,
1203 show_breakpoints: Option<bool>,
1204 git_blame_gutter_max_author_length: Option<usize>,
1205 pub display_snapshot: DisplaySnapshot,
1206 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1207 is_focused: bool,
1208 scroll_anchor: ScrollAnchor,
1209 ongoing_scroll: OngoingScroll,
1210 current_line_highlight: CurrentLineHighlight,
1211 gutter_hovered: bool,
1212}
1213
1214#[derive(Default, Debug, Clone, Copy)]
1215pub struct GutterDimensions {
1216 pub left_padding: Pixels,
1217 pub right_padding: Pixels,
1218 pub width: Pixels,
1219 pub margin: Pixels,
1220 pub git_blame_entries_width: Option<Pixels>,
1221}
1222
1223impl GutterDimensions {
1224 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1225 Self {
1226 margin: Self::default_gutter_margin(font_id, font_size, cx),
1227 ..Default::default()
1228 }
1229 }
1230
1231 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1232 -cx.text_system().descent(font_id, font_size)
1233 }
1234 /// The full width of the space taken up by the gutter.
1235 pub fn full_width(&self) -> Pixels {
1236 self.margin + self.width
1237 }
1238
1239 /// The width of the space reserved for the fold indicators,
1240 /// use alongside 'justify_end' and `gutter_width` to
1241 /// right align content with the line numbers
1242 pub fn fold_area_width(&self) -> Pixels {
1243 self.margin + self.right_padding
1244 }
1245}
1246
1247struct CharacterDimensions {
1248 em_width: Pixels,
1249 em_advance: Pixels,
1250 line_height: Pixels,
1251}
1252
1253#[derive(Debug)]
1254pub struct RemoteSelection {
1255 pub replica_id: ReplicaId,
1256 pub selection: Selection<Anchor>,
1257 pub cursor_shape: CursorShape,
1258 pub collaborator_id: CollaboratorId,
1259 pub line_mode: bool,
1260 pub user_name: Option<SharedString>,
1261 pub color: PlayerColor,
1262}
1263
1264#[derive(Clone, Debug)]
1265struct SelectionHistoryEntry {
1266 selections: Arc<[Selection<Anchor>]>,
1267 select_next_state: Option<SelectNextState>,
1268 select_prev_state: Option<SelectNextState>,
1269 add_selections_state: Option<AddSelectionsState>,
1270}
1271
1272#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1273enum SelectionHistoryMode {
1274 Normal,
1275 Undoing,
1276 Redoing,
1277 Skipping,
1278}
1279
1280#[derive(Clone, PartialEq, Eq, Hash)]
1281struct HoveredCursor {
1282 replica_id: ReplicaId,
1283 selection_id: usize,
1284}
1285
1286impl Default for SelectionHistoryMode {
1287 fn default() -> Self {
1288 Self::Normal
1289 }
1290}
1291
1292#[derive(Debug)]
1293/// SelectionEffects controls the side-effects of updating the selection.
1294///
1295/// The default behaviour does "what you mostly want":
1296/// - it pushes to the nav history if the cursor moved by >10 lines
1297/// - it re-triggers completion requests
1298/// - it scrolls to fit
1299///
1300/// You might want to modify these behaviours. For example when doing a "jump"
1301/// like go to definition, we always want to add to nav history; but when scrolling
1302/// in vim mode we never do.
1303///
1304/// Similarly, you might want to disable scrolling if you don't want the viewport to
1305/// move.
1306#[derive(Clone)]
1307pub struct SelectionEffects {
1308 nav_history: Option<bool>,
1309 completions: bool,
1310 scroll: Option<Autoscroll>,
1311}
1312
1313impl Default for SelectionEffects {
1314 fn default() -> Self {
1315 Self {
1316 nav_history: None,
1317 completions: true,
1318 scroll: Some(Autoscroll::fit()),
1319 }
1320 }
1321}
1322impl SelectionEffects {
1323 pub fn scroll(scroll: Autoscroll) -> Self {
1324 Self {
1325 scroll: Some(scroll),
1326 ..Default::default()
1327 }
1328 }
1329
1330 pub fn no_scroll() -> Self {
1331 Self {
1332 scroll: None,
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn completions(self, completions: bool) -> Self {
1338 Self {
1339 completions,
1340 ..self
1341 }
1342 }
1343
1344 pub fn nav_history(self, nav_history: bool) -> Self {
1345 Self {
1346 nav_history: Some(nav_history),
1347 ..self
1348 }
1349 }
1350}
1351
1352struct DeferredSelectionEffectsState {
1353 changed: bool,
1354 effects: SelectionEffects,
1355 old_cursor_position: Anchor,
1356 history_entry: SelectionHistoryEntry,
1357}
1358
1359#[derive(Default)]
1360struct SelectionHistory {
1361 #[allow(clippy::type_complexity)]
1362 selections_by_transaction:
1363 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1364 mode: SelectionHistoryMode,
1365 undo_stack: VecDeque<SelectionHistoryEntry>,
1366 redo_stack: VecDeque<SelectionHistoryEntry>,
1367}
1368
1369impl SelectionHistory {
1370 #[track_caller]
1371 fn insert_transaction(
1372 &mut self,
1373 transaction_id: TransactionId,
1374 selections: Arc<[Selection<Anchor>]>,
1375 ) {
1376 if selections.is_empty() {
1377 log::error!(
1378 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1379 std::panic::Location::caller()
1380 );
1381 return;
1382 }
1383 self.selections_by_transaction
1384 .insert(transaction_id, (selections, None));
1385 }
1386
1387 #[allow(clippy::type_complexity)]
1388 fn transaction(
1389 &self,
1390 transaction_id: TransactionId,
1391 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1392 self.selections_by_transaction.get(&transaction_id)
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction_mut(
1397 &mut self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get_mut(&transaction_id)
1401 }
1402
1403 fn push(&mut self, entry: SelectionHistoryEntry) {
1404 if !entry.selections.is_empty() {
1405 match self.mode {
1406 SelectionHistoryMode::Normal => {
1407 self.push_undo(entry);
1408 self.redo_stack.clear();
1409 }
1410 SelectionHistoryMode::Undoing => self.push_redo(entry),
1411 SelectionHistoryMode::Redoing => self.push_undo(entry),
1412 SelectionHistoryMode::Skipping => {}
1413 }
1414 }
1415 }
1416
1417 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1418 if self
1419 .undo_stack
1420 .back()
1421 .is_none_or(|e| e.selections != entry.selections)
1422 {
1423 self.undo_stack.push_back(entry);
1424 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1425 self.undo_stack.pop_front();
1426 }
1427 }
1428 }
1429
1430 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .redo_stack
1433 .back()
1434 .is_none_or(|e| e.selections != entry.selections)
1435 {
1436 self.redo_stack.push_back(entry);
1437 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.redo_stack.pop_front();
1439 }
1440 }
1441 }
1442}
1443
1444#[derive(Clone, Copy)]
1445pub struct RowHighlightOptions {
1446 pub autoscroll: bool,
1447 pub include_gutter: bool,
1448}
1449
1450impl Default for RowHighlightOptions {
1451 fn default() -> Self {
1452 Self {
1453 autoscroll: Default::default(),
1454 include_gutter: true,
1455 }
1456 }
1457}
1458
1459struct RowHighlight {
1460 index: usize,
1461 range: Range<Anchor>,
1462 color: Hsla,
1463 options: RowHighlightOptions,
1464 type_id: TypeId,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsState {
1469 groups: Vec<AddSelectionsGroup>,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsGroup {
1474 above: bool,
1475 stack: Vec<usize>,
1476}
1477
1478#[derive(Clone)]
1479struct SelectNextState {
1480 query: AhoCorasick,
1481 wordwise: bool,
1482 done: bool,
1483}
1484
1485impl std::fmt::Debug for SelectNextState {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 f.debug_struct(std::any::type_name::<Self>())
1488 .field("wordwise", &self.wordwise)
1489 .field("done", &self.done)
1490 .finish()
1491 }
1492}
1493
1494#[derive(Debug)]
1495struct AutocloseRegion {
1496 selection_id: usize,
1497 range: Range<Anchor>,
1498 pair: BracketPair,
1499}
1500
1501#[derive(Debug)]
1502struct SnippetState {
1503 ranges: Vec<Vec<Range<Anchor>>>,
1504 active_index: usize,
1505 choices: Vec<Option<Vec<String>>>,
1506}
1507
1508#[doc(hidden)]
1509pub struct RenameState {
1510 pub range: Range<Anchor>,
1511 pub old_name: Arc<str>,
1512 pub editor: Entity<Editor>,
1513 block_id: CustomBlockId,
1514}
1515
1516struct InvalidationStack<T>(Vec<T>);
1517
1518struct RegisteredEditPredictionProvider {
1519 provider: Arc<dyn EditPredictionProviderHandle>,
1520 _subscription: Subscription,
1521}
1522
1523#[derive(Debug, PartialEq, Eq)]
1524pub struct ActiveDiagnosticGroup {
1525 pub active_range: Range<Anchor>,
1526 pub active_message: String,
1527 pub group_id: usize,
1528 pub blocks: HashSet<CustomBlockId>,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532
1533pub(crate) enum ActiveDiagnostic {
1534 None,
1535 All,
1536 Group(ActiveDiagnosticGroup),
1537}
1538
1539#[derive(Serialize, Deserialize, Clone, Debug)]
1540pub struct ClipboardSelection {
1541 /// The number of bytes in this selection.
1542 pub len: usize,
1543 /// Whether this was a full-line selection.
1544 pub is_entire_line: bool,
1545 /// The indentation of the first line when this content was originally copied.
1546 pub first_line_indent: u32,
1547}
1548
1549// selections, scroll behavior, was newest selection reversed
1550type SelectSyntaxNodeHistoryState = (
1551 Box<[Selection<usize>]>,
1552 SelectSyntaxNodeScrollBehavior,
1553 bool,
1554);
1555
1556#[derive(Default)]
1557struct SelectSyntaxNodeHistory {
1558 stack: Vec<SelectSyntaxNodeHistoryState>,
1559 // disable temporarily to allow changing selections without losing the stack
1560 pub disable_clearing: bool,
1561}
1562
1563impl SelectSyntaxNodeHistory {
1564 pub fn try_clear(&mut self) {
1565 if !self.disable_clearing {
1566 self.stack.clear();
1567 }
1568 }
1569
1570 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1571 self.stack.push(selection);
1572 }
1573
1574 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1575 self.stack.pop()
1576 }
1577}
1578
1579enum SelectSyntaxNodeScrollBehavior {
1580 CursorTop,
1581 FitSelection,
1582 CursorBottom,
1583}
1584
1585#[derive(Debug)]
1586pub(crate) struct NavigationData {
1587 cursor_anchor: Anchor,
1588 cursor_position: Point,
1589 scroll_anchor: ScrollAnchor,
1590 scroll_top_row: u32,
1591}
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1594pub enum GotoDefinitionKind {
1595 Symbol,
1596 Declaration,
1597 Type,
1598 Implementation,
1599}
1600
1601pub enum FormatTarget {
1602 Buffers(HashSet<Entity<Buffer>>),
1603 Ranges(Vec<Range<MultiBufferPoint>>),
1604}
1605
1606pub(crate) struct FocusedBlock {
1607 id: BlockId,
1608 focus_handle: WeakFocusHandle,
1609}
1610
1611#[derive(Clone)]
1612enum JumpData {
1613 MultiBufferRow {
1614 row: MultiBufferRow,
1615 line_offset_from_top: u32,
1616 },
1617 MultiBufferPoint {
1618 excerpt_id: ExcerptId,
1619 position: Point,
1620 anchor: text::Anchor,
1621 line_offset_from_top: u32,
1622 },
1623}
1624
1625pub enum MultibufferSelectionMode {
1626 First,
1627 All,
1628}
1629
1630#[derive(Clone, Copy, Debug, Default)]
1631pub struct RewrapOptions {
1632 pub override_language_settings: bool,
1633 pub preserve_existing_whitespace: bool,
1634}
1635
1636impl Editor {
1637 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1638 let buffer = cx.new(|cx| Buffer::local("", cx));
1639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1640 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1641 }
1642
1643 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1644 let buffer = cx.new(|cx| Buffer::local("", cx));
1645 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1646 Self::new(EditorMode::full(), buffer, None, window, cx)
1647 }
1648
1649 pub fn auto_height(
1650 min_lines: usize,
1651 max_lines: usize,
1652 window: &mut Window,
1653 cx: &mut Context<Self>,
1654 ) -> Self {
1655 let buffer = cx.new(|cx| Buffer::local("", cx));
1656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1657 Self::new(
1658 EditorMode::AutoHeight {
1659 min_lines,
1660 max_lines: Some(max_lines),
1661 },
1662 buffer,
1663 None,
1664 window,
1665 cx,
1666 )
1667 }
1668
1669 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1670 /// The editor grows as tall as needed to fit its content.
1671 pub fn auto_height_unbounded(
1672 min_lines: usize,
1673 window: &mut Window,
1674 cx: &mut Context<Self>,
1675 ) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(
1679 EditorMode::AutoHeight {
1680 min_lines,
1681 max_lines: None,
1682 },
1683 buffer,
1684 None,
1685 window,
1686 cx,
1687 )
1688 }
1689
1690 pub fn for_buffer(
1691 buffer: Entity<Buffer>,
1692 project: Option<Entity<Project>>,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1697 Self::new(EditorMode::full(), buffer, project, window, cx)
1698 }
1699
1700 pub fn for_multibuffer(
1701 buffer: Entity<MultiBuffer>,
1702 project: Option<Entity<Project>>,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 Self::new(EditorMode::full(), buffer, project, window, cx)
1707 }
1708
1709 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1710 let mut clone = Self::new(
1711 self.mode.clone(),
1712 self.buffer.clone(),
1713 self.project.clone(),
1714 window,
1715 cx,
1716 );
1717 self.display_map.update(cx, |display_map, cx| {
1718 let snapshot = display_map.snapshot(cx);
1719 clone.display_map.update(cx, |display_map, cx| {
1720 display_map.set_state(&snapshot, cx);
1721 });
1722 });
1723 clone.folds_did_change(cx);
1724 clone.selections.clone_state(&self.selections);
1725 clone.scroll_manager.clone_state(&self.scroll_manager);
1726 clone.searchable = self.searchable;
1727 clone.read_only = self.read_only;
1728 clone
1729 }
1730
1731 pub fn new(
1732 mode: EditorMode,
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Editor::new_internal(mode, buffer, project, None, window, cx)
1739 }
1740
1741 fn new_internal(
1742 mode: EditorMode,
1743 multi_buffer: Entity<MultiBuffer>,
1744 project: Option<Entity<Project>>,
1745 display_map: Option<Entity<DisplayMap>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 debug_assert!(
1750 display_map.is_none() || mode.is_minimap(),
1751 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1752 );
1753
1754 let full_mode = mode.is_full();
1755 let is_minimap = mode.is_minimap();
1756 let diagnostics_max_severity = if full_mode {
1757 EditorSettings::get_global(cx)
1758 .diagnostics_max_severity
1759 .unwrap_or(DiagnosticSeverity::Hint)
1760 } else {
1761 DiagnosticSeverity::Off
1762 };
1763 let style = window.text_style();
1764 let font_size = style.font_size.to_pixels(window.rem_size());
1765 let editor = cx.entity().downgrade();
1766 let fold_placeholder = FoldPlaceholder {
1767 constrain_width: false,
1768 render: Arc::new(move |fold_id, fold_range, cx| {
1769 let editor = editor.clone();
1770 div()
1771 .id(fold_id)
1772 .bg(cx.theme().colors().ghost_element_background)
1773 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1774 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1775 .rounded_xs()
1776 .size_full()
1777 .cursor_pointer()
1778 .child("⋯")
1779 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1780 .on_click(move |_, _window, cx| {
1781 editor
1782 .update(cx, |editor, cx| {
1783 editor.unfold_ranges(
1784 &[fold_range.start..fold_range.end],
1785 true,
1786 false,
1787 cx,
1788 );
1789 cx.stop_propagation();
1790 })
1791 .ok();
1792 })
1793 .into_any()
1794 }),
1795 merge_adjacent: true,
1796 ..FoldPlaceholder::default()
1797 };
1798 let display_map = display_map.unwrap_or_else(|| {
1799 cx.new(|cx| {
1800 DisplayMap::new(
1801 multi_buffer.clone(),
1802 style.font(),
1803 font_size,
1804 None,
1805 FILE_HEADER_HEIGHT,
1806 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1807 fold_placeholder,
1808 diagnostics_max_severity,
1809 cx,
1810 )
1811 })
1812 });
1813
1814 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1815
1816 let blink_manager = cx.new(|cx| {
1817 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1818 if is_minimap {
1819 blink_manager.disable(cx);
1820 }
1821 blink_manager
1822 });
1823
1824 let soft_wrap_mode_override =
1825 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1826
1827 let mut project_subscriptions = Vec::new();
1828 if full_mode && let Some(project) = project.as_ref() {
1829 project_subscriptions.push(cx.subscribe_in(
1830 project,
1831 window,
1832 |editor, _, event, window, cx| match event {
1833 project::Event::RefreshCodeLens => {
1834 // we always query lens with actions, without storing them, always refreshing them
1835 }
1836 project::Event::RefreshInlayHints(server_id) => {
1837 editor.refresh_inlay_hints(
1838 InlayHintRefreshReason::RefreshRequested(*server_id),
1839 cx,
1840 );
1841 }
1842 project::Event::LanguageServerRemoved(..) => {
1843 if editor.tasks_update_task.is_none() {
1844 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1845 }
1846 editor.registered_buffers.clear();
1847 editor.register_visible_buffers(cx);
1848 }
1849 project::Event::LanguageServerAdded(..) => {
1850 if editor.tasks_update_task.is_none() {
1851 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1852 }
1853 }
1854 project::Event::SnippetEdit(id, snippet_edits) => {
1855 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1856 let focus_handle = editor.focus_handle(cx);
1857 if focus_handle.is_focused(window) {
1858 let snapshot = buffer.read(cx).snapshot();
1859 for (range, snippet) in snippet_edits {
1860 let editor_range =
1861 language::range_from_lsp(*range).to_offset(&snapshot);
1862 editor
1863 .insert_snippet(
1864 &[editor_range],
1865 snippet.clone(),
1866 window,
1867 cx,
1868 )
1869 .ok();
1870 }
1871 }
1872 }
1873 }
1874 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1875 let buffer_id = *buffer_id;
1876 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1877 editor.register_buffer(buffer_id, cx);
1878 editor.update_lsp_data(Some(buffer_id), window, cx);
1879 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1880 refresh_linked_ranges(editor, window, cx);
1881 editor.refresh_code_actions(window, cx);
1882 editor.refresh_document_highlights(cx);
1883 }
1884 }
1885
1886 project::Event::EntryRenamed(transaction) => {
1887 let Some(workspace) = editor.workspace() else {
1888 return;
1889 };
1890 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1891 else {
1892 return;
1893 };
1894 if active_editor.entity_id() == cx.entity_id() {
1895 let edited_buffers_already_open = {
1896 let other_editors: Vec<Entity<Editor>> = workspace
1897 .read(cx)
1898 .panes()
1899 .iter()
1900 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1901 .filter(|editor| editor.entity_id() != cx.entity_id())
1902 .collect();
1903
1904 transaction.0.keys().all(|buffer| {
1905 other_editors.iter().any(|editor| {
1906 let multi_buffer = editor.read(cx).buffer();
1907 multi_buffer.read(cx).is_singleton()
1908 && multi_buffer.read(cx).as_singleton().map_or(
1909 false,
1910 |singleton| {
1911 singleton.entity_id() == buffer.entity_id()
1912 },
1913 )
1914 })
1915 })
1916 };
1917
1918 if !edited_buffers_already_open {
1919 let workspace = workspace.downgrade();
1920 let transaction = transaction.clone();
1921 cx.defer_in(window, move |_, window, cx| {
1922 cx.spawn_in(window, async move |editor, cx| {
1923 Self::open_project_transaction(
1924 &editor,
1925 workspace,
1926 transaction,
1927 "Rename".to_string(),
1928 cx,
1929 )
1930 .await
1931 .ok()
1932 })
1933 .detach();
1934 });
1935 }
1936 }
1937 }
1938
1939 _ => {}
1940 },
1941 ));
1942 if let Some(task_inventory) = project
1943 .read(cx)
1944 .task_store()
1945 .read(cx)
1946 .task_inventory()
1947 .cloned()
1948 {
1949 project_subscriptions.push(cx.observe_in(
1950 &task_inventory,
1951 window,
1952 |editor, _, window, cx| {
1953 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1954 },
1955 ));
1956 };
1957
1958 project_subscriptions.push(cx.subscribe_in(
1959 &project.read(cx).breakpoint_store(),
1960 window,
1961 |editor, _, event, window, cx| match event {
1962 BreakpointStoreEvent::ClearDebugLines => {
1963 editor.clear_row_highlights::<ActiveDebugLine>();
1964 editor.refresh_inline_values(cx);
1965 }
1966 BreakpointStoreEvent::SetDebugLine => {
1967 if editor.go_to_active_debug_line(window, cx) {
1968 cx.stop_propagation();
1969 }
1970
1971 editor.refresh_inline_values(cx);
1972 }
1973 _ => {}
1974 },
1975 ));
1976 let git_store = project.read(cx).git_store().clone();
1977 let project = project.clone();
1978 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1979 if let GitStoreEvent::RepositoryAdded = event {
1980 this.load_diff_task = Some(
1981 update_uncommitted_diff_for_buffer(
1982 cx.entity(),
1983 &project,
1984 this.buffer.read(cx).all_buffers(),
1985 this.buffer.clone(),
1986 cx,
1987 )
1988 .shared(),
1989 );
1990 }
1991 }));
1992 }
1993
1994 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1995
1996 let inlay_hint_settings =
1997 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1998 let focus_handle = cx.focus_handle();
1999 if !is_minimap {
2000 cx.on_focus(&focus_handle, window, Self::handle_focus)
2001 .detach();
2002 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2003 .detach();
2004 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2005 .detach();
2006 cx.on_blur(&focus_handle, window, Self::handle_blur)
2007 .detach();
2008 cx.observe_pending_input(window, Self::observe_pending_input)
2009 .detach();
2010 }
2011
2012 let show_indent_guides =
2013 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2014 Some(false)
2015 } else {
2016 None
2017 };
2018
2019 let breakpoint_store = match (&mode, project.as_ref()) {
2020 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2021 _ => None,
2022 };
2023
2024 let mut code_action_providers = Vec::new();
2025 let mut load_uncommitted_diff = None;
2026 if let Some(project) = project.clone() {
2027 load_uncommitted_diff = Some(
2028 update_uncommitted_diff_for_buffer(
2029 cx.entity(),
2030 &project,
2031 multi_buffer.read(cx).all_buffers(),
2032 multi_buffer.clone(),
2033 cx,
2034 )
2035 .shared(),
2036 );
2037 code_action_providers.push(Rc::new(project) as Rc<_>);
2038 }
2039
2040 let mut editor = Self {
2041 focus_handle,
2042 show_cursor_when_unfocused: false,
2043 last_focused_descendant: None,
2044 buffer: multi_buffer.clone(),
2045 display_map: display_map.clone(),
2046 placeholder_display_map: None,
2047 selections,
2048 scroll_manager: ScrollManager::new(cx),
2049 columnar_selection_state: None,
2050 add_selections_state: None,
2051 select_next_state: None,
2052 select_prev_state: None,
2053 selection_history: SelectionHistory::default(),
2054 defer_selection_effects: false,
2055 deferred_selection_effects_state: None,
2056 autoclose_regions: Vec::new(),
2057 snippet_stack: InvalidationStack::default(),
2058 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2059 ime_transaction: None,
2060 active_diagnostics: ActiveDiagnostic::None,
2061 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2062 inline_diagnostics_update: Task::ready(()),
2063 inline_diagnostics: Vec::new(),
2064 soft_wrap_mode_override,
2065 diagnostics_max_severity,
2066 hard_wrap: None,
2067 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2068 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2069 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2070 project,
2071 blink_manager: blink_manager.clone(),
2072 show_local_selections: true,
2073 show_scrollbars: ScrollbarAxes {
2074 horizontal: full_mode,
2075 vertical: full_mode,
2076 },
2077 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2078 offset_content: !matches!(mode, EditorMode::SingleLine),
2079 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2080 show_gutter: full_mode,
2081 show_line_numbers: (!full_mode).then_some(false),
2082 use_relative_line_numbers: None,
2083 disable_expand_excerpt_buttons: !full_mode,
2084 show_git_diff_gutter: None,
2085 show_code_actions: None,
2086 show_runnables: None,
2087 show_breakpoints: None,
2088 show_wrap_guides: None,
2089 show_indent_guides,
2090 highlight_order: 0,
2091 highlighted_rows: HashMap::default(),
2092 background_highlights: HashMap::default(),
2093 gutter_highlights: HashMap::default(),
2094 scrollbar_marker_state: ScrollbarMarkerState::default(),
2095 active_indent_guides_state: ActiveIndentGuidesState::default(),
2096 nav_history: None,
2097 context_menu: RefCell::new(None),
2098 context_menu_options: None,
2099 mouse_context_menu: None,
2100 completion_tasks: Vec::new(),
2101 inline_blame_popover: None,
2102 inline_blame_popover_show_task: None,
2103 signature_help_state: SignatureHelpState::default(),
2104 auto_signature_help: None,
2105 find_all_references_task_sources: Vec::new(),
2106 next_completion_id: 0,
2107 next_inlay_id: 0,
2108 code_action_providers,
2109 available_code_actions: None,
2110 code_actions_task: None,
2111 quick_selection_highlight_task: None,
2112 debounced_selection_highlight_task: None,
2113 document_highlights_task: None,
2114 linked_editing_range_task: None,
2115 pending_rename: None,
2116 searchable: !is_minimap,
2117 cursor_shape: EditorSettings::get_global(cx)
2118 .cursor_shape
2119 .unwrap_or_default(),
2120 current_line_highlight: None,
2121 autoindent_mode: Some(AutoindentMode::EachLine),
2122 collapse_matches: false,
2123 workspace: None,
2124 input_enabled: !is_minimap,
2125 use_modal_editing: full_mode,
2126 read_only: is_minimap,
2127 use_autoclose: true,
2128 use_auto_surround: true,
2129 auto_replace_emoji_shortcode: false,
2130 jsx_tag_auto_close_enabled_in_any_buffer: false,
2131 leader_id: None,
2132 remote_id: None,
2133 hover_state: HoverState::default(),
2134 pending_mouse_down: None,
2135 hovered_link_state: None,
2136 edit_prediction_provider: None,
2137 active_edit_prediction: None,
2138 stale_edit_prediction_in_menu: None,
2139 edit_prediction_preview: EditPredictionPreview::Inactive {
2140 released_too_fast: false,
2141 },
2142 inline_diagnostics_enabled: full_mode,
2143 diagnostics_enabled: full_mode,
2144 word_completions_enabled: full_mode,
2145 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2146 gutter_hovered: false,
2147 pixel_position_of_newest_cursor: None,
2148 last_bounds: None,
2149 last_position_map: None,
2150 expect_bounds_change: None,
2151 gutter_dimensions: GutterDimensions::default(),
2152 style: None,
2153 show_cursor_names: false,
2154 hovered_cursors: HashMap::default(),
2155 next_editor_action_id: EditorActionId::default(),
2156 editor_actions: Rc::default(),
2157 edit_predictions_hidden_for_vim_mode: false,
2158 show_edit_predictions_override: None,
2159 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2160 edit_prediction_settings: EditPredictionSettings::Disabled,
2161 edit_prediction_indent_conflict: false,
2162 edit_prediction_requires_modifier_in_indent_conflict: true,
2163 custom_context_menu: None,
2164 show_git_blame_gutter: false,
2165 show_git_blame_inline: false,
2166 show_selection_menu: None,
2167 show_git_blame_inline_delay_task: None,
2168 git_blame_inline_enabled: full_mode
2169 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2170 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2171 serialize_dirty_buffers: !is_minimap
2172 && ProjectSettings::get_global(cx)
2173 .session
2174 .restore_unsaved_buffers,
2175 blame: None,
2176 blame_subscription: None,
2177 tasks: BTreeMap::default(),
2178
2179 breakpoint_store,
2180 gutter_breakpoint_indicator: (None, None),
2181 hovered_diff_hunk_row: None,
2182 _subscriptions: (!is_minimap)
2183 .then(|| {
2184 vec![
2185 cx.observe(&multi_buffer, Self::on_buffer_changed),
2186 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2187 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2188 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2189 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2190 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2191 cx.observe_window_activation(window, |editor, window, cx| {
2192 let active = window.is_window_active();
2193 editor.blink_manager.update(cx, |blink_manager, cx| {
2194 if active {
2195 blink_manager.enable(cx);
2196 } else {
2197 blink_manager.disable(cx);
2198 }
2199 });
2200 if active {
2201 editor.show_mouse_cursor(cx);
2202 }
2203 }),
2204 ]
2205 })
2206 .unwrap_or_default(),
2207 tasks_update_task: None,
2208 pull_diagnostics_task: Task::ready(()),
2209 colors: None,
2210 refresh_colors_task: Task::ready(()),
2211 inlay_hints: None,
2212 next_color_inlay_id: 0,
2213 post_scroll_update: Task::ready(()),
2214 linked_edit_ranges: Default::default(),
2215 in_project_search: false,
2216 previous_search_ranges: None,
2217 breadcrumb_header: None,
2218 focused_block: None,
2219 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2220 addons: HashMap::default(),
2221 registered_buffers: HashMap::default(),
2222 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2223 selection_mark_mode: false,
2224 toggle_fold_multiple_buffers: Task::ready(()),
2225 serialize_selections: Task::ready(()),
2226 serialize_folds: Task::ready(()),
2227 text_style_refinement: None,
2228 load_diff_task: load_uncommitted_diff,
2229 temporary_diff_override: false,
2230 mouse_cursor_hidden: false,
2231 minimap: None,
2232 hide_mouse_mode: EditorSettings::get_global(cx)
2233 .hide_mouse
2234 .unwrap_or_default(),
2235 change_list: ChangeList::new(),
2236 mode,
2237 selection_drag_state: SelectionDragState::None,
2238 folding_newlines: Task::ready(()),
2239 lookup_key: None,
2240 };
2241
2242 if is_minimap {
2243 return editor;
2244 }
2245
2246 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2247 editor
2248 ._subscriptions
2249 .push(cx.observe(breakpoints, |_, _, cx| {
2250 cx.notify();
2251 }));
2252 }
2253 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2254 editor._subscriptions.extend(project_subscriptions);
2255
2256 editor._subscriptions.push(cx.subscribe_in(
2257 &cx.entity(),
2258 window,
2259 |editor, _, e: &EditorEvent, window, cx| match e {
2260 EditorEvent::ScrollPositionChanged { local, .. } => {
2261 if *local {
2262 let new_anchor = editor.scroll_manager.anchor();
2263 let snapshot = editor.snapshot(window, cx);
2264 editor.update_restoration_data(cx, move |data| {
2265 data.scroll_position = (
2266 new_anchor.top_row(snapshot.buffer_snapshot()),
2267 new_anchor.offset,
2268 );
2269 });
2270 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2271 editor.inline_blame_popover.take();
2272 }
2273 }
2274 EditorEvent::Edited { .. } => {
2275 if !vim_enabled(cx) {
2276 let display_map = editor.display_snapshot(cx);
2277 let selections = editor.selections.all_adjusted_display(&display_map);
2278 let pop_state = editor
2279 .change_list
2280 .last()
2281 .map(|previous| {
2282 previous.len() == selections.len()
2283 && previous.iter().enumerate().all(|(ix, p)| {
2284 p.to_display_point(&display_map).row()
2285 == selections[ix].head().row()
2286 })
2287 })
2288 .unwrap_or(false);
2289 let new_positions = selections
2290 .into_iter()
2291 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2292 .collect();
2293 editor
2294 .change_list
2295 .push_to_change_list(pop_state, new_positions);
2296 }
2297 }
2298 _ => (),
2299 },
2300 ));
2301
2302 if let Some(dap_store) = editor
2303 .project
2304 .as_ref()
2305 .map(|project| project.read(cx).dap_store())
2306 {
2307 let weak_editor = cx.weak_entity();
2308
2309 editor
2310 ._subscriptions
2311 .push(
2312 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2313 let session_entity = cx.entity();
2314 weak_editor
2315 .update(cx, |editor, cx| {
2316 editor._subscriptions.push(
2317 cx.subscribe(&session_entity, Self::on_debug_session_event),
2318 );
2319 })
2320 .ok();
2321 }),
2322 );
2323
2324 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2325 editor
2326 ._subscriptions
2327 .push(cx.subscribe(&session, Self::on_debug_session_event));
2328 }
2329 }
2330
2331 // skip adding the initial selection to selection history
2332 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2333 editor.end_selection(window, cx);
2334 editor.selection_history.mode = SelectionHistoryMode::Normal;
2335
2336 editor.scroll_manager.show_scrollbars(window, cx);
2337 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2338
2339 if full_mode {
2340 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2341 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2342
2343 if editor.git_blame_inline_enabled {
2344 editor.start_git_blame_inline(false, window, cx);
2345 }
2346
2347 editor.go_to_active_debug_line(window, cx);
2348
2349 editor.minimap =
2350 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2351 editor.colors = Some(LspColorData::new(cx));
2352 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2353
2354 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2355 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2356 }
2357 editor.update_lsp_data(None, window, cx);
2358 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2359 }
2360
2361 editor
2362 }
2363
2364 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2365 self.selections.display_map(cx)
2366 }
2367
2368 pub fn deploy_mouse_context_menu(
2369 &mut self,
2370 position: gpui::Point<Pixels>,
2371 context_menu: Entity<ContextMenu>,
2372 window: &mut Window,
2373 cx: &mut Context<Self>,
2374 ) {
2375 self.mouse_context_menu = Some(MouseContextMenu::new(
2376 self,
2377 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2378 context_menu,
2379 window,
2380 cx,
2381 ));
2382 }
2383
2384 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2385 self.mouse_context_menu
2386 .as_ref()
2387 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2388 }
2389
2390 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2391 if self
2392 .selections
2393 .pending_anchor()
2394 .is_some_and(|pending_selection| {
2395 let snapshot = self.buffer().read(cx).snapshot(cx);
2396 pending_selection.range().includes(range, &snapshot)
2397 })
2398 {
2399 return true;
2400 }
2401
2402 self.selections
2403 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2404 .into_iter()
2405 .any(|selection| {
2406 // This is needed to cover a corner case, if we just check for an existing
2407 // selection in the fold range, having a cursor at the start of the fold
2408 // marks it as selected. Non-empty selections don't cause this.
2409 let length = selection.end - selection.start;
2410 length > 0
2411 })
2412 }
2413
2414 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2415 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2416 }
2417
2418 fn key_context_internal(
2419 &self,
2420 has_active_edit_prediction: bool,
2421 window: &mut Window,
2422 cx: &mut App,
2423 ) -> KeyContext {
2424 let mut key_context = KeyContext::new_with_defaults();
2425 key_context.add("Editor");
2426 let mode = match self.mode {
2427 EditorMode::SingleLine => "single_line",
2428 EditorMode::AutoHeight { .. } => "auto_height",
2429 EditorMode::Minimap { .. } => "minimap",
2430 EditorMode::Full { .. } => "full",
2431 };
2432
2433 if EditorSettings::jupyter_enabled(cx) {
2434 key_context.add("jupyter");
2435 }
2436
2437 key_context.set("mode", mode);
2438 if self.pending_rename.is_some() {
2439 key_context.add("renaming");
2440 }
2441
2442 if !self.snippet_stack.is_empty() {
2443 key_context.add("in_snippet");
2444 }
2445
2446 match self.context_menu.borrow().as_ref() {
2447 Some(CodeContextMenu::Completions(menu)) => {
2448 if menu.visible() {
2449 key_context.add("menu");
2450 key_context.add("showing_completions");
2451 }
2452 }
2453 Some(CodeContextMenu::CodeActions(menu)) => {
2454 if menu.visible() {
2455 key_context.add("menu");
2456 key_context.add("showing_code_actions")
2457 }
2458 }
2459 None => {}
2460 }
2461
2462 if self.signature_help_state.has_multiple_signatures() {
2463 key_context.add("showing_signature_help");
2464 }
2465
2466 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2467 if !self.focus_handle(cx).contains_focused(window, cx)
2468 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2469 {
2470 for addon in self.addons.values() {
2471 addon.extend_key_context(&mut key_context, cx)
2472 }
2473 }
2474
2475 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2476 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2477 Some(
2478 file.full_path(cx)
2479 .extension()?
2480 .to_string_lossy()
2481 .into_owned(),
2482 )
2483 }) {
2484 key_context.set("extension", extension);
2485 }
2486 } else {
2487 key_context.add("multibuffer");
2488 }
2489
2490 if has_active_edit_prediction {
2491 if self.edit_prediction_in_conflict() {
2492 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2493 } else {
2494 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2495 key_context.add("copilot_suggestion");
2496 }
2497 }
2498
2499 if self.selection_mark_mode {
2500 key_context.add("selection_mode");
2501 }
2502
2503 let disjoint = self.selections.disjoint_anchors();
2504 let snapshot = self.snapshot(window, cx);
2505 let snapshot = snapshot.buffer_snapshot();
2506 if self.mode == EditorMode::SingleLine
2507 && let [selection] = disjoint
2508 && selection.start == selection.end
2509 && selection.end.to_offset(snapshot) == snapshot.len()
2510 {
2511 key_context.add("end_of_input");
2512 }
2513
2514 key_context
2515 }
2516
2517 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2518 self.last_bounds.as_ref()
2519 }
2520
2521 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2522 if self.mouse_cursor_hidden {
2523 self.mouse_cursor_hidden = false;
2524 cx.notify();
2525 }
2526 }
2527
2528 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2529 let hide_mouse_cursor = match origin {
2530 HideMouseCursorOrigin::TypingAction => {
2531 matches!(
2532 self.hide_mouse_mode,
2533 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2534 )
2535 }
2536 HideMouseCursorOrigin::MovementAction => {
2537 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2538 }
2539 };
2540 if self.mouse_cursor_hidden != hide_mouse_cursor {
2541 self.mouse_cursor_hidden = hide_mouse_cursor;
2542 cx.notify();
2543 }
2544 }
2545
2546 pub fn edit_prediction_in_conflict(&self) -> bool {
2547 if !self.show_edit_predictions_in_menu() {
2548 return false;
2549 }
2550
2551 let showing_completions = self
2552 .context_menu
2553 .borrow()
2554 .as_ref()
2555 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2556
2557 showing_completions
2558 || self.edit_prediction_requires_modifier()
2559 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2560 // bindings to insert tab characters.
2561 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2562 }
2563
2564 pub fn accept_edit_prediction_keybind(
2565 &self,
2566 accept_partial: bool,
2567 window: &mut Window,
2568 cx: &mut App,
2569 ) -> AcceptEditPredictionBinding {
2570 let key_context = self.key_context_internal(true, window, cx);
2571 let in_conflict = self.edit_prediction_in_conflict();
2572
2573 let bindings = if accept_partial {
2574 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2575 } else {
2576 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2577 };
2578
2579 // TODO: if the binding contains multiple keystrokes, display all of them, not
2580 // just the first one.
2581 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2582 !in_conflict
2583 || binding
2584 .keystrokes()
2585 .first()
2586 .is_some_and(|keystroke| keystroke.modifiers().modified())
2587 }))
2588 }
2589
2590 pub fn new_file(
2591 workspace: &mut Workspace,
2592 _: &workspace::NewFile,
2593 window: &mut Window,
2594 cx: &mut Context<Workspace>,
2595 ) {
2596 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2597 "Failed to create buffer",
2598 window,
2599 cx,
2600 |e, _, _| match e.error_code() {
2601 ErrorCode::RemoteUpgradeRequired => Some(format!(
2602 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2603 e.error_tag("required").unwrap_or("the latest version")
2604 )),
2605 _ => None,
2606 },
2607 );
2608 }
2609
2610 pub fn new_in_workspace(
2611 workspace: &mut Workspace,
2612 window: &mut Window,
2613 cx: &mut Context<Workspace>,
2614 ) -> Task<Result<Entity<Editor>>> {
2615 let project = workspace.project().clone();
2616 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2617
2618 cx.spawn_in(window, async move |workspace, cx| {
2619 let buffer = create.await?;
2620 workspace.update_in(cx, |workspace, window, cx| {
2621 let editor =
2622 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2623 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2624 editor
2625 })
2626 })
2627 }
2628
2629 fn new_file_vertical(
2630 workspace: &mut Workspace,
2631 _: &workspace::NewFileSplitVertical,
2632 window: &mut Window,
2633 cx: &mut Context<Workspace>,
2634 ) {
2635 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2636 }
2637
2638 fn new_file_horizontal(
2639 workspace: &mut Workspace,
2640 _: &workspace::NewFileSplitHorizontal,
2641 window: &mut Window,
2642 cx: &mut Context<Workspace>,
2643 ) {
2644 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2645 }
2646
2647 fn new_file_split(
2648 workspace: &mut Workspace,
2649 action: &workspace::NewFileSplit,
2650 window: &mut Window,
2651 cx: &mut Context<Workspace>,
2652 ) {
2653 Self::new_file_in_direction(workspace, action.0, window, cx)
2654 }
2655
2656 fn new_file_in_direction(
2657 workspace: &mut Workspace,
2658 direction: SplitDirection,
2659 window: &mut Window,
2660 cx: &mut Context<Workspace>,
2661 ) {
2662 let project = workspace.project().clone();
2663 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2664
2665 cx.spawn_in(window, async move |workspace, cx| {
2666 let buffer = create.await?;
2667 workspace.update_in(cx, move |workspace, window, cx| {
2668 workspace.split_item(
2669 direction,
2670 Box::new(
2671 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2672 ),
2673 window,
2674 cx,
2675 )
2676 })?;
2677 anyhow::Ok(())
2678 })
2679 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2680 match e.error_code() {
2681 ErrorCode::RemoteUpgradeRequired => Some(format!(
2682 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2683 e.error_tag("required").unwrap_or("the latest version")
2684 )),
2685 _ => None,
2686 }
2687 });
2688 }
2689
2690 pub fn leader_id(&self) -> Option<CollaboratorId> {
2691 self.leader_id
2692 }
2693
2694 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2695 &self.buffer
2696 }
2697
2698 pub fn project(&self) -> Option<&Entity<Project>> {
2699 self.project.as_ref()
2700 }
2701
2702 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2703 self.workspace.as_ref()?.0.upgrade()
2704 }
2705
2706 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2707 self.buffer().read(cx).title(cx)
2708 }
2709
2710 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2711 let git_blame_gutter_max_author_length = self
2712 .render_git_blame_gutter(cx)
2713 .then(|| {
2714 if let Some(blame) = self.blame.as_ref() {
2715 let max_author_length =
2716 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2717 Some(max_author_length)
2718 } else {
2719 None
2720 }
2721 })
2722 .flatten();
2723
2724 EditorSnapshot {
2725 mode: self.mode.clone(),
2726 show_gutter: self.show_gutter,
2727 show_line_numbers: self.show_line_numbers,
2728 show_git_diff_gutter: self.show_git_diff_gutter,
2729 show_code_actions: self.show_code_actions,
2730 show_runnables: self.show_runnables,
2731 show_breakpoints: self.show_breakpoints,
2732 git_blame_gutter_max_author_length,
2733 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2734 placeholder_display_snapshot: self
2735 .placeholder_display_map
2736 .as_ref()
2737 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2738 scroll_anchor: self.scroll_manager.anchor(),
2739 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2740 is_focused: self.focus_handle.is_focused(window),
2741 current_line_highlight: self
2742 .current_line_highlight
2743 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2744 gutter_hovered: self.gutter_hovered,
2745 }
2746 }
2747
2748 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2749 self.buffer.read(cx).language_at(point, cx)
2750 }
2751
2752 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2753 self.buffer.read(cx).read(cx).file_at(point).cloned()
2754 }
2755
2756 pub fn active_excerpt(
2757 &self,
2758 cx: &App,
2759 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2760 self.buffer
2761 .read(cx)
2762 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2763 }
2764
2765 pub fn mode(&self) -> &EditorMode {
2766 &self.mode
2767 }
2768
2769 pub fn set_mode(&mut self, mode: EditorMode) {
2770 self.mode = mode;
2771 }
2772
2773 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2774 self.collaboration_hub.as_deref()
2775 }
2776
2777 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2778 self.collaboration_hub = Some(hub);
2779 }
2780
2781 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2782 self.in_project_search = in_project_search;
2783 }
2784
2785 pub fn set_custom_context_menu(
2786 &mut self,
2787 f: impl 'static
2788 + Fn(
2789 &mut Self,
2790 DisplayPoint,
2791 &mut Window,
2792 &mut Context<Self>,
2793 ) -> Option<Entity<ui::ContextMenu>>,
2794 ) {
2795 self.custom_context_menu = Some(Box::new(f))
2796 }
2797
2798 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2799 self.completion_provider = provider;
2800 }
2801
2802 #[cfg(any(test, feature = "test-support"))]
2803 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2804 self.completion_provider.clone()
2805 }
2806
2807 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2808 self.semantics_provider.clone()
2809 }
2810
2811 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2812 self.semantics_provider = provider;
2813 }
2814
2815 pub fn set_edit_prediction_provider<T>(
2816 &mut self,
2817 provider: Option<Entity<T>>,
2818 window: &mut Window,
2819 cx: &mut Context<Self>,
2820 ) where
2821 T: EditPredictionProvider,
2822 {
2823 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2824 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2825 if this.focus_handle.is_focused(window) {
2826 this.update_visible_edit_prediction(window, cx);
2827 }
2828 }),
2829 provider: Arc::new(provider),
2830 });
2831 self.update_edit_prediction_settings(cx);
2832 self.refresh_edit_prediction(false, false, window, cx);
2833 }
2834
2835 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2836 self.placeholder_display_map
2837 .as_ref()
2838 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2839 }
2840
2841 pub fn set_placeholder_text(
2842 &mut self,
2843 placeholder_text: &str,
2844 window: &mut Window,
2845 cx: &mut Context<Self>,
2846 ) {
2847 let multibuffer = cx
2848 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2849
2850 let style = window.text_style();
2851
2852 self.placeholder_display_map = Some(cx.new(|cx| {
2853 DisplayMap::new(
2854 multibuffer,
2855 style.font(),
2856 style.font_size.to_pixels(window.rem_size()),
2857 None,
2858 FILE_HEADER_HEIGHT,
2859 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2860 Default::default(),
2861 DiagnosticSeverity::Off,
2862 cx,
2863 )
2864 }));
2865 cx.notify();
2866 }
2867
2868 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2869 self.cursor_shape = cursor_shape;
2870
2871 // Disrupt blink for immediate user feedback that the cursor shape has changed
2872 self.blink_manager.update(cx, BlinkManager::show_cursor);
2873
2874 cx.notify();
2875 }
2876
2877 pub fn set_current_line_highlight(
2878 &mut self,
2879 current_line_highlight: Option<CurrentLineHighlight>,
2880 ) {
2881 self.current_line_highlight = current_line_highlight;
2882 }
2883
2884 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2885 self.collapse_matches = collapse_matches;
2886 }
2887
2888 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2889 if self.collapse_matches {
2890 return range.start..range.start;
2891 }
2892 range.clone()
2893 }
2894
2895 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2896 if self.display_map.read(cx).clip_at_line_ends != clip {
2897 self.display_map
2898 .update(cx, |map, _| map.clip_at_line_ends = clip);
2899 }
2900 }
2901
2902 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2903 self.input_enabled = input_enabled;
2904 }
2905
2906 pub fn set_edit_predictions_hidden_for_vim_mode(
2907 &mut self,
2908 hidden: bool,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 ) {
2912 if hidden != self.edit_predictions_hidden_for_vim_mode {
2913 self.edit_predictions_hidden_for_vim_mode = hidden;
2914 if hidden {
2915 self.update_visible_edit_prediction(window, cx);
2916 } else {
2917 self.refresh_edit_prediction(true, false, window, cx);
2918 }
2919 }
2920 }
2921
2922 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2923 self.menu_edit_predictions_policy = value;
2924 }
2925
2926 pub fn set_autoindent(&mut self, autoindent: bool) {
2927 if autoindent {
2928 self.autoindent_mode = Some(AutoindentMode::EachLine);
2929 } else {
2930 self.autoindent_mode = None;
2931 }
2932 }
2933
2934 pub fn read_only(&self, cx: &App) -> bool {
2935 self.read_only || self.buffer.read(cx).read_only()
2936 }
2937
2938 pub fn set_read_only(&mut self, read_only: bool) {
2939 self.read_only = read_only;
2940 }
2941
2942 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2943 self.use_autoclose = autoclose;
2944 }
2945
2946 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2947 self.use_auto_surround = auto_surround;
2948 }
2949
2950 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2951 self.auto_replace_emoji_shortcode = auto_replace;
2952 }
2953
2954 pub fn toggle_edit_predictions(
2955 &mut self,
2956 _: &ToggleEditPrediction,
2957 window: &mut Window,
2958 cx: &mut Context<Self>,
2959 ) {
2960 if self.show_edit_predictions_override.is_some() {
2961 self.set_show_edit_predictions(None, window, cx);
2962 } else {
2963 let show_edit_predictions = !self.edit_predictions_enabled();
2964 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2965 }
2966 }
2967
2968 pub fn set_show_edit_predictions(
2969 &mut self,
2970 show_edit_predictions: Option<bool>,
2971 window: &mut Window,
2972 cx: &mut Context<Self>,
2973 ) {
2974 self.show_edit_predictions_override = show_edit_predictions;
2975 self.update_edit_prediction_settings(cx);
2976
2977 if let Some(false) = show_edit_predictions {
2978 self.discard_edit_prediction(false, cx);
2979 } else {
2980 self.refresh_edit_prediction(false, true, window, cx);
2981 }
2982 }
2983
2984 fn edit_predictions_disabled_in_scope(
2985 &self,
2986 buffer: &Entity<Buffer>,
2987 buffer_position: language::Anchor,
2988 cx: &App,
2989 ) -> bool {
2990 let snapshot = buffer.read(cx).snapshot();
2991 let settings = snapshot.settings_at(buffer_position, cx);
2992
2993 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2994 return false;
2995 };
2996
2997 scope.override_name().is_some_and(|scope_name| {
2998 settings
2999 .edit_predictions_disabled_in
3000 .iter()
3001 .any(|s| s == scope_name)
3002 })
3003 }
3004
3005 pub fn set_use_modal_editing(&mut self, to: bool) {
3006 self.use_modal_editing = to;
3007 }
3008
3009 pub fn use_modal_editing(&self) -> bool {
3010 self.use_modal_editing
3011 }
3012
3013 fn selections_did_change(
3014 &mut self,
3015 local: bool,
3016 old_cursor_position: &Anchor,
3017 effects: SelectionEffects,
3018 window: &mut Window,
3019 cx: &mut Context<Self>,
3020 ) {
3021 window.invalidate_character_coordinates();
3022
3023 // Copy selections to primary selection buffer
3024 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3025 if local {
3026 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3027 let buffer_handle = self.buffer.read(cx).read(cx);
3028
3029 let mut text = String::new();
3030 for (index, selection) in selections.iter().enumerate() {
3031 let text_for_selection = buffer_handle
3032 .text_for_range(selection.start..selection.end)
3033 .collect::<String>();
3034
3035 text.push_str(&text_for_selection);
3036 if index != selections.len() - 1 {
3037 text.push('\n');
3038 }
3039 }
3040
3041 if !text.is_empty() {
3042 cx.write_to_primary(ClipboardItem::new_string(text));
3043 }
3044 }
3045
3046 let selection_anchors = self.selections.disjoint_anchors_arc();
3047
3048 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3049 self.buffer.update(cx, |buffer, cx| {
3050 buffer.set_active_selections(
3051 &selection_anchors,
3052 self.selections.line_mode(),
3053 self.cursor_shape,
3054 cx,
3055 )
3056 });
3057 }
3058 let display_map = self
3059 .display_map
3060 .update(cx, |display_map, cx| display_map.snapshot(cx));
3061 let buffer = display_map.buffer_snapshot();
3062 if self.selections.count() == 1 {
3063 self.add_selections_state = None;
3064 }
3065 self.select_next_state = None;
3066 self.select_prev_state = None;
3067 self.select_syntax_node_history.try_clear();
3068 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3069 self.snippet_stack.invalidate(&selection_anchors, buffer);
3070 self.take_rename(false, window, cx);
3071
3072 let newest_selection = self.selections.newest_anchor();
3073 let new_cursor_position = newest_selection.head();
3074 let selection_start = newest_selection.start;
3075
3076 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3077 self.push_to_nav_history(
3078 *old_cursor_position,
3079 Some(new_cursor_position.to_point(buffer)),
3080 false,
3081 effects.nav_history == Some(true),
3082 cx,
3083 );
3084 }
3085
3086 if local {
3087 if let Some(buffer_id) = new_cursor_position.buffer_id {
3088 self.register_buffer(buffer_id, cx);
3089 }
3090
3091 let mut context_menu = self.context_menu.borrow_mut();
3092 let completion_menu = match context_menu.as_ref() {
3093 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3094 Some(CodeContextMenu::CodeActions(_)) => {
3095 *context_menu = None;
3096 None
3097 }
3098 None => None,
3099 };
3100 let completion_position = completion_menu.map(|menu| menu.initial_position);
3101 drop(context_menu);
3102
3103 if effects.completions
3104 && let Some(completion_position) = completion_position
3105 {
3106 let start_offset = selection_start.to_offset(buffer);
3107 let position_matches = start_offset == completion_position.to_offset(buffer);
3108 let continue_showing = if position_matches {
3109 if self.snippet_stack.is_empty() {
3110 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3111 == Some(CharKind::Word)
3112 } else {
3113 // Snippet choices can be shown even when the cursor is in whitespace.
3114 // Dismissing the menu with actions like backspace is handled by
3115 // invalidation regions.
3116 true
3117 }
3118 } else {
3119 false
3120 };
3121
3122 if continue_showing {
3123 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3124 } else {
3125 self.hide_context_menu(window, cx);
3126 }
3127 }
3128
3129 hide_hover(self, cx);
3130
3131 if old_cursor_position.to_display_point(&display_map).row()
3132 != new_cursor_position.to_display_point(&display_map).row()
3133 {
3134 self.available_code_actions.take();
3135 }
3136 self.refresh_code_actions(window, cx);
3137 self.refresh_document_highlights(cx);
3138 refresh_linked_ranges(self, window, cx);
3139
3140 self.refresh_selected_text_highlights(false, window, cx);
3141 self.refresh_matching_bracket_highlights(window, cx);
3142 self.update_visible_edit_prediction(window, cx);
3143 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3144 self.inline_blame_popover.take();
3145 if self.git_blame_inline_enabled {
3146 self.start_inline_blame_timer(window, cx);
3147 }
3148 }
3149
3150 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3151 cx.emit(EditorEvent::SelectionsChanged { local });
3152
3153 let selections = &self.selections.disjoint_anchors_arc();
3154 if selections.len() == 1 {
3155 cx.emit(SearchEvent::ActiveMatchChanged)
3156 }
3157 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3158 let inmemory_selections = selections
3159 .iter()
3160 .map(|s| {
3161 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3162 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3163 })
3164 .collect();
3165 self.update_restoration_data(cx, |data| {
3166 data.selections = inmemory_selections;
3167 });
3168
3169 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3170 && let Some(workspace_id) =
3171 self.workspace.as_ref().and_then(|workspace| workspace.1)
3172 {
3173 let snapshot = self.buffer().read(cx).snapshot(cx);
3174 let selections = selections.clone();
3175 let background_executor = cx.background_executor().clone();
3176 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3177 self.serialize_selections = cx.background_spawn(async move {
3178 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3179 let db_selections = selections
3180 .iter()
3181 .map(|selection| {
3182 (
3183 selection.start.to_offset(&snapshot),
3184 selection.end.to_offset(&snapshot),
3185 )
3186 })
3187 .collect();
3188
3189 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3190 .await
3191 .with_context(|| {
3192 format!(
3193 "persisting editor selections for editor {editor_id}, \
3194 workspace {workspace_id:?}"
3195 )
3196 })
3197 .log_err();
3198 });
3199 }
3200 }
3201
3202 cx.notify();
3203 }
3204
3205 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3206 use text::ToOffset as _;
3207 use text::ToPoint as _;
3208
3209 if self.mode.is_minimap()
3210 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3211 {
3212 return;
3213 }
3214
3215 if !self.buffer().read(cx).is_singleton() {
3216 return;
3217 }
3218
3219 let display_snapshot = self
3220 .display_map
3221 .update(cx, |display_map, cx| display_map.snapshot(cx));
3222 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3223 return;
3224 };
3225 let inmemory_folds = display_snapshot
3226 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3227 .map(|fold| {
3228 fold.range.start.text_anchor.to_point(&snapshot)
3229 ..fold.range.end.text_anchor.to_point(&snapshot)
3230 })
3231 .collect();
3232 self.update_restoration_data(cx, |data| {
3233 data.folds = inmemory_folds;
3234 });
3235
3236 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3237 return;
3238 };
3239 let background_executor = cx.background_executor().clone();
3240 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3241 let db_folds = display_snapshot
3242 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3243 .map(|fold| {
3244 (
3245 fold.range.start.text_anchor.to_offset(&snapshot),
3246 fold.range.end.text_anchor.to_offset(&snapshot),
3247 )
3248 })
3249 .collect();
3250 self.serialize_folds = cx.background_spawn(async move {
3251 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3252 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3253 .await
3254 .with_context(|| {
3255 format!(
3256 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3257 )
3258 })
3259 .log_err();
3260 });
3261 }
3262
3263 pub fn sync_selections(
3264 &mut self,
3265 other: Entity<Editor>,
3266 cx: &mut Context<Self>,
3267 ) -> gpui::Subscription {
3268 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3269 if !other_selections.is_empty() {
3270 self.selections.change_with(cx, |selections| {
3271 selections.select_anchors(other_selections);
3272 });
3273 }
3274
3275 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3276 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3277 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3278 if other_selections.is_empty() {
3279 return;
3280 }
3281 this.selections.change_with(cx, |selections| {
3282 selections.select_anchors(other_selections);
3283 });
3284 }
3285 });
3286
3287 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3288 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3289 let these_selections = this.selections.disjoint_anchors().to_vec();
3290 if these_selections.is_empty() {
3291 return;
3292 }
3293 other.update(cx, |other_editor, cx| {
3294 other_editor.selections.change_with(cx, |selections| {
3295 selections.select_anchors(these_selections);
3296 })
3297 });
3298 }
3299 });
3300
3301 Subscription::join(other_subscription, this_subscription)
3302 }
3303
3304 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3305 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3306 /// effects of selection change occur at the end of the transaction.
3307 pub fn change_selections<R>(
3308 &mut self,
3309 effects: SelectionEffects,
3310 window: &mut Window,
3311 cx: &mut Context<Self>,
3312 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3313 ) -> R {
3314 if let Some(state) = &mut self.deferred_selection_effects_state {
3315 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3316 state.effects.completions = effects.completions;
3317 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3318 let (changed, result) = self.selections.change_with(cx, change);
3319 state.changed |= changed;
3320 return result;
3321 }
3322 let mut state = DeferredSelectionEffectsState {
3323 changed: false,
3324 effects,
3325 old_cursor_position: self.selections.newest_anchor().head(),
3326 history_entry: SelectionHistoryEntry {
3327 selections: self.selections.disjoint_anchors_arc(),
3328 select_next_state: self.select_next_state.clone(),
3329 select_prev_state: self.select_prev_state.clone(),
3330 add_selections_state: self.add_selections_state.clone(),
3331 },
3332 };
3333 let (changed, result) = self.selections.change_with(cx, change);
3334 state.changed = state.changed || changed;
3335 if self.defer_selection_effects {
3336 self.deferred_selection_effects_state = Some(state);
3337 } else {
3338 self.apply_selection_effects(state, window, cx);
3339 }
3340 result
3341 }
3342
3343 /// Defers the effects of selection change, so that the effects of multiple calls to
3344 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3345 /// to selection history and the state of popovers based on selection position aren't
3346 /// erroneously updated.
3347 pub fn with_selection_effects_deferred<R>(
3348 &mut self,
3349 window: &mut Window,
3350 cx: &mut Context<Self>,
3351 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3352 ) -> R {
3353 let already_deferred = self.defer_selection_effects;
3354 self.defer_selection_effects = true;
3355 let result = update(self, window, cx);
3356 if !already_deferred {
3357 self.defer_selection_effects = false;
3358 if let Some(state) = self.deferred_selection_effects_state.take() {
3359 self.apply_selection_effects(state, window, cx);
3360 }
3361 }
3362 result
3363 }
3364
3365 fn apply_selection_effects(
3366 &mut self,
3367 state: DeferredSelectionEffectsState,
3368 window: &mut Window,
3369 cx: &mut Context<Self>,
3370 ) {
3371 if state.changed {
3372 self.selection_history.push(state.history_entry);
3373
3374 if let Some(autoscroll) = state.effects.scroll {
3375 self.request_autoscroll(autoscroll, cx);
3376 }
3377
3378 let old_cursor_position = &state.old_cursor_position;
3379
3380 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3381
3382 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3383 self.show_signature_help(&ShowSignatureHelp, window, cx);
3384 }
3385 }
3386 }
3387
3388 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3389 where
3390 I: IntoIterator<Item = (Range<S>, T)>,
3391 S: ToOffset,
3392 T: Into<Arc<str>>,
3393 {
3394 if self.read_only(cx) {
3395 return;
3396 }
3397
3398 self.buffer
3399 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3400 }
3401
3402 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3403 where
3404 I: IntoIterator<Item = (Range<S>, T)>,
3405 S: ToOffset,
3406 T: Into<Arc<str>>,
3407 {
3408 if self.read_only(cx) {
3409 return;
3410 }
3411
3412 self.buffer.update(cx, |buffer, cx| {
3413 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3414 });
3415 }
3416
3417 pub fn edit_with_block_indent<I, S, T>(
3418 &mut self,
3419 edits: I,
3420 original_indent_columns: Vec<Option<u32>>,
3421 cx: &mut Context<Self>,
3422 ) where
3423 I: IntoIterator<Item = (Range<S>, T)>,
3424 S: ToOffset,
3425 T: Into<Arc<str>>,
3426 {
3427 if self.read_only(cx) {
3428 return;
3429 }
3430
3431 self.buffer.update(cx, |buffer, cx| {
3432 buffer.edit(
3433 edits,
3434 Some(AutoindentMode::Block {
3435 original_indent_columns,
3436 }),
3437 cx,
3438 )
3439 });
3440 }
3441
3442 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3443 self.hide_context_menu(window, cx);
3444
3445 match phase {
3446 SelectPhase::Begin {
3447 position,
3448 add,
3449 click_count,
3450 } => self.begin_selection(position, add, click_count, window, cx),
3451 SelectPhase::BeginColumnar {
3452 position,
3453 goal_column,
3454 reset,
3455 mode,
3456 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3457 SelectPhase::Extend {
3458 position,
3459 click_count,
3460 } => self.extend_selection(position, click_count, window, cx),
3461 SelectPhase::Update {
3462 position,
3463 goal_column,
3464 scroll_delta,
3465 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3466 SelectPhase::End => self.end_selection(window, cx),
3467 }
3468 }
3469
3470 fn extend_selection(
3471 &mut self,
3472 position: DisplayPoint,
3473 click_count: usize,
3474 window: &mut Window,
3475 cx: &mut Context<Self>,
3476 ) {
3477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3478 let tail = self.selections.newest::<usize>(&display_map).tail();
3479 let click_count = click_count.max(match self.selections.select_mode() {
3480 SelectMode::Character => 1,
3481 SelectMode::Word(_) => 2,
3482 SelectMode::Line(_) => 3,
3483 SelectMode::All => 4,
3484 });
3485 self.begin_selection(position, false, click_count, window, cx);
3486
3487 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3488
3489 let current_selection = match self.selections.select_mode() {
3490 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3491 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3492 };
3493
3494 let mut pending_selection = self
3495 .selections
3496 .pending_anchor()
3497 .cloned()
3498 .expect("extend_selection not called with pending selection");
3499
3500 if pending_selection
3501 .start
3502 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3503 == Ordering::Greater
3504 {
3505 pending_selection.start = current_selection.start;
3506 }
3507 if pending_selection
3508 .end
3509 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3510 == Ordering::Less
3511 {
3512 pending_selection.end = current_selection.end;
3513 pending_selection.reversed = true;
3514 }
3515
3516 let mut pending_mode = self.selections.pending_mode().unwrap();
3517 match &mut pending_mode {
3518 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3519 _ => {}
3520 }
3521
3522 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3523 SelectionEffects::scroll(Autoscroll::fit())
3524 } else {
3525 SelectionEffects::no_scroll()
3526 };
3527
3528 self.change_selections(effects, window, cx, |s| {
3529 s.set_pending(pending_selection.clone(), pending_mode);
3530 s.set_is_extending(true);
3531 });
3532 }
3533
3534 fn begin_selection(
3535 &mut self,
3536 position: DisplayPoint,
3537 add: bool,
3538 click_count: usize,
3539 window: &mut Window,
3540 cx: &mut Context<Self>,
3541 ) {
3542 if !self.focus_handle.is_focused(window) {
3543 self.last_focused_descendant = None;
3544 window.focus(&self.focus_handle);
3545 }
3546
3547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3548 let buffer = display_map.buffer_snapshot();
3549 let position = display_map.clip_point(position, Bias::Left);
3550
3551 let start;
3552 let end;
3553 let mode;
3554 let mut auto_scroll;
3555 match click_count {
3556 1 => {
3557 start = buffer.anchor_before(position.to_point(&display_map));
3558 end = start;
3559 mode = SelectMode::Character;
3560 auto_scroll = true;
3561 }
3562 2 => {
3563 let position = display_map
3564 .clip_point(position, Bias::Left)
3565 .to_offset(&display_map, Bias::Left);
3566 let (range, _) = buffer.surrounding_word(position, None);
3567 start = buffer.anchor_before(range.start);
3568 end = buffer.anchor_before(range.end);
3569 mode = SelectMode::Word(start..end);
3570 auto_scroll = true;
3571 }
3572 3 => {
3573 let position = display_map
3574 .clip_point(position, Bias::Left)
3575 .to_point(&display_map);
3576 let line_start = display_map.prev_line_boundary(position).0;
3577 let next_line_start = buffer.clip_point(
3578 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3579 Bias::Left,
3580 );
3581 start = buffer.anchor_before(line_start);
3582 end = buffer.anchor_before(next_line_start);
3583 mode = SelectMode::Line(start..end);
3584 auto_scroll = true;
3585 }
3586 _ => {
3587 start = buffer.anchor_before(0);
3588 end = buffer.anchor_before(buffer.len());
3589 mode = SelectMode::All;
3590 auto_scroll = false;
3591 }
3592 }
3593 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3594
3595 let point_to_delete: Option<usize> = {
3596 let selected_points: Vec<Selection<Point>> =
3597 self.selections.disjoint_in_range(start..end, &display_map);
3598
3599 if !add || click_count > 1 {
3600 None
3601 } else if !selected_points.is_empty() {
3602 Some(selected_points[0].id)
3603 } else {
3604 let clicked_point_already_selected =
3605 self.selections.disjoint_anchors().iter().find(|selection| {
3606 selection.start.to_point(buffer) == start.to_point(buffer)
3607 || selection.end.to_point(buffer) == end.to_point(buffer)
3608 });
3609
3610 clicked_point_already_selected.map(|selection| selection.id)
3611 }
3612 };
3613
3614 let selections_count = self.selections.count();
3615 let effects = if auto_scroll {
3616 SelectionEffects::default()
3617 } else {
3618 SelectionEffects::no_scroll()
3619 };
3620
3621 self.change_selections(effects, window, cx, |s| {
3622 if let Some(point_to_delete) = point_to_delete {
3623 s.delete(point_to_delete);
3624
3625 if selections_count == 1 {
3626 s.set_pending_anchor_range(start..end, mode);
3627 }
3628 } else {
3629 if !add {
3630 s.clear_disjoint();
3631 }
3632
3633 s.set_pending_anchor_range(start..end, mode);
3634 }
3635 });
3636 }
3637
3638 fn begin_columnar_selection(
3639 &mut self,
3640 position: DisplayPoint,
3641 goal_column: u32,
3642 reset: bool,
3643 mode: ColumnarMode,
3644 window: &mut Window,
3645 cx: &mut Context<Self>,
3646 ) {
3647 if !self.focus_handle.is_focused(window) {
3648 self.last_focused_descendant = None;
3649 window.focus(&self.focus_handle);
3650 }
3651
3652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3653
3654 if reset {
3655 let pointer_position = display_map
3656 .buffer_snapshot()
3657 .anchor_before(position.to_point(&display_map));
3658
3659 self.change_selections(
3660 SelectionEffects::scroll(Autoscroll::newest()),
3661 window,
3662 cx,
3663 |s| {
3664 s.clear_disjoint();
3665 s.set_pending_anchor_range(
3666 pointer_position..pointer_position,
3667 SelectMode::Character,
3668 );
3669 },
3670 );
3671 };
3672
3673 let tail = self.selections.newest::<Point>(&display_map).tail();
3674 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3675 self.columnar_selection_state = match mode {
3676 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3677 selection_tail: selection_anchor,
3678 display_point: if reset {
3679 if position.column() != goal_column {
3680 Some(DisplayPoint::new(position.row(), goal_column))
3681 } else {
3682 None
3683 }
3684 } else {
3685 None
3686 },
3687 }),
3688 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3689 selection_tail: selection_anchor,
3690 }),
3691 };
3692
3693 if !reset {
3694 self.select_columns(position, goal_column, &display_map, window, cx);
3695 }
3696 }
3697
3698 fn update_selection(
3699 &mut self,
3700 position: DisplayPoint,
3701 goal_column: u32,
3702 scroll_delta: gpui::Point<f32>,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3707
3708 if self.columnar_selection_state.is_some() {
3709 self.select_columns(position, goal_column, &display_map, window, cx);
3710 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3711 let buffer = display_map.buffer_snapshot();
3712 let head;
3713 let tail;
3714 let mode = self.selections.pending_mode().unwrap();
3715 match &mode {
3716 SelectMode::Character => {
3717 head = position.to_point(&display_map);
3718 tail = pending.tail().to_point(buffer);
3719 }
3720 SelectMode::Word(original_range) => {
3721 let offset = display_map
3722 .clip_point(position, Bias::Left)
3723 .to_offset(&display_map, Bias::Left);
3724 let original_range = original_range.to_offset(buffer);
3725
3726 let head_offset = if buffer.is_inside_word(offset, None)
3727 || original_range.contains(&offset)
3728 {
3729 let (word_range, _) = buffer.surrounding_word(offset, None);
3730 if word_range.start < original_range.start {
3731 word_range.start
3732 } else {
3733 word_range.end
3734 }
3735 } else {
3736 offset
3737 };
3738
3739 head = head_offset.to_point(buffer);
3740 if head_offset <= original_range.start {
3741 tail = original_range.end.to_point(buffer);
3742 } else {
3743 tail = original_range.start.to_point(buffer);
3744 }
3745 }
3746 SelectMode::Line(original_range) => {
3747 let original_range = original_range.to_point(display_map.buffer_snapshot());
3748
3749 let position = display_map
3750 .clip_point(position, Bias::Left)
3751 .to_point(&display_map);
3752 let line_start = display_map.prev_line_boundary(position).0;
3753 let next_line_start = buffer.clip_point(
3754 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3755 Bias::Left,
3756 );
3757
3758 if line_start < original_range.start {
3759 head = line_start
3760 } else {
3761 head = next_line_start
3762 }
3763
3764 if head <= original_range.start {
3765 tail = original_range.end;
3766 } else {
3767 tail = original_range.start;
3768 }
3769 }
3770 SelectMode::All => {
3771 return;
3772 }
3773 };
3774
3775 if head < tail {
3776 pending.start = buffer.anchor_before(head);
3777 pending.end = buffer.anchor_before(tail);
3778 pending.reversed = true;
3779 } else {
3780 pending.start = buffer.anchor_before(tail);
3781 pending.end = buffer.anchor_before(head);
3782 pending.reversed = false;
3783 }
3784
3785 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3786 s.set_pending(pending.clone(), mode);
3787 });
3788 } else {
3789 log::error!("update_selection dispatched with no pending selection");
3790 return;
3791 }
3792
3793 self.apply_scroll_delta(scroll_delta, window, cx);
3794 cx.notify();
3795 }
3796
3797 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3798 self.columnar_selection_state.take();
3799 if let Some(pending_mode) = self.selections.pending_mode() {
3800 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3802 s.select(selections);
3803 s.clear_pending();
3804 if s.is_extending() {
3805 s.set_is_extending(false);
3806 } else {
3807 s.set_select_mode(pending_mode);
3808 }
3809 });
3810 }
3811 }
3812
3813 fn select_columns(
3814 &mut self,
3815 head: DisplayPoint,
3816 goal_column: u32,
3817 display_map: &DisplaySnapshot,
3818 window: &mut Window,
3819 cx: &mut Context<Self>,
3820 ) {
3821 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3822 return;
3823 };
3824
3825 let tail = match columnar_state {
3826 ColumnarSelectionState::FromMouse {
3827 selection_tail,
3828 display_point,
3829 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3830 ColumnarSelectionState::FromSelection { selection_tail } => {
3831 selection_tail.to_display_point(display_map)
3832 }
3833 };
3834
3835 let start_row = cmp::min(tail.row(), head.row());
3836 let end_row = cmp::max(tail.row(), head.row());
3837 let start_column = cmp::min(tail.column(), goal_column);
3838 let end_column = cmp::max(tail.column(), goal_column);
3839 let reversed = start_column < tail.column();
3840
3841 let selection_ranges = (start_row.0..=end_row.0)
3842 .map(DisplayRow)
3843 .filter_map(|row| {
3844 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3845 || start_column <= display_map.line_len(row))
3846 && !display_map.is_block_line(row)
3847 {
3848 let start = display_map
3849 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3850 .to_point(display_map);
3851 let end = display_map
3852 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3853 .to_point(display_map);
3854 if reversed {
3855 Some(end..start)
3856 } else {
3857 Some(start..end)
3858 }
3859 } else {
3860 None
3861 }
3862 })
3863 .collect::<Vec<_>>();
3864 if selection_ranges.is_empty() {
3865 return;
3866 }
3867
3868 let ranges = match columnar_state {
3869 ColumnarSelectionState::FromMouse { .. } => {
3870 let mut non_empty_ranges = selection_ranges
3871 .iter()
3872 .filter(|selection_range| selection_range.start != selection_range.end)
3873 .peekable();
3874 if non_empty_ranges.peek().is_some() {
3875 non_empty_ranges.cloned().collect()
3876 } else {
3877 selection_ranges
3878 }
3879 }
3880 _ => selection_ranges,
3881 };
3882
3883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3884 s.select_ranges(ranges);
3885 });
3886 cx.notify();
3887 }
3888
3889 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3890 self.selections
3891 .all_adjusted(snapshot)
3892 .iter()
3893 .any(|selection| !selection.is_empty())
3894 }
3895
3896 pub fn has_pending_nonempty_selection(&self) -> bool {
3897 let pending_nonempty_selection = match self.selections.pending_anchor() {
3898 Some(Selection { start, end, .. }) => start != end,
3899 None => false,
3900 };
3901
3902 pending_nonempty_selection
3903 || (self.columnar_selection_state.is_some()
3904 && self.selections.disjoint_anchors().len() > 1)
3905 }
3906
3907 pub fn has_pending_selection(&self) -> bool {
3908 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3909 }
3910
3911 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3912 self.selection_mark_mode = false;
3913 self.selection_drag_state = SelectionDragState::None;
3914
3915 if self.clear_expanded_diff_hunks(cx) {
3916 cx.notify();
3917 return;
3918 }
3919 if self.dismiss_menus_and_popups(true, window, cx) {
3920 return;
3921 }
3922
3923 if self.mode.is_full()
3924 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3925 {
3926 return;
3927 }
3928
3929 cx.propagate();
3930 }
3931
3932 pub fn dismiss_menus_and_popups(
3933 &mut self,
3934 is_user_requested: bool,
3935 window: &mut Window,
3936 cx: &mut Context<Self>,
3937 ) -> bool {
3938 if self.take_rename(false, window, cx).is_some() {
3939 return true;
3940 }
3941
3942 if self.hide_blame_popover(true, cx) {
3943 return true;
3944 }
3945
3946 if hide_hover(self, cx) {
3947 return true;
3948 }
3949
3950 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3951 return true;
3952 }
3953
3954 if self.hide_context_menu(window, cx).is_some() {
3955 return true;
3956 }
3957
3958 if self.mouse_context_menu.take().is_some() {
3959 return true;
3960 }
3961
3962 if is_user_requested && self.discard_edit_prediction(true, cx) {
3963 return true;
3964 }
3965
3966 if self.snippet_stack.pop().is_some() {
3967 return true;
3968 }
3969
3970 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3971 self.dismiss_diagnostics(cx);
3972 return true;
3973 }
3974
3975 false
3976 }
3977
3978 fn linked_editing_ranges_for(
3979 &self,
3980 selection: Range<text::Anchor>,
3981 cx: &App,
3982 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3983 if self.linked_edit_ranges.is_empty() {
3984 return None;
3985 }
3986 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3987 selection.end.buffer_id.and_then(|end_buffer_id| {
3988 if selection.start.buffer_id != Some(end_buffer_id) {
3989 return None;
3990 }
3991 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3992 let snapshot = buffer.read(cx).snapshot();
3993 self.linked_edit_ranges
3994 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3995 .map(|ranges| (ranges, snapshot, buffer))
3996 })?;
3997 use text::ToOffset as TO;
3998 // find offset from the start of current range to current cursor position
3999 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4000
4001 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4002 let start_difference = start_offset - start_byte_offset;
4003 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4004 let end_difference = end_offset - start_byte_offset;
4005 // Current range has associated linked ranges.
4006 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4007 for range in linked_ranges.iter() {
4008 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4009 let end_offset = start_offset + end_difference;
4010 let start_offset = start_offset + start_difference;
4011 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4012 continue;
4013 }
4014 if self.selections.disjoint_anchor_ranges().any(|s| {
4015 if s.start.buffer_id != selection.start.buffer_id
4016 || s.end.buffer_id != selection.end.buffer_id
4017 {
4018 return false;
4019 }
4020 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4021 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4022 }) {
4023 continue;
4024 }
4025 let start = buffer_snapshot.anchor_after(start_offset);
4026 let end = buffer_snapshot.anchor_after(end_offset);
4027 linked_edits
4028 .entry(buffer.clone())
4029 .or_default()
4030 .push(start..end);
4031 }
4032 Some(linked_edits)
4033 }
4034
4035 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4036 let text: Arc<str> = text.into();
4037
4038 if self.read_only(cx) {
4039 return;
4040 }
4041
4042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4043
4044 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4045 let mut bracket_inserted = false;
4046 let mut edits = Vec::new();
4047 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4048 let mut new_selections = Vec::with_capacity(selections.len());
4049 let mut new_autoclose_regions = Vec::new();
4050 let snapshot = self.buffer.read(cx).read(cx);
4051 let mut clear_linked_edit_ranges = false;
4052
4053 for (selection, autoclose_region) in
4054 self.selections_with_autoclose_regions(selections, &snapshot)
4055 {
4056 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4057 // Determine if the inserted text matches the opening or closing
4058 // bracket of any of this language's bracket pairs.
4059 let mut bracket_pair = None;
4060 let mut is_bracket_pair_start = false;
4061 let mut is_bracket_pair_end = false;
4062 if !text.is_empty() {
4063 let mut bracket_pair_matching_end = None;
4064 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4065 // and they are removing the character that triggered IME popup.
4066 for (pair, enabled) in scope.brackets() {
4067 if !pair.close && !pair.surround {
4068 continue;
4069 }
4070
4071 if enabled && pair.start.ends_with(text.as_ref()) {
4072 let prefix_len = pair.start.len() - text.len();
4073 let preceding_text_matches_prefix = prefix_len == 0
4074 || (selection.start.column >= (prefix_len as u32)
4075 && snapshot.contains_str_at(
4076 Point::new(
4077 selection.start.row,
4078 selection.start.column - (prefix_len as u32),
4079 ),
4080 &pair.start[..prefix_len],
4081 ));
4082 if preceding_text_matches_prefix {
4083 bracket_pair = Some(pair.clone());
4084 is_bracket_pair_start = true;
4085 break;
4086 }
4087 }
4088 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4089 {
4090 // take first bracket pair matching end, but don't break in case a later bracket
4091 // pair matches start
4092 bracket_pair_matching_end = Some(pair.clone());
4093 }
4094 }
4095 if let Some(end) = bracket_pair_matching_end
4096 && bracket_pair.is_none()
4097 {
4098 bracket_pair = Some(end);
4099 is_bracket_pair_end = true;
4100 }
4101 }
4102
4103 if let Some(bracket_pair) = bracket_pair {
4104 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4105 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4106 let auto_surround =
4107 self.use_auto_surround && snapshot_settings.use_auto_surround;
4108 if selection.is_empty() {
4109 if is_bracket_pair_start {
4110 // If the inserted text is a suffix of an opening bracket and the
4111 // selection is preceded by the rest of the opening bracket, then
4112 // insert the closing bracket.
4113 let following_text_allows_autoclose = snapshot
4114 .chars_at(selection.start)
4115 .next()
4116 .is_none_or(|c| scope.should_autoclose_before(c));
4117
4118 let preceding_text_allows_autoclose = selection.start.column == 0
4119 || snapshot
4120 .reversed_chars_at(selection.start)
4121 .next()
4122 .is_none_or(|c| {
4123 bracket_pair.start != bracket_pair.end
4124 || !snapshot
4125 .char_classifier_at(selection.start)
4126 .is_word(c)
4127 });
4128
4129 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4130 && bracket_pair.start.len() == 1
4131 {
4132 let target = bracket_pair.start.chars().next().unwrap();
4133 let current_line_count = snapshot
4134 .reversed_chars_at(selection.start)
4135 .take_while(|&c| c != '\n')
4136 .filter(|&c| c == target)
4137 .count();
4138 current_line_count % 2 == 1
4139 } else {
4140 false
4141 };
4142
4143 if autoclose
4144 && bracket_pair.close
4145 && following_text_allows_autoclose
4146 && preceding_text_allows_autoclose
4147 && !is_closing_quote
4148 {
4149 let anchor = snapshot.anchor_before(selection.end);
4150 new_selections.push((selection.map(|_| anchor), text.len()));
4151 new_autoclose_regions.push((
4152 anchor,
4153 text.len(),
4154 selection.id,
4155 bracket_pair.clone(),
4156 ));
4157 edits.push((
4158 selection.range(),
4159 format!("{}{}", text, bracket_pair.end).into(),
4160 ));
4161 bracket_inserted = true;
4162 continue;
4163 }
4164 }
4165
4166 if let Some(region) = autoclose_region {
4167 // If the selection is followed by an auto-inserted closing bracket,
4168 // then don't insert that closing bracket again; just move the selection
4169 // past the closing bracket.
4170 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4171 && text.as_ref() == region.pair.end.as_str()
4172 && snapshot.contains_str_at(region.range.end, text.as_ref());
4173 if should_skip {
4174 let anchor = snapshot.anchor_after(selection.end);
4175 new_selections
4176 .push((selection.map(|_| anchor), region.pair.end.len()));
4177 continue;
4178 }
4179 }
4180
4181 let always_treat_brackets_as_autoclosed = snapshot
4182 .language_settings_at(selection.start, cx)
4183 .always_treat_brackets_as_autoclosed;
4184 if always_treat_brackets_as_autoclosed
4185 && is_bracket_pair_end
4186 && snapshot.contains_str_at(selection.end, text.as_ref())
4187 {
4188 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4189 // and the inserted text is a closing bracket and the selection is followed
4190 // by the closing bracket then move the selection past the closing bracket.
4191 let anchor = snapshot.anchor_after(selection.end);
4192 new_selections.push((selection.map(|_| anchor), text.len()));
4193 continue;
4194 }
4195 }
4196 // If an opening bracket is 1 character long and is typed while
4197 // text is selected, then surround that text with the bracket pair.
4198 else if auto_surround
4199 && bracket_pair.surround
4200 && is_bracket_pair_start
4201 && bracket_pair.start.chars().count() == 1
4202 {
4203 edits.push((selection.start..selection.start, text.clone()));
4204 edits.push((
4205 selection.end..selection.end,
4206 bracket_pair.end.as_str().into(),
4207 ));
4208 bracket_inserted = true;
4209 new_selections.push((
4210 Selection {
4211 id: selection.id,
4212 start: snapshot.anchor_after(selection.start),
4213 end: snapshot.anchor_before(selection.end),
4214 reversed: selection.reversed,
4215 goal: selection.goal,
4216 },
4217 0,
4218 ));
4219 continue;
4220 }
4221 }
4222 }
4223
4224 if self.auto_replace_emoji_shortcode
4225 && selection.is_empty()
4226 && text.as_ref().ends_with(':')
4227 && let Some(possible_emoji_short_code) =
4228 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4229 && !possible_emoji_short_code.is_empty()
4230 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4231 {
4232 let emoji_shortcode_start = Point::new(
4233 selection.start.row,
4234 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4235 );
4236
4237 // Remove shortcode from buffer
4238 edits.push((
4239 emoji_shortcode_start..selection.start,
4240 "".to_string().into(),
4241 ));
4242 new_selections.push((
4243 Selection {
4244 id: selection.id,
4245 start: snapshot.anchor_after(emoji_shortcode_start),
4246 end: snapshot.anchor_before(selection.start),
4247 reversed: selection.reversed,
4248 goal: selection.goal,
4249 },
4250 0,
4251 ));
4252
4253 // Insert emoji
4254 let selection_start_anchor = snapshot.anchor_after(selection.start);
4255 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4256 edits.push((selection.start..selection.end, emoji.to_string().into()));
4257
4258 continue;
4259 }
4260
4261 // If not handling any auto-close operation, then just replace the selected
4262 // text with the given input and move the selection to the end of the
4263 // newly inserted text.
4264 let anchor = snapshot.anchor_after(selection.end);
4265 if !self.linked_edit_ranges.is_empty() {
4266 let start_anchor = snapshot.anchor_before(selection.start);
4267
4268 let is_word_char = text.chars().next().is_none_or(|char| {
4269 let classifier = snapshot
4270 .char_classifier_at(start_anchor.to_offset(&snapshot))
4271 .scope_context(Some(CharScopeContext::LinkedEdit));
4272 classifier.is_word(char)
4273 });
4274
4275 if is_word_char {
4276 if let Some(ranges) = self
4277 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4278 {
4279 for (buffer, edits) in ranges {
4280 linked_edits
4281 .entry(buffer.clone())
4282 .or_default()
4283 .extend(edits.into_iter().map(|range| (range, text.clone())));
4284 }
4285 }
4286 } else {
4287 clear_linked_edit_ranges = true;
4288 }
4289 }
4290
4291 new_selections.push((selection.map(|_| anchor), 0));
4292 edits.push((selection.start..selection.end, text.clone()));
4293 }
4294
4295 drop(snapshot);
4296
4297 self.transact(window, cx, |this, window, cx| {
4298 if clear_linked_edit_ranges {
4299 this.linked_edit_ranges.clear();
4300 }
4301 let initial_buffer_versions =
4302 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4303
4304 this.buffer.update(cx, |buffer, cx| {
4305 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4306 });
4307 for (buffer, edits) in linked_edits {
4308 buffer.update(cx, |buffer, cx| {
4309 let snapshot = buffer.snapshot();
4310 let edits = edits
4311 .into_iter()
4312 .map(|(range, text)| {
4313 use text::ToPoint as TP;
4314 let end_point = TP::to_point(&range.end, &snapshot);
4315 let start_point = TP::to_point(&range.start, &snapshot);
4316 (start_point..end_point, text)
4317 })
4318 .sorted_by_key(|(range, _)| range.start);
4319 buffer.edit(edits, None, cx);
4320 })
4321 }
4322 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4323 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4324 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4325 let new_selections =
4326 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4327 .zip(new_selection_deltas)
4328 .map(|(selection, delta)| Selection {
4329 id: selection.id,
4330 start: selection.start + delta,
4331 end: selection.end + delta,
4332 reversed: selection.reversed,
4333 goal: SelectionGoal::None,
4334 })
4335 .collect::<Vec<_>>();
4336
4337 let mut i = 0;
4338 for (position, delta, selection_id, pair) in new_autoclose_regions {
4339 let position = position.to_offset(map.buffer_snapshot()) + delta;
4340 let start = map.buffer_snapshot().anchor_before(position);
4341 let end = map.buffer_snapshot().anchor_after(position);
4342 while let Some(existing_state) = this.autoclose_regions.get(i) {
4343 match existing_state
4344 .range
4345 .start
4346 .cmp(&start, map.buffer_snapshot())
4347 {
4348 Ordering::Less => i += 1,
4349 Ordering::Greater => break,
4350 Ordering::Equal => {
4351 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4352 Ordering::Less => i += 1,
4353 Ordering::Equal => break,
4354 Ordering::Greater => break,
4355 }
4356 }
4357 }
4358 }
4359 this.autoclose_regions.insert(
4360 i,
4361 AutocloseRegion {
4362 selection_id,
4363 range: start..end,
4364 pair,
4365 },
4366 );
4367 }
4368
4369 let had_active_edit_prediction = this.has_active_edit_prediction();
4370 this.change_selections(
4371 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4372 window,
4373 cx,
4374 |s| s.select(new_selections),
4375 );
4376
4377 if !bracket_inserted
4378 && let Some(on_type_format_task) =
4379 this.trigger_on_type_formatting(text.to_string(), window, cx)
4380 {
4381 on_type_format_task.detach_and_log_err(cx);
4382 }
4383
4384 let editor_settings = EditorSettings::get_global(cx);
4385 if bracket_inserted
4386 && (editor_settings.auto_signature_help
4387 || editor_settings.show_signature_help_after_edits)
4388 {
4389 this.show_signature_help(&ShowSignatureHelp, window, cx);
4390 }
4391
4392 let trigger_in_words =
4393 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4394 if this.hard_wrap.is_some() {
4395 let latest: Range<Point> = this.selections.newest(&map).range();
4396 if latest.is_empty()
4397 && this
4398 .buffer()
4399 .read(cx)
4400 .snapshot(cx)
4401 .line_len(MultiBufferRow(latest.start.row))
4402 == latest.start.column
4403 {
4404 this.rewrap_impl(
4405 RewrapOptions {
4406 override_language_settings: true,
4407 preserve_existing_whitespace: true,
4408 },
4409 cx,
4410 )
4411 }
4412 }
4413 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4414 refresh_linked_ranges(this, window, cx);
4415 this.refresh_edit_prediction(true, false, window, cx);
4416 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4417 });
4418 }
4419
4420 fn find_possible_emoji_shortcode_at_position(
4421 snapshot: &MultiBufferSnapshot,
4422 position: Point,
4423 ) -> Option<String> {
4424 let mut chars = Vec::new();
4425 let mut found_colon = false;
4426 for char in snapshot.reversed_chars_at(position).take(100) {
4427 // Found a possible emoji shortcode in the middle of the buffer
4428 if found_colon {
4429 if char.is_whitespace() {
4430 chars.reverse();
4431 return Some(chars.iter().collect());
4432 }
4433 // If the previous character is not a whitespace, we are in the middle of a word
4434 // and we only want to complete the shortcode if the word is made up of other emojis
4435 let mut containing_word = String::new();
4436 for ch in snapshot
4437 .reversed_chars_at(position)
4438 .skip(chars.len() + 1)
4439 .take(100)
4440 {
4441 if ch.is_whitespace() {
4442 break;
4443 }
4444 containing_word.push(ch);
4445 }
4446 let containing_word = containing_word.chars().rev().collect::<String>();
4447 if util::word_consists_of_emojis(containing_word.as_str()) {
4448 chars.reverse();
4449 return Some(chars.iter().collect());
4450 }
4451 }
4452
4453 if char.is_whitespace() || !char.is_ascii() {
4454 return None;
4455 }
4456 if char == ':' {
4457 found_colon = true;
4458 } else {
4459 chars.push(char);
4460 }
4461 }
4462 // Found a possible emoji shortcode at the beginning of the buffer
4463 chars.reverse();
4464 Some(chars.iter().collect())
4465 }
4466
4467 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4469 self.transact(window, cx, |this, window, cx| {
4470 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4471 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4472 let multi_buffer = this.buffer.read(cx);
4473 let buffer = multi_buffer.snapshot(cx);
4474 selections
4475 .iter()
4476 .map(|selection| {
4477 let start_point = selection.start.to_point(&buffer);
4478 let mut existing_indent =
4479 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4480 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4481 let start = selection.start;
4482 let end = selection.end;
4483 let selection_is_empty = start == end;
4484 let language_scope = buffer.language_scope_at(start);
4485 let (
4486 comment_delimiter,
4487 doc_delimiter,
4488 insert_extra_newline,
4489 indent_on_newline,
4490 indent_on_extra_newline,
4491 ) = if let Some(language) = &language_scope {
4492 let mut insert_extra_newline =
4493 insert_extra_newline_brackets(&buffer, start..end, language)
4494 || insert_extra_newline_tree_sitter(&buffer, start..end);
4495
4496 // Comment extension on newline is allowed only for cursor selections
4497 let comment_delimiter = maybe!({
4498 if !selection_is_empty {
4499 return None;
4500 }
4501
4502 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4503 return None;
4504 }
4505
4506 let delimiters = language.line_comment_prefixes();
4507 let max_len_of_delimiter =
4508 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4509 let (snapshot, range) =
4510 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4511
4512 let num_of_whitespaces = snapshot
4513 .chars_for_range(range.clone())
4514 .take_while(|c| c.is_whitespace())
4515 .count();
4516 let comment_candidate = snapshot
4517 .chars_for_range(range.clone())
4518 .skip(num_of_whitespaces)
4519 .take(max_len_of_delimiter)
4520 .collect::<String>();
4521 let (delimiter, trimmed_len) = delimiters
4522 .iter()
4523 .filter_map(|delimiter| {
4524 let prefix = delimiter.trim_end();
4525 if comment_candidate.starts_with(prefix) {
4526 Some((delimiter, prefix.len()))
4527 } else {
4528 None
4529 }
4530 })
4531 .max_by_key(|(_, len)| *len)?;
4532
4533 if let Some(BlockCommentConfig {
4534 start: block_start, ..
4535 }) = language.block_comment()
4536 {
4537 let block_start_trimmed = block_start.trim_end();
4538 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4539 let line_content = snapshot
4540 .chars_for_range(range)
4541 .skip(num_of_whitespaces)
4542 .take(block_start_trimmed.len())
4543 .collect::<String>();
4544
4545 if line_content.starts_with(block_start_trimmed) {
4546 return None;
4547 }
4548 }
4549 }
4550
4551 let cursor_is_placed_after_comment_marker =
4552 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4553 if cursor_is_placed_after_comment_marker {
4554 Some(delimiter.clone())
4555 } else {
4556 None
4557 }
4558 });
4559
4560 let mut indent_on_newline = IndentSize::spaces(0);
4561 let mut indent_on_extra_newline = IndentSize::spaces(0);
4562
4563 let doc_delimiter = maybe!({
4564 if !selection_is_empty {
4565 return None;
4566 }
4567
4568 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4569 return None;
4570 }
4571
4572 let BlockCommentConfig {
4573 start: start_tag,
4574 end: end_tag,
4575 prefix: delimiter,
4576 tab_size: len,
4577 } = language.documentation_comment()?;
4578 let is_within_block_comment = buffer
4579 .language_scope_at(start_point)
4580 .is_some_and(|scope| scope.override_name() == Some("comment"));
4581 if !is_within_block_comment {
4582 return None;
4583 }
4584
4585 let (snapshot, range) =
4586 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4587
4588 let num_of_whitespaces = snapshot
4589 .chars_for_range(range.clone())
4590 .take_while(|c| c.is_whitespace())
4591 .count();
4592
4593 // 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.
4594 let column = start_point.column;
4595 let cursor_is_after_start_tag = {
4596 let start_tag_len = start_tag.len();
4597 let start_tag_line = snapshot
4598 .chars_for_range(range.clone())
4599 .skip(num_of_whitespaces)
4600 .take(start_tag_len)
4601 .collect::<String>();
4602 if start_tag_line.starts_with(start_tag.as_ref()) {
4603 num_of_whitespaces + start_tag_len <= column as usize
4604 } else {
4605 false
4606 }
4607 };
4608
4609 let cursor_is_after_delimiter = {
4610 let delimiter_trim = delimiter.trim_end();
4611 let delimiter_line = snapshot
4612 .chars_for_range(range.clone())
4613 .skip(num_of_whitespaces)
4614 .take(delimiter_trim.len())
4615 .collect::<String>();
4616 if delimiter_line.starts_with(delimiter_trim) {
4617 num_of_whitespaces + delimiter_trim.len() <= column as usize
4618 } else {
4619 false
4620 }
4621 };
4622
4623 let cursor_is_before_end_tag_if_exists = {
4624 let mut char_position = 0u32;
4625 let mut end_tag_offset = None;
4626
4627 'outer: for chunk in snapshot.text_for_range(range) {
4628 if let Some(byte_pos) = chunk.find(&**end_tag) {
4629 let chars_before_match =
4630 chunk[..byte_pos].chars().count() as u32;
4631 end_tag_offset =
4632 Some(char_position + chars_before_match);
4633 break 'outer;
4634 }
4635 char_position += chunk.chars().count() as u32;
4636 }
4637
4638 if let Some(end_tag_offset) = end_tag_offset {
4639 let cursor_is_before_end_tag = column <= end_tag_offset;
4640 if cursor_is_after_start_tag {
4641 if cursor_is_before_end_tag {
4642 insert_extra_newline = true;
4643 }
4644 let cursor_is_at_start_of_end_tag =
4645 column == end_tag_offset;
4646 if cursor_is_at_start_of_end_tag {
4647 indent_on_extra_newline.len = *len;
4648 }
4649 }
4650 cursor_is_before_end_tag
4651 } else {
4652 true
4653 }
4654 };
4655
4656 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4657 && cursor_is_before_end_tag_if_exists
4658 {
4659 if cursor_is_after_start_tag {
4660 indent_on_newline.len = *len;
4661 }
4662 Some(delimiter.clone())
4663 } else {
4664 None
4665 }
4666 });
4667
4668 (
4669 comment_delimiter,
4670 doc_delimiter,
4671 insert_extra_newline,
4672 indent_on_newline,
4673 indent_on_extra_newline,
4674 )
4675 } else {
4676 (
4677 None,
4678 None,
4679 false,
4680 IndentSize::default(),
4681 IndentSize::default(),
4682 )
4683 };
4684
4685 let prevent_auto_indent = doc_delimiter.is_some();
4686 let delimiter = comment_delimiter.or(doc_delimiter);
4687
4688 let capacity_for_delimiter =
4689 delimiter.as_deref().map(str::len).unwrap_or_default();
4690 let mut new_text = String::with_capacity(
4691 1 + capacity_for_delimiter
4692 + existing_indent.len as usize
4693 + indent_on_newline.len as usize
4694 + indent_on_extra_newline.len as usize,
4695 );
4696 new_text.push('\n');
4697 new_text.extend(existing_indent.chars());
4698 new_text.extend(indent_on_newline.chars());
4699
4700 if let Some(delimiter) = &delimiter {
4701 new_text.push_str(delimiter);
4702 }
4703
4704 if insert_extra_newline {
4705 new_text.push('\n');
4706 new_text.extend(existing_indent.chars());
4707 new_text.extend(indent_on_extra_newline.chars());
4708 }
4709
4710 let anchor = buffer.anchor_after(end);
4711 let new_selection = selection.map(|_| anchor);
4712 (
4713 ((start..end, new_text), prevent_auto_indent),
4714 (insert_extra_newline, new_selection),
4715 )
4716 })
4717 .unzip()
4718 };
4719
4720 let mut auto_indent_edits = Vec::new();
4721 let mut edits = Vec::new();
4722 for (edit, prevent_auto_indent) in edits_with_flags {
4723 if prevent_auto_indent {
4724 edits.push(edit);
4725 } else {
4726 auto_indent_edits.push(edit);
4727 }
4728 }
4729 if !edits.is_empty() {
4730 this.edit(edits, cx);
4731 }
4732 if !auto_indent_edits.is_empty() {
4733 this.edit_with_autoindent(auto_indent_edits, cx);
4734 }
4735
4736 let buffer = this.buffer.read(cx).snapshot(cx);
4737 let new_selections = selection_info
4738 .into_iter()
4739 .map(|(extra_newline_inserted, new_selection)| {
4740 let mut cursor = new_selection.end.to_point(&buffer);
4741 if extra_newline_inserted {
4742 cursor.row -= 1;
4743 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4744 }
4745 new_selection.map(|_| cursor)
4746 })
4747 .collect();
4748
4749 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4750 this.refresh_edit_prediction(true, false, window, cx);
4751 });
4752 }
4753
4754 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4756
4757 let buffer = self.buffer.read(cx);
4758 let snapshot = buffer.snapshot(cx);
4759
4760 let mut edits = Vec::new();
4761 let mut rows = Vec::new();
4762
4763 for (rows_inserted, selection) in self
4764 .selections
4765 .all_adjusted(&self.display_snapshot(cx))
4766 .into_iter()
4767 .enumerate()
4768 {
4769 let cursor = selection.head();
4770 let row = cursor.row;
4771
4772 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4773
4774 let newline = "\n".to_string();
4775 edits.push((start_of_line..start_of_line, newline));
4776
4777 rows.push(row + rows_inserted as u32);
4778 }
4779
4780 self.transact(window, cx, |editor, window, cx| {
4781 editor.edit(edits, cx);
4782
4783 editor.change_selections(Default::default(), window, cx, |s| {
4784 let mut index = 0;
4785 s.move_cursors_with(|map, _, _| {
4786 let row = rows[index];
4787 index += 1;
4788
4789 let point = Point::new(row, 0);
4790 let boundary = map.next_line_boundary(point).1;
4791 let clipped = map.clip_point(boundary, Bias::Left);
4792
4793 (clipped, SelectionGoal::None)
4794 });
4795 });
4796
4797 let mut indent_edits = Vec::new();
4798 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4799 for row in rows {
4800 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4801 for (row, indent) in indents {
4802 if indent.len == 0 {
4803 continue;
4804 }
4805
4806 let text = match indent.kind {
4807 IndentKind::Space => " ".repeat(indent.len as usize),
4808 IndentKind::Tab => "\t".repeat(indent.len as usize),
4809 };
4810 let point = Point::new(row.0, 0);
4811 indent_edits.push((point..point, text));
4812 }
4813 }
4814 editor.edit(indent_edits, cx);
4815 });
4816 }
4817
4818 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4820
4821 let buffer = self.buffer.read(cx);
4822 let snapshot = buffer.snapshot(cx);
4823
4824 let mut edits = Vec::new();
4825 let mut rows = Vec::new();
4826 let mut rows_inserted = 0;
4827
4828 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4829 let cursor = selection.head();
4830 let row = cursor.row;
4831
4832 let point = Point::new(row + 1, 0);
4833 let start_of_line = snapshot.clip_point(point, Bias::Left);
4834
4835 let newline = "\n".to_string();
4836 edits.push((start_of_line..start_of_line, newline));
4837
4838 rows_inserted += 1;
4839 rows.push(row + rows_inserted);
4840 }
4841
4842 self.transact(window, cx, |editor, window, cx| {
4843 editor.edit(edits, cx);
4844
4845 editor.change_selections(Default::default(), window, cx, |s| {
4846 let mut index = 0;
4847 s.move_cursors_with(|map, _, _| {
4848 let row = rows[index];
4849 index += 1;
4850
4851 let point = Point::new(row, 0);
4852 let boundary = map.next_line_boundary(point).1;
4853 let clipped = map.clip_point(boundary, Bias::Left);
4854
4855 (clipped, SelectionGoal::None)
4856 });
4857 });
4858
4859 let mut indent_edits = Vec::new();
4860 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4861 for row in rows {
4862 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4863 for (row, indent) in indents {
4864 if indent.len == 0 {
4865 continue;
4866 }
4867
4868 let text = match indent.kind {
4869 IndentKind::Space => " ".repeat(indent.len as usize),
4870 IndentKind::Tab => "\t".repeat(indent.len as usize),
4871 };
4872 let point = Point::new(row.0, 0);
4873 indent_edits.push((point..point, text));
4874 }
4875 }
4876 editor.edit(indent_edits, cx);
4877 });
4878 }
4879
4880 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4881 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4882 original_indent_columns: Vec::new(),
4883 });
4884 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4885 }
4886
4887 fn insert_with_autoindent_mode(
4888 &mut self,
4889 text: &str,
4890 autoindent_mode: Option<AutoindentMode>,
4891 window: &mut Window,
4892 cx: &mut Context<Self>,
4893 ) {
4894 if self.read_only(cx) {
4895 return;
4896 }
4897
4898 let text: Arc<str> = text.into();
4899 self.transact(window, cx, |this, window, cx| {
4900 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4901 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4902 let anchors = {
4903 let snapshot = buffer.read(cx);
4904 old_selections
4905 .iter()
4906 .map(|s| {
4907 let anchor = snapshot.anchor_after(s.head());
4908 s.map(|_| anchor)
4909 })
4910 .collect::<Vec<_>>()
4911 };
4912 buffer.edit(
4913 old_selections
4914 .iter()
4915 .map(|s| (s.start..s.end, text.clone())),
4916 autoindent_mode,
4917 cx,
4918 );
4919 anchors
4920 });
4921
4922 this.change_selections(Default::default(), window, cx, |s| {
4923 s.select_anchors(selection_anchors);
4924 });
4925
4926 cx.notify();
4927 });
4928 }
4929
4930 fn trigger_completion_on_input(
4931 &mut self,
4932 text: &str,
4933 trigger_in_words: bool,
4934 window: &mut Window,
4935 cx: &mut Context<Self>,
4936 ) {
4937 let completions_source = self
4938 .context_menu
4939 .borrow()
4940 .as_ref()
4941 .and_then(|menu| match menu {
4942 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4943 CodeContextMenu::CodeActions(_) => None,
4944 });
4945
4946 match completions_source {
4947 Some(CompletionsMenuSource::Words { .. }) => {
4948 self.open_or_update_completions_menu(
4949 Some(CompletionsMenuSource::Words {
4950 ignore_threshold: false,
4951 }),
4952 None,
4953 window,
4954 cx,
4955 );
4956 }
4957 Some(CompletionsMenuSource::Normal)
4958 | Some(CompletionsMenuSource::SnippetChoices)
4959 | None
4960 if self.is_completion_trigger(
4961 text,
4962 trigger_in_words,
4963 completions_source.is_some(),
4964 cx,
4965 ) =>
4966 {
4967 self.show_completions(
4968 &ShowCompletions {
4969 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4970 },
4971 window,
4972 cx,
4973 )
4974 }
4975 _ => {
4976 self.hide_context_menu(window, cx);
4977 }
4978 }
4979 }
4980
4981 fn is_completion_trigger(
4982 &self,
4983 text: &str,
4984 trigger_in_words: bool,
4985 menu_is_open: bool,
4986 cx: &mut Context<Self>,
4987 ) -> bool {
4988 let position = self.selections.newest_anchor().head();
4989 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4990 return false;
4991 };
4992
4993 if let Some(completion_provider) = &self.completion_provider {
4994 completion_provider.is_completion_trigger(
4995 &buffer,
4996 position.text_anchor,
4997 text,
4998 trigger_in_words,
4999 menu_is_open,
5000 cx,
5001 )
5002 } else {
5003 false
5004 }
5005 }
5006
5007 /// If any empty selections is touching the start of its innermost containing autoclose
5008 /// region, expand it to select the brackets.
5009 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5010 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5011 let buffer = self.buffer.read(cx).read(cx);
5012 let new_selections = self
5013 .selections_with_autoclose_regions(selections, &buffer)
5014 .map(|(mut selection, region)| {
5015 if !selection.is_empty() {
5016 return selection;
5017 }
5018
5019 if let Some(region) = region {
5020 let mut range = region.range.to_offset(&buffer);
5021 if selection.start == range.start && range.start >= region.pair.start.len() {
5022 range.start -= region.pair.start.len();
5023 if buffer.contains_str_at(range.start, ®ion.pair.start)
5024 && buffer.contains_str_at(range.end, ®ion.pair.end)
5025 {
5026 range.end += region.pair.end.len();
5027 selection.start = range.start;
5028 selection.end = range.end;
5029
5030 return selection;
5031 }
5032 }
5033 }
5034
5035 let always_treat_brackets_as_autoclosed = buffer
5036 .language_settings_at(selection.start, cx)
5037 .always_treat_brackets_as_autoclosed;
5038
5039 if !always_treat_brackets_as_autoclosed {
5040 return selection;
5041 }
5042
5043 if let Some(scope) = buffer.language_scope_at(selection.start) {
5044 for (pair, enabled) in scope.brackets() {
5045 if !enabled || !pair.close {
5046 continue;
5047 }
5048
5049 if buffer.contains_str_at(selection.start, &pair.end) {
5050 let pair_start_len = pair.start.len();
5051 if buffer.contains_str_at(
5052 selection.start.saturating_sub(pair_start_len),
5053 &pair.start,
5054 ) {
5055 selection.start -= pair_start_len;
5056 selection.end += pair.end.len();
5057
5058 return selection;
5059 }
5060 }
5061 }
5062 }
5063
5064 selection
5065 })
5066 .collect();
5067
5068 drop(buffer);
5069 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5070 selections.select(new_selections)
5071 });
5072 }
5073
5074 /// Iterate the given selections, and for each one, find the smallest surrounding
5075 /// autoclose region. This uses the ordering of the selections and the autoclose
5076 /// regions to avoid repeated comparisons.
5077 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5078 &'a self,
5079 selections: impl IntoIterator<Item = Selection<D>>,
5080 buffer: &'a MultiBufferSnapshot,
5081 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5082 let mut i = 0;
5083 let mut regions = self.autoclose_regions.as_slice();
5084 selections.into_iter().map(move |selection| {
5085 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5086
5087 let mut enclosing = None;
5088 while let Some(pair_state) = regions.get(i) {
5089 if pair_state.range.end.to_offset(buffer) < range.start {
5090 regions = ®ions[i + 1..];
5091 i = 0;
5092 } else if pair_state.range.start.to_offset(buffer) > range.end {
5093 break;
5094 } else {
5095 if pair_state.selection_id == selection.id {
5096 enclosing = Some(pair_state);
5097 }
5098 i += 1;
5099 }
5100 }
5101
5102 (selection, enclosing)
5103 })
5104 }
5105
5106 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5107 fn invalidate_autoclose_regions(
5108 &mut self,
5109 mut selections: &[Selection<Anchor>],
5110 buffer: &MultiBufferSnapshot,
5111 ) {
5112 self.autoclose_regions.retain(|state| {
5113 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5114 return false;
5115 }
5116
5117 let mut i = 0;
5118 while let Some(selection) = selections.get(i) {
5119 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5120 selections = &selections[1..];
5121 continue;
5122 }
5123 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5124 break;
5125 }
5126 if selection.id == state.selection_id {
5127 return true;
5128 } else {
5129 i += 1;
5130 }
5131 }
5132 false
5133 });
5134 }
5135
5136 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5137 let offset = position.to_offset(buffer);
5138 let (word_range, kind) =
5139 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5140 if offset > word_range.start && kind == Some(CharKind::Word) {
5141 Some(
5142 buffer
5143 .text_for_range(word_range.start..offset)
5144 .collect::<String>(),
5145 )
5146 } else {
5147 None
5148 }
5149 }
5150
5151 pub fn visible_excerpts(
5152 &self,
5153 cx: &mut Context<Editor>,
5154 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5155 let Some(project) = self.project() else {
5156 return HashMap::default();
5157 };
5158 let project = project.read(cx);
5159 let multi_buffer = self.buffer().read(cx);
5160 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5161 let multi_buffer_visible_start = self
5162 .scroll_manager
5163 .anchor()
5164 .anchor
5165 .to_point(&multi_buffer_snapshot);
5166 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5167 multi_buffer_visible_start
5168 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5169 Bias::Left,
5170 );
5171 multi_buffer_snapshot
5172 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5173 .into_iter()
5174 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5175 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5176 let buffer_file = project::File::from_dyn(buffer.file())?;
5177 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5178 let worktree_entry = buffer_worktree
5179 .read(cx)
5180 .entry_for_id(buffer_file.project_entry_id()?)?;
5181 if worktree_entry.is_ignored {
5182 None
5183 } else {
5184 Some((
5185 excerpt_id,
5186 (
5187 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5188 buffer.version().clone(),
5189 excerpt_visible_range,
5190 ),
5191 ))
5192 }
5193 })
5194 .collect()
5195 }
5196
5197 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5198 TextLayoutDetails {
5199 text_system: window.text_system().clone(),
5200 editor_style: self.style.clone().unwrap(),
5201 rem_size: window.rem_size(),
5202 scroll_anchor: self.scroll_manager.anchor(),
5203 visible_rows: self.visible_line_count(),
5204 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5205 }
5206 }
5207
5208 fn trigger_on_type_formatting(
5209 &self,
5210 input: String,
5211 window: &mut Window,
5212 cx: &mut Context<Self>,
5213 ) -> Option<Task<Result<()>>> {
5214 if input.len() != 1 {
5215 return None;
5216 }
5217
5218 let project = self.project()?;
5219 let position = self.selections.newest_anchor().head();
5220 let (buffer, buffer_position) = self
5221 .buffer
5222 .read(cx)
5223 .text_anchor_for_position(position, cx)?;
5224
5225 let settings = language_settings::language_settings(
5226 buffer
5227 .read(cx)
5228 .language_at(buffer_position)
5229 .map(|l| l.name()),
5230 buffer.read(cx).file(),
5231 cx,
5232 );
5233 if !settings.use_on_type_format {
5234 return None;
5235 }
5236
5237 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5238 // hence we do LSP request & edit on host side only — add formats to host's history.
5239 let push_to_lsp_host_history = true;
5240 // If this is not the host, append its history with new edits.
5241 let push_to_client_history = project.read(cx).is_via_collab();
5242
5243 let on_type_formatting = project.update(cx, |project, cx| {
5244 project.on_type_format(
5245 buffer.clone(),
5246 buffer_position,
5247 input,
5248 push_to_lsp_host_history,
5249 cx,
5250 )
5251 });
5252 Some(cx.spawn_in(window, async move |editor, cx| {
5253 if let Some(transaction) = on_type_formatting.await? {
5254 if push_to_client_history {
5255 buffer
5256 .update(cx, |buffer, _| {
5257 buffer.push_transaction(transaction, Instant::now());
5258 buffer.finalize_last_transaction();
5259 })
5260 .ok();
5261 }
5262 editor.update(cx, |editor, cx| {
5263 editor.refresh_document_highlights(cx);
5264 })?;
5265 }
5266 Ok(())
5267 }))
5268 }
5269
5270 pub fn show_word_completions(
5271 &mut self,
5272 _: &ShowWordCompletions,
5273 window: &mut Window,
5274 cx: &mut Context<Self>,
5275 ) {
5276 self.open_or_update_completions_menu(
5277 Some(CompletionsMenuSource::Words {
5278 ignore_threshold: true,
5279 }),
5280 None,
5281 window,
5282 cx,
5283 );
5284 }
5285
5286 pub fn show_completions(
5287 &mut self,
5288 options: &ShowCompletions,
5289 window: &mut Window,
5290 cx: &mut Context<Self>,
5291 ) {
5292 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5293 }
5294
5295 fn open_or_update_completions_menu(
5296 &mut self,
5297 requested_source: Option<CompletionsMenuSource>,
5298 trigger: Option<&str>,
5299 window: &mut Window,
5300 cx: &mut Context<Self>,
5301 ) {
5302 if self.pending_rename.is_some() {
5303 return;
5304 }
5305
5306 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5307
5308 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5309 // inserted and selected. To handle that case, the start of the selection is used so that
5310 // the menu starts with all choices.
5311 let position = self
5312 .selections
5313 .newest_anchor()
5314 .start
5315 .bias_right(&multibuffer_snapshot);
5316 if position.diff_base_anchor.is_some() {
5317 return;
5318 }
5319 let buffer_position = multibuffer_snapshot.anchor_before(position);
5320 let Some(buffer) = buffer_position
5321 .buffer_id
5322 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5323 else {
5324 return;
5325 };
5326 let buffer_snapshot = buffer.read(cx).snapshot();
5327
5328 let query: Option<Arc<String>> =
5329 Self::completion_query(&multibuffer_snapshot, buffer_position)
5330 .map(|query| query.into());
5331
5332 drop(multibuffer_snapshot);
5333
5334 // Hide the current completions menu when query is empty. Without this, cached
5335 // completions from before the trigger char may be reused (#32774).
5336 if query.is_none() {
5337 let menu_is_open = matches!(
5338 self.context_menu.borrow().as_ref(),
5339 Some(CodeContextMenu::Completions(_))
5340 );
5341 if menu_is_open {
5342 self.hide_context_menu(window, cx);
5343 }
5344 }
5345
5346 let mut ignore_word_threshold = false;
5347 let provider = match requested_source {
5348 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5349 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5350 ignore_word_threshold = ignore_threshold;
5351 None
5352 }
5353 Some(CompletionsMenuSource::SnippetChoices) => {
5354 log::error!("bug: SnippetChoices requested_source is not handled");
5355 None
5356 }
5357 };
5358
5359 let sort_completions = provider
5360 .as_ref()
5361 .is_some_and(|provider| provider.sort_completions());
5362
5363 let filter_completions = provider
5364 .as_ref()
5365 .is_none_or(|provider| provider.filter_completions());
5366
5367 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5368 if filter_completions {
5369 menu.filter(query.clone(), provider.clone(), window, cx);
5370 }
5371 // When `is_incomplete` is false, no need to re-query completions when the current query
5372 // is a suffix of the initial query.
5373 if !menu.is_incomplete {
5374 // If the new query is a suffix of the old query (typing more characters) and
5375 // the previous result was complete, the existing completions can be filtered.
5376 //
5377 // Note that this is always true for snippet completions.
5378 let query_matches = match (&menu.initial_query, &query) {
5379 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5380 (None, _) => true,
5381 _ => false,
5382 };
5383 if query_matches {
5384 let position_matches = if menu.initial_position == position {
5385 true
5386 } else {
5387 let snapshot = self.buffer.read(cx).read(cx);
5388 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5389 };
5390 if position_matches {
5391 return;
5392 }
5393 }
5394 }
5395 };
5396
5397 let trigger_kind = match trigger {
5398 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5399 CompletionTriggerKind::TRIGGER_CHARACTER
5400 }
5401 _ => CompletionTriggerKind::INVOKED,
5402 };
5403 let completion_context = CompletionContext {
5404 trigger_character: trigger.and_then(|trigger| {
5405 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5406 Some(String::from(trigger))
5407 } else {
5408 None
5409 }
5410 }),
5411 trigger_kind,
5412 };
5413
5414 let Anchor {
5415 excerpt_id: buffer_excerpt_id,
5416 text_anchor: buffer_position,
5417 ..
5418 } = buffer_position;
5419
5420 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5421 buffer_snapshot.surrounding_word(buffer_position, None)
5422 {
5423 let word_to_exclude = buffer_snapshot
5424 .text_for_range(word_range.clone())
5425 .collect::<String>();
5426 (
5427 buffer_snapshot.anchor_before(word_range.start)
5428 ..buffer_snapshot.anchor_after(buffer_position),
5429 Some(word_to_exclude),
5430 )
5431 } else {
5432 (buffer_position..buffer_position, None)
5433 };
5434
5435 let language = buffer_snapshot
5436 .language_at(buffer_position)
5437 .map(|language| language.name());
5438
5439 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5440 .completions
5441 .clone();
5442
5443 let show_completion_documentation = buffer_snapshot
5444 .settings_at(buffer_position, cx)
5445 .show_completion_documentation;
5446
5447 // The document can be large, so stay in reasonable bounds when searching for words,
5448 // otherwise completion pop-up might be slow to appear.
5449 const WORD_LOOKUP_ROWS: u32 = 5_000;
5450 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5451 let min_word_search = buffer_snapshot.clip_point(
5452 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5453 Bias::Left,
5454 );
5455 let max_word_search = buffer_snapshot.clip_point(
5456 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5457 Bias::Right,
5458 );
5459 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5460 ..buffer_snapshot.point_to_offset(max_word_search);
5461
5462 let skip_digits = query
5463 .as_ref()
5464 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5465
5466 let omit_word_completions = !self.word_completions_enabled
5467 || (!ignore_word_threshold
5468 && match &query {
5469 Some(query) => query.chars().count() < completion_settings.words_min_length,
5470 None => completion_settings.words_min_length != 0,
5471 });
5472
5473 let (mut words, provider_responses) = match &provider {
5474 Some(provider) => {
5475 let provider_responses = provider.completions(
5476 buffer_excerpt_id,
5477 &buffer,
5478 buffer_position,
5479 completion_context,
5480 window,
5481 cx,
5482 );
5483
5484 let words = match (omit_word_completions, completion_settings.words) {
5485 (true, _) | (_, WordsCompletionMode::Disabled) => {
5486 Task::ready(BTreeMap::default())
5487 }
5488 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5489 .background_spawn(async move {
5490 buffer_snapshot.words_in_range(WordsQuery {
5491 fuzzy_contents: None,
5492 range: word_search_range,
5493 skip_digits,
5494 })
5495 }),
5496 };
5497
5498 (words, provider_responses)
5499 }
5500 None => {
5501 let words = if omit_word_completions {
5502 Task::ready(BTreeMap::default())
5503 } else {
5504 cx.background_spawn(async move {
5505 buffer_snapshot.words_in_range(WordsQuery {
5506 fuzzy_contents: None,
5507 range: word_search_range,
5508 skip_digits,
5509 })
5510 })
5511 };
5512 (words, Task::ready(Ok(Vec::new())))
5513 }
5514 };
5515
5516 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5517
5518 let id = post_inc(&mut self.next_completion_id);
5519 let task = cx.spawn_in(window, async move |editor, cx| {
5520 let Ok(()) = editor.update(cx, |this, _| {
5521 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5522 }) else {
5523 return;
5524 };
5525
5526 // TODO: Ideally completions from different sources would be selectively re-queried, so
5527 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5528 let mut completions = Vec::new();
5529 let mut is_incomplete = false;
5530 let mut display_options: Option<CompletionDisplayOptions> = None;
5531 if let Some(provider_responses) = provider_responses.await.log_err()
5532 && !provider_responses.is_empty()
5533 {
5534 for response in provider_responses {
5535 completions.extend(response.completions);
5536 is_incomplete = is_incomplete || response.is_incomplete;
5537 match display_options.as_mut() {
5538 None => {
5539 display_options = Some(response.display_options);
5540 }
5541 Some(options) => options.merge(&response.display_options),
5542 }
5543 }
5544 if completion_settings.words == WordsCompletionMode::Fallback {
5545 words = Task::ready(BTreeMap::default());
5546 }
5547 }
5548 let display_options = display_options.unwrap_or_default();
5549
5550 let mut words = words.await;
5551 if let Some(word_to_exclude) = &word_to_exclude {
5552 words.remove(word_to_exclude);
5553 }
5554 for lsp_completion in &completions {
5555 words.remove(&lsp_completion.new_text);
5556 }
5557 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5558 replace_range: word_replace_range.clone(),
5559 new_text: word.clone(),
5560 label: CodeLabel::plain(word, None),
5561 icon_path: None,
5562 documentation: None,
5563 source: CompletionSource::BufferWord {
5564 word_range,
5565 resolved: false,
5566 },
5567 insert_text_mode: Some(InsertTextMode::AS_IS),
5568 confirm: None,
5569 }));
5570
5571 let menu = if completions.is_empty() {
5572 None
5573 } else {
5574 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5575 let languages = editor
5576 .workspace
5577 .as_ref()
5578 .and_then(|(workspace, _)| workspace.upgrade())
5579 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5580 let menu = CompletionsMenu::new(
5581 id,
5582 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5583 sort_completions,
5584 show_completion_documentation,
5585 position,
5586 query.clone(),
5587 is_incomplete,
5588 buffer.clone(),
5589 completions.into(),
5590 display_options,
5591 snippet_sort_order,
5592 languages,
5593 language,
5594 cx,
5595 );
5596
5597 let query = if filter_completions { query } else { None };
5598 let matches_task = if let Some(query) = query {
5599 menu.do_async_filtering(query, cx)
5600 } else {
5601 Task::ready(menu.unfiltered_matches())
5602 };
5603 (menu, matches_task)
5604 }) else {
5605 return;
5606 };
5607
5608 let matches = matches_task.await;
5609
5610 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5611 // Newer menu already set, so exit.
5612 if let Some(CodeContextMenu::Completions(prev_menu)) =
5613 editor.context_menu.borrow().as_ref()
5614 && prev_menu.id > id
5615 {
5616 return;
5617 };
5618
5619 // Only valid to take prev_menu because it the new menu is immediately set
5620 // below, or the menu is hidden.
5621 if let Some(CodeContextMenu::Completions(prev_menu)) =
5622 editor.context_menu.borrow_mut().take()
5623 {
5624 let position_matches =
5625 if prev_menu.initial_position == menu.initial_position {
5626 true
5627 } else {
5628 let snapshot = editor.buffer.read(cx).read(cx);
5629 prev_menu.initial_position.to_offset(&snapshot)
5630 == menu.initial_position.to_offset(&snapshot)
5631 };
5632 if position_matches {
5633 // Preserve markdown cache before `set_filter_results` because it will
5634 // try to populate the documentation cache.
5635 menu.preserve_markdown_cache(prev_menu);
5636 }
5637 };
5638
5639 menu.set_filter_results(matches, provider, window, cx);
5640 }) else {
5641 return;
5642 };
5643
5644 menu.visible().then_some(menu)
5645 };
5646
5647 editor
5648 .update_in(cx, |editor, window, cx| {
5649 if editor.focus_handle.is_focused(window)
5650 && let Some(menu) = menu
5651 {
5652 *editor.context_menu.borrow_mut() =
5653 Some(CodeContextMenu::Completions(menu));
5654
5655 crate::hover_popover::hide_hover(editor, cx);
5656 if editor.show_edit_predictions_in_menu() {
5657 editor.update_visible_edit_prediction(window, cx);
5658 } else {
5659 editor.discard_edit_prediction(false, cx);
5660 }
5661
5662 cx.notify();
5663 return;
5664 }
5665
5666 if editor.completion_tasks.len() <= 1 {
5667 // If there are no more completion tasks and the last menu was empty, we should hide it.
5668 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5669 // If it was already hidden and we don't show edit predictions in the menu,
5670 // we should also show the edit prediction when available.
5671 if was_hidden && editor.show_edit_predictions_in_menu() {
5672 editor.update_visible_edit_prediction(window, cx);
5673 }
5674 }
5675 })
5676 .ok();
5677 });
5678
5679 self.completion_tasks.push((id, task));
5680 }
5681
5682 #[cfg(feature = "test-support")]
5683 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5684 let menu = self.context_menu.borrow();
5685 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5686 let completions = menu.completions.borrow();
5687 Some(completions.to_vec())
5688 } else {
5689 None
5690 }
5691 }
5692
5693 pub fn with_completions_menu_matching_id<R>(
5694 &self,
5695 id: CompletionId,
5696 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5697 ) -> R {
5698 let mut context_menu = self.context_menu.borrow_mut();
5699 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5700 return f(None);
5701 };
5702 if completions_menu.id != id {
5703 return f(None);
5704 }
5705 f(Some(completions_menu))
5706 }
5707
5708 pub fn confirm_completion(
5709 &mut self,
5710 action: &ConfirmCompletion,
5711 window: &mut Window,
5712 cx: &mut Context<Self>,
5713 ) -> Option<Task<Result<()>>> {
5714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5715 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5716 }
5717
5718 pub fn confirm_completion_insert(
5719 &mut self,
5720 _: &ConfirmCompletionInsert,
5721 window: &mut Window,
5722 cx: &mut Context<Self>,
5723 ) -> Option<Task<Result<()>>> {
5724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5725 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5726 }
5727
5728 pub fn confirm_completion_replace(
5729 &mut self,
5730 _: &ConfirmCompletionReplace,
5731 window: &mut Window,
5732 cx: &mut Context<Self>,
5733 ) -> Option<Task<Result<()>>> {
5734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5735 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5736 }
5737
5738 pub fn compose_completion(
5739 &mut self,
5740 action: &ComposeCompletion,
5741 window: &mut Window,
5742 cx: &mut Context<Self>,
5743 ) -> Option<Task<Result<()>>> {
5744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5745 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5746 }
5747
5748 fn do_completion(
5749 &mut self,
5750 item_ix: Option<usize>,
5751 intent: CompletionIntent,
5752 window: &mut Window,
5753 cx: &mut Context<Editor>,
5754 ) -> Option<Task<Result<()>>> {
5755 use language::ToOffset as _;
5756
5757 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5758 else {
5759 return None;
5760 };
5761
5762 let candidate_id = {
5763 let entries = completions_menu.entries.borrow();
5764 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5765 if self.show_edit_predictions_in_menu() {
5766 self.discard_edit_prediction(true, cx);
5767 }
5768 mat.candidate_id
5769 };
5770
5771 let completion = completions_menu
5772 .completions
5773 .borrow()
5774 .get(candidate_id)?
5775 .clone();
5776 cx.stop_propagation();
5777
5778 let buffer_handle = completions_menu.buffer.clone();
5779
5780 let CompletionEdit {
5781 new_text,
5782 snippet,
5783 replace_range,
5784 } = process_completion_for_edit(
5785 &completion,
5786 intent,
5787 &buffer_handle,
5788 &completions_menu.initial_position.text_anchor,
5789 cx,
5790 );
5791
5792 let buffer = buffer_handle.read(cx);
5793 let snapshot = self.buffer.read(cx).snapshot(cx);
5794 let newest_anchor = self.selections.newest_anchor();
5795 let replace_range_multibuffer = {
5796 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5797 excerpt.map_range_from_buffer(replace_range.clone())
5798 };
5799 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5800 return None;
5801 }
5802
5803 let old_text = buffer
5804 .text_for_range(replace_range.clone())
5805 .collect::<String>();
5806 let lookbehind = newest_anchor
5807 .start
5808 .text_anchor
5809 .to_offset(buffer)
5810 .saturating_sub(replace_range.start);
5811 let lookahead = replace_range
5812 .end
5813 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5814 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5815 let suffix = &old_text[lookbehind.min(old_text.len())..];
5816
5817 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5818 let mut ranges = Vec::new();
5819 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5820
5821 for selection in &selections {
5822 let range = if selection.id == newest_anchor.id {
5823 replace_range_multibuffer.clone()
5824 } else {
5825 let mut range = selection.range();
5826
5827 // if prefix is present, don't duplicate it
5828 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5829 range.start = range.start.saturating_sub(lookbehind);
5830
5831 // if suffix is also present, mimic the newest cursor and replace it
5832 if selection.id != newest_anchor.id
5833 && snapshot.contains_str_at(range.end, suffix)
5834 {
5835 range.end += lookahead;
5836 }
5837 }
5838 range
5839 };
5840
5841 ranges.push(range.clone());
5842
5843 if !self.linked_edit_ranges.is_empty() {
5844 let start_anchor = snapshot.anchor_before(range.start);
5845 let end_anchor = snapshot.anchor_after(range.end);
5846 if let Some(ranges) = self
5847 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5848 {
5849 for (buffer, edits) in ranges {
5850 linked_edits
5851 .entry(buffer.clone())
5852 .or_default()
5853 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5854 }
5855 }
5856 }
5857 }
5858
5859 let common_prefix_len = old_text
5860 .chars()
5861 .zip(new_text.chars())
5862 .take_while(|(a, b)| a == b)
5863 .map(|(a, _)| a.len_utf8())
5864 .sum::<usize>();
5865
5866 cx.emit(EditorEvent::InputHandled {
5867 utf16_range_to_replace: None,
5868 text: new_text[common_prefix_len..].into(),
5869 });
5870
5871 self.transact(window, cx, |editor, window, cx| {
5872 if let Some(mut snippet) = snippet {
5873 snippet.text = new_text.to_string();
5874 editor
5875 .insert_snippet(&ranges, snippet, window, cx)
5876 .log_err();
5877 } else {
5878 editor.buffer.update(cx, |multi_buffer, cx| {
5879 let auto_indent = match completion.insert_text_mode {
5880 Some(InsertTextMode::AS_IS) => None,
5881 _ => editor.autoindent_mode.clone(),
5882 };
5883 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5884 multi_buffer.edit(edits, auto_indent, cx);
5885 });
5886 }
5887 for (buffer, edits) in linked_edits {
5888 buffer.update(cx, |buffer, cx| {
5889 let snapshot = buffer.snapshot();
5890 let edits = edits
5891 .into_iter()
5892 .map(|(range, text)| {
5893 use text::ToPoint as TP;
5894 let end_point = TP::to_point(&range.end, &snapshot);
5895 let start_point = TP::to_point(&range.start, &snapshot);
5896 (start_point..end_point, text)
5897 })
5898 .sorted_by_key(|(range, _)| range.start);
5899 buffer.edit(edits, None, cx);
5900 })
5901 }
5902
5903 editor.refresh_edit_prediction(true, false, window, cx);
5904 });
5905 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5906
5907 let show_new_completions_on_confirm = completion
5908 .confirm
5909 .as_ref()
5910 .is_some_and(|confirm| confirm(intent, window, cx));
5911 if show_new_completions_on_confirm {
5912 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5913 }
5914
5915 let provider = self.completion_provider.as_ref()?;
5916 drop(completion);
5917 let apply_edits = provider.apply_additional_edits_for_completion(
5918 buffer_handle,
5919 completions_menu.completions.clone(),
5920 candidate_id,
5921 true,
5922 cx,
5923 );
5924
5925 let editor_settings = EditorSettings::get_global(cx);
5926 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5927 // After the code completion is finished, users often want to know what signatures are needed.
5928 // so we should automatically call signature_help
5929 self.show_signature_help(&ShowSignatureHelp, window, cx);
5930 }
5931
5932 Some(cx.foreground_executor().spawn(async move {
5933 apply_edits.await?;
5934 Ok(())
5935 }))
5936 }
5937
5938 pub fn toggle_code_actions(
5939 &mut self,
5940 action: &ToggleCodeActions,
5941 window: &mut Window,
5942 cx: &mut Context<Self>,
5943 ) {
5944 let quick_launch = action.quick_launch;
5945 let mut context_menu = self.context_menu.borrow_mut();
5946 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5947 if code_actions.deployed_from == action.deployed_from {
5948 // Toggle if we're selecting the same one
5949 *context_menu = None;
5950 cx.notify();
5951 return;
5952 } else {
5953 // Otherwise, clear it and start a new one
5954 *context_menu = None;
5955 cx.notify();
5956 }
5957 }
5958 drop(context_menu);
5959 let snapshot = self.snapshot(window, cx);
5960 let deployed_from = action.deployed_from.clone();
5961 let action = action.clone();
5962 self.completion_tasks.clear();
5963 self.discard_edit_prediction(false, cx);
5964
5965 let multibuffer_point = match &action.deployed_from {
5966 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5967 DisplayPoint::new(*row, 0).to_point(&snapshot)
5968 }
5969 _ => self
5970 .selections
5971 .newest::<Point>(&snapshot.display_snapshot)
5972 .head(),
5973 };
5974 let Some((buffer, buffer_row)) = snapshot
5975 .buffer_snapshot()
5976 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5977 .and_then(|(buffer_snapshot, range)| {
5978 self.buffer()
5979 .read(cx)
5980 .buffer(buffer_snapshot.remote_id())
5981 .map(|buffer| (buffer, range.start.row))
5982 })
5983 else {
5984 return;
5985 };
5986 let buffer_id = buffer.read(cx).remote_id();
5987 let tasks = self
5988 .tasks
5989 .get(&(buffer_id, buffer_row))
5990 .map(|t| Arc::new(t.to_owned()));
5991
5992 if !self.focus_handle.is_focused(window) {
5993 return;
5994 }
5995 let project = self.project.clone();
5996
5997 let code_actions_task = match deployed_from {
5998 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5999 _ => self.code_actions(buffer_row, window, cx),
6000 };
6001
6002 let runnable_task = match deployed_from {
6003 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6004 _ => {
6005 let mut task_context_task = Task::ready(None);
6006 if let Some(tasks) = &tasks
6007 && let Some(project) = project
6008 {
6009 task_context_task =
6010 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6011 }
6012
6013 cx.spawn_in(window, {
6014 let buffer = buffer.clone();
6015 async move |editor, cx| {
6016 let task_context = task_context_task.await;
6017
6018 let resolved_tasks =
6019 tasks
6020 .zip(task_context.clone())
6021 .map(|(tasks, task_context)| ResolvedTasks {
6022 templates: tasks.resolve(&task_context).collect(),
6023 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6024 multibuffer_point.row,
6025 tasks.column,
6026 )),
6027 });
6028 let debug_scenarios = editor
6029 .update(cx, |editor, cx| {
6030 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6031 })?
6032 .await;
6033 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6034 }
6035 })
6036 }
6037 };
6038
6039 cx.spawn_in(window, async move |editor, cx| {
6040 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6041 let code_actions = code_actions_task.await;
6042 let spawn_straight_away = quick_launch
6043 && resolved_tasks
6044 .as_ref()
6045 .is_some_and(|tasks| tasks.templates.len() == 1)
6046 && code_actions
6047 .as_ref()
6048 .is_none_or(|actions| actions.is_empty())
6049 && debug_scenarios.is_empty();
6050
6051 editor.update_in(cx, |editor, window, cx| {
6052 crate::hover_popover::hide_hover(editor, cx);
6053 let actions = CodeActionContents::new(
6054 resolved_tasks,
6055 code_actions,
6056 debug_scenarios,
6057 task_context.unwrap_or_default(),
6058 );
6059
6060 // Don't show the menu if there are no actions available
6061 if actions.is_empty() {
6062 cx.notify();
6063 return Task::ready(Ok(()));
6064 }
6065
6066 *editor.context_menu.borrow_mut() =
6067 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6068 buffer,
6069 actions,
6070 selected_item: Default::default(),
6071 scroll_handle: UniformListScrollHandle::default(),
6072 deployed_from,
6073 }));
6074 cx.notify();
6075 if spawn_straight_away
6076 && let Some(task) = editor.confirm_code_action(
6077 &ConfirmCodeAction { item_ix: Some(0) },
6078 window,
6079 cx,
6080 )
6081 {
6082 return task;
6083 }
6084
6085 Task::ready(Ok(()))
6086 })
6087 })
6088 .detach_and_log_err(cx);
6089 }
6090
6091 fn debug_scenarios(
6092 &mut self,
6093 resolved_tasks: &Option<ResolvedTasks>,
6094 buffer: &Entity<Buffer>,
6095 cx: &mut App,
6096 ) -> Task<Vec<task::DebugScenario>> {
6097 maybe!({
6098 let project = self.project()?;
6099 let dap_store = project.read(cx).dap_store();
6100 let mut scenarios = vec![];
6101 let resolved_tasks = resolved_tasks.as_ref()?;
6102 let buffer = buffer.read(cx);
6103 let language = buffer.language()?;
6104 let file = buffer.file();
6105 let debug_adapter = language_settings(language.name().into(), file, cx)
6106 .debuggers
6107 .first()
6108 .map(SharedString::from)
6109 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6110
6111 dap_store.update(cx, |dap_store, cx| {
6112 for (_, task) in &resolved_tasks.templates {
6113 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6114 task.original_task().clone(),
6115 debug_adapter.clone().into(),
6116 task.display_label().to_owned().into(),
6117 cx,
6118 );
6119 scenarios.push(maybe_scenario);
6120 }
6121 });
6122 Some(cx.background_spawn(async move {
6123 futures::future::join_all(scenarios)
6124 .await
6125 .into_iter()
6126 .flatten()
6127 .collect::<Vec<_>>()
6128 }))
6129 })
6130 .unwrap_or_else(|| Task::ready(vec![]))
6131 }
6132
6133 fn code_actions(
6134 &mut self,
6135 buffer_row: u32,
6136 window: &mut Window,
6137 cx: &mut Context<Self>,
6138 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6139 let mut task = self.code_actions_task.take();
6140 cx.spawn_in(window, async move |editor, cx| {
6141 while let Some(prev_task) = task {
6142 prev_task.await.log_err();
6143 task = editor
6144 .update(cx, |this, _| this.code_actions_task.take())
6145 .ok()?;
6146 }
6147
6148 editor
6149 .update(cx, |editor, cx| {
6150 editor
6151 .available_code_actions
6152 .clone()
6153 .and_then(|(location, code_actions)| {
6154 let snapshot = location.buffer.read(cx).snapshot();
6155 let point_range = location.range.to_point(&snapshot);
6156 let point_range = point_range.start.row..=point_range.end.row;
6157 if point_range.contains(&buffer_row) {
6158 Some(code_actions)
6159 } else {
6160 None
6161 }
6162 })
6163 })
6164 .ok()
6165 .flatten()
6166 })
6167 }
6168
6169 pub fn confirm_code_action(
6170 &mut self,
6171 action: &ConfirmCodeAction,
6172 window: &mut Window,
6173 cx: &mut Context<Self>,
6174 ) -> Option<Task<Result<()>>> {
6175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6176
6177 let actions_menu =
6178 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6179 menu
6180 } else {
6181 return None;
6182 };
6183
6184 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6185 let action = actions_menu.actions.get(action_ix)?;
6186 let title = action.label();
6187 let buffer = actions_menu.buffer;
6188 let workspace = self.workspace()?;
6189
6190 match action {
6191 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6192 workspace.update(cx, |workspace, cx| {
6193 workspace.schedule_resolved_task(
6194 task_source_kind,
6195 resolved_task,
6196 false,
6197 window,
6198 cx,
6199 );
6200
6201 Some(Task::ready(Ok(())))
6202 })
6203 }
6204 CodeActionsItem::CodeAction {
6205 excerpt_id,
6206 action,
6207 provider,
6208 } => {
6209 let apply_code_action =
6210 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6211 let workspace = workspace.downgrade();
6212 Some(cx.spawn_in(window, async move |editor, cx| {
6213 let project_transaction = apply_code_action.await?;
6214 Self::open_project_transaction(
6215 &editor,
6216 workspace,
6217 project_transaction,
6218 title,
6219 cx,
6220 )
6221 .await
6222 }))
6223 }
6224 CodeActionsItem::DebugScenario(scenario) => {
6225 let context = actions_menu.actions.context;
6226
6227 workspace.update(cx, |workspace, cx| {
6228 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6229 workspace.start_debug_session(
6230 scenario,
6231 context,
6232 Some(buffer),
6233 None,
6234 window,
6235 cx,
6236 );
6237 });
6238 Some(Task::ready(Ok(())))
6239 }
6240 }
6241 }
6242
6243 pub async fn open_project_transaction(
6244 editor: &WeakEntity<Editor>,
6245 workspace: WeakEntity<Workspace>,
6246 transaction: ProjectTransaction,
6247 title: String,
6248 cx: &mut AsyncWindowContext,
6249 ) -> Result<()> {
6250 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6251 cx.update(|_, cx| {
6252 entries.sort_unstable_by_key(|(buffer, _)| {
6253 buffer.read(cx).file().map(|f| f.path().clone())
6254 });
6255 })?;
6256 if entries.is_empty() {
6257 return Ok(());
6258 }
6259
6260 // If the project transaction's edits are all contained within this editor, then
6261 // avoid opening a new editor to display them.
6262
6263 if let [(buffer, transaction)] = &*entries {
6264 let excerpt = editor.update(cx, |editor, cx| {
6265 editor
6266 .buffer()
6267 .read(cx)
6268 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6269 })?;
6270 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6271 && excerpted_buffer == *buffer
6272 {
6273 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6274 let excerpt_range = excerpt_range.to_offset(buffer);
6275 buffer
6276 .edited_ranges_for_transaction::<usize>(transaction)
6277 .all(|range| {
6278 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6279 })
6280 })?;
6281
6282 if all_edits_within_excerpt {
6283 return Ok(());
6284 }
6285 }
6286 }
6287
6288 let mut ranges_to_highlight = Vec::new();
6289 let excerpt_buffer = cx.new(|cx| {
6290 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6291 for (buffer_handle, transaction) in &entries {
6292 let edited_ranges = buffer_handle
6293 .read(cx)
6294 .edited_ranges_for_transaction::<Point>(transaction)
6295 .collect::<Vec<_>>();
6296 let (ranges, _) = multibuffer.set_excerpts_for_path(
6297 PathKey::for_buffer(buffer_handle, cx),
6298 buffer_handle.clone(),
6299 edited_ranges,
6300 multibuffer_context_lines(cx),
6301 cx,
6302 );
6303
6304 ranges_to_highlight.extend(ranges);
6305 }
6306 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6307 multibuffer
6308 })?;
6309
6310 workspace.update_in(cx, |workspace, window, cx| {
6311 let project = workspace.project().clone();
6312 let editor =
6313 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6314 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6315 editor.update(cx, |editor, cx| {
6316 editor.highlight_background::<Self>(
6317 &ranges_to_highlight,
6318 |theme| theme.colors().editor_highlighted_line_background,
6319 cx,
6320 );
6321 });
6322 })?;
6323
6324 Ok(())
6325 }
6326
6327 pub fn clear_code_action_providers(&mut self) {
6328 self.code_action_providers.clear();
6329 self.available_code_actions.take();
6330 }
6331
6332 pub fn add_code_action_provider(
6333 &mut self,
6334 provider: Rc<dyn CodeActionProvider>,
6335 window: &mut Window,
6336 cx: &mut Context<Self>,
6337 ) {
6338 if self
6339 .code_action_providers
6340 .iter()
6341 .any(|existing_provider| existing_provider.id() == provider.id())
6342 {
6343 return;
6344 }
6345
6346 self.code_action_providers.push(provider);
6347 self.refresh_code_actions(window, cx);
6348 }
6349
6350 pub fn remove_code_action_provider(
6351 &mut self,
6352 id: Arc<str>,
6353 window: &mut Window,
6354 cx: &mut Context<Self>,
6355 ) {
6356 self.code_action_providers
6357 .retain(|provider| provider.id() != id);
6358 self.refresh_code_actions(window, cx);
6359 }
6360
6361 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6362 !self.code_action_providers.is_empty()
6363 && EditorSettings::get_global(cx).toolbar.code_actions
6364 }
6365
6366 pub fn has_available_code_actions(&self) -> bool {
6367 self.available_code_actions
6368 .as_ref()
6369 .is_some_and(|(_, actions)| !actions.is_empty())
6370 }
6371
6372 fn render_inline_code_actions(
6373 &self,
6374 icon_size: ui::IconSize,
6375 display_row: DisplayRow,
6376 is_active: bool,
6377 cx: &mut Context<Self>,
6378 ) -> AnyElement {
6379 let show_tooltip = !self.context_menu_visible();
6380 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6381 .icon_size(icon_size)
6382 .shape(ui::IconButtonShape::Square)
6383 .icon_color(ui::Color::Hidden)
6384 .toggle_state(is_active)
6385 .when(show_tooltip, |this| {
6386 this.tooltip({
6387 let focus_handle = self.focus_handle.clone();
6388 move |_window, cx| {
6389 Tooltip::for_action_in(
6390 "Toggle Code Actions",
6391 &ToggleCodeActions {
6392 deployed_from: None,
6393 quick_launch: false,
6394 },
6395 &focus_handle,
6396 cx,
6397 )
6398 }
6399 })
6400 })
6401 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6402 window.focus(&editor.focus_handle(cx));
6403 editor.toggle_code_actions(
6404 &crate::actions::ToggleCodeActions {
6405 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6406 display_row,
6407 )),
6408 quick_launch: false,
6409 },
6410 window,
6411 cx,
6412 );
6413 }))
6414 .into_any_element()
6415 }
6416
6417 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6418 &self.context_menu
6419 }
6420
6421 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6422 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6423 cx.background_executor()
6424 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6425 .await;
6426
6427 let (start_buffer, start, _, end, newest_selection) = this
6428 .update(cx, |this, cx| {
6429 let newest_selection = this.selections.newest_anchor().clone();
6430 if newest_selection.head().diff_base_anchor.is_some() {
6431 return None;
6432 }
6433 let display_snapshot = this.display_snapshot(cx);
6434 let newest_selection_adjusted =
6435 this.selections.newest_adjusted(&display_snapshot);
6436 let buffer = this.buffer.read(cx);
6437
6438 let (start_buffer, start) =
6439 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6440 let (end_buffer, end) =
6441 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6442
6443 Some((start_buffer, start, end_buffer, end, newest_selection))
6444 })?
6445 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6446 .context(
6447 "Expected selection to lie in a single buffer when refreshing code actions",
6448 )?;
6449 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6450 let providers = this.code_action_providers.clone();
6451 let tasks = this
6452 .code_action_providers
6453 .iter()
6454 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6455 .collect::<Vec<_>>();
6456 (providers, tasks)
6457 })?;
6458
6459 let mut actions = Vec::new();
6460 for (provider, provider_actions) in
6461 providers.into_iter().zip(future::join_all(tasks).await)
6462 {
6463 if let Some(provider_actions) = provider_actions.log_err() {
6464 actions.extend(provider_actions.into_iter().map(|action| {
6465 AvailableCodeAction {
6466 excerpt_id: newest_selection.start.excerpt_id,
6467 action,
6468 provider: provider.clone(),
6469 }
6470 }));
6471 }
6472 }
6473
6474 this.update(cx, |this, cx| {
6475 this.available_code_actions = if actions.is_empty() {
6476 None
6477 } else {
6478 Some((
6479 Location {
6480 buffer: start_buffer,
6481 range: start..end,
6482 },
6483 actions.into(),
6484 ))
6485 };
6486 cx.notify();
6487 })
6488 }));
6489 }
6490
6491 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6492 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6493 self.show_git_blame_inline = false;
6494
6495 self.show_git_blame_inline_delay_task =
6496 Some(cx.spawn_in(window, async move |this, cx| {
6497 cx.background_executor().timer(delay).await;
6498
6499 this.update(cx, |this, cx| {
6500 this.show_git_blame_inline = true;
6501 cx.notify();
6502 })
6503 .log_err();
6504 }));
6505 }
6506 }
6507
6508 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6509 let snapshot = self.snapshot(window, cx);
6510 let cursor = self
6511 .selections
6512 .newest::<Point>(&snapshot.display_snapshot)
6513 .head();
6514 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6515 else {
6516 return;
6517 };
6518
6519 let Some(blame) = self.blame.as_ref() else {
6520 return;
6521 };
6522
6523 let row_info = RowInfo {
6524 buffer_id: Some(buffer.remote_id()),
6525 buffer_row: Some(point.row),
6526 ..Default::default()
6527 };
6528 let Some((buffer, blame_entry)) = blame
6529 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6530 .flatten()
6531 else {
6532 return;
6533 };
6534
6535 let anchor = self.selections.newest_anchor().head();
6536 let position = self.to_pixel_point(anchor, &snapshot, window);
6537 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6538 self.show_blame_popover(
6539 buffer,
6540 &blame_entry,
6541 position + last_bounds.origin,
6542 true,
6543 cx,
6544 );
6545 };
6546 }
6547
6548 fn show_blame_popover(
6549 &mut self,
6550 buffer: BufferId,
6551 blame_entry: &BlameEntry,
6552 position: gpui::Point<Pixels>,
6553 ignore_timeout: bool,
6554 cx: &mut Context<Self>,
6555 ) {
6556 if let Some(state) = &mut self.inline_blame_popover {
6557 state.hide_task.take();
6558 } else {
6559 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6560 let blame_entry = blame_entry.clone();
6561 let show_task = cx.spawn(async move |editor, cx| {
6562 if !ignore_timeout {
6563 cx.background_executor()
6564 .timer(std::time::Duration::from_millis(blame_popover_delay))
6565 .await;
6566 }
6567 editor
6568 .update(cx, |editor, cx| {
6569 editor.inline_blame_popover_show_task.take();
6570 let Some(blame) = editor.blame.as_ref() else {
6571 return;
6572 };
6573 let blame = blame.read(cx);
6574 let details = blame.details_for_entry(buffer, &blame_entry);
6575 let markdown = cx.new(|cx| {
6576 Markdown::new(
6577 details
6578 .as_ref()
6579 .map(|message| message.message.clone())
6580 .unwrap_or_default(),
6581 None,
6582 None,
6583 cx,
6584 )
6585 });
6586 editor.inline_blame_popover = Some(InlineBlamePopover {
6587 position,
6588 hide_task: None,
6589 popover_bounds: None,
6590 popover_state: InlineBlamePopoverState {
6591 scroll_handle: ScrollHandle::new(),
6592 commit_message: details,
6593 markdown,
6594 },
6595 keyboard_grace: ignore_timeout,
6596 });
6597 cx.notify();
6598 })
6599 .ok();
6600 });
6601 self.inline_blame_popover_show_task = Some(show_task);
6602 }
6603 }
6604
6605 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6606 self.inline_blame_popover_show_task.take();
6607 if let Some(state) = &mut self.inline_blame_popover {
6608 let hide_task = cx.spawn(async move |editor, cx| {
6609 if !ignore_timeout {
6610 cx.background_executor()
6611 .timer(std::time::Duration::from_millis(100))
6612 .await;
6613 }
6614 editor
6615 .update(cx, |editor, cx| {
6616 editor.inline_blame_popover.take();
6617 cx.notify();
6618 })
6619 .ok();
6620 });
6621 state.hide_task = Some(hide_task);
6622 true
6623 } else {
6624 false
6625 }
6626 }
6627
6628 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6629 if self.pending_rename.is_some() {
6630 return None;
6631 }
6632
6633 let provider = self.semantics_provider.clone()?;
6634 let buffer = self.buffer.read(cx);
6635 let newest_selection = self.selections.newest_anchor().clone();
6636 let cursor_position = newest_selection.head();
6637 let (cursor_buffer, cursor_buffer_position) =
6638 buffer.text_anchor_for_position(cursor_position, cx)?;
6639 let (tail_buffer, tail_buffer_position) =
6640 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6641 if cursor_buffer != tail_buffer {
6642 return None;
6643 }
6644
6645 let snapshot = cursor_buffer.read(cx).snapshot();
6646 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6647 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6648 if start_word_range != end_word_range {
6649 self.document_highlights_task.take();
6650 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6651 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6652 return None;
6653 }
6654
6655 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6656 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6657 cx.background_executor()
6658 .timer(Duration::from_millis(debounce))
6659 .await;
6660
6661 let highlights = if let Some(highlights) = cx
6662 .update(|cx| {
6663 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6664 })
6665 .ok()
6666 .flatten()
6667 {
6668 highlights.await.log_err()
6669 } else {
6670 None
6671 };
6672
6673 if let Some(highlights) = highlights {
6674 this.update(cx, |this, cx| {
6675 if this.pending_rename.is_some() {
6676 return;
6677 }
6678
6679 let buffer = this.buffer.read(cx);
6680 if buffer
6681 .text_anchor_for_position(cursor_position, cx)
6682 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6683 {
6684 return;
6685 }
6686
6687 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6688 let mut write_ranges = Vec::new();
6689 let mut read_ranges = Vec::new();
6690 for highlight in highlights {
6691 let buffer_id = cursor_buffer.read(cx).remote_id();
6692 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6693 {
6694 let start = highlight
6695 .range
6696 .start
6697 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6698 let end = highlight
6699 .range
6700 .end
6701 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6702 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6703 continue;
6704 }
6705
6706 let range =
6707 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6708 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6709 write_ranges.push(range);
6710 } else {
6711 read_ranges.push(range);
6712 }
6713 }
6714 }
6715
6716 this.highlight_background::<DocumentHighlightRead>(
6717 &read_ranges,
6718 |theme| theme.colors().editor_document_highlight_read_background,
6719 cx,
6720 );
6721 this.highlight_background::<DocumentHighlightWrite>(
6722 &write_ranges,
6723 |theme| theme.colors().editor_document_highlight_write_background,
6724 cx,
6725 );
6726 cx.notify();
6727 })
6728 .log_err();
6729 }
6730 }));
6731 None
6732 }
6733
6734 fn prepare_highlight_query_from_selection(
6735 &mut self,
6736 window: &Window,
6737 cx: &mut Context<Editor>,
6738 ) -> Option<(String, Range<Anchor>)> {
6739 if matches!(self.mode, EditorMode::SingleLine) {
6740 return None;
6741 }
6742 if !EditorSettings::get_global(cx).selection_highlight {
6743 return None;
6744 }
6745 if self.selections.count() != 1 || self.selections.line_mode() {
6746 return None;
6747 }
6748 let snapshot = self.snapshot(window, cx);
6749 let selection = self.selections.newest::<Point>(&snapshot);
6750 // If the selection spans multiple rows OR it is empty
6751 if selection.start.row != selection.end.row
6752 || selection.start.column == selection.end.column
6753 {
6754 return None;
6755 }
6756 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6757 let query = snapshot
6758 .buffer_snapshot()
6759 .text_for_range(selection_anchor_range.clone())
6760 .collect::<String>();
6761 if query.trim().is_empty() {
6762 return None;
6763 }
6764 Some((query, selection_anchor_range))
6765 }
6766
6767 fn update_selection_occurrence_highlights(
6768 &mut self,
6769 query_text: String,
6770 query_range: Range<Anchor>,
6771 multi_buffer_range_to_query: Range<Point>,
6772 use_debounce: bool,
6773 window: &mut Window,
6774 cx: &mut Context<Editor>,
6775 ) -> Task<()> {
6776 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6777 cx.spawn_in(window, async move |editor, cx| {
6778 if use_debounce {
6779 cx.background_executor()
6780 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6781 .await;
6782 }
6783 let match_task = cx.background_spawn(async move {
6784 let buffer_ranges = multi_buffer_snapshot
6785 .range_to_buffer_ranges(multi_buffer_range_to_query)
6786 .into_iter()
6787 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6788 let mut match_ranges = Vec::new();
6789 let Ok(regex) = project::search::SearchQuery::text(
6790 query_text.clone(),
6791 false,
6792 false,
6793 false,
6794 Default::default(),
6795 Default::default(),
6796 false,
6797 None,
6798 ) else {
6799 return Vec::default();
6800 };
6801 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6802 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6803 match_ranges.extend(
6804 regex
6805 .search(buffer_snapshot, Some(search_range.clone()))
6806 .await
6807 .into_iter()
6808 .filter_map(|match_range| {
6809 let match_start = buffer_snapshot
6810 .anchor_after(search_range.start + match_range.start);
6811 let match_end = buffer_snapshot
6812 .anchor_before(search_range.start + match_range.end);
6813 let match_anchor_range = Anchor::range_in_buffer(
6814 excerpt_id,
6815 buffer_snapshot.remote_id(),
6816 match_start..match_end,
6817 );
6818 (match_anchor_range != query_range).then_some(match_anchor_range)
6819 }),
6820 );
6821 }
6822 match_ranges
6823 });
6824 let match_ranges = match_task.await;
6825 editor
6826 .update_in(cx, |editor, _, cx| {
6827 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6828 if !match_ranges.is_empty() {
6829 editor.highlight_background::<SelectedTextHighlight>(
6830 &match_ranges,
6831 |theme| theme.colors().editor_document_highlight_bracket_background,
6832 cx,
6833 )
6834 }
6835 })
6836 .log_err();
6837 })
6838 }
6839
6840 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6841 struct NewlineFold;
6842 let type_id = std::any::TypeId::of::<NewlineFold>();
6843 if !self.mode.is_single_line() {
6844 return;
6845 }
6846 let snapshot = self.snapshot(window, cx);
6847 if snapshot.buffer_snapshot().max_point().row == 0 {
6848 return;
6849 }
6850 let task = cx.background_spawn(async move {
6851 let new_newlines = snapshot
6852 .buffer_chars_at(0)
6853 .filter_map(|(c, i)| {
6854 if c == '\n' {
6855 Some(
6856 snapshot.buffer_snapshot().anchor_after(i)
6857 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6858 )
6859 } else {
6860 None
6861 }
6862 })
6863 .collect::<Vec<_>>();
6864 let existing_newlines = snapshot
6865 .folds_in_range(0..snapshot.buffer_snapshot().len())
6866 .filter_map(|fold| {
6867 if fold.placeholder.type_tag == Some(type_id) {
6868 Some(fold.range.start..fold.range.end)
6869 } else {
6870 None
6871 }
6872 })
6873 .collect::<Vec<_>>();
6874
6875 (new_newlines, existing_newlines)
6876 });
6877 self.folding_newlines = cx.spawn(async move |this, cx| {
6878 let (new_newlines, existing_newlines) = task.await;
6879 if new_newlines == existing_newlines {
6880 return;
6881 }
6882 let placeholder = FoldPlaceholder {
6883 render: Arc::new(move |_, _, cx| {
6884 div()
6885 .bg(cx.theme().status().hint_background)
6886 .border_b_1()
6887 .size_full()
6888 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6889 .border_color(cx.theme().status().hint)
6890 .child("\\n")
6891 .into_any()
6892 }),
6893 constrain_width: false,
6894 merge_adjacent: false,
6895 type_tag: Some(type_id),
6896 };
6897 let creases = new_newlines
6898 .into_iter()
6899 .map(|range| Crease::simple(range, placeholder.clone()))
6900 .collect();
6901 this.update(cx, |this, cx| {
6902 this.display_map.update(cx, |display_map, cx| {
6903 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6904 display_map.fold(creases, cx);
6905 });
6906 })
6907 .ok();
6908 });
6909 }
6910
6911 fn refresh_selected_text_highlights(
6912 &mut self,
6913 on_buffer_edit: bool,
6914 window: &mut Window,
6915 cx: &mut Context<Editor>,
6916 ) {
6917 let Some((query_text, query_range)) =
6918 self.prepare_highlight_query_from_selection(window, cx)
6919 else {
6920 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6921 self.quick_selection_highlight_task.take();
6922 self.debounced_selection_highlight_task.take();
6923 return;
6924 };
6925 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6926 if on_buffer_edit
6927 || self
6928 .quick_selection_highlight_task
6929 .as_ref()
6930 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6931 {
6932 let multi_buffer_visible_start = self
6933 .scroll_manager
6934 .anchor()
6935 .anchor
6936 .to_point(&multi_buffer_snapshot);
6937 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6938 multi_buffer_visible_start
6939 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6940 Bias::Left,
6941 );
6942 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6943 self.quick_selection_highlight_task = Some((
6944 query_range.clone(),
6945 self.update_selection_occurrence_highlights(
6946 query_text.clone(),
6947 query_range.clone(),
6948 multi_buffer_visible_range,
6949 false,
6950 window,
6951 cx,
6952 ),
6953 ));
6954 }
6955 if on_buffer_edit
6956 || self
6957 .debounced_selection_highlight_task
6958 .as_ref()
6959 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6960 {
6961 let multi_buffer_start = multi_buffer_snapshot
6962 .anchor_before(0)
6963 .to_point(&multi_buffer_snapshot);
6964 let multi_buffer_end = multi_buffer_snapshot
6965 .anchor_after(multi_buffer_snapshot.len())
6966 .to_point(&multi_buffer_snapshot);
6967 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6968 self.debounced_selection_highlight_task = Some((
6969 query_range.clone(),
6970 self.update_selection_occurrence_highlights(
6971 query_text,
6972 query_range,
6973 multi_buffer_full_range,
6974 true,
6975 window,
6976 cx,
6977 ),
6978 ));
6979 }
6980 }
6981
6982 pub fn refresh_edit_prediction(
6983 &mut self,
6984 debounce: bool,
6985 user_requested: bool,
6986 window: &mut Window,
6987 cx: &mut Context<Self>,
6988 ) -> Option<()> {
6989 if DisableAiSettings::get_global(cx).disable_ai {
6990 return None;
6991 }
6992
6993 let provider = self.edit_prediction_provider()?;
6994 let cursor = self.selections.newest_anchor().head();
6995 let (buffer, cursor_buffer_position) =
6996 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6997
6998 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6999 self.discard_edit_prediction(false, cx);
7000 return None;
7001 }
7002
7003 self.update_visible_edit_prediction(window, cx);
7004
7005 if !user_requested
7006 && (!self.should_show_edit_predictions()
7007 || !self.is_focused(window)
7008 || buffer.read(cx).is_empty())
7009 {
7010 self.discard_edit_prediction(false, cx);
7011 return None;
7012 }
7013
7014 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7015 Some(())
7016 }
7017
7018 fn show_edit_predictions_in_menu(&self) -> bool {
7019 match self.edit_prediction_settings {
7020 EditPredictionSettings::Disabled => false,
7021 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7022 }
7023 }
7024
7025 pub fn edit_predictions_enabled(&self) -> bool {
7026 match self.edit_prediction_settings {
7027 EditPredictionSettings::Disabled => false,
7028 EditPredictionSettings::Enabled { .. } => true,
7029 }
7030 }
7031
7032 fn edit_prediction_requires_modifier(&self) -> bool {
7033 match self.edit_prediction_settings {
7034 EditPredictionSettings::Disabled => false,
7035 EditPredictionSettings::Enabled {
7036 preview_requires_modifier,
7037 ..
7038 } => preview_requires_modifier,
7039 }
7040 }
7041
7042 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7043 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7044 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7045 self.discard_edit_prediction(false, cx);
7046 } else {
7047 let selection = self.selections.newest_anchor();
7048 let cursor = selection.head();
7049
7050 if let Some((buffer, cursor_buffer_position)) =
7051 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7052 {
7053 self.edit_prediction_settings =
7054 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7055 }
7056 }
7057 }
7058
7059 fn edit_prediction_settings_at_position(
7060 &self,
7061 buffer: &Entity<Buffer>,
7062 buffer_position: language::Anchor,
7063 cx: &App,
7064 ) -> EditPredictionSettings {
7065 if !self.mode.is_full()
7066 || !self.show_edit_predictions_override.unwrap_or(true)
7067 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7068 {
7069 return EditPredictionSettings::Disabled;
7070 }
7071
7072 let buffer = buffer.read(cx);
7073
7074 let file = buffer.file();
7075
7076 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7077 return EditPredictionSettings::Disabled;
7078 };
7079
7080 let by_provider = matches!(
7081 self.menu_edit_predictions_policy,
7082 MenuEditPredictionsPolicy::ByProvider
7083 );
7084
7085 let show_in_menu = by_provider
7086 && self
7087 .edit_prediction_provider
7088 .as_ref()
7089 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7090
7091 let preview_requires_modifier =
7092 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7093
7094 EditPredictionSettings::Enabled {
7095 show_in_menu,
7096 preview_requires_modifier,
7097 }
7098 }
7099
7100 fn should_show_edit_predictions(&self) -> bool {
7101 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7102 }
7103
7104 pub fn edit_prediction_preview_is_active(&self) -> bool {
7105 matches!(
7106 self.edit_prediction_preview,
7107 EditPredictionPreview::Active { .. }
7108 )
7109 }
7110
7111 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7112 let cursor = self.selections.newest_anchor().head();
7113 if let Some((buffer, cursor_position)) =
7114 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7115 {
7116 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7117 } else {
7118 false
7119 }
7120 }
7121
7122 pub fn supports_minimap(&self, cx: &App) -> bool {
7123 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7124 }
7125
7126 fn edit_predictions_enabled_in_buffer(
7127 &self,
7128 buffer: &Entity<Buffer>,
7129 buffer_position: language::Anchor,
7130 cx: &App,
7131 ) -> bool {
7132 maybe!({
7133 if self.read_only(cx) {
7134 return Some(false);
7135 }
7136 let provider = self.edit_prediction_provider()?;
7137 if !provider.is_enabled(buffer, buffer_position, cx) {
7138 return Some(false);
7139 }
7140 let buffer = buffer.read(cx);
7141 let Some(file) = buffer.file() else {
7142 return Some(true);
7143 };
7144 let settings = all_language_settings(Some(file), cx);
7145 Some(settings.edit_predictions_enabled_for_file(file, cx))
7146 })
7147 .unwrap_or(false)
7148 }
7149
7150 fn cycle_edit_prediction(
7151 &mut self,
7152 direction: Direction,
7153 window: &mut Window,
7154 cx: &mut Context<Self>,
7155 ) -> Option<()> {
7156 let provider = self.edit_prediction_provider()?;
7157 let cursor = self.selections.newest_anchor().head();
7158 let (buffer, cursor_buffer_position) =
7159 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7160 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7161 return None;
7162 }
7163
7164 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7165 self.update_visible_edit_prediction(window, cx);
7166
7167 Some(())
7168 }
7169
7170 pub fn show_edit_prediction(
7171 &mut self,
7172 _: &ShowEditPrediction,
7173 window: &mut Window,
7174 cx: &mut Context<Self>,
7175 ) {
7176 if !self.has_active_edit_prediction() {
7177 self.refresh_edit_prediction(false, true, window, cx);
7178 return;
7179 }
7180
7181 self.update_visible_edit_prediction(window, cx);
7182 }
7183
7184 pub fn display_cursor_names(
7185 &mut self,
7186 _: &DisplayCursorNames,
7187 window: &mut Window,
7188 cx: &mut Context<Self>,
7189 ) {
7190 self.show_cursor_names(window, cx);
7191 }
7192
7193 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7194 self.show_cursor_names = true;
7195 cx.notify();
7196 cx.spawn_in(window, async move |this, cx| {
7197 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7198 this.update(cx, |this, cx| {
7199 this.show_cursor_names = false;
7200 cx.notify()
7201 })
7202 .ok()
7203 })
7204 .detach();
7205 }
7206
7207 pub fn next_edit_prediction(
7208 &mut self,
7209 _: &NextEditPrediction,
7210 window: &mut Window,
7211 cx: &mut Context<Self>,
7212 ) {
7213 if self.has_active_edit_prediction() {
7214 self.cycle_edit_prediction(Direction::Next, window, cx);
7215 } else {
7216 let is_copilot_disabled = self
7217 .refresh_edit_prediction(false, true, window, cx)
7218 .is_none();
7219 if is_copilot_disabled {
7220 cx.propagate();
7221 }
7222 }
7223 }
7224
7225 pub fn previous_edit_prediction(
7226 &mut self,
7227 _: &PreviousEditPrediction,
7228 window: &mut Window,
7229 cx: &mut Context<Self>,
7230 ) {
7231 if self.has_active_edit_prediction() {
7232 self.cycle_edit_prediction(Direction::Prev, window, cx);
7233 } else {
7234 let is_copilot_disabled = self
7235 .refresh_edit_prediction(false, true, window, cx)
7236 .is_none();
7237 if is_copilot_disabled {
7238 cx.propagate();
7239 }
7240 }
7241 }
7242
7243 pub fn accept_edit_prediction(
7244 &mut self,
7245 _: &AcceptEditPrediction,
7246 window: &mut Window,
7247 cx: &mut Context<Self>,
7248 ) {
7249 if self.show_edit_predictions_in_menu() {
7250 self.hide_context_menu(window, cx);
7251 }
7252
7253 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7254 return;
7255 };
7256
7257 match &active_edit_prediction.completion {
7258 EditPrediction::MoveWithin { target, .. } => {
7259 let target = *target;
7260
7261 if let Some(position_map) = &self.last_position_map {
7262 if position_map
7263 .visible_row_range
7264 .contains(&target.to_display_point(&position_map.snapshot).row())
7265 || !self.edit_prediction_requires_modifier()
7266 {
7267 self.unfold_ranges(&[target..target], true, false, cx);
7268 // Note that this is also done in vim's handler of the Tab action.
7269 self.change_selections(
7270 SelectionEffects::scroll(Autoscroll::newest()),
7271 window,
7272 cx,
7273 |selections| {
7274 selections.select_anchor_ranges([target..target]);
7275 },
7276 );
7277 self.clear_row_highlights::<EditPredictionPreview>();
7278
7279 self.edit_prediction_preview
7280 .set_previous_scroll_position(None);
7281 } else {
7282 self.edit_prediction_preview
7283 .set_previous_scroll_position(Some(
7284 position_map.snapshot.scroll_anchor,
7285 ));
7286
7287 self.highlight_rows::<EditPredictionPreview>(
7288 target..target,
7289 cx.theme().colors().editor_highlighted_line_background,
7290 RowHighlightOptions {
7291 autoscroll: true,
7292 ..Default::default()
7293 },
7294 cx,
7295 );
7296 self.request_autoscroll(Autoscroll::fit(), cx);
7297 }
7298 }
7299 }
7300 EditPrediction::MoveOutside { snapshot, target } => {
7301 if let Some(workspace) = self.workspace() {
7302 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7303 .detach_and_log_err(cx);
7304 }
7305 }
7306 EditPrediction::Edit { edits, .. } => {
7307 self.report_edit_prediction_event(
7308 active_edit_prediction.completion_id.clone(),
7309 true,
7310 cx,
7311 );
7312
7313 if let Some(provider) = self.edit_prediction_provider() {
7314 provider.accept(cx);
7315 }
7316
7317 // Store the transaction ID and selections before applying the edit
7318 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7319
7320 let snapshot = self.buffer.read(cx).snapshot(cx);
7321 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7322
7323 self.buffer.update(cx, |buffer, cx| {
7324 buffer.edit(edits.iter().cloned(), None, cx)
7325 });
7326
7327 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7328 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7329 });
7330
7331 let selections = self.selections.disjoint_anchors_arc();
7332 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7333 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7334 if has_new_transaction {
7335 self.selection_history
7336 .insert_transaction(transaction_id_now, selections);
7337 }
7338 }
7339
7340 self.update_visible_edit_prediction(window, cx);
7341 if self.active_edit_prediction.is_none() {
7342 self.refresh_edit_prediction(true, true, window, cx);
7343 }
7344
7345 cx.notify();
7346 }
7347 }
7348
7349 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7350 }
7351
7352 pub fn accept_partial_edit_prediction(
7353 &mut self,
7354 _: &AcceptPartialEditPrediction,
7355 window: &mut Window,
7356 cx: &mut Context<Self>,
7357 ) {
7358 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7359 return;
7360 };
7361 if self.selections.count() != 1 {
7362 return;
7363 }
7364
7365 match &active_edit_prediction.completion {
7366 EditPrediction::MoveWithin { target, .. } => {
7367 let target = *target;
7368 self.change_selections(
7369 SelectionEffects::scroll(Autoscroll::newest()),
7370 window,
7371 cx,
7372 |selections| {
7373 selections.select_anchor_ranges([target..target]);
7374 },
7375 );
7376 }
7377 EditPrediction::MoveOutside { snapshot, target } => {
7378 if let Some(workspace) = self.workspace() {
7379 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7380 .detach_and_log_err(cx);
7381 }
7382 }
7383 EditPrediction::Edit { edits, .. } => {
7384 self.report_edit_prediction_event(
7385 active_edit_prediction.completion_id.clone(),
7386 true,
7387 cx,
7388 );
7389
7390 // Find an insertion that starts at the cursor position.
7391 let snapshot = self.buffer.read(cx).snapshot(cx);
7392 let cursor_offset = self
7393 .selections
7394 .newest::<usize>(&self.display_snapshot(cx))
7395 .head();
7396 let insertion = edits.iter().find_map(|(range, text)| {
7397 let range = range.to_offset(&snapshot);
7398 if range.is_empty() && range.start == cursor_offset {
7399 Some(text)
7400 } else {
7401 None
7402 }
7403 });
7404
7405 if let Some(text) = insertion {
7406 let mut partial_completion = text
7407 .chars()
7408 .by_ref()
7409 .take_while(|c| c.is_alphabetic())
7410 .collect::<String>();
7411 if partial_completion.is_empty() {
7412 partial_completion = text
7413 .chars()
7414 .by_ref()
7415 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7416 .collect::<String>();
7417 }
7418
7419 cx.emit(EditorEvent::InputHandled {
7420 utf16_range_to_replace: None,
7421 text: partial_completion.clone().into(),
7422 });
7423
7424 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7425
7426 self.refresh_edit_prediction(true, true, window, cx);
7427 cx.notify();
7428 } else {
7429 self.accept_edit_prediction(&Default::default(), window, cx);
7430 }
7431 }
7432 }
7433 }
7434
7435 fn discard_edit_prediction(
7436 &mut self,
7437 should_report_edit_prediction_event: bool,
7438 cx: &mut Context<Self>,
7439 ) -> bool {
7440 if should_report_edit_prediction_event {
7441 let completion_id = self
7442 .active_edit_prediction
7443 .as_ref()
7444 .and_then(|active_completion| active_completion.completion_id.clone());
7445
7446 self.report_edit_prediction_event(completion_id, false, cx);
7447 }
7448
7449 if let Some(provider) = self.edit_prediction_provider() {
7450 provider.discard(cx);
7451 }
7452
7453 self.take_active_edit_prediction(cx)
7454 }
7455
7456 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7457 let Some(provider) = self.edit_prediction_provider() else {
7458 return;
7459 };
7460
7461 let Some((_, buffer, _)) = self
7462 .buffer
7463 .read(cx)
7464 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7465 else {
7466 return;
7467 };
7468
7469 let extension = buffer
7470 .read(cx)
7471 .file()
7472 .and_then(|file| Some(file.path().extension()?.to_string()));
7473
7474 let event_type = match accepted {
7475 true => "Edit Prediction Accepted",
7476 false => "Edit Prediction Discarded",
7477 };
7478 telemetry::event!(
7479 event_type,
7480 provider = provider.name(),
7481 prediction_id = id,
7482 suggestion_accepted = accepted,
7483 file_extension = extension,
7484 );
7485 }
7486
7487 fn open_editor_at_anchor(
7488 snapshot: &language::BufferSnapshot,
7489 target: language::Anchor,
7490 workspace: &Entity<Workspace>,
7491 window: &mut Window,
7492 cx: &mut App,
7493 ) -> Task<Result<()>> {
7494 workspace.update(cx, |workspace, cx| {
7495 let path = snapshot.file().map(|file| file.full_path(cx));
7496 let Some(path) =
7497 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7498 else {
7499 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7500 };
7501 let target = text::ToPoint::to_point(&target, snapshot);
7502 let item = workspace.open_path(path, None, true, window, cx);
7503 window.spawn(cx, async move |cx| {
7504 let Some(editor) = item.await?.downcast::<Editor>() else {
7505 return Ok(());
7506 };
7507 editor
7508 .update_in(cx, |editor, window, cx| {
7509 editor.go_to_singleton_buffer_point(target, window, cx);
7510 })
7511 .ok();
7512 anyhow::Ok(())
7513 })
7514 })
7515 }
7516
7517 pub fn has_active_edit_prediction(&self) -> bool {
7518 self.active_edit_prediction.is_some()
7519 }
7520
7521 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7522 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7523 return false;
7524 };
7525
7526 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7527 self.clear_highlights::<EditPredictionHighlight>(cx);
7528 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7529 true
7530 }
7531
7532 /// Returns true when we're displaying the edit prediction popover below the cursor
7533 /// like we are not previewing and the LSP autocomplete menu is visible
7534 /// or we are in `when_holding_modifier` mode.
7535 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7536 if self.edit_prediction_preview_is_active()
7537 || !self.show_edit_predictions_in_menu()
7538 || !self.edit_predictions_enabled()
7539 {
7540 return false;
7541 }
7542
7543 if self.has_visible_completions_menu() {
7544 return true;
7545 }
7546
7547 has_completion && self.edit_prediction_requires_modifier()
7548 }
7549
7550 fn handle_modifiers_changed(
7551 &mut self,
7552 modifiers: Modifiers,
7553 position_map: &PositionMap,
7554 window: &mut Window,
7555 cx: &mut Context<Self>,
7556 ) {
7557 if self.show_edit_predictions_in_menu() {
7558 self.update_edit_prediction_preview(&modifiers, window, cx);
7559 }
7560
7561 self.update_selection_mode(&modifiers, position_map, window, cx);
7562
7563 let mouse_position = window.mouse_position();
7564 if !position_map.text_hitbox.is_hovered(window) {
7565 return;
7566 }
7567
7568 self.update_hovered_link(
7569 position_map.point_for_position(mouse_position),
7570 &position_map.snapshot,
7571 modifiers,
7572 window,
7573 cx,
7574 )
7575 }
7576
7577 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7578 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7579 if invert {
7580 match multi_cursor_setting {
7581 MultiCursorModifier::Alt => modifiers.alt,
7582 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7583 }
7584 } else {
7585 match multi_cursor_setting {
7586 MultiCursorModifier::Alt => modifiers.secondary(),
7587 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7588 }
7589 }
7590 }
7591
7592 fn columnar_selection_mode(
7593 modifiers: &Modifiers,
7594 cx: &mut Context<Self>,
7595 ) -> Option<ColumnarMode> {
7596 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7597 if Self::multi_cursor_modifier(false, modifiers, cx) {
7598 Some(ColumnarMode::FromMouse)
7599 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7600 Some(ColumnarMode::FromSelection)
7601 } else {
7602 None
7603 }
7604 } else {
7605 None
7606 }
7607 }
7608
7609 fn update_selection_mode(
7610 &mut self,
7611 modifiers: &Modifiers,
7612 position_map: &PositionMap,
7613 window: &mut Window,
7614 cx: &mut Context<Self>,
7615 ) {
7616 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7617 return;
7618 };
7619 if self.selections.pending_anchor().is_none() {
7620 return;
7621 }
7622
7623 let mouse_position = window.mouse_position();
7624 let point_for_position = position_map.point_for_position(mouse_position);
7625 let position = point_for_position.previous_valid;
7626
7627 self.select(
7628 SelectPhase::BeginColumnar {
7629 position,
7630 reset: false,
7631 mode,
7632 goal_column: point_for_position.exact_unclipped.column(),
7633 },
7634 window,
7635 cx,
7636 );
7637 }
7638
7639 fn update_edit_prediction_preview(
7640 &mut self,
7641 modifiers: &Modifiers,
7642 window: &mut Window,
7643 cx: &mut Context<Self>,
7644 ) {
7645 let mut modifiers_held = false;
7646 if let Some(accept_keystroke) = self
7647 .accept_edit_prediction_keybind(false, window, cx)
7648 .keystroke()
7649 {
7650 modifiers_held = modifiers_held
7651 || (accept_keystroke.modifiers() == modifiers
7652 && accept_keystroke.modifiers().modified());
7653 };
7654 if let Some(accept_partial_keystroke) = self
7655 .accept_edit_prediction_keybind(true, window, cx)
7656 .keystroke()
7657 {
7658 modifiers_held = modifiers_held
7659 || (accept_partial_keystroke.modifiers() == modifiers
7660 && accept_partial_keystroke.modifiers().modified());
7661 }
7662
7663 if modifiers_held {
7664 if matches!(
7665 self.edit_prediction_preview,
7666 EditPredictionPreview::Inactive { .. }
7667 ) {
7668 self.edit_prediction_preview = EditPredictionPreview::Active {
7669 previous_scroll_position: None,
7670 since: Instant::now(),
7671 };
7672
7673 self.update_visible_edit_prediction(window, cx);
7674 cx.notify();
7675 }
7676 } else if let EditPredictionPreview::Active {
7677 previous_scroll_position,
7678 since,
7679 } = self.edit_prediction_preview
7680 {
7681 if let (Some(previous_scroll_position), Some(position_map)) =
7682 (previous_scroll_position, self.last_position_map.as_ref())
7683 {
7684 self.set_scroll_position(
7685 previous_scroll_position
7686 .scroll_position(&position_map.snapshot.display_snapshot),
7687 window,
7688 cx,
7689 );
7690 }
7691
7692 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7693 released_too_fast: since.elapsed() < Duration::from_millis(200),
7694 };
7695 self.clear_row_highlights::<EditPredictionPreview>();
7696 self.update_visible_edit_prediction(window, cx);
7697 cx.notify();
7698 }
7699 }
7700
7701 fn update_visible_edit_prediction(
7702 &mut self,
7703 _window: &mut Window,
7704 cx: &mut Context<Self>,
7705 ) -> Option<()> {
7706 if DisableAiSettings::get_global(cx).disable_ai {
7707 return None;
7708 }
7709
7710 if self.ime_transaction.is_some() {
7711 self.discard_edit_prediction(false, cx);
7712 return None;
7713 }
7714
7715 let selection = self.selections.newest_anchor();
7716 let cursor = selection.head();
7717 let multibuffer = self.buffer.read(cx).snapshot(cx);
7718 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7719 let excerpt_id = cursor.excerpt_id;
7720
7721 let show_in_menu = self.show_edit_predictions_in_menu();
7722 let completions_menu_has_precedence = !show_in_menu
7723 && (self.context_menu.borrow().is_some()
7724 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7725
7726 if completions_menu_has_precedence
7727 || !offset_selection.is_empty()
7728 || self
7729 .active_edit_prediction
7730 .as_ref()
7731 .is_some_and(|completion| {
7732 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7733 return false;
7734 };
7735 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7736 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7737 !invalidation_range.contains(&offset_selection.head())
7738 })
7739 {
7740 self.discard_edit_prediction(false, cx);
7741 return None;
7742 }
7743
7744 self.take_active_edit_prediction(cx);
7745 let Some(provider) = self.edit_prediction_provider() else {
7746 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7747 return None;
7748 };
7749
7750 let (buffer, cursor_buffer_position) =
7751 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7752
7753 self.edit_prediction_settings =
7754 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7755
7756 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7757
7758 if self.edit_prediction_indent_conflict {
7759 let cursor_point = cursor.to_point(&multibuffer);
7760
7761 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7762
7763 if let Some((_, indent)) = indents.iter().next()
7764 && indent.len == cursor_point.column
7765 {
7766 self.edit_prediction_indent_conflict = false;
7767 }
7768 }
7769
7770 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7771
7772 let (completion_id, edits, edit_preview) = match edit_prediction {
7773 edit_prediction::EditPrediction::Local {
7774 id,
7775 edits,
7776 edit_preview,
7777 } => (id, edits, edit_preview),
7778 edit_prediction::EditPrediction::Jump {
7779 id,
7780 snapshot,
7781 target,
7782 } => {
7783 self.stale_edit_prediction_in_menu = None;
7784 self.active_edit_prediction = Some(EditPredictionState {
7785 inlay_ids: vec![],
7786 completion: EditPrediction::MoveOutside { snapshot, target },
7787 completion_id: id,
7788 invalidation_range: None,
7789 });
7790 cx.notify();
7791 return Some(());
7792 }
7793 };
7794
7795 let edits = edits
7796 .into_iter()
7797 .flat_map(|(range, new_text)| {
7798 Some((
7799 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7800 new_text,
7801 ))
7802 })
7803 .collect::<Vec<_>>();
7804 if edits.is_empty() {
7805 return None;
7806 }
7807
7808 let first_edit_start = edits.first().unwrap().0.start;
7809 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7810 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7811
7812 let last_edit_end = edits.last().unwrap().0.end;
7813 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7814 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7815
7816 let cursor_row = cursor.to_point(&multibuffer).row;
7817
7818 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7819
7820 let mut inlay_ids = Vec::new();
7821 let invalidation_row_range;
7822 let move_invalidation_row_range = if cursor_row < edit_start_row {
7823 Some(cursor_row..edit_end_row)
7824 } else if cursor_row > edit_end_row {
7825 Some(edit_start_row..cursor_row)
7826 } else {
7827 None
7828 };
7829 let supports_jump = self
7830 .edit_prediction_provider
7831 .as_ref()
7832 .map(|provider| provider.provider.supports_jump_to_edit())
7833 .unwrap_or(true);
7834
7835 let is_move = supports_jump
7836 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7837 let completion = if is_move {
7838 invalidation_row_range =
7839 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7840 let target = first_edit_start;
7841 EditPrediction::MoveWithin { target, snapshot }
7842 } else {
7843 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7844 && !self.edit_predictions_hidden_for_vim_mode;
7845
7846 if show_completions_in_buffer {
7847 if edits
7848 .iter()
7849 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7850 {
7851 let mut inlays = Vec::new();
7852 for (range, new_text) in &edits {
7853 let inlay = Inlay::edit_prediction(
7854 post_inc(&mut self.next_inlay_id),
7855 range.start,
7856 new_text.as_str(),
7857 );
7858 inlay_ids.push(inlay.id);
7859 inlays.push(inlay);
7860 }
7861
7862 self.splice_inlays(&[], inlays, cx);
7863 } else {
7864 let background_color = cx.theme().status().deleted_background;
7865 self.highlight_text::<EditPredictionHighlight>(
7866 edits.iter().map(|(range, _)| range.clone()).collect(),
7867 HighlightStyle {
7868 background_color: Some(background_color),
7869 ..Default::default()
7870 },
7871 cx,
7872 );
7873 }
7874 }
7875
7876 invalidation_row_range = edit_start_row..edit_end_row;
7877
7878 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7879 if provider.show_tab_accept_marker() {
7880 EditDisplayMode::TabAccept
7881 } else {
7882 EditDisplayMode::Inline
7883 }
7884 } else {
7885 EditDisplayMode::DiffPopover
7886 };
7887
7888 EditPrediction::Edit {
7889 edits,
7890 edit_preview,
7891 display_mode,
7892 snapshot,
7893 }
7894 };
7895
7896 let invalidation_range = multibuffer
7897 .anchor_before(Point::new(invalidation_row_range.start, 0))
7898 ..multibuffer.anchor_after(Point::new(
7899 invalidation_row_range.end,
7900 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7901 ));
7902
7903 self.stale_edit_prediction_in_menu = None;
7904 self.active_edit_prediction = Some(EditPredictionState {
7905 inlay_ids,
7906 completion,
7907 completion_id,
7908 invalidation_range: Some(invalidation_range),
7909 });
7910
7911 cx.notify();
7912
7913 Some(())
7914 }
7915
7916 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7917 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7918 }
7919
7920 fn clear_tasks(&mut self) {
7921 self.tasks.clear()
7922 }
7923
7924 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7925 if self.tasks.insert(key, value).is_some() {
7926 // This case should hopefully be rare, but just in case...
7927 log::error!(
7928 "multiple different run targets found on a single line, only the last target will be rendered"
7929 )
7930 }
7931 }
7932
7933 /// Get all display points of breakpoints that will be rendered within editor
7934 ///
7935 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7936 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7937 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7938 fn active_breakpoints(
7939 &self,
7940 range: Range<DisplayRow>,
7941 window: &mut Window,
7942 cx: &mut Context<Self>,
7943 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7944 let mut breakpoint_display_points = HashMap::default();
7945
7946 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7947 return breakpoint_display_points;
7948 };
7949
7950 let snapshot = self.snapshot(window, cx);
7951
7952 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7953 let Some(project) = self.project() else {
7954 return breakpoint_display_points;
7955 };
7956
7957 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7958 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7959
7960 for (buffer_snapshot, range, excerpt_id) in
7961 multi_buffer_snapshot.range_to_buffer_ranges(range)
7962 {
7963 let Some(buffer) = project
7964 .read(cx)
7965 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7966 else {
7967 continue;
7968 };
7969 let breakpoints = breakpoint_store.read(cx).breakpoints(
7970 &buffer,
7971 Some(
7972 buffer_snapshot.anchor_before(range.start)
7973 ..buffer_snapshot.anchor_after(range.end),
7974 ),
7975 buffer_snapshot,
7976 cx,
7977 );
7978 for (breakpoint, state) in breakpoints {
7979 let multi_buffer_anchor =
7980 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7981 let position = multi_buffer_anchor
7982 .to_point(&multi_buffer_snapshot)
7983 .to_display_point(&snapshot);
7984
7985 breakpoint_display_points.insert(
7986 position.row(),
7987 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7988 );
7989 }
7990 }
7991
7992 breakpoint_display_points
7993 }
7994
7995 fn breakpoint_context_menu(
7996 &self,
7997 anchor: Anchor,
7998 window: &mut Window,
7999 cx: &mut Context<Self>,
8000 ) -> Entity<ui::ContextMenu> {
8001 let weak_editor = cx.weak_entity();
8002 let focus_handle = self.focus_handle(cx);
8003
8004 let row = self
8005 .buffer
8006 .read(cx)
8007 .snapshot(cx)
8008 .summary_for_anchor::<Point>(&anchor)
8009 .row;
8010
8011 let breakpoint = self
8012 .breakpoint_at_row(row, window, cx)
8013 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8014
8015 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8016 "Edit Log Breakpoint"
8017 } else {
8018 "Set Log Breakpoint"
8019 };
8020
8021 let condition_breakpoint_msg = if breakpoint
8022 .as_ref()
8023 .is_some_and(|bp| bp.1.condition.is_some())
8024 {
8025 "Edit Condition Breakpoint"
8026 } else {
8027 "Set Condition Breakpoint"
8028 };
8029
8030 let hit_condition_breakpoint_msg = if breakpoint
8031 .as_ref()
8032 .is_some_and(|bp| bp.1.hit_condition.is_some())
8033 {
8034 "Edit Hit Condition Breakpoint"
8035 } else {
8036 "Set Hit Condition Breakpoint"
8037 };
8038
8039 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8040 "Unset Breakpoint"
8041 } else {
8042 "Set Breakpoint"
8043 };
8044
8045 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8046
8047 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8048 BreakpointState::Enabled => Some("Disable"),
8049 BreakpointState::Disabled => Some("Enable"),
8050 });
8051
8052 let (anchor, breakpoint) =
8053 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8054
8055 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8056 menu.on_blur_subscription(Subscription::new(|| {}))
8057 .context(focus_handle)
8058 .when(run_to_cursor, |this| {
8059 let weak_editor = weak_editor.clone();
8060 this.entry("Run to cursor", None, move |window, cx| {
8061 weak_editor
8062 .update(cx, |editor, cx| {
8063 editor.change_selections(
8064 SelectionEffects::no_scroll(),
8065 window,
8066 cx,
8067 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8068 );
8069 })
8070 .ok();
8071
8072 window.dispatch_action(Box::new(RunToCursor), cx);
8073 })
8074 .separator()
8075 })
8076 .when_some(toggle_state_msg, |this, msg| {
8077 this.entry(msg, None, {
8078 let weak_editor = weak_editor.clone();
8079 let breakpoint = breakpoint.clone();
8080 move |_window, cx| {
8081 weak_editor
8082 .update(cx, |this, cx| {
8083 this.edit_breakpoint_at_anchor(
8084 anchor,
8085 breakpoint.as_ref().clone(),
8086 BreakpointEditAction::InvertState,
8087 cx,
8088 );
8089 })
8090 .log_err();
8091 }
8092 })
8093 })
8094 .entry(set_breakpoint_msg, None, {
8095 let weak_editor = weak_editor.clone();
8096 let breakpoint = breakpoint.clone();
8097 move |_window, cx| {
8098 weak_editor
8099 .update(cx, |this, cx| {
8100 this.edit_breakpoint_at_anchor(
8101 anchor,
8102 breakpoint.as_ref().clone(),
8103 BreakpointEditAction::Toggle,
8104 cx,
8105 );
8106 })
8107 .log_err();
8108 }
8109 })
8110 .entry(log_breakpoint_msg, None, {
8111 let breakpoint = breakpoint.clone();
8112 let weak_editor = weak_editor.clone();
8113 move |window, cx| {
8114 weak_editor
8115 .update(cx, |this, cx| {
8116 this.add_edit_breakpoint_block(
8117 anchor,
8118 breakpoint.as_ref(),
8119 BreakpointPromptEditAction::Log,
8120 window,
8121 cx,
8122 );
8123 })
8124 .log_err();
8125 }
8126 })
8127 .entry(condition_breakpoint_msg, None, {
8128 let breakpoint = breakpoint.clone();
8129 let weak_editor = weak_editor.clone();
8130 move |window, cx| {
8131 weak_editor
8132 .update(cx, |this, cx| {
8133 this.add_edit_breakpoint_block(
8134 anchor,
8135 breakpoint.as_ref(),
8136 BreakpointPromptEditAction::Condition,
8137 window,
8138 cx,
8139 );
8140 })
8141 .log_err();
8142 }
8143 })
8144 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8145 weak_editor
8146 .update(cx, |this, cx| {
8147 this.add_edit_breakpoint_block(
8148 anchor,
8149 breakpoint.as_ref(),
8150 BreakpointPromptEditAction::HitCondition,
8151 window,
8152 cx,
8153 );
8154 })
8155 .log_err();
8156 })
8157 })
8158 }
8159
8160 fn render_breakpoint(
8161 &self,
8162 position: Anchor,
8163 row: DisplayRow,
8164 breakpoint: &Breakpoint,
8165 state: Option<BreakpointSessionState>,
8166 cx: &mut Context<Self>,
8167 ) -> IconButton {
8168 let is_rejected = state.is_some_and(|s| !s.verified);
8169 // Is it a breakpoint that shows up when hovering over gutter?
8170 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8171 (false, false),
8172 |PhantomBreakpointIndicator {
8173 is_active,
8174 display_row,
8175 collides_with_existing_breakpoint,
8176 }| {
8177 (
8178 is_active && display_row == row,
8179 collides_with_existing_breakpoint,
8180 )
8181 },
8182 );
8183
8184 let (color, icon) = {
8185 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8186 (false, false) => ui::IconName::DebugBreakpoint,
8187 (true, false) => ui::IconName::DebugLogBreakpoint,
8188 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8189 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8190 };
8191
8192 let color = if is_phantom {
8193 Color::Hint
8194 } else if is_rejected {
8195 Color::Disabled
8196 } else {
8197 Color::Debugger
8198 };
8199
8200 (color, icon)
8201 };
8202
8203 let breakpoint = Arc::from(breakpoint.clone());
8204
8205 let alt_as_text = gpui::Keystroke {
8206 modifiers: Modifiers::secondary_key(),
8207 ..Default::default()
8208 };
8209 let primary_action_text = if breakpoint.is_disabled() {
8210 "Enable breakpoint"
8211 } else if is_phantom && !collides_with_existing {
8212 "Set breakpoint"
8213 } else {
8214 "Unset breakpoint"
8215 };
8216 let focus_handle = self.focus_handle.clone();
8217
8218 let meta = if is_rejected {
8219 SharedString::from("No executable code is associated with this line.")
8220 } else if collides_with_existing && !breakpoint.is_disabled() {
8221 SharedString::from(format!(
8222 "{alt_as_text}-click to disable,\nright-click for more options."
8223 ))
8224 } else {
8225 SharedString::from("Right-click for more options.")
8226 };
8227 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8228 .icon_size(IconSize::XSmall)
8229 .size(ui::ButtonSize::None)
8230 .when(is_rejected, |this| {
8231 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8232 })
8233 .icon_color(color)
8234 .style(ButtonStyle::Transparent)
8235 .on_click(cx.listener({
8236 move |editor, event: &ClickEvent, window, cx| {
8237 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8238 BreakpointEditAction::InvertState
8239 } else {
8240 BreakpointEditAction::Toggle
8241 };
8242
8243 window.focus(&editor.focus_handle(cx));
8244 editor.edit_breakpoint_at_anchor(
8245 position,
8246 breakpoint.as_ref().clone(),
8247 edit_action,
8248 cx,
8249 );
8250 }
8251 }))
8252 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8253 editor.set_breakpoint_context_menu(
8254 row,
8255 Some(position),
8256 event.position(),
8257 window,
8258 cx,
8259 );
8260 }))
8261 .tooltip(move |_window, cx| {
8262 Tooltip::with_meta_in(
8263 primary_action_text,
8264 Some(&ToggleBreakpoint),
8265 meta.clone(),
8266 &focus_handle,
8267 cx,
8268 )
8269 })
8270 }
8271
8272 fn build_tasks_context(
8273 project: &Entity<Project>,
8274 buffer: &Entity<Buffer>,
8275 buffer_row: u32,
8276 tasks: &Arc<RunnableTasks>,
8277 cx: &mut Context<Self>,
8278 ) -> Task<Option<task::TaskContext>> {
8279 let position = Point::new(buffer_row, tasks.column);
8280 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8281 let location = Location {
8282 buffer: buffer.clone(),
8283 range: range_start..range_start,
8284 };
8285 // Fill in the environmental variables from the tree-sitter captures
8286 let mut captured_task_variables = TaskVariables::default();
8287 for (capture_name, value) in tasks.extra_variables.clone() {
8288 captured_task_variables.insert(
8289 task::VariableName::Custom(capture_name.into()),
8290 value.clone(),
8291 );
8292 }
8293 project.update(cx, |project, cx| {
8294 project.task_store().update(cx, |task_store, cx| {
8295 task_store.task_context_for_location(captured_task_variables, location, cx)
8296 })
8297 })
8298 }
8299
8300 pub fn spawn_nearest_task(
8301 &mut self,
8302 action: &SpawnNearestTask,
8303 window: &mut Window,
8304 cx: &mut Context<Self>,
8305 ) {
8306 let Some((workspace, _)) = self.workspace.clone() else {
8307 return;
8308 };
8309 let Some(project) = self.project.clone() else {
8310 return;
8311 };
8312
8313 // Try to find a closest, enclosing node using tree-sitter that has a task
8314 let Some((buffer, buffer_row, tasks)) = self
8315 .find_enclosing_node_task(cx)
8316 // Or find the task that's closest in row-distance.
8317 .or_else(|| self.find_closest_task(cx))
8318 else {
8319 return;
8320 };
8321
8322 let reveal_strategy = action.reveal;
8323 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8324 cx.spawn_in(window, async move |_, cx| {
8325 let context = task_context.await?;
8326 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8327
8328 let resolved = &mut resolved_task.resolved;
8329 resolved.reveal = reveal_strategy;
8330
8331 workspace
8332 .update_in(cx, |workspace, window, cx| {
8333 workspace.schedule_resolved_task(
8334 task_source_kind,
8335 resolved_task,
8336 false,
8337 window,
8338 cx,
8339 );
8340 })
8341 .ok()
8342 })
8343 .detach();
8344 }
8345
8346 fn find_closest_task(
8347 &mut self,
8348 cx: &mut Context<Self>,
8349 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8350 let cursor_row = self
8351 .selections
8352 .newest_adjusted(&self.display_snapshot(cx))
8353 .head()
8354 .row;
8355
8356 let ((buffer_id, row), tasks) = self
8357 .tasks
8358 .iter()
8359 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8360
8361 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8362 let tasks = Arc::new(tasks.to_owned());
8363 Some((buffer, *row, tasks))
8364 }
8365
8366 fn find_enclosing_node_task(
8367 &mut self,
8368 cx: &mut Context<Self>,
8369 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8370 let snapshot = self.buffer.read(cx).snapshot(cx);
8371 let offset = self
8372 .selections
8373 .newest::<usize>(&self.display_snapshot(cx))
8374 .head();
8375 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8376 let buffer_id = excerpt.buffer().remote_id();
8377
8378 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8379 let mut cursor = layer.node().walk();
8380
8381 while cursor.goto_first_child_for_byte(offset).is_some() {
8382 if cursor.node().end_byte() == offset {
8383 cursor.goto_next_sibling();
8384 }
8385 }
8386
8387 // Ascend to the smallest ancestor that contains the range and has a task.
8388 loop {
8389 let node = cursor.node();
8390 let node_range = node.byte_range();
8391 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8392
8393 // Check if this node contains our offset
8394 if node_range.start <= offset && node_range.end >= offset {
8395 // If it contains offset, check for task
8396 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8397 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8398 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8399 }
8400 }
8401
8402 if !cursor.goto_parent() {
8403 break;
8404 }
8405 }
8406 None
8407 }
8408
8409 fn render_run_indicator(
8410 &self,
8411 _style: &EditorStyle,
8412 is_active: bool,
8413 row: DisplayRow,
8414 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8415 cx: &mut Context<Self>,
8416 ) -> IconButton {
8417 let color = Color::Muted;
8418 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8419
8420 IconButton::new(
8421 ("run_indicator", row.0 as usize),
8422 ui::IconName::PlayOutlined,
8423 )
8424 .shape(ui::IconButtonShape::Square)
8425 .icon_size(IconSize::XSmall)
8426 .icon_color(color)
8427 .toggle_state(is_active)
8428 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8429 let quick_launch = match e {
8430 ClickEvent::Keyboard(_) => true,
8431 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8432 };
8433
8434 window.focus(&editor.focus_handle(cx));
8435 editor.toggle_code_actions(
8436 &ToggleCodeActions {
8437 deployed_from: Some(CodeActionSource::RunMenu(row)),
8438 quick_launch,
8439 },
8440 window,
8441 cx,
8442 );
8443 }))
8444 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8445 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8446 }))
8447 }
8448
8449 pub fn context_menu_visible(&self) -> bool {
8450 !self.edit_prediction_preview_is_active()
8451 && self
8452 .context_menu
8453 .borrow()
8454 .as_ref()
8455 .is_some_and(|menu| menu.visible())
8456 }
8457
8458 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8459 self.context_menu
8460 .borrow()
8461 .as_ref()
8462 .map(|menu| menu.origin())
8463 }
8464
8465 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8466 self.context_menu_options = Some(options);
8467 }
8468
8469 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8470 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8471
8472 fn render_edit_prediction_popover(
8473 &mut self,
8474 text_bounds: &Bounds<Pixels>,
8475 content_origin: gpui::Point<Pixels>,
8476 right_margin: Pixels,
8477 editor_snapshot: &EditorSnapshot,
8478 visible_row_range: Range<DisplayRow>,
8479 scroll_top: ScrollOffset,
8480 scroll_bottom: ScrollOffset,
8481 line_layouts: &[LineWithInvisibles],
8482 line_height: Pixels,
8483 scroll_position: gpui::Point<ScrollOffset>,
8484 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8485 newest_selection_head: Option<DisplayPoint>,
8486 editor_width: Pixels,
8487 style: &EditorStyle,
8488 window: &mut Window,
8489 cx: &mut App,
8490 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8491 if self.mode().is_minimap() {
8492 return None;
8493 }
8494 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8495
8496 if self.edit_prediction_visible_in_cursor_popover(true) {
8497 return None;
8498 }
8499
8500 match &active_edit_prediction.completion {
8501 EditPrediction::MoveWithin { target, .. } => {
8502 let target_display_point = target.to_display_point(editor_snapshot);
8503
8504 if self.edit_prediction_requires_modifier() {
8505 if !self.edit_prediction_preview_is_active() {
8506 return None;
8507 }
8508
8509 self.render_edit_prediction_modifier_jump_popover(
8510 text_bounds,
8511 content_origin,
8512 visible_row_range,
8513 line_layouts,
8514 line_height,
8515 scroll_pixel_position,
8516 newest_selection_head,
8517 target_display_point,
8518 window,
8519 cx,
8520 )
8521 } else {
8522 self.render_edit_prediction_eager_jump_popover(
8523 text_bounds,
8524 content_origin,
8525 editor_snapshot,
8526 visible_row_range,
8527 scroll_top,
8528 scroll_bottom,
8529 line_height,
8530 scroll_pixel_position,
8531 target_display_point,
8532 editor_width,
8533 window,
8534 cx,
8535 )
8536 }
8537 }
8538 EditPrediction::Edit {
8539 display_mode: EditDisplayMode::Inline,
8540 ..
8541 } => None,
8542 EditPrediction::Edit {
8543 display_mode: EditDisplayMode::TabAccept,
8544 edits,
8545 ..
8546 } => {
8547 let range = &edits.first()?.0;
8548 let target_display_point = range.end.to_display_point(editor_snapshot);
8549
8550 self.render_edit_prediction_end_of_line_popover(
8551 "Accept",
8552 editor_snapshot,
8553 visible_row_range,
8554 target_display_point,
8555 line_height,
8556 scroll_pixel_position,
8557 content_origin,
8558 editor_width,
8559 window,
8560 cx,
8561 )
8562 }
8563 EditPrediction::Edit {
8564 edits,
8565 edit_preview,
8566 display_mode: EditDisplayMode::DiffPopover,
8567 snapshot,
8568 } => self.render_edit_prediction_diff_popover(
8569 text_bounds,
8570 content_origin,
8571 right_margin,
8572 editor_snapshot,
8573 visible_row_range,
8574 line_layouts,
8575 line_height,
8576 scroll_position,
8577 scroll_pixel_position,
8578 newest_selection_head,
8579 editor_width,
8580 style,
8581 edits,
8582 edit_preview,
8583 snapshot,
8584 window,
8585 cx,
8586 ),
8587 EditPrediction::MoveOutside { snapshot, .. } => {
8588 let file_name = snapshot
8589 .file()
8590 .map(|file| file.file_name(cx))
8591 .unwrap_or("untitled");
8592 let mut element = self
8593 .render_edit_prediction_line_popover(
8594 format!("Jump to {file_name}"),
8595 Some(IconName::ZedPredict),
8596 window,
8597 cx,
8598 )
8599 .into_any();
8600
8601 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8602 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8603 let origin_y = text_bounds.size.height - size.height - px(30.);
8604 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8605 element.prepaint_at(origin, window, cx);
8606
8607 Some((element, origin))
8608 }
8609 }
8610 }
8611
8612 fn render_edit_prediction_modifier_jump_popover(
8613 &mut self,
8614 text_bounds: &Bounds<Pixels>,
8615 content_origin: gpui::Point<Pixels>,
8616 visible_row_range: Range<DisplayRow>,
8617 line_layouts: &[LineWithInvisibles],
8618 line_height: Pixels,
8619 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8620 newest_selection_head: Option<DisplayPoint>,
8621 target_display_point: DisplayPoint,
8622 window: &mut Window,
8623 cx: &mut App,
8624 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8625 let scrolled_content_origin =
8626 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8627
8628 const SCROLL_PADDING_Y: Pixels = px(12.);
8629
8630 if target_display_point.row() < visible_row_range.start {
8631 return self.render_edit_prediction_scroll_popover(
8632 |_| SCROLL_PADDING_Y,
8633 IconName::ArrowUp,
8634 visible_row_range,
8635 line_layouts,
8636 newest_selection_head,
8637 scrolled_content_origin,
8638 window,
8639 cx,
8640 );
8641 } else if target_display_point.row() >= visible_row_range.end {
8642 return self.render_edit_prediction_scroll_popover(
8643 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8644 IconName::ArrowDown,
8645 visible_row_range,
8646 line_layouts,
8647 newest_selection_head,
8648 scrolled_content_origin,
8649 window,
8650 cx,
8651 );
8652 }
8653
8654 const POLE_WIDTH: Pixels = px(2.);
8655
8656 let line_layout =
8657 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8658 let target_column = target_display_point.column() as usize;
8659
8660 let target_x = line_layout.x_for_index(target_column);
8661 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8662 - scroll_pixel_position.y;
8663
8664 let flag_on_right = target_x < text_bounds.size.width / 2.;
8665
8666 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8667 border_color.l += 0.001;
8668
8669 let mut element = v_flex()
8670 .items_end()
8671 .when(flag_on_right, |el| el.items_start())
8672 .child(if flag_on_right {
8673 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8674 .rounded_bl(px(0.))
8675 .rounded_tl(px(0.))
8676 .border_l_2()
8677 .border_color(border_color)
8678 } else {
8679 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8680 .rounded_br(px(0.))
8681 .rounded_tr(px(0.))
8682 .border_r_2()
8683 .border_color(border_color)
8684 })
8685 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8686 .into_any();
8687
8688 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8689
8690 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8691 - point(
8692 if flag_on_right {
8693 POLE_WIDTH
8694 } else {
8695 size.width - POLE_WIDTH
8696 },
8697 size.height - line_height,
8698 );
8699
8700 origin.x = origin.x.max(content_origin.x);
8701
8702 element.prepaint_at(origin, window, cx);
8703
8704 Some((element, origin))
8705 }
8706
8707 fn render_edit_prediction_scroll_popover(
8708 &mut self,
8709 to_y: impl Fn(Size<Pixels>) -> Pixels,
8710 scroll_icon: IconName,
8711 visible_row_range: Range<DisplayRow>,
8712 line_layouts: &[LineWithInvisibles],
8713 newest_selection_head: Option<DisplayPoint>,
8714 scrolled_content_origin: gpui::Point<Pixels>,
8715 window: &mut Window,
8716 cx: &mut App,
8717 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8718 let mut element = self
8719 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8720 .into_any();
8721
8722 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8723
8724 let cursor = newest_selection_head?;
8725 let cursor_row_layout =
8726 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8727 let cursor_column = cursor.column() as usize;
8728
8729 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8730
8731 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8732
8733 element.prepaint_at(origin, window, cx);
8734 Some((element, origin))
8735 }
8736
8737 fn render_edit_prediction_eager_jump_popover(
8738 &mut self,
8739 text_bounds: &Bounds<Pixels>,
8740 content_origin: gpui::Point<Pixels>,
8741 editor_snapshot: &EditorSnapshot,
8742 visible_row_range: Range<DisplayRow>,
8743 scroll_top: ScrollOffset,
8744 scroll_bottom: ScrollOffset,
8745 line_height: Pixels,
8746 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8747 target_display_point: DisplayPoint,
8748 editor_width: Pixels,
8749 window: &mut Window,
8750 cx: &mut App,
8751 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8752 if target_display_point.row().as_f64() < scroll_top {
8753 let mut element = self
8754 .render_edit_prediction_line_popover(
8755 "Jump to Edit",
8756 Some(IconName::ArrowUp),
8757 window,
8758 cx,
8759 )
8760 .into_any();
8761
8762 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8763 let offset = point(
8764 (text_bounds.size.width - size.width) / 2.,
8765 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8766 );
8767
8768 let origin = text_bounds.origin + offset;
8769 element.prepaint_at(origin, window, cx);
8770 Some((element, origin))
8771 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8772 let mut element = self
8773 .render_edit_prediction_line_popover(
8774 "Jump to Edit",
8775 Some(IconName::ArrowDown),
8776 window,
8777 cx,
8778 )
8779 .into_any();
8780
8781 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8782 let offset = point(
8783 (text_bounds.size.width - size.width) / 2.,
8784 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8785 );
8786
8787 let origin = text_bounds.origin + offset;
8788 element.prepaint_at(origin, window, cx);
8789 Some((element, origin))
8790 } else {
8791 self.render_edit_prediction_end_of_line_popover(
8792 "Jump to Edit",
8793 editor_snapshot,
8794 visible_row_range,
8795 target_display_point,
8796 line_height,
8797 scroll_pixel_position,
8798 content_origin,
8799 editor_width,
8800 window,
8801 cx,
8802 )
8803 }
8804 }
8805
8806 fn render_edit_prediction_end_of_line_popover(
8807 self: &mut Editor,
8808 label: &'static str,
8809 editor_snapshot: &EditorSnapshot,
8810 visible_row_range: Range<DisplayRow>,
8811 target_display_point: DisplayPoint,
8812 line_height: Pixels,
8813 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8814 content_origin: gpui::Point<Pixels>,
8815 editor_width: Pixels,
8816 window: &mut Window,
8817 cx: &mut App,
8818 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8819 let target_line_end = DisplayPoint::new(
8820 target_display_point.row(),
8821 editor_snapshot.line_len(target_display_point.row()),
8822 );
8823
8824 let mut element = self
8825 .render_edit_prediction_line_popover(label, None, window, cx)
8826 .into_any();
8827
8828 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8829
8830 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8831
8832 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8833 let mut origin = start_point
8834 + line_origin
8835 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8836 origin.x = origin.x.max(content_origin.x);
8837
8838 let max_x = content_origin.x + editor_width - size.width;
8839
8840 if origin.x > max_x {
8841 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8842
8843 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8844 origin.y += offset;
8845 IconName::ArrowUp
8846 } else {
8847 origin.y -= offset;
8848 IconName::ArrowDown
8849 };
8850
8851 element = self
8852 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8853 .into_any();
8854
8855 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8856
8857 origin.x = content_origin.x + editor_width - size.width - px(2.);
8858 }
8859
8860 element.prepaint_at(origin, window, cx);
8861 Some((element, origin))
8862 }
8863
8864 fn render_edit_prediction_diff_popover(
8865 self: &Editor,
8866 text_bounds: &Bounds<Pixels>,
8867 content_origin: gpui::Point<Pixels>,
8868 right_margin: Pixels,
8869 editor_snapshot: &EditorSnapshot,
8870 visible_row_range: Range<DisplayRow>,
8871 line_layouts: &[LineWithInvisibles],
8872 line_height: Pixels,
8873 scroll_position: gpui::Point<ScrollOffset>,
8874 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8875 newest_selection_head: Option<DisplayPoint>,
8876 editor_width: Pixels,
8877 style: &EditorStyle,
8878 edits: &Vec<(Range<Anchor>, String)>,
8879 edit_preview: &Option<language::EditPreview>,
8880 snapshot: &language::BufferSnapshot,
8881 window: &mut Window,
8882 cx: &mut App,
8883 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8884 let edit_start = edits
8885 .first()
8886 .unwrap()
8887 .0
8888 .start
8889 .to_display_point(editor_snapshot);
8890 let edit_end = edits
8891 .last()
8892 .unwrap()
8893 .0
8894 .end
8895 .to_display_point(editor_snapshot);
8896
8897 let is_visible = visible_row_range.contains(&edit_start.row())
8898 || visible_row_range.contains(&edit_end.row());
8899 if !is_visible {
8900 return None;
8901 }
8902
8903 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8904 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8905 } else {
8906 // Fallback for providers without edit_preview
8907 crate::edit_prediction_fallback_text(edits, cx)
8908 };
8909
8910 let styled_text = highlighted_edits.to_styled_text(&style.text);
8911 let line_count = highlighted_edits.text.lines().count();
8912
8913 const BORDER_WIDTH: Pixels = px(1.);
8914
8915 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8916 let has_keybind = keybind.is_some();
8917
8918 let mut element = h_flex()
8919 .items_start()
8920 .child(
8921 h_flex()
8922 .bg(cx.theme().colors().editor_background)
8923 .border(BORDER_WIDTH)
8924 .shadow_xs()
8925 .border_color(cx.theme().colors().border)
8926 .rounded_l_lg()
8927 .when(line_count > 1, |el| el.rounded_br_lg())
8928 .pr_1()
8929 .child(styled_text),
8930 )
8931 .child(
8932 h_flex()
8933 .h(line_height + BORDER_WIDTH * 2.)
8934 .px_1p5()
8935 .gap_1()
8936 // Workaround: For some reason, there's a gap if we don't do this
8937 .ml(-BORDER_WIDTH)
8938 .shadow(vec![gpui::BoxShadow {
8939 color: gpui::black().opacity(0.05),
8940 offset: point(px(1.), px(1.)),
8941 blur_radius: px(2.),
8942 spread_radius: px(0.),
8943 }])
8944 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8945 .border(BORDER_WIDTH)
8946 .border_color(cx.theme().colors().border)
8947 .rounded_r_lg()
8948 .id("edit_prediction_diff_popover_keybind")
8949 .when(!has_keybind, |el| {
8950 let status_colors = cx.theme().status();
8951
8952 el.bg(status_colors.error_background)
8953 .border_color(status_colors.error.opacity(0.6))
8954 .child(Icon::new(IconName::Info).color(Color::Error))
8955 .cursor_default()
8956 .hoverable_tooltip(move |_window, cx| {
8957 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8958 })
8959 })
8960 .children(keybind),
8961 )
8962 .into_any();
8963
8964 let longest_row =
8965 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8966 let longest_line_width = if visible_row_range.contains(&longest_row) {
8967 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8968 } else {
8969 layout_line(
8970 longest_row,
8971 editor_snapshot,
8972 style,
8973 editor_width,
8974 |_| false,
8975 window,
8976 cx,
8977 )
8978 .width
8979 };
8980
8981 let viewport_bounds =
8982 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8983 right: -right_margin,
8984 ..Default::default()
8985 });
8986
8987 let x_after_longest = Pixels::from(
8988 ScrollPixelOffset::from(
8989 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8990 ) - scroll_pixel_position.x,
8991 );
8992
8993 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8994
8995 // Fully visible if it can be displayed within the window (allow overlapping other
8996 // panes). However, this is only allowed if the popover starts within text_bounds.
8997 let can_position_to_the_right = x_after_longest < text_bounds.right()
8998 && x_after_longest + element_bounds.width < viewport_bounds.right();
8999
9000 let mut origin = if can_position_to_the_right {
9001 point(
9002 x_after_longest,
9003 text_bounds.origin.y
9004 + Pixels::from(
9005 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9006 - scroll_pixel_position.y,
9007 ),
9008 )
9009 } else {
9010 let cursor_row = newest_selection_head.map(|head| head.row());
9011 let above_edit = edit_start
9012 .row()
9013 .0
9014 .checked_sub(line_count as u32)
9015 .map(DisplayRow);
9016 let below_edit = Some(edit_end.row() + 1);
9017 let above_cursor =
9018 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9019 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9020
9021 // Place the edit popover adjacent to the edit if there is a location
9022 // available that is onscreen and does not obscure the cursor. Otherwise,
9023 // place it adjacent to the cursor.
9024 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9025 .into_iter()
9026 .flatten()
9027 .find(|&start_row| {
9028 let end_row = start_row + line_count as u32;
9029 visible_row_range.contains(&start_row)
9030 && visible_row_range.contains(&end_row)
9031 && cursor_row
9032 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9033 })?;
9034
9035 content_origin
9036 + point(
9037 Pixels::from(-scroll_pixel_position.x),
9038 Pixels::from(
9039 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9040 ),
9041 )
9042 };
9043
9044 origin.x -= BORDER_WIDTH;
9045
9046 window.defer_draw(element, origin, 1);
9047
9048 // Do not return an element, since it will already be drawn due to defer_draw.
9049 None
9050 }
9051
9052 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9053 px(30.)
9054 }
9055
9056 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9057 if self.read_only(cx) {
9058 cx.theme().players().read_only()
9059 } else {
9060 self.style.as_ref().unwrap().local_player
9061 }
9062 }
9063
9064 fn render_edit_prediction_accept_keybind(
9065 &self,
9066 window: &mut Window,
9067 cx: &mut App,
9068 ) -> Option<AnyElement> {
9069 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9070 let accept_keystroke = accept_binding.keystroke()?;
9071
9072 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9073
9074 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9075 Color::Accent
9076 } else {
9077 Color::Muted
9078 };
9079
9080 h_flex()
9081 .px_0p5()
9082 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9083 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9084 .text_size(TextSize::XSmall.rems(cx))
9085 .child(h_flex().children(ui::render_modifiers(
9086 accept_keystroke.modifiers(),
9087 PlatformStyle::platform(),
9088 Some(modifiers_color),
9089 Some(IconSize::XSmall.rems().into()),
9090 true,
9091 )))
9092 .when(is_platform_style_mac, |parent| {
9093 parent.child(accept_keystroke.key().to_string())
9094 })
9095 .when(!is_platform_style_mac, |parent| {
9096 parent.child(
9097 Key::new(
9098 util::capitalize(accept_keystroke.key()),
9099 Some(Color::Default),
9100 )
9101 .size(Some(IconSize::XSmall.rems().into())),
9102 )
9103 })
9104 .into_any()
9105 .into()
9106 }
9107
9108 fn render_edit_prediction_line_popover(
9109 &self,
9110 label: impl Into<SharedString>,
9111 icon: Option<IconName>,
9112 window: &mut Window,
9113 cx: &mut App,
9114 ) -> Stateful<Div> {
9115 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9116
9117 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9118 let has_keybind = keybind.is_some();
9119
9120 h_flex()
9121 .id("ep-line-popover")
9122 .py_0p5()
9123 .pl_1()
9124 .pr(padding_right)
9125 .gap_1()
9126 .rounded_md()
9127 .border_1()
9128 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9129 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9130 .shadow_xs()
9131 .when(!has_keybind, |el| {
9132 let status_colors = cx.theme().status();
9133
9134 el.bg(status_colors.error_background)
9135 .border_color(status_colors.error.opacity(0.6))
9136 .pl_2()
9137 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9138 .cursor_default()
9139 .hoverable_tooltip(move |_window, cx| {
9140 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9141 })
9142 })
9143 .children(keybind)
9144 .child(
9145 Label::new(label)
9146 .size(LabelSize::Small)
9147 .when(!has_keybind, |el| {
9148 el.color(cx.theme().status().error.into()).strikethrough()
9149 }),
9150 )
9151 .when(!has_keybind, |el| {
9152 el.child(
9153 h_flex().ml_1().child(
9154 Icon::new(IconName::Info)
9155 .size(IconSize::Small)
9156 .color(cx.theme().status().error.into()),
9157 ),
9158 )
9159 })
9160 .when_some(icon, |element, icon| {
9161 element.child(
9162 div()
9163 .mt(px(1.5))
9164 .child(Icon::new(icon).size(IconSize::Small)),
9165 )
9166 })
9167 }
9168
9169 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9170 let accent_color = cx.theme().colors().text_accent;
9171 let editor_bg_color = cx.theme().colors().editor_background;
9172 editor_bg_color.blend(accent_color.opacity(0.1))
9173 }
9174
9175 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9176 let accent_color = cx.theme().colors().text_accent;
9177 let editor_bg_color = cx.theme().colors().editor_background;
9178 editor_bg_color.blend(accent_color.opacity(0.6))
9179 }
9180 fn get_prediction_provider_icon_name(
9181 provider: &Option<RegisteredEditPredictionProvider>,
9182 ) -> IconName {
9183 match provider {
9184 Some(provider) => match provider.provider.name() {
9185 "copilot" => IconName::Copilot,
9186 "supermaven" => IconName::Supermaven,
9187 _ => IconName::ZedPredict,
9188 },
9189 None => IconName::ZedPredict,
9190 }
9191 }
9192
9193 fn render_edit_prediction_cursor_popover(
9194 &self,
9195 min_width: Pixels,
9196 max_width: Pixels,
9197 cursor_point: Point,
9198 style: &EditorStyle,
9199 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9200 _window: &Window,
9201 cx: &mut Context<Editor>,
9202 ) -> Option<AnyElement> {
9203 let provider = self.edit_prediction_provider.as_ref()?;
9204 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9205
9206 let is_refreshing = provider.provider.is_refreshing(cx);
9207
9208 fn pending_completion_container(icon: IconName) -> Div {
9209 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9210 }
9211
9212 let completion = match &self.active_edit_prediction {
9213 Some(prediction) => {
9214 if !self.has_visible_completions_menu() {
9215 const RADIUS: Pixels = px(6.);
9216 const BORDER_WIDTH: Pixels = px(1.);
9217
9218 return Some(
9219 h_flex()
9220 .elevation_2(cx)
9221 .border(BORDER_WIDTH)
9222 .border_color(cx.theme().colors().border)
9223 .when(accept_keystroke.is_none(), |el| {
9224 el.border_color(cx.theme().status().error)
9225 })
9226 .rounded(RADIUS)
9227 .rounded_tl(px(0.))
9228 .overflow_hidden()
9229 .child(div().px_1p5().child(match &prediction.completion {
9230 EditPrediction::MoveWithin { target, snapshot } => {
9231 use text::ToPoint as _;
9232 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9233 {
9234 Icon::new(IconName::ZedPredictDown)
9235 } else {
9236 Icon::new(IconName::ZedPredictUp)
9237 }
9238 }
9239 EditPrediction::MoveOutside { .. } => {
9240 // TODO [zeta2] custom icon for external jump?
9241 Icon::new(provider_icon)
9242 }
9243 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9244 }))
9245 .child(
9246 h_flex()
9247 .gap_1()
9248 .py_1()
9249 .px_2()
9250 .rounded_r(RADIUS - BORDER_WIDTH)
9251 .border_l_1()
9252 .border_color(cx.theme().colors().border)
9253 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9254 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9255 el.child(
9256 Label::new("Hold")
9257 .size(LabelSize::Small)
9258 .when(accept_keystroke.is_none(), |el| {
9259 el.strikethrough()
9260 })
9261 .line_height_style(LineHeightStyle::UiLabel),
9262 )
9263 })
9264 .id("edit_prediction_cursor_popover_keybind")
9265 .when(accept_keystroke.is_none(), |el| {
9266 let status_colors = cx.theme().status();
9267
9268 el.bg(status_colors.error_background)
9269 .border_color(status_colors.error.opacity(0.6))
9270 .child(Icon::new(IconName::Info).color(Color::Error))
9271 .cursor_default()
9272 .hoverable_tooltip(move |_window, cx| {
9273 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9274 .into()
9275 })
9276 })
9277 .when_some(
9278 accept_keystroke.as_ref(),
9279 |el, accept_keystroke| {
9280 el.child(h_flex().children(ui::render_modifiers(
9281 accept_keystroke.modifiers(),
9282 PlatformStyle::platform(),
9283 Some(Color::Default),
9284 Some(IconSize::XSmall.rems().into()),
9285 false,
9286 )))
9287 },
9288 ),
9289 )
9290 .into_any(),
9291 );
9292 }
9293
9294 self.render_edit_prediction_cursor_popover_preview(
9295 prediction,
9296 cursor_point,
9297 style,
9298 cx,
9299 )?
9300 }
9301
9302 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9303 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9304 stale_completion,
9305 cursor_point,
9306 style,
9307 cx,
9308 )?,
9309
9310 None => pending_completion_container(provider_icon)
9311 .child(Label::new("...").size(LabelSize::Small)),
9312 },
9313
9314 None => pending_completion_container(provider_icon)
9315 .child(Label::new("...").size(LabelSize::Small)),
9316 };
9317
9318 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9319 completion
9320 .with_animation(
9321 "loading-completion",
9322 Animation::new(Duration::from_secs(2))
9323 .repeat()
9324 .with_easing(pulsating_between(0.4, 0.8)),
9325 |label, delta| label.opacity(delta),
9326 )
9327 .into_any_element()
9328 } else {
9329 completion.into_any_element()
9330 };
9331
9332 let has_completion = self.active_edit_prediction.is_some();
9333
9334 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9335 Some(
9336 h_flex()
9337 .min_w(min_width)
9338 .max_w(max_width)
9339 .flex_1()
9340 .elevation_2(cx)
9341 .border_color(cx.theme().colors().border)
9342 .child(
9343 div()
9344 .flex_1()
9345 .py_1()
9346 .px_2()
9347 .overflow_hidden()
9348 .child(completion),
9349 )
9350 .when_some(accept_keystroke, |el, accept_keystroke| {
9351 if !accept_keystroke.modifiers().modified() {
9352 return el;
9353 }
9354
9355 el.child(
9356 h_flex()
9357 .h_full()
9358 .border_l_1()
9359 .rounded_r_lg()
9360 .border_color(cx.theme().colors().border)
9361 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9362 .gap_1()
9363 .py_1()
9364 .px_2()
9365 .child(
9366 h_flex()
9367 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9368 .when(is_platform_style_mac, |parent| parent.gap_1())
9369 .child(h_flex().children(ui::render_modifiers(
9370 accept_keystroke.modifiers(),
9371 PlatformStyle::platform(),
9372 Some(if !has_completion {
9373 Color::Muted
9374 } else {
9375 Color::Default
9376 }),
9377 None,
9378 false,
9379 ))),
9380 )
9381 .child(Label::new("Preview").into_any_element())
9382 .opacity(if has_completion { 1.0 } else { 0.4 }),
9383 )
9384 })
9385 .into_any(),
9386 )
9387 }
9388
9389 fn render_edit_prediction_cursor_popover_preview(
9390 &self,
9391 completion: &EditPredictionState,
9392 cursor_point: Point,
9393 style: &EditorStyle,
9394 cx: &mut Context<Editor>,
9395 ) -> Option<Div> {
9396 use text::ToPoint as _;
9397
9398 fn render_relative_row_jump(
9399 prefix: impl Into<String>,
9400 current_row: u32,
9401 target_row: u32,
9402 ) -> Div {
9403 let (row_diff, arrow) = if target_row < current_row {
9404 (current_row - target_row, IconName::ArrowUp)
9405 } else {
9406 (target_row - current_row, IconName::ArrowDown)
9407 };
9408
9409 h_flex()
9410 .child(
9411 Label::new(format!("{}{}", prefix.into(), row_diff))
9412 .color(Color::Muted)
9413 .size(LabelSize::Small),
9414 )
9415 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9416 }
9417
9418 let supports_jump = self
9419 .edit_prediction_provider
9420 .as_ref()
9421 .map(|provider| provider.provider.supports_jump_to_edit())
9422 .unwrap_or(true);
9423
9424 match &completion.completion {
9425 EditPrediction::MoveWithin {
9426 target, snapshot, ..
9427 } => {
9428 if !supports_jump {
9429 return None;
9430 }
9431
9432 Some(
9433 h_flex()
9434 .px_2()
9435 .gap_2()
9436 .flex_1()
9437 .child(
9438 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9439 Icon::new(IconName::ZedPredictDown)
9440 } else {
9441 Icon::new(IconName::ZedPredictUp)
9442 },
9443 )
9444 .child(Label::new("Jump to Edit")),
9445 )
9446 }
9447 EditPrediction::MoveOutside { snapshot, .. } => {
9448 let file_name = snapshot
9449 .file()
9450 .map(|file| file.file_name(cx))
9451 .unwrap_or("untitled");
9452 Some(
9453 h_flex()
9454 .px_2()
9455 .gap_2()
9456 .flex_1()
9457 .child(Icon::new(IconName::ZedPredict))
9458 .child(Label::new(format!("Jump to {file_name}"))),
9459 )
9460 }
9461 EditPrediction::Edit {
9462 edits,
9463 edit_preview,
9464 snapshot,
9465 display_mode: _,
9466 } => {
9467 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9468
9469 let (highlighted_edits, has_more_lines) =
9470 if let Some(edit_preview) = edit_preview.as_ref() {
9471 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9472 .first_line_preview()
9473 } else {
9474 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9475 };
9476
9477 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9478 .with_default_highlights(&style.text, highlighted_edits.highlights);
9479
9480 let preview = h_flex()
9481 .gap_1()
9482 .min_w_16()
9483 .child(styled_text)
9484 .when(has_more_lines, |parent| parent.child("…"));
9485
9486 let left = if supports_jump && first_edit_row != cursor_point.row {
9487 render_relative_row_jump("", cursor_point.row, first_edit_row)
9488 .into_any_element()
9489 } else {
9490 let icon_name =
9491 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9492 Icon::new(icon_name).into_any_element()
9493 };
9494
9495 Some(
9496 h_flex()
9497 .h_full()
9498 .flex_1()
9499 .gap_2()
9500 .pr_1()
9501 .overflow_x_hidden()
9502 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9503 .child(left)
9504 .child(preview),
9505 )
9506 }
9507 }
9508 }
9509
9510 pub fn render_context_menu(
9511 &self,
9512 style: &EditorStyle,
9513 max_height_in_lines: u32,
9514 window: &mut Window,
9515 cx: &mut Context<Editor>,
9516 ) -> Option<AnyElement> {
9517 let menu = self.context_menu.borrow();
9518 let menu = menu.as_ref()?;
9519 if !menu.visible() {
9520 return None;
9521 };
9522 Some(menu.render(style, max_height_in_lines, window, cx))
9523 }
9524
9525 fn render_context_menu_aside(
9526 &mut self,
9527 max_size: Size<Pixels>,
9528 window: &mut Window,
9529 cx: &mut Context<Editor>,
9530 ) -> Option<AnyElement> {
9531 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9532 if menu.visible() {
9533 menu.render_aside(max_size, window, cx)
9534 } else {
9535 None
9536 }
9537 })
9538 }
9539
9540 fn hide_context_menu(
9541 &mut self,
9542 window: &mut Window,
9543 cx: &mut Context<Self>,
9544 ) -> Option<CodeContextMenu> {
9545 cx.notify();
9546 self.completion_tasks.clear();
9547 let context_menu = self.context_menu.borrow_mut().take();
9548 self.stale_edit_prediction_in_menu.take();
9549 self.update_visible_edit_prediction(window, cx);
9550 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9551 && let Some(completion_provider) = &self.completion_provider
9552 {
9553 completion_provider.selection_changed(None, window, cx);
9554 }
9555 context_menu
9556 }
9557
9558 fn show_snippet_choices(
9559 &mut self,
9560 choices: &Vec<String>,
9561 selection: Range<Anchor>,
9562 cx: &mut Context<Self>,
9563 ) {
9564 let Some((_, buffer, _)) = self
9565 .buffer()
9566 .read(cx)
9567 .excerpt_containing(selection.start, cx)
9568 else {
9569 return;
9570 };
9571 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9572 else {
9573 return;
9574 };
9575 if buffer != end_buffer {
9576 log::error!("expected anchor range to have matching buffer IDs");
9577 return;
9578 }
9579
9580 let id = post_inc(&mut self.next_completion_id);
9581 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9582 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9583 CompletionsMenu::new_snippet_choices(
9584 id,
9585 true,
9586 choices,
9587 selection,
9588 buffer,
9589 snippet_sort_order,
9590 ),
9591 ));
9592 }
9593
9594 pub fn insert_snippet(
9595 &mut self,
9596 insertion_ranges: &[Range<usize>],
9597 snippet: Snippet,
9598 window: &mut Window,
9599 cx: &mut Context<Self>,
9600 ) -> Result<()> {
9601 struct Tabstop<T> {
9602 is_end_tabstop: bool,
9603 ranges: Vec<Range<T>>,
9604 choices: Option<Vec<String>>,
9605 }
9606
9607 let tabstops = self.buffer.update(cx, |buffer, cx| {
9608 let snippet_text: Arc<str> = snippet.text.clone().into();
9609 let edits = insertion_ranges
9610 .iter()
9611 .cloned()
9612 .map(|range| (range, snippet_text.clone()));
9613 let autoindent_mode = AutoindentMode::Block {
9614 original_indent_columns: Vec::new(),
9615 };
9616 buffer.edit(edits, Some(autoindent_mode), cx);
9617
9618 let snapshot = &*buffer.read(cx);
9619 let snippet = &snippet;
9620 snippet
9621 .tabstops
9622 .iter()
9623 .map(|tabstop| {
9624 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9625 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9626 });
9627 let mut tabstop_ranges = tabstop
9628 .ranges
9629 .iter()
9630 .flat_map(|tabstop_range| {
9631 let mut delta = 0_isize;
9632 insertion_ranges.iter().map(move |insertion_range| {
9633 let insertion_start = insertion_range.start as isize + delta;
9634 delta +=
9635 snippet.text.len() as isize - insertion_range.len() as isize;
9636
9637 let start = ((insertion_start + tabstop_range.start) as usize)
9638 .min(snapshot.len());
9639 let end = ((insertion_start + tabstop_range.end) as usize)
9640 .min(snapshot.len());
9641 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9642 })
9643 })
9644 .collect::<Vec<_>>();
9645 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9646
9647 Tabstop {
9648 is_end_tabstop,
9649 ranges: tabstop_ranges,
9650 choices: tabstop.choices.clone(),
9651 }
9652 })
9653 .collect::<Vec<_>>()
9654 });
9655 if let Some(tabstop) = tabstops.first() {
9656 self.change_selections(Default::default(), window, cx, |s| {
9657 // Reverse order so that the first range is the newest created selection.
9658 // Completions will use it and autoscroll will prioritize it.
9659 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9660 });
9661
9662 if let Some(choices) = &tabstop.choices
9663 && let Some(selection) = tabstop.ranges.first()
9664 {
9665 self.show_snippet_choices(choices, selection.clone(), cx)
9666 }
9667
9668 // If we're already at the last tabstop and it's at the end of the snippet,
9669 // we're done, we don't need to keep the state around.
9670 if !tabstop.is_end_tabstop {
9671 let choices = tabstops
9672 .iter()
9673 .map(|tabstop| tabstop.choices.clone())
9674 .collect();
9675
9676 let ranges = tabstops
9677 .into_iter()
9678 .map(|tabstop| tabstop.ranges)
9679 .collect::<Vec<_>>();
9680
9681 self.snippet_stack.push(SnippetState {
9682 active_index: 0,
9683 ranges,
9684 choices,
9685 });
9686 }
9687
9688 // Check whether the just-entered snippet ends with an auto-closable bracket.
9689 if self.autoclose_regions.is_empty() {
9690 let snapshot = self.buffer.read(cx).snapshot(cx);
9691 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9692 let selection_head = selection.head();
9693 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9694 continue;
9695 };
9696
9697 let mut bracket_pair = None;
9698 let max_lookup_length = scope
9699 .brackets()
9700 .map(|(pair, _)| {
9701 pair.start
9702 .as_str()
9703 .chars()
9704 .count()
9705 .max(pair.end.as_str().chars().count())
9706 })
9707 .max();
9708 if let Some(max_lookup_length) = max_lookup_length {
9709 let next_text = snapshot
9710 .chars_at(selection_head)
9711 .take(max_lookup_length)
9712 .collect::<String>();
9713 let prev_text = snapshot
9714 .reversed_chars_at(selection_head)
9715 .take(max_lookup_length)
9716 .collect::<String>();
9717
9718 for (pair, enabled) in scope.brackets() {
9719 if enabled
9720 && pair.close
9721 && prev_text.starts_with(pair.start.as_str())
9722 && next_text.starts_with(pair.end.as_str())
9723 {
9724 bracket_pair = Some(pair.clone());
9725 break;
9726 }
9727 }
9728 }
9729
9730 if let Some(pair) = bracket_pair {
9731 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9732 let autoclose_enabled =
9733 self.use_autoclose && snapshot_settings.use_autoclose;
9734 if autoclose_enabled {
9735 let start = snapshot.anchor_after(selection_head);
9736 let end = snapshot.anchor_after(selection_head);
9737 self.autoclose_regions.push(AutocloseRegion {
9738 selection_id: selection.id,
9739 range: start..end,
9740 pair,
9741 });
9742 }
9743 }
9744 }
9745 }
9746 }
9747 Ok(())
9748 }
9749
9750 pub fn move_to_next_snippet_tabstop(
9751 &mut self,
9752 window: &mut Window,
9753 cx: &mut Context<Self>,
9754 ) -> bool {
9755 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9756 }
9757
9758 pub fn move_to_prev_snippet_tabstop(
9759 &mut self,
9760 window: &mut Window,
9761 cx: &mut Context<Self>,
9762 ) -> bool {
9763 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9764 }
9765
9766 pub fn move_to_snippet_tabstop(
9767 &mut self,
9768 bias: Bias,
9769 window: &mut Window,
9770 cx: &mut Context<Self>,
9771 ) -> bool {
9772 if let Some(mut snippet) = self.snippet_stack.pop() {
9773 match bias {
9774 Bias::Left => {
9775 if snippet.active_index > 0 {
9776 snippet.active_index -= 1;
9777 } else {
9778 self.snippet_stack.push(snippet);
9779 return false;
9780 }
9781 }
9782 Bias::Right => {
9783 if snippet.active_index + 1 < snippet.ranges.len() {
9784 snippet.active_index += 1;
9785 } else {
9786 self.snippet_stack.push(snippet);
9787 return false;
9788 }
9789 }
9790 }
9791 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9792 self.change_selections(Default::default(), window, cx, |s| {
9793 // Reverse order so that the first range is the newest created selection.
9794 // Completions will use it and autoscroll will prioritize it.
9795 s.select_ranges(current_ranges.iter().rev().cloned())
9796 });
9797
9798 if let Some(choices) = &snippet.choices[snippet.active_index]
9799 && let Some(selection) = current_ranges.first()
9800 {
9801 self.show_snippet_choices(choices, selection.clone(), cx);
9802 }
9803
9804 // If snippet state is not at the last tabstop, push it back on the stack
9805 if snippet.active_index + 1 < snippet.ranges.len() {
9806 self.snippet_stack.push(snippet);
9807 }
9808 return true;
9809 }
9810 }
9811
9812 false
9813 }
9814
9815 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9816 self.transact(window, cx, |this, window, cx| {
9817 this.select_all(&SelectAll, window, cx);
9818 this.insert("", window, cx);
9819 });
9820 }
9821
9822 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9823 if self.read_only(cx) {
9824 return;
9825 }
9826 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9827 self.transact(window, cx, |this, window, cx| {
9828 this.select_autoclose_pair(window, cx);
9829
9830 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9831
9832 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9833 if !this.linked_edit_ranges.is_empty() {
9834 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9835 let snapshot = this.buffer.read(cx).snapshot(cx);
9836
9837 for selection in selections.iter() {
9838 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9839 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9840 if selection_start.buffer_id != selection_end.buffer_id {
9841 continue;
9842 }
9843 if let Some(ranges) =
9844 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9845 {
9846 for (buffer, entries) in ranges {
9847 linked_ranges.entry(buffer).or_default().extend(entries);
9848 }
9849 }
9850 }
9851 }
9852
9853 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9854 for selection in &mut selections {
9855 if selection.is_empty() {
9856 let old_head = selection.head();
9857 let mut new_head =
9858 movement::left(&display_map, old_head.to_display_point(&display_map))
9859 .to_point(&display_map);
9860 if let Some((buffer, line_buffer_range)) = display_map
9861 .buffer_snapshot()
9862 .buffer_line_for_row(MultiBufferRow(old_head.row))
9863 {
9864 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9865 let indent_len = match indent_size.kind {
9866 IndentKind::Space => {
9867 buffer.settings_at(line_buffer_range.start, cx).tab_size
9868 }
9869 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9870 };
9871 if old_head.column <= indent_size.len && old_head.column > 0 {
9872 let indent_len = indent_len.get();
9873 new_head = cmp::min(
9874 new_head,
9875 MultiBufferPoint::new(
9876 old_head.row,
9877 ((old_head.column - 1) / indent_len) * indent_len,
9878 ),
9879 );
9880 }
9881 }
9882
9883 selection.set_head(new_head, SelectionGoal::None);
9884 }
9885 }
9886
9887 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9888 this.insert("", window, cx);
9889 let empty_str: Arc<str> = Arc::from("");
9890 for (buffer, edits) in linked_ranges {
9891 let snapshot = buffer.read(cx).snapshot();
9892 use text::ToPoint as TP;
9893
9894 let edits = edits
9895 .into_iter()
9896 .map(|range| {
9897 let end_point = TP::to_point(&range.end, &snapshot);
9898 let mut start_point = TP::to_point(&range.start, &snapshot);
9899
9900 if end_point == start_point {
9901 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9902 .saturating_sub(1);
9903 start_point =
9904 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9905 };
9906
9907 (start_point..end_point, empty_str.clone())
9908 })
9909 .sorted_by_key(|(range, _)| range.start)
9910 .collect::<Vec<_>>();
9911 buffer.update(cx, |this, cx| {
9912 this.edit(edits, None, cx);
9913 })
9914 }
9915 this.refresh_edit_prediction(true, false, window, cx);
9916 refresh_linked_ranges(this, window, cx);
9917 });
9918 }
9919
9920 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9921 if self.read_only(cx) {
9922 return;
9923 }
9924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9925 self.transact(window, cx, |this, window, cx| {
9926 this.change_selections(Default::default(), window, cx, |s| {
9927 s.move_with(|map, selection| {
9928 if selection.is_empty() {
9929 let cursor = movement::right(map, selection.head());
9930 selection.end = cursor;
9931 selection.reversed = true;
9932 selection.goal = SelectionGoal::None;
9933 }
9934 })
9935 });
9936 this.insert("", window, cx);
9937 this.refresh_edit_prediction(true, false, window, cx);
9938 });
9939 }
9940
9941 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9942 if self.mode.is_single_line() {
9943 cx.propagate();
9944 return;
9945 }
9946
9947 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9948 if self.move_to_prev_snippet_tabstop(window, cx) {
9949 return;
9950 }
9951 self.outdent(&Outdent, window, cx);
9952 }
9953
9954 pub fn next_snippet_tabstop(
9955 &mut self,
9956 _: &NextSnippetTabstop,
9957 window: &mut Window,
9958 cx: &mut Context<Self>,
9959 ) {
9960 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9961 return;
9962 }
9963
9964 if self.move_to_next_snippet_tabstop(window, cx) {
9965 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9966 return;
9967 }
9968 }
9969
9970 pub fn previous_snippet_tabstop(
9971 &mut self,
9972 _: &PreviousSnippetTabstop,
9973 window: &mut Window,
9974 cx: &mut Context<Self>,
9975 ) {
9976 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9977 return;
9978 }
9979
9980 if self.move_to_prev_snippet_tabstop(window, cx) {
9981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9982 return;
9983 }
9984 }
9985
9986 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9987 if self.mode.is_single_line() {
9988 cx.propagate();
9989 return;
9990 }
9991
9992 if self.move_to_next_snippet_tabstop(window, cx) {
9993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9994 return;
9995 }
9996 if self.read_only(cx) {
9997 return;
9998 }
9999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10000 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10001 let buffer = self.buffer.read(cx);
10002 let snapshot = buffer.snapshot(cx);
10003 let rows_iter = selections.iter().map(|s| s.head().row);
10004 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10005
10006 let has_some_cursor_in_whitespace = selections
10007 .iter()
10008 .filter(|selection| selection.is_empty())
10009 .any(|selection| {
10010 let cursor = selection.head();
10011 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10012 cursor.column < current_indent.len
10013 });
10014
10015 let mut edits = Vec::new();
10016 let mut prev_edited_row = 0;
10017 let mut row_delta = 0;
10018 for selection in &mut selections {
10019 if selection.start.row != prev_edited_row {
10020 row_delta = 0;
10021 }
10022 prev_edited_row = selection.end.row;
10023
10024 // If the selection is non-empty, then increase the indentation of the selected lines.
10025 if !selection.is_empty() {
10026 row_delta =
10027 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10028 continue;
10029 }
10030
10031 let cursor = selection.head();
10032 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10033 if let Some(suggested_indent) =
10034 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10035 {
10036 // Don't do anything if already at suggested indent
10037 // and there is any other cursor which is not
10038 if has_some_cursor_in_whitespace
10039 && cursor.column == current_indent.len
10040 && current_indent.len == suggested_indent.len
10041 {
10042 continue;
10043 }
10044
10045 // Adjust line and move cursor to suggested indent
10046 // if cursor is not at suggested indent
10047 if cursor.column < suggested_indent.len
10048 && cursor.column <= current_indent.len
10049 && current_indent.len <= suggested_indent.len
10050 {
10051 selection.start = Point::new(cursor.row, suggested_indent.len);
10052 selection.end = selection.start;
10053 if row_delta == 0 {
10054 edits.extend(Buffer::edit_for_indent_size_adjustment(
10055 cursor.row,
10056 current_indent,
10057 suggested_indent,
10058 ));
10059 row_delta = suggested_indent.len - current_indent.len;
10060 }
10061 continue;
10062 }
10063
10064 // If current indent is more than suggested indent
10065 // only move cursor to current indent and skip indent
10066 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10067 selection.start = Point::new(cursor.row, current_indent.len);
10068 selection.end = selection.start;
10069 continue;
10070 }
10071 }
10072
10073 // Otherwise, insert a hard or soft tab.
10074 let settings = buffer.language_settings_at(cursor, cx);
10075 let tab_size = if settings.hard_tabs {
10076 IndentSize::tab()
10077 } else {
10078 let tab_size = settings.tab_size.get();
10079 let indent_remainder = snapshot
10080 .text_for_range(Point::new(cursor.row, 0)..cursor)
10081 .flat_map(str::chars)
10082 .fold(row_delta % tab_size, |counter: u32, c| {
10083 if c == '\t' {
10084 0
10085 } else {
10086 (counter + 1) % tab_size
10087 }
10088 });
10089
10090 let chars_to_next_tab_stop = tab_size - indent_remainder;
10091 IndentSize::spaces(chars_to_next_tab_stop)
10092 };
10093 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10094 selection.end = selection.start;
10095 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10096 row_delta += tab_size.len;
10097 }
10098
10099 self.transact(window, cx, |this, window, cx| {
10100 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10101 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10102 this.refresh_edit_prediction(true, false, window, cx);
10103 });
10104 }
10105
10106 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10107 if self.read_only(cx) {
10108 return;
10109 }
10110 if self.mode.is_single_line() {
10111 cx.propagate();
10112 return;
10113 }
10114
10115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10116 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10117 let mut prev_edited_row = 0;
10118 let mut row_delta = 0;
10119 let mut edits = Vec::new();
10120 let buffer = self.buffer.read(cx);
10121 let snapshot = buffer.snapshot(cx);
10122 for selection in &mut selections {
10123 if selection.start.row != prev_edited_row {
10124 row_delta = 0;
10125 }
10126 prev_edited_row = selection.end.row;
10127
10128 row_delta =
10129 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10130 }
10131
10132 self.transact(window, cx, |this, window, cx| {
10133 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10134 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10135 });
10136 }
10137
10138 fn indent_selection(
10139 buffer: &MultiBuffer,
10140 snapshot: &MultiBufferSnapshot,
10141 selection: &mut Selection<Point>,
10142 edits: &mut Vec<(Range<Point>, String)>,
10143 delta_for_start_row: u32,
10144 cx: &App,
10145 ) -> u32 {
10146 let settings = buffer.language_settings_at(selection.start, cx);
10147 let tab_size = settings.tab_size.get();
10148 let indent_kind = if settings.hard_tabs {
10149 IndentKind::Tab
10150 } else {
10151 IndentKind::Space
10152 };
10153 let mut start_row = selection.start.row;
10154 let mut end_row = selection.end.row + 1;
10155
10156 // If a selection ends at the beginning of a line, don't indent
10157 // that last line.
10158 if selection.end.column == 0 && selection.end.row > selection.start.row {
10159 end_row -= 1;
10160 }
10161
10162 // Avoid re-indenting a row that has already been indented by a
10163 // previous selection, but still update this selection's column
10164 // to reflect that indentation.
10165 if delta_for_start_row > 0 {
10166 start_row += 1;
10167 selection.start.column += delta_for_start_row;
10168 if selection.end.row == selection.start.row {
10169 selection.end.column += delta_for_start_row;
10170 }
10171 }
10172
10173 let mut delta_for_end_row = 0;
10174 let has_multiple_rows = start_row + 1 != end_row;
10175 for row in start_row..end_row {
10176 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10177 let indent_delta = match (current_indent.kind, indent_kind) {
10178 (IndentKind::Space, IndentKind::Space) => {
10179 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10180 IndentSize::spaces(columns_to_next_tab_stop)
10181 }
10182 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10183 (_, IndentKind::Tab) => IndentSize::tab(),
10184 };
10185
10186 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10187 0
10188 } else {
10189 selection.start.column
10190 };
10191 let row_start = Point::new(row, start);
10192 edits.push((
10193 row_start..row_start,
10194 indent_delta.chars().collect::<String>(),
10195 ));
10196
10197 // Update this selection's endpoints to reflect the indentation.
10198 if row == selection.start.row {
10199 selection.start.column += indent_delta.len;
10200 }
10201 if row == selection.end.row {
10202 selection.end.column += indent_delta.len;
10203 delta_for_end_row = indent_delta.len;
10204 }
10205 }
10206
10207 if selection.start.row == selection.end.row {
10208 delta_for_start_row + delta_for_end_row
10209 } else {
10210 delta_for_end_row
10211 }
10212 }
10213
10214 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10215 if self.read_only(cx) {
10216 return;
10217 }
10218 if self.mode.is_single_line() {
10219 cx.propagate();
10220 return;
10221 }
10222
10223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10224 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10225 let selections = self.selections.all::<Point>(&display_map);
10226 let mut deletion_ranges = Vec::new();
10227 let mut last_outdent = None;
10228 {
10229 let buffer = self.buffer.read(cx);
10230 let snapshot = buffer.snapshot(cx);
10231 for selection in &selections {
10232 let settings = buffer.language_settings_at(selection.start, cx);
10233 let tab_size = settings.tab_size.get();
10234 let mut rows = selection.spanned_rows(false, &display_map);
10235
10236 // Avoid re-outdenting a row that has already been outdented by a
10237 // previous selection.
10238 if let Some(last_row) = last_outdent
10239 && last_row == rows.start
10240 {
10241 rows.start = rows.start.next_row();
10242 }
10243 let has_multiple_rows = rows.len() > 1;
10244 for row in rows.iter_rows() {
10245 let indent_size = snapshot.indent_size_for_line(row);
10246 if indent_size.len > 0 {
10247 let deletion_len = match indent_size.kind {
10248 IndentKind::Space => {
10249 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10250 if columns_to_prev_tab_stop == 0 {
10251 tab_size
10252 } else {
10253 columns_to_prev_tab_stop
10254 }
10255 }
10256 IndentKind::Tab => 1,
10257 };
10258 let start = if has_multiple_rows
10259 || deletion_len > selection.start.column
10260 || indent_size.len < selection.start.column
10261 {
10262 0
10263 } else {
10264 selection.start.column - deletion_len
10265 };
10266 deletion_ranges.push(
10267 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10268 );
10269 last_outdent = Some(row);
10270 }
10271 }
10272 }
10273 }
10274
10275 self.transact(window, cx, |this, window, cx| {
10276 this.buffer.update(cx, |buffer, cx| {
10277 let empty_str: Arc<str> = Arc::default();
10278 buffer.edit(
10279 deletion_ranges
10280 .into_iter()
10281 .map(|range| (range, empty_str.clone())),
10282 None,
10283 cx,
10284 );
10285 });
10286 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10287 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10288 });
10289 }
10290
10291 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10292 if self.read_only(cx) {
10293 return;
10294 }
10295 if self.mode.is_single_line() {
10296 cx.propagate();
10297 return;
10298 }
10299
10300 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10301 let selections = self
10302 .selections
10303 .all::<usize>(&self.display_snapshot(cx))
10304 .into_iter()
10305 .map(|s| s.range());
10306
10307 self.transact(window, cx, |this, window, cx| {
10308 this.buffer.update(cx, |buffer, cx| {
10309 buffer.autoindent_ranges(selections, cx);
10310 });
10311 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10312 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10313 });
10314 }
10315
10316 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10318 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10319 let selections = self.selections.all::<Point>(&display_map);
10320
10321 let mut new_cursors = Vec::new();
10322 let mut edit_ranges = Vec::new();
10323 let mut selections = selections.iter().peekable();
10324 while let Some(selection) = selections.next() {
10325 let mut rows = selection.spanned_rows(false, &display_map);
10326
10327 // Accumulate contiguous regions of rows that we want to delete.
10328 while let Some(next_selection) = selections.peek() {
10329 let next_rows = next_selection.spanned_rows(false, &display_map);
10330 if next_rows.start <= rows.end {
10331 rows.end = next_rows.end;
10332 selections.next().unwrap();
10333 } else {
10334 break;
10335 }
10336 }
10337
10338 let buffer = display_map.buffer_snapshot();
10339 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10340 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10341 // If there's a line after the range, delete the \n from the end of the row range
10342 (
10343 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10344 rows.end,
10345 )
10346 } else {
10347 // If there isn't a line after the range, delete the \n from the line before the
10348 // start of the row range
10349 edit_start = edit_start.saturating_sub(1);
10350 (buffer.len(), rows.start.previous_row())
10351 };
10352
10353 let text_layout_details = self.text_layout_details(window);
10354 let x = display_map.x_for_display_point(
10355 selection.head().to_display_point(&display_map),
10356 &text_layout_details,
10357 );
10358 let row = Point::new(target_row.0, 0)
10359 .to_display_point(&display_map)
10360 .row();
10361 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10362
10363 new_cursors.push((
10364 selection.id,
10365 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10366 SelectionGoal::None,
10367 ));
10368 edit_ranges.push(edit_start..edit_end);
10369 }
10370
10371 self.transact(window, cx, |this, window, cx| {
10372 let buffer = this.buffer.update(cx, |buffer, cx| {
10373 let empty_str: Arc<str> = Arc::default();
10374 buffer.edit(
10375 edit_ranges
10376 .into_iter()
10377 .map(|range| (range, empty_str.clone())),
10378 None,
10379 cx,
10380 );
10381 buffer.snapshot(cx)
10382 });
10383 let new_selections = new_cursors
10384 .into_iter()
10385 .map(|(id, cursor, goal)| {
10386 let cursor = cursor.to_point(&buffer);
10387 Selection {
10388 id,
10389 start: cursor,
10390 end: cursor,
10391 reversed: false,
10392 goal,
10393 }
10394 })
10395 .collect();
10396
10397 this.change_selections(Default::default(), window, cx, |s| {
10398 s.select(new_selections);
10399 });
10400 });
10401 }
10402
10403 pub fn join_lines_impl(
10404 &mut self,
10405 insert_whitespace: bool,
10406 window: &mut Window,
10407 cx: &mut Context<Self>,
10408 ) {
10409 if self.read_only(cx) {
10410 return;
10411 }
10412 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10413 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10414 let start = MultiBufferRow(selection.start.row);
10415 // Treat single line selections as if they include the next line. Otherwise this action
10416 // would do nothing for single line selections individual cursors.
10417 let end = if selection.start.row == selection.end.row {
10418 MultiBufferRow(selection.start.row + 1)
10419 } else {
10420 MultiBufferRow(selection.end.row)
10421 };
10422
10423 if let Some(last_row_range) = row_ranges.last_mut()
10424 && start <= last_row_range.end
10425 {
10426 last_row_range.end = end;
10427 continue;
10428 }
10429 row_ranges.push(start..end);
10430 }
10431
10432 let snapshot = self.buffer.read(cx).snapshot(cx);
10433 let mut cursor_positions = Vec::new();
10434 for row_range in &row_ranges {
10435 let anchor = snapshot.anchor_before(Point::new(
10436 row_range.end.previous_row().0,
10437 snapshot.line_len(row_range.end.previous_row()),
10438 ));
10439 cursor_positions.push(anchor..anchor);
10440 }
10441
10442 self.transact(window, cx, |this, window, cx| {
10443 for row_range in row_ranges.into_iter().rev() {
10444 for row in row_range.iter_rows().rev() {
10445 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10446 let next_line_row = row.next_row();
10447 let indent = snapshot.indent_size_for_line(next_line_row);
10448 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10449
10450 let replace =
10451 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10452 " "
10453 } else {
10454 ""
10455 };
10456
10457 this.buffer.update(cx, |buffer, cx| {
10458 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10459 });
10460 }
10461 }
10462
10463 this.change_selections(Default::default(), window, cx, |s| {
10464 s.select_anchor_ranges(cursor_positions)
10465 });
10466 });
10467 }
10468
10469 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10471 self.join_lines_impl(true, window, cx);
10472 }
10473
10474 pub fn sort_lines_case_sensitive(
10475 &mut self,
10476 _: &SortLinesCaseSensitive,
10477 window: &mut Window,
10478 cx: &mut Context<Self>,
10479 ) {
10480 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10481 }
10482
10483 pub fn sort_lines_by_length(
10484 &mut self,
10485 _: &SortLinesByLength,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 self.manipulate_immutable_lines(window, cx, |lines| {
10490 lines.sort_by_key(|&line| line.chars().count())
10491 })
10492 }
10493
10494 pub fn sort_lines_case_insensitive(
10495 &mut self,
10496 _: &SortLinesCaseInsensitive,
10497 window: &mut Window,
10498 cx: &mut Context<Self>,
10499 ) {
10500 self.manipulate_immutable_lines(window, cx, |lines| {
10501 lines.sort_by_key(|line| line.to_lowercase())
10502 })
10503 }
10504
10505 pub fn unique_lines_case_insensitive(
10506 &mut self,
10507 _: &UniqueLinesCaseInsensitive,
10508 window: &mut Window,
10509 cx: &mut Context<Self>,
10510 ) {
10511 self.manipulate_immutable_lines(window, cx, |lines| {
10512 let mut seen = HashSet::default();
10513 lines.retain(|line| seen.insert(line.to_lowercase()));
10514 })
10515 }
10516
10517 pub fn unique_lines_case_sensitive(
10518 &mut self,
10519 _: &UniqueLinesCaseSensitive,
10520 window: &mut Window,
10521 cx: &mut Context<Self>,
10522 ) {
10523 self.manipulate_immutable_lines(window, cx, |lines| {
10524 let mut seen = HashSet::default();
10525 lines.retain(|line| seen.insert(*line));
10526 })
10527 }
10528
10529 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10530 let snapshot = self.buffer.read(cx).snapshot(cx);
10531 for selection in self.selections.disjoint_anchors_arc().iter() {
10532 if snapshot
10533 .language_at(selection.start)
10534 .and_then(|lang| lang.config().wrap_characters.as_ref())
10535 .is_some()
10536 {
10537 return true;
10538 }
10539 }
10540 false
10541 }
10542
10543 fn wrap_selections_in_tag(
10544 &mut self,
10545 _: &WrapSelectionsInTag,
10546 window: &mut Window,
10547 cx: &mut Context<Self>,
10548 ) {
10549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10550
10551 let snapshot = self.buffer.read(cx).snapshot(cx);
10552
10553 let mut edits = Vec::new();
10554 let mut boundaries = Vec::new();
10555
10556 for selection in self
10557 .selections
10558 .all_adjusted(&self.display_snapshot(cx))
10559 .iter()
10560 {
10561 let Some(wrap_config) = snapshot
10562 .language_at(selection.start)
10563 .and_then(|lang| lang.config().wrap_characters.clone())
10564 else {
10565 continue;
10566 };
10567
10568 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10569 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10570
10571 let start_before = snapshot.anchor_before(selection.start);
10572 let end_after = snapshot.anchor_after(selection.end);
10573
10574 edits.push((start_before..start_before, open_tag));
10575 edits.push((end_after..end_after, close_tag));
10576
10577 boundaries.push((
10578 start_before,
10579 end_after,
10580 wrap_config.start_prefix.len(),
10581 wrap_config.end_suffix.len(),
10582 ));
10583 }
10584
10585 if edits.is_empty() {
10586 return;
10587 }
10588
10589 self.transact(window, cx, |this, window, cx| {
10590 let buffer = this.buffer.update(cx, |buffer, cx| {
10591 buffer.edit(edits, None, cx);
10592 buffer.snapshot(cx)
10593 });
10594
10595 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10596 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10597 boundaries.into_iter()
10598 {
10599 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10600 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10601 new_selections.push(open_offset..open_offset);
10602 new_selections.push(close_offset..close_offset);
10603 }
10604
10605 this.change_selections(Default::default(), window, cx, |s| {
10606 s.select_ranges(new_selections);
10607 });
10608
10609 this.request_autoscroll(Autoscroll::fit(), cx);
10610 });
10611 }
10612
10613 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10614 let Some(project) = self.project.clone() else {
10615 return;
10616 };
10617 self.reload(project, window, cx)
10618 .detach_and_notify_err(window, cx);
10619 }
10620
10621 pub fn restore_file(
10622 &mut self,
10623 _: &::git::RestoreFile,
10624 window: &mut Window,
10625 cx: &mut Context<Self>,
10626 ) {
10627 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10628 let mut buffer_ids = HashSet::default();
10629 let snapshot = self.buffer().read(cx).snapshot(cx);
10630 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10631 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10632 }
10633
10634 let buffer = self.buffer().read(cx);
10635 let ranges = buffer_ids
10636 .into_iter()
10637 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10638 .collect::<Vec<_>>();
10639
10640 self.restore_hunks_in_ranges(ranges, window, cx);
10641 }
10642
10643 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10645 let selections = self
10646 .selections
10647 .all(&self.display_snapshot(cx))
10648 .into_iter()
10649 .map(|s| s.range())
10650 .collect();
10651 self.restore_hunks_in_ranges(selections, window, cx);
10652 }
10653
10654 pub fn restore_hunks_in_ranges(
10655 &mut self,
10656 ranges: Vec<Range<Point>>,
10657 window: &mut Window,
10658 cx: &mut Context<Editor>,
10659 ) {
10660 let mut revert_changes = HashMap::default();
10661 let chunk_by = self
10662 .snapshot(window, cx)
10663 .hunks_for_ranges(ranges)
10664 .into_iter()
10665 .chunk_by(|hunk| hunk.buffer_id);
10666 for (buffer_id, hunks) in &chunk_by {
10667 let hunks = hunks.collect::<Vec<_>>();
10668 for hunk in &hunks {
10669 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10670 }
10671 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10672 }
10673 drop(chunk_by);
10674 if !revert_changes.is_empty() {
10675 self.transact(window, cx, |editor, window, cx| {
10676 editor.restore(revert_changes, window, cx);
10677 });
10678 }
10679 }
10680
10681 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10682 if let Some(status) = self
10683 .addons
10684 .iter()
10685 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10686 {
10687 return Some(status);
10688 }
10689 self.project
10690 .as_ref()?
10691 .read(cx)
10692 .status_for_buffer_id(buffer_id, cx)
10693 }
10694
10695 pub fn open_active_item_in_terminal(
10696 &mut self,
10697 _: &OpenInTerminal,
10698 window: &mut Window,
10699 cx: &mut Context<Self>,
10700 ) {
10701 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10702 let project_path = buffer.read(cx).project_path(cx)?;
10703 let project = self.project()?.read(cx);
10704 let entry = project.entry_for_path(&project_path, cx)?;
10705 let parent = match &entry.canonical_path {
10706 Some(canonical_path) => canonical_path.to_path_buf(),
10707 None => project.absolute_path(&project_path, cx)?,
10708 }
10709 .parent()?
10710 .to_path_buf();
10711 Some(parent)
10712 }) {
10713 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10714 }
10715 }
10716
10717 fn set_breakpoint_context_menu(
10718 &mut self,
10719 display_row: DisplayRow,
10720 position: Option<Anchor>,
10721 clicked_point: gpui::Point<Pixels>,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 let source = self
10726 .buffer
10727 .read(cx)
10728 .snapshot(cx)
10729 .anchor_before(Point::new(display_row.0, 0u32));
10730
10731 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10732
10733 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10734 self,
10735 source,
10736 clicked_point,
10737 context_menu,
10738 window,
10739 cx,
10740 );
10741 }
10742
10743 fn add_edit_breakpoint_block(
10744 &mut self,
10745 anchor: Anchor,
10746 breakpoint: &Breakpoint,
10747 edit_action: BreakpointPromptEditAction,
10748 window: &mut Window,
10749 cx: &mut Context<Self>,
10750 ) {
10751 let weak_editor = cx.weak_entity();
10752 let bp_prompt = cx.new(|cx| {
10753 BreakpointPromptEditor::new(
10754 weak_editor,
10755 anchor,
10756 breakpoint.clone(),
10757 edit_action,
10758 window,
10759 cx,
10760 )
10761 });
10762
10763 let height = bp_prompt.update(cx, |this, cx| {
10764 this.prompt
10765 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10766 });
10767 let cloned_prompt = bp_prompt.clone();
10768 let blocks = vec![BlockProperties {
10769 style: BlockStyle::Sticky,
10770 placement: BlockPlacement::Above(anchor),
10771 height: Some(height),
10772 render: Arc::new(move |cx| {
10773 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10774 cloned_prompt.clone().into_any_element()
10775 }),
10776 priority: 0,
10777 }];
10778
10779 let focus_handle = bp_prompt.focus_handle(cx);
10780 window.focus(&focus_handle);
10781
10782 let block_ids = self.insert_blocks(blocks, None, cx);
10783 bp_prompt.update(cx, |prompt, _| {
10784 prompt.add_block_ids(block_ids);
10785 });
10786 }
10787
10788 pub(crate) fn breakpoint_at_row(
10789 &self,
10790 row: u32,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) -> Option<(Anchor, Breakpoint)> {
10794 let snapshot = self.snapshot(window, cx);
10795 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10796
10797 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10798 }
10799
10800 pub(crate) fn breakpoint_at_anchor(
10801 &self,
10802 breakpoint_position: Anchor,
10803 snapshot: &EditorSnapshot,
10804 cx: &mut Context<Self>,
10805 ) -> Option<(Anchor, Breakpoint)> {
10806 let buffer = self
10807 .buffer
10808 .read(cx)
10809 .buffer_for_anchor(breakpoint_position, cx)?;
10810
10811 let enclosing_excerpt = breakpoint_position.excerpt_id;
10812 let buffer_snapshot = buffer.read(cx).snapshot();
10813
10814 let row = buffer_snapshot
10815 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10816 .row;
10817
10818 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10819 let anchor_end = snapshot
10820 .buffer_snapshot()
10821 .anchor_after(Point::new(row, line_len));
10822
10823 self.breakpoint_store
10824 .as_ref()?
10825 .read_with(cx, |breakpoint_store, cx| {
10826 breakpoint_store
10827 .breakpoints(
10828 &buffer,
10829 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10830 &buffer_snapshot,
10831 cx,
10832 )
10833 .next()
10834 .and_then(|(bp, _)| {
10835 let breakpoint_row = buffer_snapshot
10836 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10837 .row;
10838
10839 if breakpoint_row == row {
10840 snapshot
10841 .buffer_snapshot()
10842 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10843 .map(|position| (position, bp.bp.clone()))
10844 } else {
10845 None
10846 }
10847 })
10848 })
10849 }
10850
10851 pub fn edit_log_breakpoint(
10852 &mut self,
10853 _: &EditLogBreakpoint,
10854 window: &mut Window,
10855 cx: &mut Context<Self>,
10856 ) {
10857 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10858 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10859 message: None,
10860 state: BreakpointState::Enabled,
10861 condition: None,
10862 hit_condition: None,
10863 });
10864
10865 self.add_edit_breakpoint_block(
10866 anchor,
10867 &breakpoint,
10868 BreakpointPromptEditAction::Log,
10869 window,
10870 cx,
10871 );
10872 }
10873 }
10874
10875 fn breakpoints_at_cursors(
10876 &self,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10880 let snapshot = self.snapshot(window, cx);
10881 let cursors = self
10882 .selections
10883 .disjoint_anchors_arc()
10884 .iter()
10885 .map(|selection| {
10886 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10887
10888 let breakpoint_position = self
10889 .breakpoint_at_row(cursor_position.row, window, cx)
10890 .map(|bp| bp.0)
10891 .unwrap_or_else(|| {
10892 snapshot
10893 .display_snapshot
10894 .buffer_snapshot()
10895 .anchor_after(Point::new(cursor_position.row, 0))
10896 });
10897
10898 let breakpoint = self
10899 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10900 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10901
10902 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10903 })
10904 // 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.
10905 .collect::<HashMap<Anchor, _>>();
10906
10907 cursors.into_iter().collect()
10908 }
10909
10910 pub fn enable_breakpoint(
10911 &mut self,
10912 _: &crate::actions::EnableBreakpoint,
10913 window: &mut Window,
10914 cx: &mut Context<Self>,
10915 ) {
10916 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10917 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10918 continue;
10919 };
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::InvertState,
10924 cx,
10925 );
10926 }
10927 }
10928
10929 pub fn disable_breakpoint(
10930 &mut self,
10931 _: &crate::actions::DisableBreakpoint,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10936 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10937 continue;
10938 };
10939 self.edit_breakpoint_at_anchor(
10940 anchor,
10941 breakpoint,
10942 BreakpointEditAction::InvertState,
10943 cx,
10944 );
10945 }
10946 }
10947
10948 pub fn toggle_breakpoint(
10949 &mut self,
10950 _: &crate::actions::ToggleBreakpoint,
10951 window: &mut Window,
10952 cx: &mut Context<Self>,
10953 ) {
10954 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10955 if let Some(breakpoint) = breakpoint {
10956 self.edit_breakpoint_at_anchor(
10957 anchor,
10958 breakpoint,
10959 BreakpointEditAction::Toggle,
10960 cx,
10961 );
10962 } else {
10963 self.edit_breakpoint_at_anchor(
10964 anchor,
10965 Breakpoint::new_standard(),
10966 BreakpointEditAction::Toggle,
10967 cx,
10968 );
10969 }
10970 }
10971 }
10972
10973 pub fn edit_breakpoint_at_anchor(
10974 &mut self,
10975 breakpoint_position: Anchor,
10976 breakpoint: Breakpoint,
10977 edit_action: BreakpointEditAction,
10978 cx: &mut Context<Self>,
10979 ) {
10980 let Some(breakpoint_store) = &self.breakpoint_store else {
10981 return;
10982 };
10983
10984 let Some(buffer) = self
10985 .buffer
10986 .read(cx)
10987 .buffer_for_anchor(breakpoint_position, cx)
10988 else {
10989 return;
10990 };
10991
10992 breakpoint_store.update(cx, |breakpoint_store, cx| {
10993 breakpoint_store.toggle_breakpoint(
10994 buffer,
10995 BreakpointWithPosition {
10996 position: breakpoint_position.text_anchor,
10997 bp: breakpoint,
10998 },
10999 edit_action,
11000 cx,
11001 );
11002 });
11003
11004 cx.notify();
11005 }
11006
11007 #[cfg(any(test, feature = "test-support"))]
11008 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11009 self.breakpoint_store.clone()
11010 }
11011
11012 pub fn prepare_restore_change(
11013 &self,
11014 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11015 hunk: &MultiBufferDiffHunk,
11016 cx: &mut App,
11017 ) -> Option<()> {
11018 if hunk.is_created_file() {
11019 return None;
11020 }
11021 let buffer = self.buffer.read(cx);
11022 let diff = buffer.diff_for(hunk.buffer_id)?;
11023 let buffer = buffer.buffer(hunk.buffer_id)?;
11024 let buffer = buffer.read(cx);
11025 let original_text = diff
11026 .read(cx)
11027 .base_text()
11028 .as_rope()
11029 .slice(hunk.diff_base_byte_range.clone());
11030 let buffer_snapshot = buffer.snapshot();
11031 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11032 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11033 probe
11034 .0
11035 .start
11036 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11037 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11038 }) {
11039 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11040 Some(())
11041 } else {
11042 None
11043 }
11044 }
11045
11046 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11047 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11048 }
11049
11050 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11051 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11052 }
11053
11054 fn manipulate_lines<M>(
11055 &mut self,
11056 window: &mut Window,
11057 cx: &mut Context<Self>,
11058 mut manipulate: M,
11059 ) where
11060 M: FnMut(&str) -> LineManipulationResult,
11061 {
11062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11063
11064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11065 let buffer = self.buffer.read(cx).snapshot(cx);
11066
11067 let mut edits = Vec::new();
11068
11069 let selections = self.selections.all::<Point>(&display_map);
11070 let mut selections = selections.iter().peekable();
11071 let mut contiguous_row_selections = Vec::new();
11072 let mut new_selections = Vec::new();
11073 let mut added_lines = 0;
11074 let mut removed_lines = 0;
11075
11076 while let Some(selection) = selections.next() {
11077 let (start_row, end_row) = consume_contiguous_rows(
11078 &mut contiguous_row_selections,
11079 selection,
11080 &display_map,
11081 &mut selections,
11082 );
11083
11084 let start_point = Point::new(start_row.0, 0);
11085 let end_point = Point::new(
11086 end_row.previous_row().0,
11087 buffer.line_len(end_row.previous_row()),
11088 );
11089 let text = buffer
11090 .text_for_range(start_point..end_point)
11091 .collect::<String>();
11092
11093 let LineManipulationResult {
11094 new_text,
11095 line_count_before,
11096 line_count_after,
11097 } = manipulate(&text);
11098
11099 edits.push((start_point..end_point, new_text));
11100
11101 // Selections must change based on added and removed line count
11102 let start_row =
11103 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11104 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11105 new_selections.push(Selection {
11106 id: selection.id,
11107 start: start_row,
11108 end: end_row,
11109 goal: SelectionGoal::None,
11110 reversed: selection.reversed,
11111 });
11112
11113 if line_count_after > line_count_before {
11114 added_lines += line_count_after - line_count_before;
11115 } else if line_count_before > line_count_after {
11116 removed_lines += line_count_before - line_count_after;
11117 }
11118 }
11119
11120 self.transact(window, cx, |this, window, cx| {
11121 let buffer = this.buffer.update(cx, |buffer, cx| {
11122 buffer.edit(edits, None, cx);
11123 buffer.snapshot(cx)
11124 });
11125
11126 // Recalculate offsets on newly edited buffer
11127 let new_selections = new_selections
11128 .iter()
11129 .map(|s| {
11130 let start_point = Point::new(s.start.0, 0);
11131 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11132 Selection {
11133 id: s.id,
11134 start: buffer.point_to_offset(start_point),
11135 end: buffer.point_to_offset(end_point),
11136 goal: s.goal,
11137 reversed: s.reversed,
11138 }
11139 })
11140 .collect();
11141
11142 this.change_selections(Default::default(), window, cx, |s| {
11143 s.select(new_selections);
11144 });
11145
11146 this.request_autoscroll(Autoscroll::fit(), cx);
11147 });
11148 }
11149
11150 fn manipulate_immutable_lines<Fn>(
11151 &mut self,
11152 window: &mut Window,
11153 cx: &mut Context<Self>,
11154 mut callback: Fn,
11155 ) where
11156 Fn: FnMut(&mut Vec<&str>),
11157 {
11158 self.manipulate_lines(window, cx, |text| {
11159 let mut lines: Vec<&str> = text.split('\n').collect();
11160 let line_count_before = lines.len();
11161
11162 callback(&mut lines);
11163
11164 LineManipulationResult {
11165 new_text: lines.join("\n"),
11166 line_count_before,
11167 line_count_after: lines.len(),
11168 }
11169 });
11170 }
11171
11172 fn manipulate_mutable_lines<Fn>(
11173 &mut self,
11174 window: &mut Window,
11175 cx: &mut Context<Self>,
11176 mut callback: Fn,
11177 ) where
11178 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11179 {
11180 self.manipulate_lines(window, cx, |text| {
11181 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11182 let line_count_before = lines.len();
11183
11184 callback(&mut lines);
11185
11186 LineManipulationResult {
11187 new_text: lines.join("\n"),
11188 line_count_before,
11189 line_count_after: lines.len(),
11190 }
11191 });
11192 }
11193
11194 pub fn convert_indentation_to_spaces(
11195 &mut self,
11196 _: &ConvertIndentationToSpaces,
11197 window: &mut Window,
11198 cx: &mut Context<Self>,
11199 ) {
11200 let settings = self.buffer.read(cx).language_settings(cx);
11201 let tab_size = settings.tab_size.get() as usize;
11202
11203 self.manipulate_mutable_lines(window, cx, |lines| {
11204 // Allocates a reasonably sized scratch buffer once for the whole loop
11205 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11206 // Avoids recomputing spaces that could be inserted many times
11207 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11208 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11209 .collect();
11210
11211 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11212 let mut chars = line.as_ref().chars();
11213 let mut col = 0;
11214 let mut changed = false;
11215
11216 for ch in chars.by_ref() {
11217 match ch {
11218 ' ' => {
11219 reindented_line.push(' ');
11220 col += 1;
11221 }
11222 '\t' => {
11223 // \t are converted to spaces depending on the current column
11224 let spaces_len = tab_size - (col % tab_size);
11225 reindented_line.extend(&space_cache[spaces_len - 1]);
11226 col += spaces_len;
11227 changed = true;
11228 }
11229 _ => {
11230 // If we dont append before break, the character is consumed
11231 reindented_line.push(ch);
11232 break;
11233 }
11234 }
11235 }
11236
11237 if !changed {
11238 reindented_line.clear();
11239 continue;
11240 }
11241 // Append the rest of the line and replace old reference with new one
11242 reindented_line.extend(chars);
11243 *line = Cow::Owned(reindented_line.clone());
11244 reindented_line.clear();
11245 }
11246 });
11247 }
11248
11249 pub fn convert_indentation_to_tabs(
11250 &mut self,
11251 _: &ConvertIndentationToTabs,
11252 window: &mut Window,
11253 cx: &mut Context<Self>,
11254 ) {
11255 let settings = self.buffer.read(cx).language_settings(cx);
11256 let tab_size = settings.tab_size.get() as usize;
11257
11258 self.manipulate_mutable_lines(window, cx, |lines| {
11259 // Allocates a reasonably sized buffer once for the whole loop
11260 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11261 // Avoids recomputing spaces that could be inserted many times
11262 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11263 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11264 .collect();
11265
11266 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11267 let mut chars = line.chars();
11268 let mut spaces_count = 0;
11269 let mut first_non_indent_char = None;
11270 let mut changed = false;
11271
11272 for ch in chars.by_ref() {
11273 match ch {
11274 ' ' => {
11275 // Keep track of spaces. Append \t when we reach tab_size
11276 spaces_count += 1;
11277 changed = true;
11278 if spaces_count == tab_size {
11279 reindented_line.push('\t');
11280 spaces_count = 0;
11281 }
11282 }
11283 '\t' => {
11284 reindented_line.push('\t');
11285 spaces_count = 0;
11286 }
11287 _ => {
11288 // Dont append it yet, we might have remaining spaces
11289 first_non_indent_char = Some(ch);
11290 break;
11291 }
11292 }
11293 }
11294
11295 if !changed {
11296 reindented_line.clear();
11297 continue;
11298 }
11299 // Remaining spaces that didn't make a full tab stop
11300 if spaces_count > 0 {
11301 reindented_line.extend(&space_cache[spaces_count - 1]);
11302 }
11303 // If we consume an extra character that was not indentation, add it back
11304 if let Some(extra_char) = first_non_indent_char {
11305 reindented_line.push(extra_char);
11306 }
11307 // Append the rest of the line and replace old reference with new one
11308 reindented_line.extend(chars);
11309 *line = Cow::Owned(reindented_line.clone());
11310 reindented_line.clear();
11311 }
11312 });
11313 }
11314
11315 pub fn convert_to_upper_case(
11316 &mut self,
11317 _: &ConvertToUpperCase,
11318 window: &mut Window,
11319 cx: &mut Context<Self>,
11320 ) {
11321 self.manipulate_text(window, cx, |text| text.to_uppercase())
11322 }
11323
11324 pub fn convert_to_lower_case(
11325 &mut self,
11326 _: &ConvertToLowerCase,
11327 window: &mut Window,
11328 cx: &mut Context<Self>,
11329 ) {
11330 self.manipulate_text(window, cx, |text| text.to_lowercase())
11331 }
11332
11333 pub fn convert_to_title_case(
11334 &mut self,
11335 _: &ConvertToTitleCase,
11336 window: &mut Window,
11337 cx: &mut Context<Self>,
11338 ) {
11339 self.manipulate_text(window, cx, |text| {
11340 text.split('\n')
11341 .map(|line| line.to_case(Case::Title))
11342 .join("\n")
11343 })
11344 }
11345
11346 pub fn convert_to_snake_case(
11347 &mut self,
11348 _: &ConvertToSnakeCase,
11349 window: &mut Window,
11350 cx: &mut Context<Self>,
11351 ) {
11352 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11353 }
11354
11355 pub fn convert_to_kebab_case(
11356 &mut self,
11357 _: &ConvertToKebabCase,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 ) {
11361 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11362 }
11363
11364 pub fn convert_to_upper_camel_case(
11365 &mut self,
11366 _: &ConvertToUpperCamelCase,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 ) {
11370 self.manipulate_text(window, cx, |text| {
11371 text.split('\n')
11372 .map(|line| line.to_case(Case::UpperCamel))
11373 .join("\n")
11374 })
11375 }
11376
11377 pub fn convert_to_lower_camel_case(
11378 &mut self,
11379 _: &ConvertToLowerCamelCase,
11380 window: &mut Window,
11381 cx: &mut Context<Self>,
11382 ) {
11383 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11384 }
11385
11386 pub fn convert_to_opposite_case(
11387 &mut self,
11388 _: &ConvertToOppositeCase,
11389 window: &mut Window,
11390 cx: &mut Context<Self>,
11391 ) {
11392 self.manipulate_text(window, cx, |text| {
11393 text.chars()
11394 .fold(String::with_capacity(text.len()), |mut t, c| {
11395 if c.is_uppercase() {
11396 t.extend(c.to_lowercase());
11397 } else {
11398 t.extend(c.to_uppercase());
11399 }
11400 t
11401 })
11402 })
11403 }
11404
11405 pub fn convert_to_sentence_case(
11406 &mut self,
11407 _: &ConvertToSentenceCase,
11408 window: &mut Window,
11409 cx: &mut Context<Self>,
11410 ) {
11411 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11412 }
11413
11414 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11415 self.manipulate_text(window, cx, |text| {
11416 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11417 if has_upper_case_characters {
11418 text.to_lowercase()
11419 } else {
11420 text.to_uppercase()
11421 }
11422 })
11423 }
11424
11425 pub fn convert_to_rot13(
11426 &mut self,
11427 _: &ConvertToRot13,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.manipulate_text(window, cx, |text| {
11432 text.chars()
11433 .map(|c| match c {
11434 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11435 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11436 _ => c,
11437 })
11438 .collect()
11439 })
11440 }
11441
11442 pub fn convert_to_rot47(
11443 &mut self,
11444 _: &ConvertToRot47,
11445 window: &mut Window,
11446 cx: &mut Context<Self>,
11447 ) {
11448 self.manipulate_text(window, cx, |text| {
11449 text.chars()
11450 .map(|c| {
11451 let code_point = c as u32;
11452 if code_point >= 33 && code_point <= 126 {
11453 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11454 }
11455 c
11456 })
11457 .collect()
11458 })
11459 }
11460
11461 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11462 where
11463 Fn: FnMut(&str) -> String,
11464 {
11465 let buffer = self.buffer.read(cx).snapshot(cx);
11466
11467 let mut new_selections = Vec::new();
11468 let mut edits = Vec::new();
11469 let mut selection_adjustment = 0i32;
11470
11471 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11472 let selection_is_empty = selection.is_empty();
11473
11474 let (start, end) = if selection_is_empty {
11475 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11476 (word_range.start, word_range.end)
11477 } else {
11478 (
11479 buffer.point_to_offset(selection.start),
11480 buffer.point_to_offset(selection.end),
11481 )
11482 };
11483
11484 let text = buffer.text_for_range(start..end).collect::<String>();
11485 let old_length = text.len() as i32;
11486 let text = callback(&text);
11487
11488 new_selections.push(Selection {
11489 start: (start as i32 - selection_adjustment) as usize,
11490 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11491 goal: SelectionGoal::None,
11492 id: selection.id,
11493 reversed: selection.reversed,
11494 });
11495
11496 selection_adjustment += old_length - text.len() as i32;
11497
11498 edits.push((start..end, text));
11499 }
11500
11501 self.transact(window, cx, |this, window, cx| {
11502 this.buffer.update(cx, |buffer, cx| {
11503 buffer.edit(edits, None, cx);
11504 });
11505
11506 this.change_selections(Default::default(), window, cx, |s| {
11507 s.select(new_selections);
11508 });
11509
11510 this.request_autoscroll(Autoscroll::fit(), cx);
11511 });
11512 }
11513
11514 pub fn move_selection_on_drop(
11515 &mut self,
11516 selection: &Selection<Anchor>,
11517 target: DisplayPoint,
11518 is_cut: bool,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11523 let buffer = display_map.buffer_snapshot();
11524 let mut edits = Vec::new();
11525 let insert_point = display_map
11526 .clip_point(target, Bias::Left)
11527 .to_point(&display_map);
11528 let text = buffer
11529 .text_for_range(selection.start..selection.end)
11530 .collect::<String>();
11531 if is_cut {
11532 edits.push(((selection.start..selection.end), String::new()));
11533 }
11534 let insert_anchor = buffer.anchor_before(insert_point);
11535 edits.push(((insert_anchor..insert_anchor), text));
11536 let last_edit_start = insert_anchor.bias_left(buffer);
11537 let last_edit_end = insert_anchor.bias_right(buffer);
11538 self.transact(window, cx, |this, window, cx| {
11539 this.buffer.update(cx, |buffer, cx| {
11540 buffer.edit(edits, None, cx);
11541 });
11542 this.change_selections(Default::default(), window, cx, |s| {
11543 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11544 });
11545 });
11546 }
11547
11548 pub fn clear_selection_drag_state(&mut self) {
11549 self.selection_drag_state = SelectionDragState::None;
11550 }
11551
11552 pub fn duplicate(
11553 &mut self,
11554 upwards: bool,
11555 whole_lines: bool,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11560
11561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11562 let buffer = display_map.buffer_snapshot();
11563 let selections = self.selections.all::<Point>(&display_map);
11564
11565 let mut edits = Vec::new();
11566 let mut selections_iter = selections.iter().peekable();
11567 while let Some(selection) = selections_iter.next() {
11568 let mut rows = selection.spanned_rows(false, &display_map);
11569 // duplicate line-wise
11570 if whole_lines || selection.start == selection.end {
11571 // Avoid duplicating the same lines twice.
11572 while let Some(next_selection) = selections_iter.peek() {
11573 let next_rows = next_selection.spanned_rows(false, &display_map);
11574 if next_rows.start < rows.end {
11575 rows.end = next_rows.end;
11576 selections_iter.next().unwrap();
11577 } else {
11578 break;
11579 }
11580 }
11581
11582 // Copy the text from the selected row region and splice it either at the start
11583 // or end of the region.
11584 let start = Point::new(rows.start.0, 0);
11585 let end = Point::new(
11586 rows.end.previous_row().0,
11587 buffer.line_len(rows.end.previous_row()),
11588 );
11589
11590 let mut text = buffer.text_for_range(start..end).collect::<String>();
11591
11592 let insert_location = if upwards {
11593 // When duplicating upward, we need to insert before the current line.
11594 // If we're on the last line and it doesn't end with a newline,
11595 // we need to add a newline before the duplicated content.
11596 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11597 && buffer.max_point().column > 0
11598 && !text.ends_with('\n');
11599
11600 if needs_leading_newline {
11601 text.insert(0, '\n');
11602 end
11603 } else {
11604 text.push('\n');
11605 Point::new(rows.start.0, 0)
11606 }
11607 } else {
11608 text.push('\n');
11609 start
11610 };
11611 edits.push((insert_location..insert_location, text));
11612 } else {
11613 // duplicate character-wise
11614 let start = selection.start;
11615 let end = selection.end;
11616 let text = buffer.text_for_range(start..end).collect::<String>();
11617 edits.push((selection.end..selection.end, text));
11618 }
11619 }
11620
11621 self.transact(window, cx, |this, window, cx| {
11622 this.buffer.update(cx, |buffer, cx| {
11623 buffer.edit(edits, None, cx);
11624 });
11625
11626 // When duplicating upward with whole lines, move the cursor to the duplicated line
11627 if upwards && whole_lines {
11628 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11629
11630 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11631 let mut new_ranges = Vec::new();
11632 let selections = s.all::<Point>(&display_map);
11633 let mut selections_iter = selections.iter().peekable();
11634
11635 while let Some(first_selection) = selections_iter.next() {
11636 // Group contiguous selections together to find the total row span
11637 let mut group_selections = vec![first_selection];
11638 let mut rows = first_selection.spanned_rows(false, &display_map);
11639
11640 while let Some(next_selection) = selections_iter.peek() {
11641 let next_rows = next_selection.spanned_rows(false, &display_map);
11642 if next_rows.start < rows.end {
11643 rows.end = next_rows.end;
11644 group_selections.push(selections_iter.next().unwrap());
11645 } else {
11646 break;
11647 }
11648 }
11649
11650 let row_count = rows.end.0 - rows.start.0;
11651
11652 // Move all selections in this group up by the total number of duplicated rows
11653 for selection in group_selections {
11654 let new_start = Point::new(
11655 selection.start.row.saturating_sub(row_count),
11656 selection.start.column,
11657 );
11658
11659 let new_end = Point::new(
11660 selection.end.row.saturating_sub(row_count),
11661 selection.end.column,
11662 );
11663
11664 new_ranges.push(new_start..new_end);
11665 }
11666 }
11667
11668 s.select_ranges(new_ranges);
11669 });
11670 }
11671
11672 this.request_autoscroll(Autoscroll::fit(), cx);
11673 });
11674 }
11675
11676 pub fn duplicate_line_up(
11677 &mut self,
11678 _: &DuplicateLineUp,
11679 window: &mut Window,
11680 cx: &mut Context<Self>,
11681 ) {
11682 self.duplicate(true, true, window, cx);
11683 }
11684
11685 pub fn duplicate_line_down(
11686 &mut self,
11687 _: &DuplicateLineDown,
11688 window: &mut Window,
11689 cx: &mut Context<Self>,
11690 ) {
11691 self.duplicate(false, true, window, cx);
11692 }
11693
11694 pub fn duplicate_selection(
11695 &mut self,
11696 _: &DuplicateSelection,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.duplicate(false, false, window, cx);
11701 }
11702
11703 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11704 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11705 if self.mode.is_single_line() {
11706 cx.propagate();
11707 return;
11708 }
11709
11710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11711 let buffer = self.buffer.read(cx).snapshot(cx);
11712
11713 let mut edits = Vec::new();
11714 let mut unfold_ranges = Vec::new();
11715 let mut refold_creases = Vec::new();
11716
11717 let selections = self.selections.all::<Point>(&display_map);
11718 let mut selections = selections.iter().peekable();
11719 let mut contiguous_row_selections = Vec::new();
11720 let mut new_selections = Vec::new();
11721
11722 while let Some(selection) = selections.next() {
11723 // Find all the selections that span a contiguous row range
11724 let (start_row, end_row) = consume_contiguous_rows(
11725 &mut contiguous_row_selections,
11726 selection,
11727 &display_map,
11728 &mut selections,
11729 );
11730
11731 // Move the text spanned by the row range to be before the line preceding the row range
11732 if start_row.0 > 0 {
11733 let range_to_move = Point::new(
11734 start_row.previous_row().0,
11735 buffer.line_len(start_row.previous_row()),
11736 )
11737 ..Point::new(
11738 end_row.previous_row().0,
11739 buffer.line_len(end_row.previous_row()),
11740 );
11741 let insertion_point = display_map
11742 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11743 .0;
11744
11745 // Don't move lines across excerpts
11746 if buffer
11747 .excerpt_containing(insertion_point..range_to_move.end)
11748 .is_some()
11749 {
11750 let text = buffer
11751 .text_for_range(range_to_move.clone())
11752 .flat_map(|s| s.chars())
11753 .skip(1)
11754 .chain(['\n'])
11755 .collect::<String>();
11756
11757 edits.push((
11758 buffer.anchor_after(range_to_move.start)
11759 ..buffer.anchor_before(range_to_move.end),
11760 String::new(),
11761 ));
11762 let insertion_anchor = buffer.anchor_after(insertion_point);
11763 edits.push((insertion_anchor..insertion_anchor, text));
11764
11765 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11766
11767 // Move selections up
11768 new_selections.extend(contiguous_row_selections.drain(..).map(
11769 |mut selection| {
11770 selection.start.row -= row_delta;
11771 selection.end.row -= row_delta;
11772 selection
11773 },
11774 ));
11775
11776 // Move folds up
11777 unfold_ranges.push(range_to_move.clone());
11778 for fold in display_map.folds_in_range(
11779 buffer.anchor_before(range_to_move.start)
11780 ..buffer.anchor_after(range_to_move.end),
11781 ) {
11782 let mut start = fold.range.start.to_point(&buffer);
11783 let mut end = fold.range.end.to_point(&buffer);
11784 start.row -= row_delta;
11785 end.row -= row_delta;
11786 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11787 }
11788 }
11789 }
11790
11791 // If we didn't move line(s), preserve the existing selections
11792 new_selections.append(&mut contiguous_row_selections);
11793 }
11794
11795 self.transact(window, cx, |this, window, cx| {
11796 this.unfold_ranges(&unfold_ranges, true, true, cx);
11797 this.buffer.update(cx, |buffer, cx| {
11798 for (range, text) in edits {
11799 buffer.edit([(range, text)], None, cx);
11800 }
11801 });
11802 this.fold_creases(refold_creases, true, window, cx);
11803 this.change_selections(Default::default(), window, cx, |s| {
11804 s.select(new_selections);
11805 })
11806 });
11807 }
11808
11809 pub fn move_line_down(
11810 &mut self,
11811 _: &MoveLineDown,
11812 window: &mut Window,
11813 cx: &mut Context<Self>,
11814 ) {
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11816 if self.mode.is_single_line() {
11817 cx.propagate();
11818 return;
11819 }
11820
11821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11822 let buffer = self.buffer.read(cx).snapshot(cx);
11823
11824 let mut edits = Vec::new();
11825 let mut unfold_ranges = Vec::new();
11826 let mut refold_creases = Vec::new();
11827
11828 let selections = self.selections.all::<Point>(&display_map);
11829 let mut selections = selections.iter().peekable();
11830 let mut contiguous_row_selections = Vec::new();
11831 let mut new_selections = Vec::new();
11832
11833 while let Some(selection) = selections.next() {
11834 // Find all the selections that span a contiguous row range
11835 let (start_row, end_row) = consume_contiguous_rows(
11836 &mut contiguous_row_selections,
11837 selection,
11838 &display_map,
11839 &mut selections,
11840 );
11841
11842 // Move the text spanned by the row range to be after the last line of the row range
11843 if end_row.0 <= buffer.max_point().row {
11844 let range_to_move =
11845 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11846 let insertion_point = display_map
11847 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11848 .0;
11849
11850 // Don't move lines across excerpt boundaries
11851 if buffer
11852 .excerpt_containing(range_to_move.start..insertion_point)
11853 .is_some()
11854 {
11855 let mut text = String::from("\n");
11856 text.extend(buffer.text_for_range(range_to_move.clone()));
11857 text.pop(); // Drop trailing newline
11858 edits.push((
11859 buffer.anchor_after(range_to_move.start)
11860 ..buffer.anchor_before(range_to_move.end),
11861 String::new(),
11862 ));
11863 let insertion_anchor = buffer.anchor_after(insertion_point);
11864 edits.push((insertion_anchor..insertion_anchor, text));
11865
11866 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11867
11868 // Move selections down
11869 new_selections.extend(contiguous_row_selections.drain(..).map(
11870 |mut selection| {
11871 selection.start.row += row_delta;
11872 selection.end.row += row_delta;
11873 selection
11874 },
11875 ));
11876
11877 // Move folds down
11878 unfold_ranges.push(range_to_move.clone());
11879 for fold in display_map.folds_in_range(
11880 buffer.anchor_before(range_to_move.start)
11881 ..buffer.anchor_after(range_to_move.end),
11882 ) {
11883 let mut start = fold.range.start.to_point(&buffer);
11884 let mut end = fold.range.end.to_point(&buffer);
11885 start.row += row_delta;
11886 end.row += row_delta;
11887 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11888 }
11889 }
11890 }
11891
11892 // If we didn't move line(s), preserve the existing selections
11893 new_selections.append(&mut contiguous_row_selections);
11894 }
11895
11896 self.transact(window, cx, |this, window, cx| {
11897 this.unfold_ranges(&unfold_ranges, true, true, cx);
11898 this.buffer.update(cx, |buffer, cx| {
11899 for (range, text) in edits {
11900 buffer.edit([(range, text)], None, cx);
11901 }
11902 });
11903 this.fold_creases(refold_creases, true, window, cx);
11904 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11905 });
11906 }
11907
11908 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11910 let text_layout_details = &self.text_layout_details(window);
11911 self.transact(window, cx, |this, window, cx| {
11912 let edits = this.change_selections(Default::default(), window, cx, |s| {
11913 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11914 s.move_with(|display_map, selection| {
11915 if !selection.is_empty() {
11916 return;
11917 }
11918
11919 let mut head = selection.head();
11920 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11921 if head.column() == display_map.line_len(head.row()) {
11922 transpose_offset = display_map
11923 .buffer_snapshot()
11924 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11925 }
11926
11927 if transpose_offset == 0 {
11928 return;
11929 }
11930
11931 *head.column_mut() += 1;
11932 head = display_map.clip_point(head, Bias::Right);
11933 let goal = SelectionGoal::HorizontalPosition(
11934 display_map
11935 .x_for_display_point(head, text_layout_details)
11936 .into(),
11937 );
11938 selection.collapse_to(head, goal);
11939
11940 let transpose_start = display_map
11941 .buffer_snapshot()
11942 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11943 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11944 let transpose_end = display_map
11945 .buffer_snapshot()
11946 .clip_offset(transpose_offset + 1, Bias::Right);
11947 if let Some(ch) = display_map
11948 .buffer_snapshot()
11949 .chars_at(transpose_start)
11950 .next()
11951 {
11952 edits.push((transpose_start..transpose_offset, String::new()));
11953 edits.push((transpose_end..transpose_end, ch.to_string()));
11954 }
11955 }
11956 });
11957 edits
11958 });
11959 this.buffer
11960 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11961 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11962 this.change_selections(Default::default(), window, cx, |s| {
11963 s.select(selections);
11964 });
11965 });
11966 }
11967
11968 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11970 if self.mode.is_single_line() {
11971 cx.propagate();
11972 return;
11973 }
11974
11975 self.rewrap_impl(RewrapOptions::default(), cx)
11976 }
11977
11978 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11979 let buffer = self.buffer.read(cx).snapshot(cx);
11980 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11981
11982 #[derive(Clone, Debug, PartialEq)]
11983 enum CommentFormat {
11984 /// single line comment, with prefix for line
11985 Line(String),
11986 /// single line within a block comment, with prefix for line
11987 BlockLine(String),
11988 /// a single line of a block comment that includes the initial delimiter
11989 BlockCommentWithStart(BlockCommentConfig),
11990 /// a single line of a block comment that includes the ending delimiter
11991 BlockCommentWithEnd(BlockCommentConfig),
11992 }
11993
11994 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11995 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11996 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11997 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11998 .peekable();
11999
12000 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12001 row
12002 } else {
12003 return Vec::new();
12004 };
12005
12006 let language_settings = buffer.language_settings_at(selection.head(), cx);
12007 let language_scope = buffer.language_scope_at(selection.head());
12008
12009 let indent_and_prefix_for_row =
12010 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12011 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12012 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12013 &language_scope
12014 {
12015 let indent_end = Point::new(row, indent.len);
12016 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12017 let line_text_after_indent = buffer
12018 .text_for_range(indent_end..line_end)
12019 .collect::<String>();
12020
12021 let is_within_comment_override = buffer
12022 .language_scope_at(indent_end)
12023 .is_some_and(|scope| scope.override_name() == Some("comment"));
12024 let comment_delimiters = if is_within_comment_override {
12025 // we are within a comment syntax node, but we don't
12026 // yet know what kind of comment: block, doc or line
12027 match (
12028 language_scope.documentation_comment(),
12029 language_scope.block_comment(),
12030 ) {
12031 (Some(config), _) | (_, Some(config))
12032 if buffer.contains_str_at(indent_end, &config.start) =>
12033 {
12034 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12035 }
12036 (Some(config), _) | (_, Some(config))
12037 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12038 {
12039 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12040 }
12041 (Some(config), _) | (_, Some(config))
12042 if buffer.contains_str_at(indent_end, &config.prefix) =>
12043 {
12044 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12045 }
12046 (_, _) => language_scope
12047 .line_comment_prefixes()
12048 .iter()
12049 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12050 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12051 }
12052 } else {
12053 // we not in an overridden comment node, but we may
12054 // be within a non-overridden line comment node
12055 language_scope
12056 .line_comment_prefixes()
12057 .iter()
12058 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12059 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12060 };
12061
12062 let rewrap_prefix = language_scope
12063 .rewrap_prefixes()
12064 .iter()
12065 .find_map(|prefix_regex| {
12066 prefix_regex.find(&line_text_after_indent).map(|mat| {
12067 if mat.start() == 0 {
12068 Some(mat.as_str().to_string())
12069 } else {
12070 None
12071 }
12072 })
12073 })
12074 .flatten();
12075 (comment_delimiters, rewrap_prefix)
12076 } else {
12077 (None, None)
12078 };
12079 (indent, comment_prefix, rewrap_prefix)
12080 };
12081
12082 let mut ranges = Vec::new();
12083 let from_empty_selection = selection.is_empty();
12084
12085 let mut current_range_start = first_row;
12086 let mut prev_row = first_row;
12087 let (
12088 mut current_range_indent,
12089 mut current_range_comment_delimiters,
12090 mut current_range_rewrap_prefix,
12091 ) = indent_and_prefix_for_row(first_row);
12092
12093 for row in non_blank_rows_iter.skip(1) {
12094 let has_paragraph_break = row > prev_row + 1;
12095
12096 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12097 indent_and_prefix_for_row(row);
12098
12099 let has_indent_change = row_indent != current_range_indent;
12100 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12101
12102 let has_boundary_change = has_comment_change
12103 || row_rewrap_prefix.is_some()
12104 || (has_indent_change && current_range_comment_delimiters.is_some());
12105
12106 if has_paragraph_break || has_boundary_change {
12107 ranges.push((
12108 language_settings.clone(),
12109 Point::new(current_range_start, 0)
12110 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12111 current_range_indent,
12112 current_range_comment_delimiters.clone(),
12113 current_range_rewrap_prefix.clone(),
12114 from_empty_selection,
12115 ));
12116 current_range_start = row;
12117 current_range_indent = row_indent;
12118 current_range_comment_delimiters = row_comment_delimiters;
12119 current_range_rewrap_prefix = row_rewrap_prefix;
12120 }
12121 prev_row = row;
12122 }
12123
12124 ranges.push((
12125 language_settings.clone(),
12126 Point::new(current_range_start, 0)
12127 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12128 current_range_indent,
12129 current_range_comment_delimiters,
12130 current_range_rewrap_prefix,
12131 from_empty_selection,
12132 ));
12133
12134 ranges
12135 });
12136
12137 let mut edits = Vec::new();
12138 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12139
12140 for (
12141 language_settings,
12142 wrap_range,
12143 mut indent_size,
12144 comment_prefix,
12145 rewrap_prefix,
12146 from_empty_selection,
12147 ) in wrap_ranges
12148 {
12149 let mut start_row = wrap_range.start.row;
12150 let mut end_row = wrap_range.end.row;
12151
12152 // Skip selections that overlap with a range that has already been rewrapped.
12153 let selection_range = start_row..end_row;
12154 if rewrapped_row_ranges
12155 .iter()
12156 .any(|range| range.overlaps(&selection_range))
12157 {
12158 continue;
12159 }
12160
12161 let tab_size = language_settings.tab_size;
12162
12163 let (line_prefix, inside_comment) = match &comment_prefix {
12164 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12165 (Some(prefix.as_str()), true)
12166 }
12167 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12168 (Some(prefix.as_ref()), true)
12169 }
12170 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12171 start: _,
12172 end: _,
12173 prefix,
12174 tab_size,
12175 })) => {
12176 indent_size.len += tab_size;
12177 (Some(prefix.as_ref()), true)
12178 }
12179 None => (None, false),
12180 };
12181 let indent_prefix = indent_size.chars().collect::<String>();
12182 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12183
12184 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12185 RewrapBehavior::InComments => inside_comment,
12186 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12187 RewrapBehavior::Anywhere => true,
12188 };
12189
12190 let should_rewrap = options.override_language_settings
12191 || allow_rewrap_based_on_language
12192 || self.hard_wrap.is_some();
12193 if !should_rewrap {
12194 continue;
12195 }
12196
12197 if from_empty_selection {
12198 'expand_upwards: while start_row > 0 {
12199 let prev_row = start_row - 1;
12200 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12201 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12202 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12203 {
12204 start_row = prev_row;
12205 } else {
12206 break 'expand_upwards;
12207 }
12208 }
12209
12210 'expand_downwards: while end_row < buffer.max_point().row {
12211 let next_row = end_row + 1;
12212 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12213 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12214 && !buffer.is_line_blank(MultiBufferRow(next_row))
12215 {
12216 end_row = next_row;
12217 } else {
12218 break 'expand_downwards;
12219 }
12220 }
12221 }
12222
12223 let start = Point::new(start_row, 0);
12224 let start_offset = ToOffset::to_offset(&start, &buffer);
12225 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12226 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12227 let mut first_line_delimiter = None;
12228 let mut last_line_delimiter = None;
12229 let Some(lines_without_prefixes) = selection_text
12230 .lines()
12231 .enumerate()
12232 .map(|(ix, line)| {
12233 let line_trimmed = line.trim_start();
12234 if rewrap_prefix.is_some() && ix > 0 {
12235 Ok(line_trimmed)
12236 } else if let Some(
12237 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12238 start,
12239 prefix,
12240 end,
12241 tab_size,
12242 })
12243 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12244 start,
12245 prefix,
12246 end,
12247 tab_size,
12248 }),
12249 ) = &comment_prefix
12250 {
12251 let line_trimmed = line_trimmed
12252 .strip_prefix(start.as_ref())
12253 .map(|s| {
12254 let mut indent_size = indent_size;
12255 indent_size.len -= tab_size;
12256 let indent_prefix: String = indent_size.chars().collect();
12257 first_line_delimiter = Some((indent_prefix, start));
12258 s.trim_start()
12259 })
12260 .unwrap_or(line_trimmed);
12261 let line_trimmed = line_trimmed
12262 .strip_suffix(end.as_ref())
12263 .map(|s| {
12264 last_line_delimiter = Some(end);
12265 s.trim_end()
12266 })
12267 .unwrap_or(line_trimmed);
12268 let line_trimmed = line_trimmed
12269 .strip_prefix(prefix.as_ref())
12270 .unwrap_or(line_trimmed);
12271 Ok(line_trimmed)
12272 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12273 line_trimmed.strip_prefix(prefix).with_context(|| {
12274 format!("line did not start with prefix {prefix:?}: {line:?}")
12275 })
12276 } else {
12277 line_trimmed
12278 .strip_prefix(&line_prefix.trim_start())
12279 .with_context(|| {
12280 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12281 })
12282 }
12283 })
12284 .collect::<Result<Vec<_>, _>>()
12285 .log_err()
12286 else {
12287 continue;
12288 };
12289
12290 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12291 buffer
12292 .language_settings_at(Point::new(start_row, 0), cx)
12293 .preferred_line_length as usize
12294 });
12295
12296 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12297 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12298 } else {
12299 line_prefix.clone()
12300 };
12301
12302 let wrapped_text = {
12303 let mut wrapped_text = wrap_with_prefix(
12304 line_prefix,
12305 subsequent_lines_prefix,
12306 lines_without_prefixes.join("\n"),
12307 wrap_column,
12308 tab_size,
12309 options.preserve_existing_whitespace,
12310 );
12311
12312 if let Some((indent, delimiter)) = first_line_delimiter {
12313 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12314 }
12315 if let Some(last_line) = last_line_delimiter {
12316 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12317 }
12318
12319 wrapped_text
12320 };
12321
12322 // TODO: should always use char-based diff while still supporting cursor behavior that
12323 // matches vim.
12324 let mut diff_options = DiffOptions::default();
12325 if options.override_language_settings {
12326 diff_options.max_word_diff_len = 0;
12327 diff_options.max_word_diff_line_count = 0;
12328 } else {
12329 diff_options.max_word_diff_len = usize::MAX;
12330 diff_options.max_word_diff_line_count = usize::MAX;
12331 }
12332
12333 for (old_range, new_text) in
12334 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12335 {
12336 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12337 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12338 edits.push((edit_start..edit_end, new_text));
12339 }
12340
12341 rewrapped_row_ranges.push(start_row..=end_row);
12342 }
12343
12344 self.buffer
12345 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12346 }
12347
12348 pub fn cut_common(
12349 &mut self,
12350 cut_no_selection_line: bool,
12351 window: &mut Window,
12352 cx: &mut Context<Self>,
12353 ) -> ClipboardItem {
12354 let mut text = String::new();
12355 let buffer = self.buffer.read(cx).snapshot(cx);
12356 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12357 let mut clipboard_selections = Vec::with_capacity(selections.len());
12358 {
12359 let max_point = buffer.max_point();
12360 let mut is_first = true;
12361 for selection in &mut selections {
12362 let is_entire_line =
12363 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12364 if is_entire_line {
12365 selection.start = Point::new(selection.start.row, 0);
12366 if !selection.is_empty() && selection.end.column == 0 {
12367 selection.end = cmp::min(max_point, selection.end);
12368 } else {
12369 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12370 }
12371 selection.goal = SelectionGoal::None;
12372 }
12373 if is_first {
12374 is_first = false;
12375 } else {
12376 text += "\n";
12377 }
12378 let mut len = 0;
12379 for chunk in buffer.text_for_range(selection.start..selection.end) {
12380 text.push_str(chunk);
12381 len += chunk.len();
12382 }
12383 clipboard_selections.push(ClipboardSelection {
12384 len,
12385 is_entire_line,
12386 first_line_indent: buffer
12387 .indent_size_for_line(MultiBufferRow(selection.start.row))
12388 .len,
12389 });
12390 }
12391 }
12392
12393 self.transact(window, cx, |this, window, cx| {
12394 this.change_selections(Default::default(), window, cx, |s| {
12395 s.select(selections);
12396 });
12397 this.insert("", window, cx);
12398 });
12399 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12400 }
12401
12402 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12403 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12404 let item = self.cut_common(true, window, cx);
12405 cx.write_to_clipboard(item);
12406 }
12407
12408 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12409 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12410 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12411 s.move_with(|snapshot, sel| {
12412 if sel.is_empty() {
12413 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12414 }
12415 if sel.is_empty() {
12416 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12417 }
12418 });
12419 });
12420 let item = self.cut_common(false, window, cx);
12421 cx.set_global(KillRing(item))
12422 }
12423
12424 pub fn kill_ring_yank(
12425 &mut self,
12426 _: &KillRingYank,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) {
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12431 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12432 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12433 (kill_ring.text().to_string(), kill_ring.metadata_json())
12434 } else {
12435 return;
12436 }
12437 } else {
12438 return;
12439 };
12440 self.do_paste(&text, metadata, false, window, cx);
12441 }
12442
12443 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12444 self.do_copy(true, cx);
12445 }
12446
12447 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12448 self.do_copy(false, cx);
12449 }
12450
12451 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12452 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12453 let buffer = self.buffer.read(cx).read(cx);
12454 let mut text = String::new();
12455
12456 let mut clipboard_selections = Vec::with_capacity(selections.len());
12457 {
12458 let max_point = buffer.max_point();
12459 let mut is_first = true;
12460 for selection in &selections {
12461 let mut start = selection.start;
12462 let mut end = selection.end;
12463 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12464 let mut add_trailing_newline = false;
12465 if is_entire_line {
12466 start = Point::new(start.row, 0);
12467 let next_line_start = Point::new(end.row + 1, 0);
12468 if next_line_start <= max_point {
12469 end = next_line_start;
12470 } else {
12471 // We're on the last line without a trailing newline.
12472 // Copy to the end of the line and add a newline afterwards.
12473 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12474 add_trailing_newline = true;
12475 }
12476 }
12477
12478 let mut trimmed_selections = Vec::new();
12479 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12480 let row = MultiBufferRow(start.row);
12481 let first_indent = buffer.indent_size_for_line(row);
12482 if first_indent.len == 0 || start.column > first_indent.len {
12483 trimmed_selections.push(start..end);
12484 } else {
12485 trimmed_selections.push(
12486 Point::new(row.0, first_indent.len)
12487 ..Point::new(row.0, buffer.line_len(row)),
12488 );
12489 for row in start.row + 1..=end.row {
12490 let mut line_len = buffer.line_len(MultiBufferRow(row));
12491 if row == end.row {
12492 line_len = end.column;
12493 }
12494 if line_len == 0 {
12495 trimmed_selections
12496 .push(Point::new(row, 0)..Point::new(row, line_len));
12497 continue;
12498 }
12499 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12500 if row_indent_size.len >= first_indent.len {
12501 trimmed_selections.push(
12502 Point::new(row, first_indent.len)..Point::new(row, line_len),
12503 );
12504 } else {
12505 trimmed_selections.clear();
12506 trimmed_selections.push(start..end);
12507 break;
12508 }
12509 }
12510 }
12511 } else {
12512 trimmed_selections.push(start..end);
12513 }
12514
12515 for trimmed_range in trimmed_selections {
12516 if is_first {
12517 is_first = false;
12518 } else {
12519 text += "\n";
12520 }
12521 let mut len = 0;
12522 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12523 text.push_str(chunk);
12524 len += chunk.len();
12525 }
12526 if add_trailing_newline {
12527 text.push('\n');
12528 len += 1;
12529 }
12530 clipboard_selections.push(ClipboardSelection {
12531 len,
12532 is_entire_line,
12533 first_line_indent: buffer
12534 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12535 .len,
12536 });
12537 }
12538 }
12539 }
12540
12541 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12542 text,
12543 clipboard_selections,
12544 ));
12545 }
12546
12547 pub fn do_paste(
12548 &mut self,
12549 text: &String,
12550 clipboard_selections: Option<Vec<ClipboardSelection>>,
12551 handle_entire_lines: bool,
12552 window: &mut Window,
12553 cx: &mut Context<Self>,
12554 ) {
12555 if self.read_only(cx) {
12556 return;
12557 }
12558
12559 let clipboard_text = Cow::Borrowed(text.as_str());
12560
12561 self.transact(window, cx, |this, window, cx| {
12562 let had_active_edit_prediction = this.has_active_edit_prediction();
12563 let display_map = this.display_snapshot(cx);
12564 let old_selections = this.selections.all::<usize>(&display_map);
12565 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12566
12567 if let Some(mut clipboard_selections) = clipboard_selections {
12568 let all_selections_were_entire_line =
12569 clipboard_selections.iter().all(|s| s.is_entire_line);
12570 let first_selection_indent_column =
12571 clipboard_selections.first().map(|s| s.first_line_indent);
12572 if clipboard_selections.len() != old_selections.len() {
12573 clipboard_selections.drain(..);
12574 }
12575 let mut auto_indent_on_paste = true;
12576
12577 this.buffer.update(cx, |buffer, cx| {
12578 let snapshot = buffer.read(cx);
12579 auto_indent_on_paste = snapshot
12580 .language_settings_at(cursor_offset, cx)
12581 .auto_indent_on_paste;
12582
12583 let mut start_offset = 0;
12584 let mut edits = Vec::new();
12585 let mut original_indent_columns = Vec::new();
12586 for (ix, selection) in old_selections.iter().enumerate() {
12587 let to_insert;
12588 let entire_line;
12589 let original_indent_column;
12590 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12591 let end_offset = start_offset + clipboard_selection.len;
12592 to_insert = &clipboard_text[start_offset..end_offset];
12593 entire_line = clipboard_selection.is_entire_line;
12594 start_offset = end_offset + 1;
12595 original_indent_column = Some(clipboard_selection.first_line_indent);
12596 } else {
12597 to_insert = &*clipboard_text;
12598 entire_line = all_selections_were_entire_line;
12599 original_indent_column = first_selection_indent_column
12600 }
12601
12602 let (range, to_insert) =
12603 if selection.is_empty() && handle_entire_lines && entire_line {
12604 // If the corresponding selection was empty when this slice of the
12605 // clipboard text was written, then the entire line containing the
12606 // selection was copied. If this selection is also currently empty,
12607 // then paste the line before the current line of the buffer.
12608 let column = selection.start.to_point(&snapshot).column as usize;
12609 let line_start = selection.start - column;
12610 (line_start..line_start, Cow::Borrowed(to_insert))
12611 } else {
12612 let language = snapshot.language_at(selection.head());
12613 let range = selection.range();
12614 if let Some(language) = language
12615 && language.name() == "Markdown".into()
12616 {
12617 edit_for_markdown_paste(
12618 &snapshot,
12619 range,
12620 to_insert,
12621 url::Url::parse(to_insert).ok(),
12622 )
12623 } else {
12624 (range, Cow::Borrowed(to_insert))
12625 }
12626 };
12627
12628 edits.push((range, to_insert));
12629 original_indent_columns.push(original_indent_column);
12630 }
12631 drop(snapshot);
12632
12633 buffer.edit(
12634 edits,
12635 if auto_indent_on_paste {
12636 Some(AutoindentMode::Block {
12637 original_indent_columns,
12638 })
12639 } else {
12640 None
12641 },
12642 cx,
12643 );
12644 });
12645
12646 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12647 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12648 } else {
12649 let url = url::Url::parse(&clipboard_text).ok();
12650
12651 let auto_indent_mode = if !clipboard_text.is_empty() {
12652 Some(AutoindentMode::Block {
12653 original_indent_columns: Vec::new(),
12654 })
12655 } else {
12656 None
12657 };
12658
12659 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12660 let snapshot = buffer.snapshot(cx);
12661
12662 let anchors = old_selections
12663 .iter()
12664 .map(|s| {
12665 let anchor = snapshot.anchor_after(s.head());
12666 s.map(|_| anchor)
12667 })
12668 .collect::<Vec<_>>();
12669
12670 let mut edits = Vec::new();
12671
12672 for selection in old_selections.iter() {
12673 let language = snapshot.language_at(selection.head());
12674 let range = selection.range();
12675
12676 let (edit_range, edit_text) = if let Some(language) = language
12677 && language.name() == "Markdown".into()
12678 {
12679 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12680 } else {
12681 (range, clipboard_text.clone())
12682 };
12683
12684 edits.push((edit_range, edit_text));
12685 }
12686
12687 drop(snapshot);
12688 buffer.edit(edits, auto_indent_mode, cx);
12689
12690 anchors
12691 });
12692
12693 this.change_selections(Default::default(), window, cx, |s| {
12694 s.select_anchors(selection_anchors);
12695 });
12696 }
12697
12698 let trigger_in_words =
12699 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12700
12701 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12702 });
12703 }
12704
12705 pub fn diff_clipboard_with_selection(
12706 &mut self,
12707 _: &DiffClipboardWithSelection,
12708 window: &mut Window,
12709 cx: &mut Context<Self>,
12710 ) {
12711 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12712
12713 if selections.is_empty() {
12714 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12715 return;
12716 };
12717
12718 let clipboard_text = match cx.read_from_clipboard() {
12719 Some(item) => match item.entries().first() {
12720 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12721 _ => None,
12722 },
12723 None => None,
12724 };
12725
12726 let Some(clipboard_text) = clipboard_text else {
12727 log::warn!("Clipboard doesn't contain text.");
12728 return;
12729 };
12730
12731 window.dispatch_action(
12732 Box::new(DiffClipboardWithSelectionData {
12733 clipboard_text,
12734 editor: cx.entity(),
12735 }),
12736 cx,
12737 );
12738 }
12739
12740 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12741 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12742 if let Some(item) = cx.read_from_clipboard() {
12743 let entries = item.entries();
12744
12745 match entries.first() {
12746 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12747 // of all the pasted entries.
12748 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12749 .do_paste(
12750 clipboard_string.text(),
12751 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12752 true,
12753 window,
12754 cx,
12755 ),
12756 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12757 }
12758 }
12759 }
12760
12761 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12762 if self.read_only(cx) {
12763 return;
12764 }
12765
12766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12767
12768 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12769 if let Some((selections, _)) =
12770 self.selection_history.transaction(transaction_id).cloned()
12771 {
12772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12773 s.select_anchors(selections.to_vec());
12774 });
12775 } else {
12776 log::error!(
12777 "No entry in selection_history found for undo. \
12778 This may correspond to a bug where undo does not update the selection. \
12779 If this is occurring, please add details to \
12780 https://github.com/zed-industries/zed/issues/22692"
12781 );
12782 }
12783 self.request_autoscroll(Autoscroll::fit(), cx);
12784 self.unmark_text(window, cx);
12785 self.refresh_edit_prediction(true, false, window, cx);
12786 cx.emit(EditorEvent::Edited { transaction_id });
12787 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12788 }
12789 }
12790
12791 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12792 if self.read_only(cx) {
12793 return;
12794 }
12795
12796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12797
12798 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12799 if let Some((_, Some(selections))) =
12800 self.selection_history.transaction(transaction_id).cloned()
12801 {
12802 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12803 s.select_anchors(selections.to_vec());
12804 });
12805 } else {
12806 log::error!(
12807 "No entry in selection_history found for redo. \
12808 This may correspond to a bug where undo does not update the selection. \
12809 If this is occurring, please add details to \
12810 https://github.com/zed-industries/zed/issues/22692"
12811 );
12812 }
12813 self.request_autoscroll(Autoscroll::fit(), cx);
12814 self.unmark_text(window, cx);
12815 self.refresh_edit_prediction(true, false, window, cx);
12816 cx.emit(EditorEvent::Edited { transaction_id });
12817 }
12818 }
12819
12820 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12821 self.buffer
12822 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12823 }
12824
12825 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12826 self.buffer
12827 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12828 }
12829
12830 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12831 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12832 self.change_selections(Default::default(), window, cx, |s| {
12833 s.move_with(|map, selection| {
12834 let cursor = if selection.is_empty() {
12835 movement::left(map, selection.start)
12836 } else {
12837 selection.start
12838 };
12839 selection.collapse_to(cursor, SelectionGoal::None);
12840 });
12841 })
12842 }
12843
12844 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12846 self.change_selections(Default::default(), window, cx, |s| {
12847 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12848 })
12849 }
12850
12851 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12853 self.change_selections(Default::default(), window, cx, |s| {
12854 s.move_with(|map, selection| {
12855 let cursor = if selection.is_empty() {
12856 movement::right(map, selection.end)
12857 } else {
12858 selection.end
12859 };
12860 selection.collapse_to(cursor, SelectionGoal::None)
12861 });
12862 })
12863 }
12864
12865 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12866 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12867 self.change_selections(Default::default(), window, cx, |s| {
12868 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12869 });
12870 }
12871
12872 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12873 if self.take_rename(true, window, cx).is_some() {
12874 return;
12875 }
12876
12877 if self.mode.is_single_line() {
12878 cx.propagate();
12879 return;
12880 }
12881
12882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12883
12884 let text_layout_details = &self.text_layout_details(window);
12885 let selection_count = self.selections.count();
12886 let first_selection = self.selections.first_anchor();
12887
12888 self.change_selections(Default::default(), window, cx, |s| {
12889 s.move_with(|map, selection| {
12890 if !selection.is_empty() {
12891 selection.goal = SelectionGoal::None;
12892 }
12893 let (cursor, goal) = movement::up(
12894 map,
12895 selection.start,
12896 selection.goal,
12897 false,
12898 text_layout_details,
12899 );
12900 selection.collapse_to(cursor, goal);
12901 });
12902 });
12903
12904 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12905 {
12906 cx.propagate();
12907 }
12908 }
12909
12910 pub fn move_up_by_lines(
12911 &mut self,
12912 action: &MoveUpByLines,
12913 window: &mut Window,
12914 cx: &mut Context<Self>,
12915 ) {
12916 if self.take_rename(true, window, cx).is_some() {
12917 return;
12918 }
12919
12920 if self.mode.is_single_line() {
12921 cx.propagate();
12922 return;
12923 }
12924
12925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12926
12927 let text_layout_details = &self.text_layout_details(window);
12928
12929 self.change_selections(Default::default(), window, cx, |s| {
12930 s.move_with(|map, selection| {
12931 if !selection.is_empty() {
12932 selection.goal = SelectionGoal::None;
12933 }
12934 let (cursor, goal) = movement::up_by_rows(
12935 map,
12936 selection.start,
12937 action.lines,
12938 selection.goal,
12939 false,
12940 text_layout_details,
12941 );
12942 selection.collapse_to(cursor, goal);
12943 });
12944 })
12945 }
12946
12947 pub fn move_down_by_lines(
12948 &mut self,
12949 action: &MoveDownByLines,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 if self.take_rename(true, window, cx).is_some() {
12954 return;
12955 }
12956
12957 if self.mode.is_single_line() {
12958 cx.propagate();
12959 return;
12960 }
12961
12962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12963
12964 let text_layout_details = &self.text_layout_details(window);
12965
12966 self.change_selections(Default::default(), window, cx, |s| {
12967 s.move_with(|map, selection| {
12968 if !selection.is_empty() {
12969 selection.goal = SelectionGoal::None;
12970 }
12971 let (cursor, goal) = movement::down_by_rows(
12972 map,
12973 selection.start,
12974 action.lines,
12975 selection.goal,
12976 false,
12977 text_layout_details,
12978 );
12979 selection.collapse_to(cursor, goal);
12980 });
12981 })
12982 }
12983
12984 pub fn select_down_by_lines(
12985 &mut self,
12986 action: &SelectDownByLines,
12987 window: &mut Window,
12988 cx: &mut Context<Self>,
12989 ) {
12990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12991 let text_layout_details = &self.text_layout_details(window);
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_heads_with(|map, head, goal| {
12994 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12995 })
12996 })
12997 }
12998
12999 pub fn select_up_by_lines(
13000 &mut self,
13001 action: &SelectUpByLines,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13006 let text_layout_details = &self.text_layout_details(window);
13007 self.change_selections(Default::default(), window, cx, |s| {
13008 s.move_heads_with(|map, head, goal| {
13009 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13010 })
13011 })
13012 }
13013
13014 pub fn select_page_up(
13015 &mut self,
13016 _: &SelectPageUp,
13017 window: &mut Window,
13018 cx: &mut Context<Self>,
13019 ) {
13020 let Some(row_count) = self.visible_row_count() else {
13021 return;
13022 };
13023
13024 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13025
13026 let text_layout_details = &self.text_layout_details(window);
13027
13028 self.change_selections(Default::default(), window, cx, |s| {
13029 s.move_heads_with(|map, head, goal| {
13030 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13031 })
13032 })
13033 }
13034
13035 pub fn move_page_up(
13036 &mut self,
13037 action: &MovePageUp,
13038 window: &mut Window,
13039 cx: &mut Context<Self>,
13040 ) {
13041 if self.take_rename(true, window, cx).is_some() {
13042 return;
13043 }
13044
13045 if self
13046 .context_menu
13047 .borrow_mut()
13048 .as_mut()
13049 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13050 .unwrap_or(false)
13051 {
13052 return;
13053 }
13054
13055 if matches!(self.mode, EditorMode::SingleLine) {
13056 cx.propagate();
13057 return;
13058 }
13059
13060 let Some(row_count) = self.visible_row_count() else {
13061 return;
13062 };
13063
13064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13065
13066 let effects = if action.center_cursor {
13067 SelectionEffects::scroll(Autoscroll::center())
13068 } else {
13069 SelectionEffects::default()
13070 };
13071
13072 let text_layout_details = &self.text_layout_details(window);
13073
13074 self.change_selections(effects, window, cx, |s| {
13075 s.move_with(|map, selection| {
13076 if !selection.is_empty() {
13077 selection.goal = SelectionGoal::None;
13078 }
13079 let (cursor, goal) = movement::up_by_rows(
13080 map,
13081 selection.end,
13082 row_count,
13083 selection.goal,
13084 false,
13085 text_layout_details,
13086 );
13087 selection.collapse_to(cursor, goal);
13088 });
13089 });
13090 }
13091
13092 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13094 let text_layout_details = &self.text_layout_details(window);
13095 self.change_selections(Default::default(), window, cx, |s| {
13096 s.move_heads_with(|map, head, goal| {
13097 movement::up(map, head, goal, false, text_layout_details)
13098 })
13099 })
13100 }
13101
13102 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13103 self.take_rename(true, window, cx);
13104
13105 if self.mode.is_single_line() {
13106 cx.propagate();
13107 return;
13108 }
13109
13110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13111
13112 let text_layout_details = &self.text_layout_details(window);
13113 let selection_count = self.selections.count();
13114 let first_selection = self.selections.first_anchor();
13115
13116 self.change_selections(Default::default(), window, cx, |s| {
13117 s.move_with(|map, selection| {
13118 if !selection.is_empty() {
13119 selection.goal = SelectionGoal::None;
13120 }
13121 let (cursor, goal) = movement::down(
13122 map,
13123 selection.end,
13124 selection.goal,
13125 false,
13126 text_layout_details,
13127 );
13128 selection.collapse_to(cursor, goal);
13129 });
13130 });
13131
13132 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13133 {
13134 cx.propagate();
13135 }
13136 }
13137
13138 pub fn select_page_down(
13139 &mut self,
13140 _: &SelectPageDown,
13141 window: &mut Window,
13142 cx: &mut Context<Self>,
13143 ) {
13144 let Some(row_count) = self.visible_row_count() else {
13145 return;
13146 };
13147
13148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13149
13150 let text_layout_details = &self.text_layout_details(window);
13151
13152 self.change_selections(Default::default(), window, cx, |s| {
13153 s.move_heads_with(|map, head, goal| {
13154 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13155 })
13156 })
13157 }
13158
13159 pub fn move_page_down(
13160 &mut self,
13161 action: &MovePageDown,
13162 window: &mut Window,
13163 cx: &mut Context<Self>,
13164 ) {
13165 if self.take_rename(true, window, cx).is_some() {
13166 return;
13167 }
13168
13169 if self
13170 .context_menu
13171 .borrow_mut()
13172 .as_mut()
13173 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13174 .unwrap_or(false)
13175 {
13176 return;
13177 }
13178
13179 if matches!(self.mode, EditorMode::SingleLine) {
13180 cx.propagate();
13181 return;
13182 }
13183
13184 let Some(row_count) = self.visible_row_count() else {
13185 return;
13186 };
13187
13188 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13189
13190 let effects = if action.center_cursor {
13191 SelectionEffects::scroll(Autoscroll::center())
13192 } else {
13193 SelectionEffects::default()
13194 };
13195
13196 let text_layout_details = &self.text_layout_details(window);
13197 self.change_selections(effects, window, cx, |s| {
13198 s.move_with(|map, selection| {
13199 if !selection.is_empty() {
13200 selection.goal = SelectionGoal::None;
13201 }
13202 let (cursor, goal) = movement::down_by_rows(
13203 map,
13204 selection.end,
13205 row_count,
13206 selection.goal,
13207 false,
13208 text_layout_details,
13209 );
13210 selection.collapse_to(cursor, goal);
13211 });
13212 });
13213 }
13214
13215 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13217 let text_layout_details = &self.text_layout_details(window);
13218 self.change_selections(Default::default(), window, cx, |s| {
13219 s.move_heads_with(|map, head, goal| {
13220 movement::down(map, head, goal, false, text_layout_details)
13221 })
13222 });
13223 }
13224
13225 pub fn context_menu_first(
13226 &mut self,
13227 _: &ContextMenuFirst,
13228 window: &mut Window,
13229 cx: &mut Context<Self>,
13230 ) {
13231 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13232 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13233 }
13234 }
13235
13236 pub fn context_menu_prev(
13237 &mut self,
13238 _: &ContextMenuPrevious,
13239 window: &mut Window,
13240 cx: &mut Context<Self>,
13241 ) {
13242 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13243 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13244 }
13245 }
13246
13247 pub fn context_menu_next(
13248 &mut self,
13249 _: &ContextMenuNext,
13250 window: &mut Window,
13251 cx: &mut Context<Self>,
13252 ) {
13253 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13254 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13255 }
13256 }
13257
13258 pub fn context_menu_last(
13259 &mut self,
13260 _: &ContextMenuLast,
13261 window: &mut Window,
13262 cx: &mut Context<Self>,
13263 ) {
13264 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13265 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13266 }
13267 }
13268
13269 pub fn signature_help_prev(
13270 &mut self,
13271 _: &SignatureHelpPrevious,
13272 _: &mut Window,
13273 cx: &mut Context<Self>,
13274 ) {
13275 if let Some(popover) = self.signature_help_state.popover_mut() {
13276 if popover.current_signature == 0 {
13277 popover.current_signature = popover.signatures.len() - 1;
13278 } else {
13279 popover.current_signature -= 1;
13280 }
13281 cx.notify();
13282 }
13283 }
13284
13285 pub fn signature_help_next(
13286 &mut self,
13287 _: &SignatureHelpNext,
13288 _: &mut Window,
13289 cx: &mut Context<Self>,
13290 ) {
13291 if let Some(popover) = self.signature_help_state.popover_mut() {
13292 if popover.current_signature + 1 == popover.signatures.len() {
13293 popover.current_signature = 0;
13294 } else {
13295 popover.current_signature += 1;
13296 }
13297 cx.notify();
13298 }
13299 }
13300
13301 pub fn move_to_previous_word_start(
13302 &mut self,
13303 _: &MoveToPreviousWordStart,
13304 window: &mut Window,
13305 cx: &mut Context<Self>,
13306 ) {
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_cursors_with(|map, head, _| {
13310 (
13311 movement::previous_word_start(map, head),
13312 SelectionGoal::None,
13313 )
13314 });
13315 })
13316 }
13317
13318 pub fn move_to_previous_subword_start(
13319 &mut self,
13320 _: &MoveToPreviousSubwordStart,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13325 self.change_selections(Default::default(), window, cx, |s| {
13326 s.move_cursors_with(|map, head, _| {
13327 (
13328 movement::previous_subword_start(map, head),
13329 SelectionGoal::None,
13330 )
13331 });
13332 })
13333 }
13334
13335 pub fn select_to_previous_word_start(
13336 &mut self,
13337 _: &SelectToPreviousWordStart,
13338 window: &mut Window,
13339 cx: &mut Context<Self>,
13340 ) {
13341 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13342 self.change_selections(Default::default(), window, cx, |s| {
13343 s.move_heads_with(|map, head, _| {
13344 (
13345 movement::previous_word_start(map, head),
13346 SelectionGoal::None,
13347 )
13348 });
13349 })
13350 }
13351
13352 pub fn select_to_previous_subword_start(
13353 &mut self,
13354 _: &SelectToPreviousSubwordStart,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13359 self.change_selections(Default::default(), window, cx, |s| {
13360 s.move_heads_with(|map, head, _| {
13361 (
13362 movement::previous_subword_start(map, head),
13363 SelectionGoal::None,
13364 )
13365 });
13366 })
13367 }
13368
13369 pub fn delete_to_previous_word_start(
13370 &mut self,
13371 action: &DeleteToPreviousWordStart,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13376 self.transact(window, cx, |this, window, cx| {
13377 this.select_autoclose_pair(window, cx);
13378 this.change_selections(Default::default(), window, cx, |s| {
13379 s.move_with(|map, selection| {
13380 if selection.is_empty() {
13381 let mut cursor = if action.ignore_newlines {
13382 movement::previous_word_start(map, selection.head())
13383 } else {
13384 movement::previous_word_start_or_newline(map, selection.head())
13385 };
13386 cursor = movement::adjust_greedy_deletion(
13387 map,
13388 selection.head(),
13389 cursor,
13390 action.ignore_brackets,
13391 );
13392 selection.set_head(cursor, SelectionGoal::None);
13393 }
13394 });
13395 });
13396 this.insert("", window, cx);
13397 });
13398 }
13399
13400 pub fn delete_to_previous_subword_start(
13401 &mut self,
13402 _: &DeleteToPreviousSubwordStart,
13403 window: &mut Window,
13404 cx: &mut Context<Self>,
13405 ) {
13406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13407 self.transact(window, cx, |this, window, cx| {
13408 this.select_autoclose_pair(window, cx);
13409 this.change_selections(Default::default(), window, cx, |s| {
13410 s.move_with(|map, selection| {
13411 if selection.is_empty() {
13412 let mut cursor = movement::previous_subword_start(map, selection.head());
13413 cursor =
13414 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13415 selection.set_head(cursor, SelectionGoal::None);
13416 }
13417 });
13418 });
13419 this.insert("", window, cx);
13420 });
13421 }
13422
13423 pub fn move_to_next_word_end(
13424 &mut self,
13425 _: &MoveToNextWordEnd,
13426 window: &mut Window,
13427 cx: &mut Context<Self>,
13428 ) {
13429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13430 self.change_selections(Default::default(), window, cx, |s| {
13431 s.move_cursors_with(|map, head, _| {
13432 (movement::next_word_end(map, head), SelectionGoal::None)
13433 });
13434 })
13435 }
13436
13437 pub fn move_to_next_subword_end(
13438 &mut self,
13439 _: &MoveToNextSubwordEnd,
13440 window: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13444 self.change_selections(Default::default(), window, cx, |s| {
13445 s.move_cursors_with(|map, head, _| {
13446 (movement::next_subword_end(map, head), SelectionGoal::None)
13447 });
13448 })
13449 }
13450
13451 pub fn select_to_next_word_end(
13452 &mut self,
13453 _: &SelectToNextWordEnd,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_heads_with(|map, head, _| {
13460 (movement::next_word_end(map, head), SelectionGoal::None)
13461 });
13462 })
13463 }
13464
13465 pub fn select_to_next_subword_end(
13466 &mut self,
13467 _: &SelectToNextSubwordEnd,
13468 window: &mut Window,
13469 cx: &mut Context<Self>,
13470 ) {
13471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13472 self.change_selections(Default::default(), window, cx, |s| {
13473 s.move_heads_with(|map, head, _| {
13474 (movement::next_subword_end(map, head), SelectionGoal::None)
13475 });
13476 })
13477 }
13478
13479 pub fn delete_to_next_word_end(
13480 &mut self,
13481 action: &DeleteToNextWordEnd,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13486 self.transact(window, cx, |this, window, cx| {
13487 this.change_selections(Default::default(), window, cx, |s| {
13488 s.move_with(|map, selection| {
13489 if selection.is_empty() {
13490 let mut cursor = if action.ignore_newlines {
13491 movement::next_word_end(map, selection.head())
13492 } else {
13493 movement::next_word_end_or_newline(map, selection.head())
13494 };
13495 cursor = movement::adjust_greedy_deletion(
13496 map,
13497 selection.head(),
13498 cursor,
13499 action.ignore_brackets,
13500 );
13501 selection.set_head(cursor, SelectionGoal::None);
13502 }
13503 });
13504 });
13505 this.insert("", window, cx);
13506 });
13507 }
13508
13509 pub fn delete_to_next_subword_end(
13510 &mut self,
13511 _: &DeleteToNextSubwordEnd,
13512 window: &mut Window,
13513 cx: &mut Context<Self>,
13514 ) {
13515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13516 self.transact(window, cx, |this, window, cx| {
13517 this.change_selections(Default::default(), window, cx, |s| {
13518 s.move_with(|map, selection| {
13519 if selection.is_empty() {
13520 let mut cursor = movement::next_subword_end(map, selection.head());
13521 cursor =
13522 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13523 selection.set_head(cursor, SelectionGoal::None);
13524 }
13525 });
13526 });
13527 this.insert("", window, cx);
13528 });
13529 }
13530
13531 pub fn move_to_beginning_of_line(
13532 &mut self,
13533 action: &MoveToBeginningOfLine,
13534 window: &mut Window,
13535 cx: &mut Context<Self>,
13536 ) {
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13538 self.change_selections(Default::default(), window, cx, |s| {
13539 s.move_cursors_with(|map, head, _| {
13540 (
13541 movement::indented_line_beginning(
13542 map,
13543 head,
13544 action.stop_at_soft_wraps,
13545 action.stop_at_indent,
13546 ),
13547 SelectionGoal::None,
13548 )
13549 });
13550 })
13551 }
13552
13553 pub fn select_to_beginning_of_line(
13554 &mut self,
13555 action: &SelectToBeginningOfLine,
13556 window: &mut Window,
13557 cx: &mut Context<Self>,
13558 ) {
13559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13560 self.change_selections(Default::default(), window, cx, |s| {
13561 s.move_heads_with(|map, head, _| {
13562 (
13563 movement::indented_line_beginning(
13564 map,
13565 head,
13566 action.stop_at_soft_wraps,
13567 action.stop_at_indent,
13568 ),
13569 SelectionGoal::None,
13570 )
13571 });
13572 });
13573 }
13574
13575 pub fn delete_to_beginning_of_line(
13576 &mut self,
13577 action: &DeleteToBeginningOfLine,
13578 window: &mut Window,
13579 cx: &mut Context<Self>,
13580 ) {
13581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13582 self.transact(window, cx, |this, window, cx| {
13583 this.change_selections(Default::default(), window, cx, |s| {
13584 s.move_with(|_, selection| {
13585 selection.reversed = true;
13586 });
13587 });
13588
13589 this.select_to_beginning_of_line(
13590 &SelectToBeginningOfLine {
13591 stop_at_soft_wraps: false,
13592 stop_at_indent: action.stop_at_indent,
13593 },
13594 window,
13595 cx,
13596 );
13597 this.backspace(&Backspace, window, cx);
13598 });
13599 }
13600
13601 pub fn move_to_end_of_line(
13602 &mut self,
13603 action: &MoveToEndOfLine,
13604 window: &mut Window,
13605 cx: &mut Context<Self>,
13606 ) {
13607 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13608 self.change_selections(Default::default(), window, cx, |s| {
13609 s.move_cursors_with(|map, head, _| {
13610 (
13611 movement::line_end(map, head, action.stop_at_soft_wraps),
13612 SelectionGoal::None,
13613 )
13614 });
13615 })
13616 }
13617
13618 pub fn select_to_end_of_line(
13619 &mut self,
13620 action: &SelectToEndOfLine,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13625 self.change_selections(Default::default(), window, cx, |s| {
13626 s.move_heads_with(|map, head, _| {
13627 (
13628 movement::line_end(map, head, action.stop_at_soft_wraps),
13629 SelectionGoal::None,
13630 )
13631 });
13632 })
13633 }
13634
13635 pub fn delete_to_end_of_line(
13636 &mut self,
13637 _: &DeleteToEndOfLine,
13638 window: &mut Window,
13639 cx: &mut Context<Self>,
13640 ) {
13641 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13642 self.transact(window, cx, |this, window, cx| {
13643 this.select_to_end_of_line(
13644 &SelectToEndOfLine {
13645 stop_at_soft_wraps: false,
13646 },
13647 window,
13648 cx,
13649 );
13650 this.delete(&Delete, window, cx);
13651 });
13652 }
13653
13654 pub fn cut_to_end_of_line(
13655 &mut self,
13656 action: &CutToEndOfLine,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13661 self.transact(window, cx, |this, window, cx| {
13662 this.select_to_end_of_line(
13663 &SelectToEndOfLine {
13664 stop_at_soft_wraps: false,
13665 },
13666 window,
13667 cx,
13668 );
13669 if !action.stop_at_newlines {
13670 this.change_selections(Default::default(), window, cx, |s| {
13671 s.move_with(|_, sel| {
13672 if sel.is_empty() {
13673 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13674 }
13675 });
13676 });
13677 }
13678 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13679 let item = this.cut_common(false, window, cx);
13680 cx.write_to_clipboard(item);
13681 });
13682 }
13683
13684 pub fn move_to_start_of_paragraph(
13685 &mut self,
13686 _: &MoveToStartOfParagraph,
13687 window: &mut Window,
13688 cx: &mut Context<Self>,
13689 ) {
13690 if matches!(self.mode, EditorMode::SingleLine) {
13691 cx.propagate();
13692 return;
13693 }
13694 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13695 self.change_selections(Default::default(), window, cx, |s| {
13696 s.move_with(|map, selection| {
13697 selection.collapse_to(
13698 movement::start_of_paragraph(map, selection.head(), 1),
13699 SelectionGoal::None,
13700 )
13701 });
13702 })
13703 }
13704
13705 pub fn move_to_end_of_paragraph(
13706 &mut self,
13707 _: &MoveToEndOfParagraph,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 if matches!(self.mode, EditorMode::SingleLine) {
13712 cx.propagate();
13713 return;
13714 }
13715 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13716 self.change_selections(Default::default(), window, cx, |s| {
13717 s.move_with(|map, selection| {
13718 selection.collapse_to(
13719 movement::end_of_paragraph(map, selection.head(), 1),
13720 SelectionGoal::None,
13721 )
13722 });
13723 })
13724 }
13725
13726 pub fn select_to_start_of_paragraph(
13727 &mut self,
13728 _: &SelectToStartOfParagraph,
13729 window: &mut Window,
13730 cx: &mut Context<Self>,
13731 ) {
13732 if matches!(self.mode, EditorMode::SingleLine) {
13733 cx.propagate();
13734 return;
13735 }
13736 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13737 self.change_selections(Default::default(), window, cx, |s| {
13738 s.move_heads_with(|map, head, _| {
13739 (
13740 movement::start_of_paragraph(map, head, 1),
13741 SelectionGoal::None,
13742 )
13743 });
13744 })
13745 }
13746
13747 pub fn select_to_end_of_paragraph(
13748 &mut self,
13749 _: &SelectToEndOfParagraph,
13750 window: &mut Window,
13751 cx: &mut Context<Self>,
13752 ) {
13753 if matches!(self.mode, EditorMode::SingleLine) {
13754 cx.propagate();
13755 return;
13756 }
13757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13758 self.change_selections(Default::default(), window, cx, |s| {
13759 s.move_heads_with(|map, head, _| {
13760 (
13761 movement::end_of_paragraph(map, head, 1),
13762 SelectionGoal::None,
13763 )
13764 });
13765 })
13766 }
13767
13768 pub fn move_to_start_of_excerpt(
13769 &mut self,
13770 _: &MoveToStartOfExcerpt,
13771 window: &mut Window,
13772 cx: &mut Context<Self>,
13773 ) {
13774 if matches!(self.mode, EditorMode::SingleLine) {
13775 cx.propagate();
13776 return;
13777 }
13778 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13779 self.change_selections(Default::default(), window, cx, |s| {
13780 s.move_with(|map, selection| {
13781 selection.collapse_to(
13782 movement::start_of_excerpt(
13783 map,
13784 selection.head(),
13785 workspace::searchable::Direction::Prev,
13786 ),
13787 SelectionGoal::None,
13788 )
13789 });
13790 })
13791 }
13792
13793 pub fn move_to_start_of_next_excerpt(
13794 &mut self,
13795 _: &MoveToStartOfNextExcerpt,
13796 window: &mut Window,
13797 cx: &mut Context<Self>,
13798 ) {
13799 if matches!(self.mode, EditorMode::SingleLine) {
13800 cx.propagate();
13801 return;
13802 }
13803
13804 self.change_selections(Default::default(), window, cx, |s| {
13805 s.move_with(|map, selection| {
13806 selection.collapse_to(
13807 movement::start_of_excerpt(
13808 map,
13809 selection.head(),
13810 workspace::searchable::Direction::Next,
13811 ),
13812 SelectionGoal::None,
13813 )
13814 });
13815 })
13816 }
13817
13818 pub fn move_to_end_of_excerpt(
13819 &mut self,
13820 _: &MoveToEndOfExcerpt,
13821 window: &mut Window,
13822 cx: &mut Context<Self>,
13823 ) {
13824 if matches!(self.mode, EditorMode::SingleLine) {
13825 cx.propagate();
13826 return;
13827 }
13828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13829 self.change_selections(Default::default(), window, cx, |s| {
13830 s.move_with(|map, selection| {
13831 selection.collapse_to(
13832 movement::end_of_excerpt(
13833 map,
13834 selection.head(),
13835 workspace::searchable::Direction::Next,
13836 ),
13837 SelectionGoal::None,
13838 )
13839 });
13840 })
13841 }
13842
13843 pub fn move_to_end_of_previous_excerpt(
13844 &mut self,
13845 _: &MoveToEndOfPreviousExcerpt,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 if matches!(self.mode, EditorMode::SingleLine) {
13850 cx.propagate();
13851 return;
13852 }
13853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13854 self.change_selections(Default::default(), window, cx, |s| {
13855 s.move_with(|map, selection| {
13856 selection.collapse_to(
13857 movement::end_of_excerpt(
13858 map,
13859 selection.head(),
13860 workspace::searchable::Direction::Prev,
13861 ),
13862 SelectionGoal::None,
13863 )
13864 });
13865 })
13866 }
13867
13868 pub fn select_to_start_of_excerpt(
13869 &mut self,
13870 _: &SelectToStartOfExcerpt,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 if matches!(self.mode, EditorMode::SingleLine) {
13875 cx.propagate();
13876 return;
13877 }
13878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13879 self.change_selections(Default::default(), window, cx, |s| {
13880 s.move_heads_with(|map, head, _| {
13881 (
13882 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13883 SelectionGoal::None,
13884 )
13885 });
13886 })
13887 }
13888
13889 pub fn select_to_start_of_next_excerpt(
13890 &mut self,
13891 _: &SelectToStartOfNextExcerpt,
13892 window: &mut Window,
13893 cx: &mut Context<Self>,
13894 ) {
13895 if matches!(self.mode, EditorMode::SingleLine) {
13896 cx.propagate();
13897 return;
13898 }
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13900 self.change_selections(Default::default(), window, cx, |s| {
13901 s.move_heads_with(|map, head, _| {
13902 (
13903 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13904 SelectionGoal::None,
13905 )
13906 });
13907 })
13908 }
13909
13910 pub fn select_to_end_of_excerpt(
13911 &mut self,
13912 _: &SelectToEndOfExcerpt,
13913 window: &mut Window,
13914 cx: &mut Context<Self>,
13915 ) {
13916 if matches!(self.mode, EditorMode::SingleLine) {
13917 cx.propagate();
13918 return;
13919 }
13920 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13921 self.change_selections(Default::default(), window, cx, |s| {
13922 s.move_heads_with(|map, head, _| {
13923 (
13924 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13925 SelectionGoal::None,
13926 )
13927 });
13928 })
13929 }
13930
13931 pub fn select_to_end_of_previous_excerpt(
13932 &mut self,
13933 _: &SelectToEndOfPreviousExcerpt,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 if matches!(self.mode, EditorMode::SingleLine) {
13938 cx.propagate();
13939 return;
13940 }
13941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13942 self.change_selections(Default::default(), window, cx, |s| {
13943 s.move_heads_with(|map, head, _| {
13944 (
13945 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13946 SelectionGoal::None,
13947 )
13948 });
13949 })
13950 }
13951
13952 pub fn move_to_beginning(
13953 &mut self,
13954 _: &MoveToBeginning,
13955 window: &mut Window,
13956 cx: &mut Context<Self>,
13957 ) {
13958 if matches!(self.mode, EditorMode::SingleLine) {
13959 cx.propagate();
13960 return;
13961 }
13962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13963 self.change_selections(Default::default(), window, cx, |s| {
13964 s.select_ranges(vec![0..0]);
13965 });
13966 }
13967
13968 pub fn select_to_beginning(
13969 &mut self,
13970 _: &SelectToBeginning,
13971 window: &mut Window,
13972 cx: &mut Context<Self>,
13973 ) {
13974 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13975 selection.set_head(Point::zero(), SelectionGoal::None);
13976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13977 self.change_selections(Default::default(), window, cx, |s| {
13978 s.select(vec![selection]);
13979 });
13980 }
13981
13982 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13983 if matches!(self.mode, EditorMode::SingleLine) {
13984 cx.propagate();
13985 return;
13986 }
13987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13988 let cursor = self.buffer.read(cx).read(cx).len();
13989 self.change_selections(Default::default(), window, cx, |s| {
13990 s.select_ranges(vec![cursor..cursor])
13991 });
13992 }
13993
13994 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13995 self.nav_history = nav_history;
13996 }
13997
13998 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13999 self.nav_history.as_ref()
14000 }
14001
14002 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14003 self.push_to_nav_history(
14004 self.selections.newest_anchor().head(),
14005 None,
14006 false,
14007 true,
14008 cx,
14009 );
14010 }
14011
14012 fn push_to_nav_history(
14013 &mut self,
14014 cursor_anchor: Anchor,
14015 new_position: Option<Point>,
14016 is_deactivate: bool,
14017 always: bool,
14018 cx: &mut Context<Self>,
14019 ) {
14020 if let Some(nav_history) = self.nav_history.as_mut() {
14021 let buffer = self.buffer.read(cx).read(cx);
14022 let cursor_position = cursor_anchor.to_point(&buffer);
14023 let scroll_state = self.scroll_manager.anchor();
14024 let scroll_top_row = scroll_state.top_row(&buffer);
14025 drop(buffer);
14026
14027 if let Some(new_position) = new_position {
14028 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14029 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14030 return;
14031 }
14032 }
14033
14034 nav_history.push(
14035 Some(NavigationData {
14036 cursor_anchor,
14037 cursor_position,
14038 scroll_anchor: scroll_state,
14039 scroll_top_row,
14040 }),
14041 cx,
14042 );
14043 cx.emit(EditorEvent::PushedToNavHistory {
14044 anchor: cursor_anchor,
14045 is_deactivate,
14046 })
14047 }
14048 }
14049
14050 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14052 let buffer = self.buffer.read(cx).snapshot(cx);
14053 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14054 selection.set_head(buffer.len(), SelectionGoal::None);
14055 self.change_selections(Default::default(), window, cx, |s| {
14056 s.select(vec![selection]);
14057 });
14058 }
14059
14060 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14062 let end = self.buffer.read(cx).read(cx).len();
14063 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14064 s.select_ranges(vec![0..end]);
14065 });
14066 }
14067
14068 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14071 let mut selections = self.selections.all::<Point>(&display_map);
14072 let max_point = display_map.buffer_snapshot().max_point();
14073 for selection in &mut selections {
14074 let rows = selection.spanned_rows(true, &display_map);
14075 selection.start = Point::new(rows.start.0, 0);
14076 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14077 selection.reversed = false;
14078 }
14079 self.change_selections(Default::default(), window, cx, |s| {
14080 s.select(selections);
14081 });
14082 }
14083
14084 pub fn split_selection_into_lines(
14085 &mut self,
14086 action: &SplitSelectionIntoLines,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) {
14090 let selections = self
14091 .selections
14092 .all::<Point>(&self.display_snapshot(cx))
14093 .into_iter()
14094 .map(|selection| selection.start..selection.end)
14095 .collect::<Vec<_>>();
14096 self.unfold_ranges(&selections, true, true, cx);
14097
14098 let mut new_selection_ranges = Vec::new();
14099 {
14100 let buffer = self.buffer.read(cx).read(cx);
14101 for selection in selections {
14102 for row in selection.start.row..selection.end.row {
14103 let line_start = Point::new(row, 0);
14104 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14105
14106 if action.keep_selections {
14107 // Keep the selection range for each line
14108 let selection_start = if row == selection.start.row {
14109 selection.start
14110 } else {
14111 line_start
14112 };
14113 new_selection_ranges.push(selection_start..line_end);
14114 } else {
14115 // Collapse to cursor at end of line
14116 new_selection_ranges.push(line_end..line_end);
14117 }
14118 }
14119
14120 let is_multiline_selection = selection.start.row != selection.end.row;
14121 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14122 // so this action feels more ergonomic when paired with other selection operations
14123 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14124 if !should_skip_last {
14125 if action.keep_selections {
14126 if is_multiline_selection {
14127 let line_start = Point::new(selection.end.row, 0);
14128 new_selection_ranges.push(line_start..selection.end);
14129 } else {
14130 new_selection_ranges.push(selection.start..selection.end);
14131 }
14132 } else {
14133 new_selection_ranges.push(selection.end..selection.end);
14134 }
14135 }
14136 }
14137 }
14138 self.change_selections(Default::default(), window, cx, |s| {
14139 s.select_ranges(new_selection_ranges);
14140 });
14141 }
14142
14143 pub fn add_selection_above(
14144 &mut self,
14145 action: &AddSelectionAbove,
14146 window: &mut Window,
14147 cx: &mut Context<Self>,
14148 ) {
14149 self.add_selection(true, action.skip_soft_wrap, window, cx);
14150 }
14151
14152 pub fn add_selection_below(
14153 &mut self,
14154 action: &AddSelectionBelow,
14155 window: &mut Window,
14156 cx: &mut Context<Self>,
14157 ) {
14158 self.add_selection(false, action.skip_soft_wrap, window, cx);
14159 }
14160
14161 fn add_selection(
14162 &mut self,
14163 above: bool,
14164 skip_soft_wrap: bool,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14169
14170 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14171 let all_selections = self.selections.all::<Point>(&display_map);
14172 let text_layout_details = self.text_layout_details(window);
14173
14174 let (mut columnar_selections, new_selections_to_columnarize) = {
14175 if let Some(state) = self.add_selections_state.as_ref() {
14176 let columnar_selection_ids: HashSet<_> = state
14177 .groups
14178 .iter()
14179 .flat_map(|group| group.stack.iter())
14180 .copied()
14181 .collect();
14182
14183 all_selections
14184 .into_iter()
14185 .partition(|s| columnar_selection_ids.contains(&s.id))
14186 } else {
14187 (Vec::new(), all_selections)
14188 }
14189 };
14190
14191 let mut state = self
14192 .add_selections_state
14193 .take()
14194 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14195
14196 for selection in new_selections_to_columnarize {
14197 let range = selection.display_range(&display_map).sorted();
14198 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14199 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14200 let positions = start_x.min(end_x)..start_x.max(end_x);
14201 let mut stack = Vec::new();
14202 for row in range.start.row().0..=range.end.row().0 {
14203 if let Some(selection) = self.selections.build_columnar_selection(
14204 &display_map,
14205 DisplayRow(row),
14206 &positions,
14207 selection.reversed,
14208 &text_layout_details,
14209 ) {
14210 stack.push(selection.id);
14211 columnar_selections.push(selection);
14212 }
14213 }
14214 if !stack.is_empty() {
14215 if above {
14216 stack.reverse();
14217 }
14218 state.groups.push(AddSelectionsGroup { above, stack });
14219 }
14220 }
14221
14222 let mut final_selections = Vec::new();
14223 let end_row = if above {
14224 DisplayRow(0)
14225 } else {
14226 display_map.max_point().row()
14227 };
14228
14229 let mut last_added_item_per_group = HashMap::default();
14230 for group in state.groups.iter_mut() {
14231 if let Some(last_id) = group.stack.last() {
14232 last_added_item_per_group.insert(*last_id, group);
14233 }
14234 }
14235
14236 for selection in columnar_selections {
14237 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14238 if above == group.above {
14239 let range = selection.display_range(&display_map).sorted();
14240 debug_assert_eq!(range.start.row(), range.end.row());
14241 let mut row = range.start.row();
14242 let positions =
14243 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14244 Pixels::from(start)..Pixels::from(end)
14245 } else {
14246 let start_x =
14247 display_map.x_for_display_point(range.start, &text_layout_details);
14248 let end_x =
14249 display_map.x_for_display_point(range.end, &text_layout_details);
14250 start_x.min(end_x)..start_x.max(end_x)
14251 };
14252
14253 let mut maybe_new_selection = None;
14254 let direction = if above { -1 } else { 1 };
14255
14256 while row != end_row {
14257 if skip_soft_wrap {
14258 row = display_map
14259 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14260 .row();
14261 } else if above {
14262 row.0 -= 1;
14263 } else {
14264 row.0 += 1;
14265 }
14266
14267 if let Some(new_selection) = self.selections.build_columnar_selection(
14268 &display_map,
14269 row,
14270 &positions,
14271 selection.reversed,
14272 &text_layout_details,
14273 ) {
14274 maybe_new_selection = Some(new_selection);
14275 break;
14276 }
14277 }
14278
14279 if let Some(new_selection) = maybe_new_selection {
14280 group.stack.push(new_selection.id);
14281 if above {
14282 final_selections.push(new_selection);
14283 final_selections.push(selection);
14284 } else {
14285 final_selections.push(selection);
14286 final_selections.push(new_selection);
14287 }
14288 } else {
14289 final_selections.push(selection);
14290 }
14291 } else {
14292 group.stack.pop();
14293 }
14294 } else {
14295 final_selections.push(selection);
14296 }
14297 }
14298
14299 self.change_selections(Default::default(), window, cx, |s| {
14300 s.select(final_selections);
14301 });
14302
14303 let final_selection_ids: HashSet<_> = self
14304 .selections
14305 .all::<Point>(&display_map)
14306 .iter()
14307 .map(|s| s.id)
14308 .collect();
14309 state.groups.retain_mut(|group| {
14310 // selections might get merged above so we remove invalid items from stacks
14311 group.stack.retain(|id| final_selection_ids.contains(id));
14312
14313 // single selection in stack can be treated as initial state
14314 group.stack.len() > 1
14315 });
14316
14317 if !state.groups.is_empty() {
14318 self.add_selections_state = Some(state);
14319 }
14320 }
14321
14322 fn select_match_ranges(
14323 &mut self,
14324 range: Range<usize>,
14325 reversed: bool,
14326 replace_newest: bool,
14327 auto_scroll: Option<Autoscroll>,
14328 window: &mut Window,
14329 cx: &mut Context<Editor>,
14330 ) {
14331 self.unfold_ranges(
14332 std::slice::from_ref(&range),
14333 false,
14334 auto_scroll.is_some(),
14335 cx,
14336 );
14337 let effects = if let Some(scroll) = auto_scroll {
14338 SelectionEffects::scroll(scroll)
14339 } else {
14340 SelectionEffects::no_scroll()
14341 };
14342 self.change_selections(effects, window, cx, |s| {
14343 if replace_newest {
14344 s.delete(s.newest_anchor().id);
14345 }
14346 if reversed {
14347 s.insert_range(range.end..range.start);
14348 } else {
14349 s.insert_range(range);
14350 }
14351 });
14352 }
14353
14354 pub fn select_next_match_internal(
14355 &mut self,
14356 display_map: &DisplaySnapshot,
14357 replace_newest: bool,
14358 autoscroll: Option<Autoscroll>,
14359 window: &mut Window,
14360 cx: &mut Context<Self>,
14361 ) -> Result<()> {
14362 let buffer = display_map.buffer_snapshot();
14363 let mut selections = self.selections.all::<usize>(&display_map);
14364 if let Some(mut select_next_state) = self.select_next_state.take() {
14365 let query = &select_next_state.query;
14366 if !select_next_state.done {
14367 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14368 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14369 let mut next_selected_range = None;
14370
14371 let bytes_after_last_selection =
14372 buffer.bytes_in_range(last_selection.end..buffer.len());
14373 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14374 let query_matches = query
14375 .stream_find_iter(bytes_after_last_selection)
14376 .map(|result| (last_selection.end, result))
14377 .chain(
14378 query
14379 .stream_find_iter(bytes_before_first_selection)
14380 .map(|result| (0, result)),
14381 );
14382
14383 for (start_offset, query_match) in query_matches {
14384 let query_match = query_match.unwrap(); // can only fail due to I/O
14385 let offset_range =
14386 start_offset + query_match.start()..start_offset + query_match.end();
14387
14388 if !select_next_state.wordwise
14389 || (!buffer.is_inside_word(offset_range.start, None)
14390 && !buffer.is_inside_word(offset_range.end, None))
14391 {
14392 let idx = selections
14393 .partition_point(|selection| selection.end <= offset_range.start);
14394 let overlaps = selections
14395 .get(idx)
14396 .map_or(false, |selection| selection.start < offset_range.end);
14397
14398 if !overlaps {
14399 next_selected_range = Some(offset_range);
14400 break;
14401 }
14402 }
14403 }
14404
14405 if let Some(next_selected_range) = next_selected_range {
14406 self.select_match_ranges(
14407 next_selected_range,
14408 last_selection.reversed,
14409 replace_newest,
14410 autoscroll,
14411 window,
14412 cx,
14413 );
14414 } else {
14415 select_next_state.done = true;
14416 }
14417 }
14418
14419 self.select_next_state = Some(select_next_state);
14420 } else {
14421 let mut only_carets = true;
14422 let mut same_text_selected = true;
14423 let mut selected_text = None;
14424
14425 let mut selections_iter = selections.iter().peekable();
14426 while let Some(selection) = selections_iter.next() {
14427 if selection.start != selection.end {
14428 only_carets = false;
14429 }
14430
14431 if same_text_selected {
14432 if selected_text.is_none() {
14433 selected_text =
14434 Some(buffer.text_for_range(selection.range()).collect::<String>());
14435 }
14436
14437 if let Some(next_selection) = selections_iter.peek() {
14438 if next_selection.range().len() == selection.range().len() {
14439 let next_selected_text = buffer
14440 .text_for_range(next_selection.range())
14441 .collect::<String>();
14442 if Some(next_selected_text) != selected_text {
14443 same_text_selected = false;
14444 selected_text = None;
14445 }
14446 } else {
14447 same_text_selected = false;
14448 selected_text = None;
14449 }
14450 }
14451 }
14452 }
14453
14454 if only_carets {
14455 for selection in &mut selections {
14456 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14457 selection.start = word_range.start;
14458 selection.end = word_range.end;
14459 selection.goal = SelectionGoal::None;
14460 selection.reversed = false;
14461 self.select_match_ranges(
14462 selection.start..selection.end,
14463 selection.reversed,
14464 replace_newest,
14465 autoscroll,
14466 window,
14467 cx,
14468 );
14469 }
14470
14471 if selections.len() == 1 {
14472 let selection = selections
14473 .last()
14474 .expect("ensured that there's only one selection");
14475 let query = buffer
14476 .text_for_range(selection.start..selection.end)
14477 .collect::<String>();
14478 let is_empty = query.is_empty();
14479 let select_state = SelectNextState {
14480 query: AhoCorasick::new(&[query])?,
14481 wordwise: true,
14482 done: is_empty,
14483 };
14484 self.select_next_state = Some(select_state);
14485 } else {
14486 self.select_next_state = None;
14487 }
14488 } else if let Some(selected_text) = selected_text {
14489 self.select_next_state = Some(SelectNextState {
14490 query: AhoCorasick::new(&[selected_text])?,
14491 wordwise: false,
14492 done: false,
14493 });
14494 self.select_next_match_internal(
14495 display_map,
14496 replace_newest,
14497 autoscroll,
14498 window,
14499 cx,
14500 )?;
14501 }
14502 }
14503 Ok(())
14504 }
14505
14506 pub fn select_all_matches(
14507 &mut self,
14508 _action: &SelectAllMatches,
14509 window: &mut Window,
14510 cx: &mut Context<Self>,
14511 ) -> Result<()> {
14512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14513
14514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14515
14516 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14517 let Some(select_next_state) = self.select_next_state.as_mut() else {
14518 return Ok(());
14519 };
14520 if select_next_state.done {
14521 return Ok(());
14522 }
14523
14524 let mut new_selections = Vec::new();
14525
14526 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14527 let buffer = display_map.buffer_snapshot();
14528 let query_matches = select_next_state
14529 .query
14530 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14531
14532 for query_match in query_matches.into_iter() {
14533 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14534 let offset_range = if reversed {
14535 query_match.end()..query_match.start()
14536 } else {
14537 query_match.start()..query_match.end()
14538 };
14539
14540 if !select_next_state.wordwise
14541 || (!buffer.is_inside_word(offset_range.start, None)
14542 && !buffer.is_inside_word(offset_range.end, None))
14543 {
14544 new_selections.push(offset_range.start..offset_range.end);
14545 }
14546 }
14547
14548 select_next_state.done = true;
14549
14550 if new_selections.is_empty() {
14551 log::error!("bug: new_selections is empty in select_all_matches");
14552 return Ok(());
14553 }
14554
14555 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14556 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14557 selections.select_ranges(new_selections)
14558 });
14559
14560 Ok(())
14561 }
14562
14563 pub fn select_next(
14564 &mut self,
14565 action: &SelectNext,
14566 window: &mut Window,
14567 cx: &mut Context<Self>,
14568 ) -> Result<()> {
14569 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14570 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14571 self.select_next_match_internal(
14572 &display_map,
14573 action.replace_newest,
14574 Some(Autoscroll::newest()),
14575 window,
14576 cx,
14577 )?;
14578 Ok(())
14579 }
14580
14581 pub fn select_previous(
14582 &mut self,
14583 action: &SelectPrevious,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) -> Result<()> {
14587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14589 let buffer = display_map.buffer_snapshot();
14590 let mut selections = self.selections.all::<usize>(&display_map);
14591 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14592 let query = &select_prev_state.query;
14593 if !select_prev_state.done {
14594 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14595 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14596 let mut next_selected_range = None;
14597 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14598 let bytes_before_last_selection =
14599 buffer.reversed_bytes_in_range(0..last_selection.start);
14600 let bytes_after_first_selection =
14601 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14602 let query_matches = query
14603 .stream_find_iter(bytes_before_last_selection)
14604 .map(|result| (last_selection.start, result))
14605 .chain(
14606 query
14607 .stream_find_iter(bytes_after_first_selection)
14608 .map(|result| (buffer.len(), result)),
14609 );
14610 for (end_offset, query_match) in query_matches {
14611 let query_match = query_match.unwrap(); // can only fail due to I/O
14612 let offset_range =
14613 end_offset - query_match.end()..end_offset - query_match.start();
14614
14615 if !select_prev_state.wordwise
14616 || (!buffer.is_inside_word(offset_range.start, None)
14617 && !buffer.is_inside_word(offset_range.end, None))
14618 {
14619 next_selected_range = Some(offset_range);
14620 break;
14621 }
14622 }
14623
14624 if let Some(next_selected_range) = next_selected_range {
14625 self.select_match_ranges(
14626 next_selected_range,
14627 last_selection.reversed,
14628 action.replace_newest,
14629 Some(Autoscroll::newest()),
14630 window,
14631 cx,
14632 );
14633 } else {
14634 select_prev_state.done = true;
14635 }
14636 }
14637
14638 self.select_prev_state = Some(select_prev_state);
14639 } else {
14640 let mut only_carets = true;
14641 let mut same_text_selected = true;
14642 let mut selected_text = None;
14643
14644 let mut selections_iter = selections.iter().peekable();
14645 while let Some(selection) = selections_iter.next() {
14646 if selection.start != selection.end {
14647 only_carets = false;
14648 }
14649
14650 if same_text_selected {
14651 if selected_text.is_none() {
14652 selected_text =
14653 Some(buffer.text_for_range(selection.range()).collect::<String>());
14654 }
14655
14656 if let Some(next_selection) = selections_iter.peek() {
14657 if next_selection.range().len() == selection.range().len() {
14658 let next_selected_text = buffer
14659 .text_for_range(next_selection.range())
14660 .collect::<String>();
14661 if Some(next_selected_text) != selected_text {
14662 same_text_selected = false;
14663 selected_text = None;
14664 }
14665 } else {
14666 same_text_selected = false;
14667 selected_text = None;
14668 }
14669 }
14670 }
14671 }
14672
14673 if only_carets {
14674 for selection in &mut selections {
14675 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14676 selection.start = word_range.start;
14677 selection.end = word_range.end;
14678 selection.goal = SelectionGoal::None;
14679 selection.reversed = false;
14680 self.select_match_ranges(
14681 selection.start..selection.end,
14682 selection.reversed,
14683 action.replace_newest,
14684 Some(Autoscroll::newest()),
14685 window,
14686 cx,
14687 );
14688 }
14689 if selections.len() == 1 {
14690 let selection = selections
14691 .last()
14692 .expect("ensured that there's only one selection");
14693 let query = buffer
14694 .text_for_range(selection.start..selection.end)
14695 .collect::<String>();
14696 let is_empty = query.is_empty();
14697 let select_state = SelectNextState {
14698 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14699 wordwise: true,
14700 done: is_empty,
14701 };
14702 self.select_prev_state = Some(select_state);
14703 } else {
14704 self.select_prev_state = None;
14705 }
14706 } else if let Some(selected_text) = selected_text {
14707 self.select_prev_state = Some(SelectNextState {
14708 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14709 wordwise: false,
14710 done: false,
14711 });
14712 self.select_previous(action, window, cx)?;
14713 }
14714 }
14715 Ok(())
14716 }
14717
14718 pub fn find_next_match(
14719 &mut self,
14720 _: &FindNextMatch,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) -> Result<()> {
14724 let selections = self.selections.disjoint_anchors_arc();
14725 match selections.first() {
14726 Some(first) if selections.len() >= 2 => {
14727 self.change_selections(Default::default(), window, cx, |s| {
14728 s.select_ranges([first.range()]);
14729 });
14730 }
14731 _ => self.select_next(
14732 &SelectNext {
14733 replace_newest: true,
14734 },
14735 window,
14736 cx,
14737 )?,
14738 }
14739 Ok(())
14740 }
14741
14742 pub fn find_previous_match(
14743 &mut self,
14744 _: &FindPreviousMatch,
14745 window: &mut Window,
14746 cx: &mut Context<Self>,
14747 ) -> Result<()> {
14748 let selections = self.selections.disjoint_anchors_arc();
14749 match selections.last() {
14750 Some(last) if selections.len() >= 2 => {
14751 self.change_selections(Default::default(), window, cx, |s| {
14752 s.select_ranges([last.range()]);
14753 });
14754 }
14755 _ => self.select_previous(
14756 &SelectPrevious {
14757 replace_newest: true,
14758 },
14759 window,
14760 cx,
14761 )?,
14762 }
14763 Ok(())
14764 }
14765
14766 pub fn toggle_comments(
14767 &mut self,
14768 action: &ToggleComments,
14769 window: &mut Window,
14770 cx: &mut Context<Self>,
14771 ) {
14772 if self.read_only(cx) {
14773 return;
14774 }
14775 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14776 let text_layout_details = &self.text_layout_details(window);
14777 self.transact(window, cx, |this, window, cx| {
14778 let mut selections = this
14779 .selections
14780 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14781 let mut edits = Vec::new();
14782 let mut selection_edit_ranges = Vec::new();
14783 let mut last_toggled_row = None;
14784 let snapshot = this.buffer.read(cx).read(cx);
14785 let empty_str: Arc<str> = Arc::default();
14786 let mut suffixes_inserted = Vec::new();
14787 let ignore_indent = action.ignore_indent;
14788
14789 fn comment_prefix_range(
14790 snapshot: &MultiBufferSnapshot,
14791 row: MultiBufferRow,
14792 comment_prefix: &str,
14793 comment_prefix_whitespace: &str,
14794 ignore_indent: bool,
14795 ) -> Range<Point> {
14796 let indent_size = if ignore_indent {
14797 0
14798 } else {
14799 snapshot.indent_size_for_line(row).len
14800 };
14801
14802 let start = Point::new(row.0, indent_size);
14803
14804 let mut line_bytes = snapshot
14805 .bytes_in_range(start..snapshot.max_point())
14806 .flatten()
14807 .copied();
14808
14809 // If this line currently begins with the line comment prefix, then record
14810 // the range containing the prefix.
14811 if line_bytes
14812 .by_ref()
14813 .take(comment_prefix.len())
14814 .eq(comment_prefix.bytes())
14815 {
14816 // Include any whitespace that matches the comment prefix.
14817 let matching_whitespace_len = line_bytes
14818 .zip(comment_prefix_whitespace.bytes())
14819 .take_while(|(a, b)| a == b)
14820 .count() as u32;
14821 let end = Point::new(
14822 start.row,
14823 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14824 );
14825 start..end
14826 } else {
14827 start..start
14828 }
14829 }
14830
14831 fn comment_suffix_range(
14832 snapshot: &MultiBufferSnapshot,
14833 row: MultiBufferRow,
14834 comment_suffix: &str,
14835 comment_suffix_has_leading_space: bool,
14836 ) -> Range<Point> {
14837 let end = Point::new(row.0, snapshot.line_len(row));
14838 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14839
14840 let mut line_end_bytes = snapshot
14841 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14842 .flatten()
14843 .copied();
14844
14845 let leading_space_len = if suffix_start_column > 0
14846 && line_end_bytes.next() == Some(b' ')
14847 && comment_suffix_has_leading_space
14848 {
14849 1
14850 } else {
14851 0
14852 };
14853
14854 // If this line currently begins with the line comment prefix, then record
14855 // the range containing the prefix.
14856 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14857 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14858 start..end
14859 } else {
14860 end..end
14861 }
14862 }
14863
14864 // TODO: Handle selections that cross excerpts
14865 for selection in &mut selections {
14866 let start_column = snapshot
14867 .indent_size_for_line(MultiBufferRow(selection.start.row))
14868 .len;
14869 let language = if let Some(language) =
14870 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14871 {
14872 language
14873 } else {
14874 continue;
14875 };
14876
14877 selection_edit_ranges.clear();
14878
14879 // If multiple selections contain a given row, avoid processing that
14880 // row more than once.
14881 let mut start_row = MultiBufferRow(selection.start.row);
14882 if last_toggled_row == Some(start_row) {
14883 start_row = start_row.next_row();
14884 }
14885 let end_row =
14886 if selection.end.row > selection.start.row && selection.end.column == 0 {
14887 MultiBufferRow(selection.end.row - 1)
14888 } else {
14889 MultiBufferRow(selection.end.row)
14890 };
14891 last_toggled_row = Some(end_row);
14892
14893 if start_row > end_row {
14894 continue;
14895 }
14896
14897 // If the language has line comments, toggle those.
14898 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14899
14900 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14901 if ignore_indent {
14902 full_comment_prefixes = full_comment_prefixes
14903 .into_iter()
14904 .map(|s| Arc::from(s.trim_end()))
14905 .collect();
14906 }
14907
14908 if !full_comment_prefixes.is_empty() {
14909 let first_prefix = full_comment_prefixes
14910 .first()
14911 .expect("prefixes is non-empty");
14912 let prefix_trimmed_lengths = full_comment_prefixes
14913 .iter()
14914 .map(|p| p.trim_end_matches(' ').len())
14915 .collect::<SmallVec<[usize; 4]>>();
14916
14917 let mut all_selection_lines_are_comments = true;
14918
14919 for row in start_row.0..=end_row.0 {
14920 let row = MultiBufferRow(row);
14921 if start_row < end_row && snapshot.is_line_blank(row) {
14922 continue;
14923 }
14924
14925 let prefix_range = full_comment_prefixes
14926 .iter()
14927 .zip(prefix_trimmed_lengths.iter().copied())
14928 .map(|(prefix, trimmed_prefix_len)| {
14929 comment_prefix_range(
14930 snapshot.deref(),
14931 row,
14932 &prefix[..trimmed_prefix_len],
14933 &prefix[trimmed_prefix_len..],
14934 ignore_indent,
14935 )
14936 })
14937 .max_by_key(|range| range.end.column - range.start.column)
14938 .expect("prefixes is non-empty");
14939
14940 if prefix_range.is_empty() {
14941 all_selection_lines_are_comments = false;
14942 }
14943
14944 selection_edit_ranges.push(prefix_range);
14945 }
14946
14947 if all_selection_lines_are_comments {
14948 edits.extend(
14949 selection_edit_ranges
14950 .iter()
14951 .cloned()
14952 .map(|range| (range, empty_str.clone())),
14953 );
14954 } else {
14955 let min_column = selection_edit_ranges
14956 .iter()
14957 .map(|range| range.start.column)
14958 .min()
14959 .unwrap_or(0);
14960 edits.extend(selection_edit_ranges.iter().map(|range| {
14961 let position = Point::new(range.start.row, min_column);
14962 (position..position, first_prefix.clone())
14963 }));
14964 }
14965 } else if let Some(BlockCommentConfig {
14966 start: full_comment_prefix,
14967 end: comment_suffix,
14968 ..
14969 }) = language.block_comment()
14970 {
14971 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14972 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14973 let prefix_range = comment_prefix_range(
14974 snapshot.deref(),
14975 start_row,
14976 comment_prefix,
14977 comment_prefix_whitespace,
14978 ignore_indent,
14979 );
14980 let suffix_range = comment_suffix_range(
14981 snapshot.deref(),
14982 end_row,
14983 comment_suffix.trim_start_matches(' '),
14984 comment_suffix.starts_with(' '),
14985 );
14986
14987 if prefix_range.is_empty() || suffix_range.is_empty() {
14988 edits.push((
14989 prefix_range.start..prefix_range.start,
14990 full_comment_prefix.clone(),
14991 ));
14992 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14993 suffixes_inserted.push((end_row, comment_suffix.len()));
14994 } else {
14995 edits.push((prefix_range, empty_str.clone()));
14996 edits.push((suffix_range, empty_str.clone()));
14997 }
14998 } else {
14999 continue;
15000 }
15001 }
15002
15003 drop(snapshot);
15004 this.buffer.update(cx, |buffer, cx| {
15005 buffer.edit(edits, None, cx);
15006 });
15007
15008 // Adjust selections so that they end before any comment suffixes that
15009 // were inserted.
15010 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15011 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15012 let snapshot = this.buffer.read(cx).read(cx);
15013 for selection in &mut selections {
15014 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15015 match row.cmp(&MultiBufferRow(selection.end.row)) {
15016 Ordering::Less => {
15017 suffixes_inserted.next();
15018 continue;
15019 }
15020 Ordering::Greater => break,
15021 Ordering::Equal => {
15022 if selection.end.column == snapshot.line_len(row) {
15023 if selection.is_empty() {
15024 selection.start.column -= suffix_len as u32;
15025 }
15026 selection.end.column -= suffix_len as u32;
15027 }
15028 break;
15029 }
15030 }
15031 }
15032 }
15033
15034 drop(snapshot);
15035 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15036
15037 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15038 let selections_on_single_row = selections.windows(2).all(|selections| {
15039 selections[0].start.row == selections[1].start.row
15040 && selections[0].end.row == selections[1].end.row
15041 && selections[0].start.row == selections[0].end.row
15042 });
15043 let selections_selecting = selections
15044 .iter()
15045 .any(|selection| selection.start != selection.end);
15046 let advance_downwards = action.advance_downwards
15047 && selections_on_single_row
15048 && !selections_selecting
15049 && !matches!(this.mode, EditorMode::SingleLine);
15050
15051 if advance_downwards {
15052 let snapshot = this.buffer.read(cx).snapshot(cx);
15053
15054 this.change_selections(Default::default(), window, cx, |s| {
15055 s.move_cursors_with(|display_snapshot, display_point, _| {
15056 let mut point = display_point.to_point(display_snapshot);
15057 point.row += 1;
15058 point = snapshot.clip_point(point, Bias::Left);
15059 let display_point = point.to_display_point(display_snapshot);
15060 let goal = SelectionGoal::HorizontalPosition(
15061 display_snapshot
15062 .x_for_display_point(display_point, text_layout_details)
15063 .into(),
15064 );
15065 (display_point, goal)
15066 })
15067 });
15068 }
15069 });
15070 }
15071
15072 pub fn select_enclosing_symbol(
15073 &mut self,
15074 _: &SelectEnclosingSymbol,
15075 window: &mut Window,
15076 cx: &mut Context<Self>,
15077 ) {
15078 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15079
15080 let buffer = self.buffer.read(cx).snapshot(cx);
15081 let old_selections = self
15082 .selections
15083 .all::<usize>(&self.display_snapshot(cx))
15084 .into_boxed_slice();
15085
15086 fn update_selection(
15087 selection: &Selection<usize>,
15088 buffer_snap: &MultiBufferSnapshot,
15089 ) -> Option<Selection<usize>> {
15090 let cursor = selection.head();
15091 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15092 for symbol in symbols.iter().rev() {
15093 let start = symbol.range.start.to_offset(buffer_snap);
15094 let end = symbol.range.end.to_offset(buffer_snap);
15095 let new_range = start..end;
15096 if start < selection.start || end > selection.end {
15097 return Some(Selection {
15098 id: selection.id,
15099 start: new_range.start,
15100 end: new_range.end,
15101 goal: SelectionGoal::None,
15102 reversed: selection.reversed,
15103 });
15104 }
15105 }
15106 None
15107 }
15108
15109 let mut selected_larger_symbol = false;
15110 let new_selections = old_selections
15111 .iter()
15112 .map(|selection| match update_selection(selection, &buffer) {
15113 Some(new_selection) => {
15114 if new_selection.range() != selection.range() {
15115 selected_larger_symbol = true;
15116 }
15117 new_selection
15118 }
15119 None => selection.clone(),
15120 })
15121 .collect::<Vec<_>>();
15122
15123 if selected_larger_symbol {
15124 self.change_selections(Default::default(), window, cx, |s| {
15125 s.select(new_selections);
15126 });
15127 }
15128 }
15129
15130 pub fn select_larger_syntax_node(
15131 &mut self,
15132 _: &SelectLargerSyntaxNode,
15133 window: &mut Window,
15134 cx: &mut Context<Self>,
15135 ) {
15136 let Some(visible_row_count) = self.visible_row_count() else {
15137 return;
15138 };
15139 let old_selections: Box<[_]> = self
15140 .selections
15141 .all::<usize>(&self.display_snapshot(cx))
15142 .into();
15143 if old_selections.is_empty() {
15144 return;
15145 }
15146
15147 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15148
15149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15150 let buffer = self.buffer.read(cx).snapshot(cx);
15151
15152 let mut selected_larger_node = false;
15153 let mut new_selections = old_selections
15154 .iter()
15155 .map(|selection| {
15156 let old_range = selection.start..selection.end;
15157
15158 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15159 // manually select word at selection
15160 if ["string_content", "inline"].contains(&node.kind()) {
15161 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15162 // ignore if word is already selected
15163 if !word_range.is_empty() && old_range != word_range {
15164 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15165 // only select word if start and end point belongs to same word
15166 if word_range == last_word_range {
15167 selected_larger_node = true;
15168 return Selection {
15169 id: selection.id,
15170 start: word_range.start,
15171 end: word_range.end,
15172 goal: SelectionGoal::None,
15173 reversed: selection.reversed,
15174 };
15175 }
15176 }
15177 }
15178 }
15179
15180 let mut new_range = old_range.clone();
15181 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15182 new_range = range;
15183 if !node.is_named() {
15184 continue;
15185 }
15186 if !display_map.intersects_fold(new_range.start)
15187 && !display_map.intersects_fold(new_range.end)
15188 {
15189 break;
15190 }
15191 }
15192
15193 selected_larger_node |= new_range != old_range;
15194 Selection {
15195 id: selection.id,
15196 start: new_range.start,
15197 end: new_range.end,
15198 goal: SelectionGoal::None,
15199 reversed: selection.reversed,
15200 }
15201 })
15202 .collect::<Vec<_>>();
15203
15204 if !selected_larger_node {
15205 return; // don't put this call in the history
15206 }
15207
15208 // scroll based on transformation done to the last selection created by the user
15209 let (last_old, last_new) = old_selections
15210 .last()
15211 .zip(new_selections.last().cloned())
15212 .expect("old_selections isn't empty");
15213
15214 // revert selection
15215 let is_selection_reversed = {
15216 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15217 new_selections.last_mut().expect("checked above").reversed =
15218 should_newest_selection_be_reversed;
15219 should_newest_selection_be_reversed
15220 };
15221
15222 if selected_larger_node {
15223 self.select_syntax_node_history.disable_clearing = true;
15224 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15225 s.select(new_selections.clone());
15226 });
15227 self.select_syntax_node_history.disable_clearing = false;
15228 }
15229
15230 let start_row = last_new.start.to_display_point(&display_map).row().0;
15231 let end_row = last_new.end.to_display_point(&display_map).row().0;
15232 let selection_height = end_row - start_row + 1;
15233 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15234
15235 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15236 let scroll_behavior = if fits_on_the_screen {
15237 self.request_autoscroll(Autoscroll::fit(), cx);
15238 SelectSyntaxNodeScrollBehavior::FitSelection
15239 } else if is_selection_reversed {
15240 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15241 SelectSyntaxNodeScrollBehavior::CursorTop
15242 } else {
15243 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15244 SelectSyntaxNodeScrollBehavior::CursorBottom
15245 };
15246
15247 self.select_syntax_node_history.push((
15248 old_selections,
15249 scroll_behavior,
15250 is_selection_reversed,
15251 ));
15252 }
15253
15254 pub fn select_smaller_syntax_node(
15255 &mut self,
15256 _: &SelectSmallerSyntaxNode,
15257 window: &mut Window,
15258 cx: &mut Context<Self>,
15259 ) {
15260 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15261
15262 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15263 self.select_syntax_node_history.pop()
15264 {
15265 if let Some(selection) = selections.last_mut() {
15266 selection.reversed = is_selection_reversed;
15267 }
15268
15269 self.select_syntax_node_history.disable_clearing = true;
15270 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15271 s.select(selections.to_vec());
15272 });
15273 self.select_syntax_node_history.disable_clearing = false;
15274
15275 match scroll_behavior {
15276 SelectSyntaxNodeScrollBehavior::CursorTop => {
15277 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15278 }
15279 SelectSyntaxNodeScrollBehavior::FitSelection => {
15280 self.request_autoscroll(Autoscroll::fit(), cx);
15281 }
15282 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15283 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15284 }
15285 }
15286 }
15287 }
15288
15289 pub fn unwrap_syntax_node(
15290 &mut self,
15291 _: &UnwrapSyntaxNode,
15292 window: &mut Window,
15293 cx: &mut Context<Self>,
15294 ) {
15295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15296
15297 let buffer = self.buffer.read(cx).snapshot(cx);
15298 let selections = self
15299 .selections
15300 .all::<usize>(&self.display_snapshot(cx))
15301 .into_iter()
15302 // subtracting the offset requires sorting
15303 .sorted_by_key(|i| i.start);
15304
15305 let full_edits = selections
15306 .into_iter()
15307 .filter_map(|selection| {
15308 let child = if selection.is_empty()
15309 && let Some((_, ancestor_range)) =
15310 buffer.syntax_ancestor(selection.start..selection.end)
15311 {
15312 ancestor_range
15313 } else {
15314 selection.range()
15315 };
15316
15317 let mut parent = child.clone();
15318 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15319 parent = ancestor_range;
15320 if parent.start < child.start || parent.end > child.end {
15321 break;
15322 }
15323 }
15324
15325 if parent == child {
15326 return None;
15327 }
15328 let text = buffer.text_for_range(child).collect::<String>();
15329 Some((selection.id, parent, text))
15330 })
15331 .collect::<Vec<_>>();
15332 if full_edits.is_empty() {
15333 return;
15334 }
15335
15336 self.transact(window, cx, |this, window, cx| {
15337 this.buffer.update(cx, |buffer, cx| {
15338 buffer.edit(
15339 full_edits
15340 .iter()
15341 .map(|(_, p, t)| (p.clone(), t.clone()))
15342 .collect::<Vec<_>>(),
15343 None,
15344 cx,
15345 );
15346 });
15347 this.change_selections(Default::default(), window, cx, |s| {
15348 let mut offset = 0;
15349 let mut selections = vec![];
15350 for (id, parent, text) in full_edits {
15351 let start = parent.start - offset;
15352 offset += parent.len() - text.len();
15353 selections.push(Selection {
15354 id,
15355 start,
15356 end: start + text.len(),
15357 reversed: false,
15358 goal: Default::default(),
15359 });
15360 }
15361 s.select(selections);
15362 });
15363 });
15364 }
15365
15366 pub fn select_next_syntax_node(
15367 &mut self,
15368 _: &SelectNextSyntaxNode,
15369 window: &mut Window,
15370 cx: &mut Context<Self>,
15371 ) {
15372 let old_selections: Box<[_]> = self
15373 .selections
15374 .all::<usize>(&self.display_snapshot(cx))
15375 .into();
15376 if old_selections.is_empty() {
15377 return;
15378 }
15379
15380 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15381
15382 let buffer = self.buffer.read(cx).snapshot(cx);
15383 let mut selected_sibling = false;
15384
15385 let new_selections = old_selections
15386 .iter()
15387 .map(|selection| {
15388 let old_range = selection.start..selection.end;
15389
15390 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15391 let new_range = node.byte_range();
15392 selected_sibling = true;
15393 Selection {
15394 id: selection.id,
15395 start: new_range.start,
15396 end: new_range.end,
15397 goal: SelectionGoal::None,
15398 reversed: selection.reversed,
15399 }
15400 } else {
15401 selection.clone()
15402 }
15403 })
15404 .collect::<Vec<_>>();
15405
15406 if selected_sibling {
15407 self.change_selections(
15408 SelectionEffects::scroll(Autoscroll::fit()),
15409 window,
15410 cx,
15411 |s| {
15412 s.select(new_selections);
15413 },
15414 );
15415 }
15416 }
15417
15418 pub fn select_prev_syntax_node(
15419 &mut self,
15420 _: &SelectPreviousSyntaxNode,
15421 window: &mut Window,
15422 cx: &mut Context<Self>,
15423 ) {
15424 let old_selections: Box<[_]> = self
15425 .selections
15426 .all::<usize>(&self.display_snapshot(cx))
15427 .into();
15428 if old_selections.is_empty() {
15429 return;
15430 }
15431
15432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15433
15434 let buffer = self.buffer.read(cx).snapshot(cx);
15435 let mut selected_sibling = false;
15436
15437 let new_selections = old_selections
15438 .iter()
15439 .map(|selection| {
15440 let old_range = selection.start..selection.end;
15441
15442 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15443 let new_range = node.byte_range();
15444 selected_sibling = true;
15445 Selection {
15446 id: selection.id,
15447 start: new_range.start,
15448 end: new_range.end,
15449 goal: SelectionGoal::None,
15450 reversed: selection.reversed,
15451 }
15452 } else {
15453 selection.clone()
15454 }
15455 })
15456 .collect::<Vec<_>>();
15457
15458 if selected_sibling {
15459 self.change_selections(
15460 SelectionEffects::scroll(Autoscroll::fit()),
15461 window,
15462 cx,
15463 |s| {
15464 s.select(new_selections);
15465 },
15466 );
15467 }
15468 }
15469
15470 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15471 if !EditorSettings::get_global(cx).gutter.runnables {
15472 self.clear_tasks();
15473 return Task::ready(());
15474 }
15475 let project = self.project().map(Entity::downgrade);
15476 let task_sources = self.lsp_task_sources(cx);
15477 let multi_buffer = self.buffer.downgrade();
15478 cx.spawn_in(window, async move |editor, cx| {
15479 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15480 let Some(project) = project.and_then(|p| p.upgrade()) else {
15481 return;
15482 };
15483 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15484 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15485 }) else {
15486 return;
15487 };
15488
15489 let hide_runnables = project
15490 .update(cx, |project, _| project.is_via_collab())
15491 .unwrap_or(true);
15492 if hide_runnables {
15493 return;
15494 }
15495 let new_rows =
15496 cx.background_spawn({
15497 let snapshot = display_snapshot.clone();
15498 async move {
15499 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15500 }
15501 })
15502 .await;
15503 let Ok(lsp_tasks) =
15504 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15505 else {
15506 return;
15507 };
15508 let lsp_tasks = lsp_tasks.await;
15509
15510 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15511 lsp_tasks
15512 .into_iter()
15513 .flat_map(|(kind, tasks)| {
15514 tasks.into_iter().filter_map(move |(location, task)| {
15515 Some((kind.clone(), location?, task))
15516 })
15517 })
15518 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15519 let buffer = location.target.buffer;
15520 let buffer_snapshot = buffer.read(cx).snapshot();
15521 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15522 |(excerpt_id, snapshot, _)| {
15523 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15524 display_snapshot
15525 .buffer_snapshot()
15526 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15527 } else {
15528 None
15529 }
15530 },
15531 );
15532 if let Some(offset) = offset {
15533 let task_buffer_range =
15534 location.target.range.to_point(&buffer_snapshot);
15535 let context_buffer_range =
15536 task_buffer_range.to_offset(&buffer_snapshot);
15537 let context_range = BufferOffset(context_buffer_range.start)
15538 ..BufferOffset(context_buffer_range.end);
15539
15540 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15541 .or_insert_with(|| RunnableTasks {
15542 templates: Vec::new(),
15543 offset,
15544 column: task_buffer_range.start.column,
15545 extra_variables: HashMap::default(),
15546 context_range,
15547 })
15548 .templates
15549 .push((kind, task.original_task().clone()));
15550 }
15551
15552 acc
15553 })
15554 }) else {
15555 return;
15556 };
15557
15558 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15559 buffer.language_settings(cx).tasks.prefer_lsp
15560 }) else {
15561 return;
15562 };
15563
15564 let rows = Self::runnable_rows(
15565 project,
15566 display_snapshot,
15567 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15568 new_rows,
15569 cx.clone(),
15570 )
15571 .await;
15572 editor
15573 .update(cx, |editor, _| {
15574 editor.clear_tasks();
15575 for (key, mut value) in rows {
15576 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15577 value.templates.extend(lsp_tasks.templates);
15578 }
15579
15580 editor.insert_tasks(key, value);
15581 }
15582 for (key, value) in lsp_tasks_by_rows {
15583 editor.insert_tasks(key, value);
15584 }
15585 })
15586 .ok();
15587 })
15588 }
15589 fn fetch_runnable_ranges(
15590 snapshot: &DisplaySnapshot,
15591 range: Range<Anchor>,
15592 ) -> Vec<language::RunnableRange> {
15593 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15594 }
15595
15596 fn runnable_rows(
15597 project: Entity<Project>,
15598 snapshot: DisplaySnapshot,
15599 prefer_lsp: bool,
15600 runnable_ranges: Vec<RunnableRange>,
15601 cx: AsyncWindowContext,
15602 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15603 cx.spawn(async move |cx| {
15604 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15605 for mut runnable in runnable_ranges {
15606 let Some(tasks) = cx
15607 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15608 .ok()
15609 else {
15610 continue;
15611 };
15612 let mut tasks = tasks.await;
15613
15614 if prefer_lsp {
15615 tasks.retain(|(task_kind, _)| {
15616 !matches!(task_kind, TaskSourceKind::Language { .. })
15617 });
15618 }
15619 if tasks.is_empty() {
15620 continue;
15621 }
15622
15623 let point = runnable
15624 .run_range
15625 .start
15626 .to_point(&snapshot.buffer_snapshot());
15627 let Some(row) = snapshot
15628 .buffer_snapshot()
15629 .buffer_line_for_row(MultiBufferRow(point.row))
15630 .map(|(_, range)| range.start.row)
15631 else {
15632 continue;
15633 };
15634
15635 let context_range =
15636 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15637 runnable_rows.push((
15638 (runnable.buffer_id, row),
15639 RunnableTasks {
15640 templates: tasks,
15641 offset: snapshot
15642 .buffer_snapshot()
15643 .anchor_before(runnable.run_range.start),
15644 context_range,
15645 column: point.column,
15646 extra_variables: runnable.extra_captures,
15647 },
15648 ));
15649 }
15650 runnable_rows
15651 })
15652 }
15653
15654 fn templates_with_tags(
15655 project: &Entity<Project>,
15656 runnable: &mut Runnable,
15657 cx: &mut App,
15658 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15659 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15660 let (worktree_id, file) = project
15661 .buffer_for_id(runnable.buffer, cx)
15662 .and_then(|buffer| buffer.read(cx).file())
15663 .map(|file| (file.worktree_id(cx), file.clone()))
15664 .unzip();
15665
15666 (
15667 project.task_store().read(cx).task_inventory().cloned(),
15668 worktree_id,
15669 file,
15670 )
15671 });
15672
15673 let tags = mem::take(&mut runnable.tags);
15674 let language = runnable.language.clone();
15675 cx.spawn(async move |cx| {
15676 let mut templates_with_tags = Vec::new();
15677 if let Some(inventory) = inventory {
15678 for RunnableTag(tag) in tags {
15679 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15680 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15681 }) else {
15682 return templates_with_tags;
15683 };
15684 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15685 move |(_, template)| {
15686 template.tags.iter().any(|source_tag| source_tag == &tag)
15687 },
15688 ));
15689 }
15690 }
15691 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15692
15693 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15694 // Strongest source wins; if we have worktree tag binding, prefer that to
15695 // global and language bindings;
15696 // if we have a global binding, prefer that to language binding.
15697 let first_mismatch = templates_with_tags
15698 .iter()
15699 .position(|(tag_source, _)| tag_source != leading_tag_source);
15700 if let Some(index) = first_mismatch {
15701 templates_with_tags.truncate(index);
15702 }
15703 }
15704
15705 templates_with_tags
15706 })
15707 }
15708
15709 pub fn move_to_enclosing_bracket(
15710 &mut self,
15711 _: &MoveToEnclosingBracket,
15712 window: &mut Window,
15713 cx: &mut Context<Self>,
15714 ) {
15715 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15716 self.change_selections(Default::default(), window, cx, |s| {
15717 s.move_offsets_with(|snapshot, selection| {
15718 let Some(enclosing_bracket_ranges) =
15719 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15720 else {
15721 return;
15722 };
15723
15724 let mut best_length = usize::MAX;
15725 let mut best_inside = false;
15726 let mut best_in_bracket_range = false;
15727 let mut best_destination = None;
15728 for (open, close) in enclosing_bracket_ranges {
15729 let close = close.to_inclusive();
15730 let length = close.end() - open.start;
15731 let inside = selection.start >= open.end && selection.end <= *close.start();
15732 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15733 || close.contains(&selection.head());
15734
15735 // If best is next to a bracket and current isn't, skip
15736 if !in_bracket_range && best_in_bracket_range {
15737 continue;
15738 }
15739
15740 // Prefer smaller lengths unless best is inside and current isn't
15741 if length > best_length && (best_inside || !inside) {
15742 continue;
15743 }
15744
15745 best_length = length;
15746 best_inside = inside;
15747 best_in_bracket_range = in_bracket_range;
15748 best_destination = Some(
15749 if close.contains(&selection.start) && close.contains(&selection.end) {
15750 if inside { open.end } else { open.start }
15751 } else if inside {
15752 *close.start()
15753 } else {
15754 *close.end()
15755 },
15756 );
15757 }
15758
15759 if let Some(destination) = best_destination {
15760 selection.collapse_to(destination, SelectionGoal::None);
15761 }
15762 })
15763 });
15764 }
15765
15766 pub fn undo_selection(
15767 &mut self,
15768 _: &UndoSelection,
15769 window: &mut Window,
15770 cx: &mut Context<Self>,
15771 ) {
15772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15773 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15774 self.selection_history.mode = SelectionHistoryMode::Undoing;
15775 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15776 this.end_selection(window, cx);
15777 this.change_selections(
15778 SelectionEffects::scroll(Autoscroll::newest()),
15779 window,
15780 cx,
15781 |s| s.select_anchors(entry.selections.to_vec()),
15782 );
15783 });
15784 self.selection_history.mode = SelectionHistoryMode::Normal;
15785
15786 self.select_next_state = entry.select_next_state;
15787 self.select_prev_state = entry.select_prev_state;
15788 self.add_selections_state = entry.add_selections_state;
15789 }
15790 }
15791
15792 pub fn redo_selection(
15793 &mut self,
15794 _: &RedoSelection,
15795 window: &mut Window,
15796 cx: &mut Context<Self>,
15797 ) {
15798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15799 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15800 self.selection_history.mode = SelectionHistoryMode::Redoing;
15801 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15802 this.end_selection(window, cx);
15803 this.change_selections(
15804 SelectionEffects::scroll(Autoscroll::newest()),
15805 window,
15806 cx,
15807 |s| s.select_anchors(entry.selections.to_vec()),
15808 );
15809 });
15810 self.selection_history.mode = SelectionHistoryMode::Normal;
15811
15812 self.select_next_state = entry.select_next_state;
15813 self.select_prev_state = entry.select_prev_state;
15814 self.add_selections_state = entry.add_selections_state;
15815 }
15816 }
15817
15818 pub fn expand_excerpts(
15819 &mut self,
15820 action: &ExpandExcerpts,
15821 _: &mut Window,
15822 cx: &mut Context<Self>,
15823 ) {
15824 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15825 }
15826
15827 pub fn expand_excerpts_down(
15828 &mut self,
15829 action: &ExpandExcerptsDown,
15830 _: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) {
15833 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15834 }
15835
15836 pub fn expand_excerpts_up(
15837 &mut self,
15838 action: &ExpandExcerptsUp,
15839 _: &mut Window,
15840 cx: &mut Context<Self>,
15841 ) {
15842 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15843 }
15844
15845 pub fn expand_excerpts_for_direction(
15846 &mut self,
15847 lines: u32,
15848 direction: ExpandExcerptDirection,
15849
15850 cx: &mut Context<Self>,
15851 ) {
15852 let selections = self.selections.disjoint_anchors_arc();
15853
15854 let lines = if lines == 0 {
15855 EditorSettings::get_global(cx).expand_excerpt_lines
15856 } else {
15857 lines
15858 };
15859
15860 self.buffer.update(cx, |buffer, cx| {
15861 let snapshot = buffer.snapshot(cx);
15862 let mut excerpt_ids = selections
15863 .iter()
15864 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15865 .collect::<Vec<_>>();
15866 excerpt_ids.sort();
15867 excerpt_ids.dedup();
15868 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15869 })
15870 }
15871
15872 pub fn expand_excerpt(
15873 &mut self,
15874 excerpt: ExcerptId,
15875 direction: ExpandExcerptDirection,
15876 window: &mut Window,
15877 cx: &mut Context<Self>,
15878 ) {
15879 let current_scroll_position = self.scroll_position(cx);
15880 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15881 let mut scroll = None;
15882
15883 if direction == ExpandExcerptDirection::Down {
15884 let multi_buffer = self.buffer.read(cx);
15885 let snapshot = multi_buffer.snapshot(cx);
15886 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15887 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15888 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15889 {
15890 let buffer_snapshot = buffer.read(cx).snapshot();
15891 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15892 let last_row = buffer_snapshot.max_point().row;
15893 let lines_below = last_row.saturating_sub(excerpt_end_row);
15894 if lines_below >= lines_to_expand {
15895 scroll = Some(
15896 current_scroll_position
15897 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15898 );
15899 }
15900 }
15901 }
15902 if direction == ExpandExcerptDirection::Up
15903 && self
15904 .buffer
15905 .read(cx)
15906 .snapshot(cx)
15907 .excerpt_before(excerpt)
15908 .is_none()
15909 {
15910 scroll = Some(current_scroll_position);
15911 }
15912
15913 self.buffer.update(cx, |buffer, cx| {
15914 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15915 });
15916
15917 if let Some(new_scroll_position) = scroll {
15918 self.set_scroll_position(new_scroll_position, window, cx);
15919 }
15920 }
15921
15922 pub fn go_to_singleton_buffer_point(
15923 &mut self,
15924 point: Point,
15925 window: &mut Window,
15926 cx: &mut Context<Self>,
15927 ) {
15928 self.go_to_singleton_buffer_range(point..point, window, cx);
15929 }
15930
15931 pub fn go_to_singleton_buffer_range(
15932 &mut self,
15933 range: Range<Point>,
15934 window: &mut Window,
15935 cx: &mut Context<Self>,
15936 ) {
15937 let multibuffer = self.buffer().read(cx);
15938 let Some(buffer) = multibuffer.as_singleton() else {
15939 return;
15940 };
15941 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15942 return;
15943 };
15944 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15945 return;
15946 };
15947 self.change_selections(
15948 SelectionEffects::default().nav_history(true),
15949 window,
15950 cx,
15951 |s| s.select_anchor_ranges([start..end]),
15952 );
15953 }
15954
15955 pub fn go_to_diagnostic(
15956 &mut self,
15957 action: &GoToDiagnostic,
15958 window: &mut Window,
15959 cx: &mut Context<Self>,
15960 ) {
15961 if !self.diagnostics_enabled() {
15962 return;
15963 }
15964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15965 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15966 }
15967
15968 pub fn go_to_prev_diagnostic(
15969 &mut self,
15970 action: &GoToPreviousDiagnostic,
15971 window: &mut Window,
15972 cx: &mut Context<Self>,
15973 ) {
15974 if !self.diagnostics_enabled() {
15975 return;
15976 }
15977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15978 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15979 }
15980
15981 pub fn go_to_diagnostic_impl(
15982 &mut self,
15983 direction: Direction,
15984 severity: GoToDiagnosticSeverityFilter,
15985 window: &mut Window,
15986 cx: &mut Context<Self>,
15987 ) {
15988 let buffer = self.buffer.read(cx).snapshot(cx);
15989 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15990
15991 let mut active_group_id = None;
15992 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15993 && active_group.active_range.start.to_offset(&buffer) == selection.start
15994 {
15995 active_group_id = Some(active_group.group_id);
15996 }
15997
15998 fn filtered<'a>(
15999 snapshot: EditorSnapshot,
16000 severity: GoToDiagnosticSeverityFilter,
16001 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16002 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16003 diagnostics
16004 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16005 .filter(|entry| entry.range.start != entry.range.end)
16006 .filter(|entry| !entry.diagnostic.is_unnecessary)
16007 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16008 }
16009
16010 let snapshot = self.snapshot(window, cx);
16011 let before = filtered(
16012 snapshot.clone(),
16013 severity,
16014 buffer
16015 .diagnostics_in_range(0..selection.start)
16016 .filter(|entry| entry.range.start <= selection.start),
16017 );
16018 let after = filtered(
16019 snapshot,
16020 severity,
16021 buffer
16022 .diagnostics_in_range(selection.start..buffer.len())
16023 .filter(|entry| entry.range.start >= selection.start),
16024 );
16025
16026 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16027 if direction == Direction::Prev {
16028 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16029 {
16030 for diagnostic in prev_diagnostics.into_iter().rev() {
16031 if diagnostic.range.start != selection.start
16032 || active_group_id
16033 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16034 {
16035 found = Some(diagnostic);
16036 break 'outer;
16037 }
16038 }
16039 }
16040 } else {
16041 for diagnostic in after.chain(before) {
16042 if diagnostic.range.start != selection.start
16043 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16044 {
16045 found = Some(diagnostic);
16046 break;
16047 }
16048 }
16049 }
16050 let Some(next_diagnostic) = found else {
16051 return;
16052 };
16053
16054 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16055 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16056 return;
16057 };
16058 self.change_selections(Default::default(), window, cx, |s| {
16059 s.select_ranges(vec![
16060 next_diagnostic.range.start..next_diagnostic.range.start,
16061 ])
16062 });
16063 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16064 self.refresh_edit_prediction(false, true, window, cx);
16065 }
16066
16067 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16069 let snapshot = self.snapshot(window, cx);
16070 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16071 self.go_to_hunk_before_or_after_position(
16072 &snapshot,
16073 selection.head(),
16074 Direction::Next,
16075 window,
16076 cx,
16077 );
16078 }
16079
16080 pub fn go_to_hunk_before_or_after_position(
16081 &mut self,
16082 snapshot: &EditorSnapshot,
16083 position: Point,
16084 direction: Direction,
16085 window: &mut Window,
16086 cx: &mut Context<Editor>,
16087 ) {
16088 let row = if direction == Direction::Next {
16089 self.hunk_after_position(snapshot, position)
16090 .map(|hunk| hunk.row_range.start)
16091 } else {
16092 self.hunk_before_position(snapshot, position)
16093 };
16094
16095 if let Some(row) = row {
16096 let destination = Point::new(row.0, 0);
16097 let autoscroll = Autoscroll::center();
16098
16099 self.unfold_ranges(&[destination..destination], false, false, cx);
16100 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16101 s.select_ranges([destination..destination]);
16102 });
16103 }
16104 }
16105
16106 fn hunk_after_position(
16107 &mut self,
16108 snapshot: &EditorSnapshot,
16109 position: Point,
16110 ) -> Option<MultiBufferDiffHunk> {
16111 snapshot
16112 .buffer_snapshot()
16113 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16114 .find(|hunk| hunk.row_range.start.0 > position.row)
16115 .or_else(|| {
16116 snapshot
16117 .buffer_snapshot()
16118 .diff_hunks_in_range(Point::zero()..position)
16119 .find(|hunk| hunk.row_range.end.0 < position.row)
16120 })
16121 }
16122
16123 fn go_to_prev_hunk(
16124 &mut self,
16125 _: &GoToPreviousHunk,
16126 window: &mut Window,
16127 cx: &mut Context<Self>,
16128 ) {
16129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16130 let snapshot = self.snapshot(window, cx);
16131 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16132 self.go_to_hunk_before_or_after_position(
16133 &snapshot,
16134 selection.head(),
16135 Direction::Prev,
16136 window,
16137 cx,
16138 );
16139 }
16140
16141 fn hunk_before_position(
16142 &mut self,
16143 snapshot: &EditorSnapshot,
16144 position: Point,
16145 ) -> Option<MultiBufferRow> {
16146 snapshot
16147 .buffer_snapshot()
16148 .diff_hunk_before(position)
16149 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16150 }
16151
16152 fn go_to_next_change(
16153 &mut self,
16154 _: &GoToNextChange,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) {
16158 if let Some(selections) = self
16159 .change_list
16160 .next_change(1, Direction::Next)
16161 .map(|s| s.to_vec())
16162 {
16163 self.change_selections(Default::default(), window, cx, |s| {
16164 let map = s.display_map();
16165 s.select_display_ranges(selections.iter().map(|a| {
16166 let point = a.to_display_point(&map);
16167 point..point
16168 }))
16169 })
16170 }
16171 }
16172
16173 fn go_to_previous_change(
16174 &mut self,
16175 _: &GoToPreviousChange,
16176 window: &mut Window,
16177 cx: &mut Context<Self>,
16178 ) {
16179 if let Some(selections) = self
16180 .change_list
16181 .next_change(1, Direction::Prev)
16182 .map(|s| s.to_vec())
16183 {
16184 self.change_selections(Default::default(), window, cx, |s| {
16185 let map = s.display_map();
16186 s.select_display_ranges(selections.iter().map(|a| {
16187 let point = a.to_display_point(&map);
16188 point..point
16189 }))
16190 })
16191 }
16192 }
16193
16194 pub fn go_to_next_document_highlight(
16195 &mut self,
16196 _: &GoToNextDocumentHighlight,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16201 }
16202
16203 pub fn go_to_prev_document_highlight(
16204 &mut self,
16205 _: &GoToPreviousDocumentHighlight,
16206 window: &mut Window,
16207 cx: &mut Context<Self>,
16208 ) {
16209 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16210 }
16211
16212 pub fn go_to_document_highlight_before_or_after_position(
16213 &mut self,
16214 direction: Direction,
16215 window: &mut Window,
16216 cx: &mut Context<Editor>,
16217 ) {
16218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16219 let snapshot = self.snapshot(window, cx);
16220 let buffer = &snapshot.buffer_snapshot();
16221 let position = self
16222 .selections
16223 .newest::<Point>(&snapshot.display_snapshot)
16224 .head();
16225 let anchor_position = buffer.anchor_after(position);
16226
16227 // Get all document highlights (both read and write)
16228 let mut all_highlights = Vec::new();
16229
16230 if let Some((_, read_highlights)) = self
16231 .background_highlights
16232 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16233 {
16234 all_highlights.extend(read_highlights.iter());
16235 }
16236
16237 if let Some((_, write_highlights)) = self
16238 .background_highlights
16239 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16240 {
16241 all_highlights.extend(write_highlights.iter());
16242 }
16243
16244 if all_highlights.is_empty() {
16245 return;
16246 }
16247
16248 // Sort highlights by position
16249 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16250
16251 let target_highlight = match direction {
16252 Direction::Next => {
16253 // Find the first highlight after the current position
16254 all_highlights
16255 .iter()
16256 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16257 }
16258 Direction::Prev => {
16259 // Find the last highlight before the current position
16260 all_highlights
16261 .iter()
16262 .rev()
16263 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16264 }
16265 };
16266
16267 if let Some(highlight) = target_highlight {
16268 let destination = highlight.start.to_point(buffer);
16269 let autoscroll = Autoscroll::center();
16270
16271 self.unfold_ranges(&[destination..destination], false, false, cx);
16272 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16273 s.select_ranges([destination..destination]);
16274 });
16275 }
16276 }
16277
16278 fn go_to_line<T: 'static>(
16279 &mut self,
16280 position: Anchor,
16281 highlight_color: Option<Hsla>,
16282 window: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) {
16285 let snapshot = self.snapshot(window, cx).display_snapshot;
16286 let position = position.to_point(&snapshot.buffer_snapshot());
16287 let start = snapshot
16288 .buffer_snapshot()
16289 .clip_point(Point::new(position.row, 0), Bias::Left);
16290 let end = start + Point::new(1, 0);
16291 let start = snapshot.buffer_snapshot().anchor_before(start);
16292 let end = snapshot.buffer_snapshot().anchor_before(end);
16293
16294 self.highlight_rows::<T>(
16295 start..end,
16296 highlight_color
16297 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16298 Default::default(),
16299 cx,
16300 );
16301
16302 if self.buffer.read(cx).is_singleton() {
16303 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16304 }
16305 }
16306
16307 pub fn go_to_definition(
16308 &mut self,
16309 _: &GoToDefinition,
16310 window: &mut Window,
16311 cx: &mut Context<Self>,
16312 ) -> Task<Result<Navigated>> {
16313 let definition =
16314 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16315 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16316 cx.spawn_in(window, async move |editor, cx| {
16317 if definition.await? == Navigated::Yes {
16318 return Ok(Navigated::Yes);
16319 }
16320 match fallback_strategy {
16321 GoToDefinitionFallback::None => Ok(Navigated::No),
16322 GoToDefinitionFallback::FindAllReferences => {
16323 match editor.update_in(cx, |editor, window, cx| {
16324 editor.find_all_references(&FindAllReferences, window, cx)
16325 })? {
16326 Some(references) => references.await,
16327 None => Ok(Navigated::No),
16328 }
16329 }
16330 }
16331 })
16332 }
16333
16334 pub fn go_to_declaration(
16335 &mut self,
16336 _: &GoToDeclaration,
16337 window: &mut Window,
16338 cx: &mut Context<Self>,
16339 ) -> Task<Result<Navigated>> {
16340 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16341 }
16342
16343 pub fn go_to_declaration_split(
16344 &mut self,
16345 _: &GoToDeclaration,
16346 window: &mut Window,
16347 cx: &mut Context<Self>,
16348 ) -> Task<Result<Navigated>> {
16349 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16350 }
16351
16352 pub fn go_to_implementation(
16353 &mut self,
16354 _: &GoToImplementation,
16355 window: &mut Window,
16356 cx: &mut Context<Self>,
16357 ) -> Task<Result<Navigated>> {
16358 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16359 }
16360
16361 pub fn go_to_implementation_split(
16362 &mut self,
16363 _: &GoToImplementationSplit,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) -> Task<Result<Navigated>> {
16367 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16368 }
16369
16370 pub fn go_to_type_definition(
16371 &mut self,
16372 _: &GoToTypeDefinition,
16373 window: &mut Window,
16374 cx: &mut Context<Self>,
16375 ) -> Task<Result<Navigated>> {
16376 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16377 }
16378
16379 pub fn go_to_definition_split(
16380 &mut self,
16381 _: &GoToDefinitionSplit,
16382 window: &mut Window,
16383 cx: &mut Context<Self>,
16384 ) -> Task<Result<Navigated>> {
16385 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16386 }
16387
16388 pub fn go_to_type_definition_split(
16389 &mut self,
16390 _: &GoToTypeDefinitionSplit,
16391 window: &mut Window,
16392 cx: &mut Context<Self>,
16393 ) -> Task<Result<Navigated>> {
16394 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16395 }
16396
16397 fn go_to_definition_of_kind(
16398 &mut self,
16399 kind: GotoDefinitionKind,
16400 split: bool,
16401 window: &mut Window,
16402 cx: &mut Context<Self>,
16403 ) -> Task<Result<Navigated>> {
16404 let Some(provider) = self.semantics_provider.clone() else {
16405 return Task::ready(Ok(Navigated::No));
16406 };
16407 let head = self
16408 .selections
16409 .newest::<usize>(&self.display_snapshot(cx))
16410 .head();
16411 let buffer = self.buffer.read(cx);
16412 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16413 return Task::ready(Ok(Navigated::No));
16414 };
16415 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16416 return Task::ready(Ok(Navigated::No));
16417 };
16418
16419 cx.spawn_in(window, async move |editor, cx| {
16420 let Some(definitions) = definitions.await? else {
16421 return Ok(Navigated::No);
16422 };
16423 let navigated = editor
16424 .update_in(cx, |editor, window, cx| {
16425 editor.navigate_to_hover_links(
16426 Some(kind),
16427 definitions
16428 .into_iter()
16429 .filter(|location| {
16430 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16431 })
16432 .map(HoverLink::Text)
16433 .collect::<Vec<_>>(),
16434 split,
16435 window,
16436 cx,
16437 )
16438 })?
16439 .await?;
16440 anyhow::Ok(navigated)
16441 })
16442 }
16443
16444 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16445 let selection = self.selections.newest_anchor();
16446 let head = selection.head();
16447 let tail = selection.tail();
16448
16449 let Some((buffer, start_position)) =
16450 self.buffer.read(cx).text_anchor_for_position(head, cx)
16451 else {
16452 return;
16453 };
16454
16455 let end_position = if head != tail {
16456 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16457 return;
16458 };
16459 Some(pos)
16460 } else {
16461 None
16462 };
16463
16464 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16465 let url = if let Some(end_pos) = end_position {
16466 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16467 } else {
16468 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16469 };
16470
16471 if let Some(url) = url {
16472 cx.update(|window, cx| {
16473 if parse_zed_link(&url, cx).is_some() {
16474 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16475 } else {
16476 cx.open_url(&url);
16477 }
16478 })?;
16479 }
16480
16481 anyhow::Ok(())
16482 });
16483
16484 url_finder.detach();
16485 }
16486
16487 pub fn open_selected_filename(
16488 &mut self,
16489 _: &OpenSelectedFilename,
16490 window: &mut Window,
16491 cx: &mut Context<Self>,
16492 ) {
16493 let Some(workspace) = self.workspace() else {
16494 return;
16495 };
16496
16497 let position = self.selections.newest_anchor().head();
16498
16499 let Some((buffer, buffer_position)) =
16500 self.buffer.read(cx).text_anchor_for_position(position, cx)
16501 else {
16502 return;
16503 };
16504
16505 let project = self.project.clone();
16506
16507 cx.spawn_in(window, async move |_, cx| {
16508 let result = find_file(&buffer, project, buffer_position, cx).await;
16509
16510 if let Some((_, path)) = result {
16511 workspace
16512 .update_in(cx, |workspace, window, cx| {
16513 workspace.open_resolved_path(path, window, cx)
16514 })?
16515 .await?;
16516 }
16517 anyhow::Ok(())
16518 })
16519 .detach();
16520 }
16521
16522 pub(crate) fn navigate_to_hover_links(
16523 &mut self,
16524 kind: Option<GotoDefinitionKind>,
16525 definitions: Vec<HoverLink>,
16526 split: bool,
16527 window: &mut Window,
16528 cx: &mut Context<Editor>,
16529 ) -> Task<Result<Navigated>> {
16530 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16531 let mut first_url_or_file = None;
16532 let definitions: Vec<_> = definitions
16533 .into_iter()
16534 .filter_map(|def| match def {
16535 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16536 HoverLink::InlayHint(lsp_location, server_id) => {
16537 let computation =
16538 self.compute_target_location(lsp_location, server_id, window, cx);
16539 Some(cx.background_spawn(computation))
16540 }
16541 HoverLink::Url(url) => {
16542 first_url_or_file = Some(Either::Left(url));
16543 None
16544 }
16545 HoverLink::File(path) => {
16546 first_url_or_file = Some(Either::Right(path));
16547 None
16548 }
16549 })
16550 .collect();
16551
16552 let workspace = self.workspace();
16553
16554 cx.spawn_in(window, async move |editor, cx| {
16555 let locations: Vec<Location> = future::join_all(definitions)
16556 .await
16557 .into_iter()
16558 .filter_map(|location| location.transpose())
16559 .collect::<Result<_>>()
16560 .context("location tasks")?;
16561 let mut locations = cx.update(|_, cx| {
16562 locations
16563 .into_iter()
16564 .map(|location| {
16565 let buffer = location.buffer.read(cx);
16566 (location.buffer, location.range.to_point(buffer))
16567 })
16568 .into_group_map()
16569 })?;
16570 let mut num_locations = 0;
16571 for ranges in locations.values_mut() {
16572 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16573 ranges.dedup();
16574 num_locations += ranges.len();
16575 }
16576
16577 if num_locations > 1 {
16578 let Some(workspace) = workspace else {
16579 return Ok(Navigated::No);
16580 };
16581
16582 let tab_kind = match kind {
16583 Some(GotoDefinitionKind::Implementation) => "Implementations",
16584 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16585 Some(GotoDefinitionKind::Declaration) => "Declarations",
16586 Some(GotoDefinitionKind::Type) => "Types",
16587 };
16588 let title = editor
16589 .update_in(cx, |_, _, cx| {
16590 let target = locations
16591 .iter()
16592 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16593 .map(|(buffer, location)| {
16594 buffer
16595 .read(cx)
16596 .text_for_range(location.clone())
16597 .collect::<String>()
16598 })
16599 .filter(|text| !text.contains('\n'))
16600 .unique()
16601 .take(3)
16602 .join(", ");
16603 if target.is_empty() {
16604 tab_kind.to_owned()
16605 } else {
16606 format!("{tab_kind} for {target}")
16607 }
16608 })
16609 .context("buffer title")?;
16610
16611 let opened = workspace
16612 .update_in(cx, |workspace, window, cx| {
16613 Self::open_locations_in_multibuffer(
16614 workspace,
16615 locations,
16616 title,
16617 split,
16618 MultibufferSelectionMode::First,
16619 window,
16620 cx,
16621 )
16622 })
16623 .is_ok();
16624
16625 anyhow::Ok(Navigated::from_bool(opened))
16626 } else if num_locations == 0 {
16627 // If there is one url or file, open it directly
16628 match first_url_or_file {
16629 Some(Either::Left(url)) => {
16630 cx.update(|_, cx| cx.open_url(&url))?;
16631 Ok(Navigated::Yes)
16632 }
16633 Some(Either::Right(path)) => {
16634 let Some(workspace) = workspace else {
16635 return Ok(Navigated::No);
16636 };
16637
16638 workspace
16639 .update_in(cx, |workspace, window, cx| {
16640 workspace.open_resolved_path(path, window, cx)
16641 })?
16642 .await?;
16643 Ok(Navigated::Yes)
16644 }
16645 None => Ok(Navigated::No),
16646 }
16647 } else {
16648 let Some(workspace) = workspace else {
16649 return Ok(Navigated::No);
16650 };
16651
16652 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16653 let target_range = target_ranges.first().unwrap().clone();
16654
16655 editor.update_in(cx, |editor, window, cx| {
16656 let range = target_range.to_point(target_buffer.read(cx));
16657 let range = editor.range_for_match(&range);
16658 let range = collapse_multiline_range(range);
16659
16660 if !split
16661 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16662 {
16663 editor.go_to_singleton_buffer_range(range, window, cx);
16664 } else {
16665 let pane = workspace.read(cx).active_pane().clone();
16666 window.defer(cx, move |window, cx| {
16667 let target_editor: Entity<Self> =
16668 workspace.update(cx, |workspace, cx| {
16669 let pane = if split {
16670 workspace.adjacent_pane(window, cx)
16671 } else {
16672 workspace.active_pane().clone()
16673 };
16674
16675 workspace.open_project_item(
16676 pane,
16677 target_buffer.clone(),
16678 true,
16679 true,
16680 window,
16681 cx,
16682 )
16683 });
16684 target_editor.update(cx, |target_editor, cx| {
16685 // When selecting a definition in a different buffer, disable the nav history
16686 // to avoid creating a history entry at the previous cursor location.
16687 pane.update(cx, |pane, _| pane.disable_history());
16688 target_editor.go_to_singleton_buffer_range(range, window, cx);
16689 pane.update(cx, |pane, _| pane.enable_history());
16690 });
16691 });
16692 }
16693 Navigated::Yes
16694 })
16695 }
16696 })
16697 }
16698
16699 fn compute_target_location(
16700 &self,
16701 lsp_location: lsp::Location,
16702 server_id: LanguageServerId,
16703 window: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) -> Task<anyhow::Result<Option<Location>>> {
16706 let Some(project) = self.project.clone() else {
16707 return Task::ready(Ok(None));
16708 };
16709
16710 cx.spawn_in(window, async move |editor, cx| {
16711 let location_task = editor.update(cx, |_, cx| {
16712 project.update(cx, |project, cx| {
16713 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16714 })
16715 })?;
16716 let location = Some({
16717 let target_buffer_handle = location_task.await.context("open local buffer")?;
16718 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16719 let target_start = target_buffer
16720 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16721 let target_end = target_buffer
16722 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16723 target_buffer.anchor_after(target_start)
16724 ..target_buffer.anchor_before(target_end)
16725 })?;
16726 Location {
16727 buffer: target_buffer_handle,
16728 range,
16729 }
16730 });
16731 Ok(location)
16732 })
16733 }
16734
16735 fn go_to_next_reference(
16736 &mut self,
16737 _: &GoToNextReference,
16738 window: &mut Window,
16739 cx: &mut Context<Self>,
16740 ) {
16741 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16742 if let Some(task) = task {
16743 task.detach();
16744 };
16745 }
16746
16747 fn go_to_prev_reference(
16748 &mut self,
16749 _: &GoToPreviousReference,
16750 window: &mut Window,
16751 cx: &mut Context<Self>,
16752 ) {
16753 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16754 if let Some(task) = task {
16755 task.detach();
16756 };
16757 }
16758
16759 pub fn go_to_reference_before_or_after_position(
16760 &mut self,
16761 direction: Direction,
16762 count: usize,
16763 window: &mut Window,
16764 cx: &mut Context<Self>,
16765 ) -> Option<Task<Result<()>>> {
16766 let selection = self.selections.newest_anchor();
16767 let head = selection.head();
16768
16769 let multi_buffer = self.buffer.read(cx);
16770
16771 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16772 let workspace = self.workspace()?;
16773 let project = workspace.read(cx).project().clone();
16774 let references =
16775 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16776 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16777 let Some(locations) = references.await? else {
16778 return Ok(());
16779 };
16780
16781 if locations.is_empty() {
16782 // totally normal - the cursor may be on something which is not
16783 // a symbol (e.g. a keyword)
16784 log::info!("no references found under cursor");
16785 return Ok(());
16786 }
16787
16788 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16789
16790 let multi_buffer_snapshot =
16791 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16792
16793 let (locations, current_location_index) =
16794 multi_buffer.update(cx, |multi_buffer, cx| {
16795 let mut locations = locations
16796 .into_iter()
16797 .filter_map(|loc| {
16798 let start = multi_buffer.buffer_anchor_to_anchor(
16799 &loc.buffer,
16800 loc.range.start,
16801 cx,
16802 )?;
16803 let end = multi_buffer.buffer_anchor_to_anchor(
16804 &loc.buffer,
16805 loc.range.end,
16806 cx,
16807 )?;
16808 Some(start..end)
16809 })
16810 .collect::<Vec<_>>();
16811
16812 // There is an O(n) implementation, but given this list will be
16813 // small (usually <100 items), the extra O(log(n)) factor isn't
16814 // worth the (surprisingly large amount of) extra complexity.
16815 locations
16816 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16817
16818 let head_offset = head.to_offset(&multi_buffer_snapshot);
16819
16820 let current_location_index = locations.iter().position(|loc| {
16821 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16822 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16823 });
16824
16825 (locations, current_location_index)
16826 })?;
16827
16828 let Some(current_location_index) = current_location_index else {
16829 // This indicates something has gone wrong, because we already
16830 // handle the "no references" case above
16831 log::error!(
16832 "failed to find current reference under cursor. Total references: {}",
16833 locations.len()
16834 );
16835 return Ok(());
16836 };
16837
16838 let destination_location_index = match direction {
16839 Direction::Next => (current_location_index + count) % locations.len(),
16840 Direction::Prev => {
16841 (current_location_index + locations.len() - count % locations.len())
16842 % locations.len()
16843 }
16844 };
16845
16846 // TODO(cameron): is this needed?
16847 // the thinking is to avoid "jumping to the current location" (avoid
16848 // polluting "jumplist" in vim terms)
16849 if current_location_index == destination_location_index {
16850 return Ok(());
16851 }
16852
16853 let Range { start, end } = locations[destination_location_index];
16854
16855 editor.update_in(cx, |editor, window, cx| {
16856 let effects = SelectionEffects::default();
16857
16858 editor.unfold_ranges(&[start..end], false, false, cx);
16859 editor.change_selections(effects, window, cx, |s| {
16860 s.select_ranges([start..start]);
16861 });
16862 })?;
16863
16864 Ok(())
16865 }))
16866 }
16867
16868 pub fn find_all_references(
16869 &mut self,
16870 _: &FindAllReferences,
16871 window: &mut Window,
16872 cx: &mut Context<Self>,
16873 ) -> Option<Task<Result<Navigated>>> {
16874 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16875 let multi_buffer = self.buffer.read(cx);
16876 let head = selection.head();
16877
16878 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16879 let head_anchor = multi_buffer_snapshot.anchor_at(
16880 head,
16881 if head < selection.tail() {
16882 Bias::Right
16883 } else {
16884 Bias::Left
16885 },
16886 );
16887
16888 match self
16889 .find_all_references_task_sources
16890 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16891 {
16892 Ok(_) => {
16893 log::info!(
16894 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16895 );
16896 return None;
16897 }
16898 Err(i) => {
16899 self.find_all_references_task_sources.insert(i, head_anchor);
16900 }
16901 }
16902
16903 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16904 let workspace = self.workspace()?;
16905 let project = workspace.read(cx).project().clone();
16906 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16907 Some(cx.spawn_in(window, async move |editor, cx| {
16908 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16909 if let Ok(i) = editor
16910 .find_all_references_task_sources
16911 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16912 {
16913 editor.find_all_references_task_sources.remove(i);
16914 }
16915 });
16916
16917 let Some(locations) = references.await? else {
16918 return anyhow::Ok(Navigated::No);
16919 };
16920 let mut locations = cx.update(|_, cx| {
16921 locations
16922 .into_iter()
16923 .map(|location| {
16924 let buffer = location.buffer.read(cx);
16925 (location.buffer, location.range.to_point(buffer))
16926 })
16927 .into_group_map()
16928 })?;
16929 if locations.is_empty() {
16930 return anyhow::Ok(Navigated::No);
16931 }
16932 for ranges in locations.values_mut() {
16933 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16934 ranges.dedup();
16935 }
16936
16937 workspace.update_in(cx, |workspace, window, cx| {
16938 let target = locations
16939 .iter()
16940 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16941 .map(|(buffer, location)| {
16942 buffer
16943 .read(cx)
16944 .text_for_range(location.clone())
16945 .collect::<String>()
16946 })
16947 .filter(|text| !text.contains('\n'))
16948 .unique()
16949 .take(3)
16950 .join(", ");
16951 let title = if target.is_empty() {
16952 "References".to_owned()
16953 } else {
16954 format!("References to {target}")
16955 };
16956 Self::open_locations_in_multibuffer(
16957 workspace,
16958 locations,
16959 title,
16960 false,
16961 MultibufferSelectionMode::First,
16962 window,
16963 cx,
16964 );
16965 Navigated::Yes
16966 })
16967 }))
16968 }
16969
16970 /// Opens a multibuffer with the given project locations in it
16971 pub fn open_locations_in_multibuffer(
16972 workspace: &mut Workspace,
16973 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16974 title: String,
16975 split: bool,
16976 multibuffer_selection_mode: MultibufferSelectionMode,
16977 window: &mut Window,
16978 cx: &mut Context<Workspace>,
16979 ) {
16980 if locations.is_empty() {
16981 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16982 return;
16983 }
16984
16985 let capability = workspace.project().read(cx).capability();
16986 let mut ranges = <Vec<Range<Anchor>>>::new();
16987
16988 // a key to find existing multibuffer editors with the same set of locations
16989 // to prevent us from opening more and more multibuffer tabs for searches and the like
16990 let mut key = (title.clone(), vec![]);
16991 let excerpt_buffer = cx.new(|cx| {
16992 let key = &mut key.1;
16993 let mut multibuffer = MultiBuffer::new(capability);
16994 for (buffer, mut ranges_for_buffer) in locations {
16995 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16996 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16997 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16998 PathKey::for_buffer(&buffer, cx),
16999 buffer.clone(),
17000 ranges_for_buffer,
17001 multibuffer_context_lines(cx),
17002 cx,
17003 );
17004 ranges.extend(new_ranges)
17005 }
17006
17007 multibuffer.with_title(title)
17008 });
17009 let existing = workspace.active_pane().update(cx, |pane, cx| {
17010 pane.items()
17011 .filter_map(|item| item.downcast::<Editor>())
17012 .find(|editor| {
17013 editor
17014 .read(cx)
17015 .lookup_key
17016 .as_ref()
17017 .and_then(|it| {
17018 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17019 })
17020 .is_some_and(|it| *it == key)
17021 })
17022 });
17023 let editor = existing.unwrap_or_else(|| {
17024 cx.new(|cx| {
17025 let mut editor = Editor::for_multibuffer(
17026 excerpt_buffer,
17027 Some(workspace.project().clone()),
17028 window,
17029 cx,
17030 );
17031 editor.lookup_key = Some(Box::new(key));
17032 editor
17033 })
17034 });
17035 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17036 MultibufferSelectionMode::First => {
17037 if let Some(first_range) = ranges.first() {
17038 editor.change_selections(
17039 SelectionEffects::no_scroll(),
17040 window,
17041 cx,
17042 |selections| {
17043 selections.clear_disjoint();
17044 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17045 },
17046 );
17047 }
17048 editor.highlight_background::<Self>(
17049 &ranges,
17050 |theme| theme.colors().editor_highlighted_line_background,
17051 cx,
17052 );
17053 }
17054 MultibufferSelectionMode::All => {
17055 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17056 selections.clear_disjoint();
17057 selections.select_anchor_ranges(ranges);
17058 });
17059 }
17060 });
17061
17062 let item = Box::new(editor);
17063 let item_id = item.item_id();
17064
17065 if split {
17066 let pane = workspace.adjacent_pane(window, cx);
17067 workspace.add_item(pane, item, None, true, true, window, cx);
17068 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17069 let (preview_item_id, preview_item_idx) =
17070 workspace.active_pane().read_with(cx, |pane, _| {
17071 (pane.preview_item_id(), pane.preview_item_idx())
17072 });
17073
17074 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17075
17076 if let Some(preview_item_id) = preview_item_id {
17077 workspace.active_pane().update(cx, |pane, cx| {
17078 pane.remove_item(preview_item_id, false, false, window, cx);
17079 });
17080 }
17081 } else {
17082 workspace.add_item_to_active_pane(item, None, true, window, cx);
17083 }
17084 workspace.active_pane().update(cx, |pane, cx| {
17085 pane.set_preview_item_id(Some(item_id), cx);
17086 });
17087 }
17088
17089 pub fn rename(
17090 &mut self,
17091 _: &Rename,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) -> Option<Task<Result<()>>> {
17095 use language::ToOffset as _;
17096
17097 let provider = self.semantics_provider.clone()?;
17098 let selection = self.selections.newest_anchor().clone();
17099 let (cursor_buffer, cursor_buffer_position) = self
17100 .buffer
17101 .read(cx)
17102 .text_anchor_for_position(selection.head(), cx)?;
17103 let (tail_buffer, cursor_buffer_position_end) = self
17104 .buffer
17105 .read(cx)
17106 .text_anchor_for_position(selection.tail(), cx)?;
17107 if tail_buffer != cursor_buffer {
17108 return None;
17109 }
17110
17111 let snapshot = cursor_buffer.read(cx).snapshot();
17112 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17113 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17114 let prepare_rename = provider
17115 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17116 .unwrap_or_else(|| Task::ready(Ok(None)));
17117 drop(snapshot);
17118
17119 Some(cx.spawn_in(window, async move |this, cx| {
17120 let rename_range = if let Some(range) = prepare_rename.await? {
17121 Some(range)
17122 } else {
17123 this.update(cx, |this, cx| {
17124 let buffer = this.buffer.read(cx).snapshot(cx);
17125 let mut buffer_highlights = this
17126 .document_highlights_for_position(selection.head(), &buffer)
17127 .filter(|highlight| {
17128 highlight.start.excerpt_id == selection.head().excerpt_id
17129 && highlight.end.excerpt_id == selection.head().excerpt_id
17130 });
17131 buffer_highlights
17132 .next()
17133 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17134 })?
17135 };
17136 if let Some(rename_range) = rename_range {
17137 this.update_in(cx, |this, window, cx| {
17138 let snapshot = cursor_buffer.read(cx).snapshot();
17139 let rename_buffer_range = rename_range.to_offset(&snapshot);
17140 let cursor_offset_in_rename_range =
17141 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17142 let cursor_offset_in_rename_range_end =
17143 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17144
17145 this.take_rename(false, window, cx);
17146 let buffer = this.buffer.read(cx).read(cx);
17147 let cursor_offset = selection.head().to_offset(&buffer);
17148 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17149 let rename_end = rename_start + rename_buffer_range.len();
17150 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17151 let mut old_highlight_id = None;
17152 let old_name: Arc<str> = buffer
17153 .chunks(rename_start..rename_end, true)
17154 .map(|chunk| {
17155 if old_highlight_id.is_none() {
17156 old_highlight_id = chunk.syntax_highlight_id;
17157 }
17158 chunk.text
17159 })
17160 .collect::<String>()
17161 .into();
17162
17163 drop(buffer);
17164
17165 // Position the selection in the rename editor so that it matches the current selection.
17166 this.show_local_selections = false;
17167 let rename_editor = cx.new(|cx| {
17168 let mut editor = Editor::single_line(window, cx);
17169 editor.buffer.update(cx, |buffer, cx| {
17170 buffer.edit([(0..0, old_name.clone())], None, cx)
17171 });
17172 let rename_selection_range = match cursor_offset_in_rename_range
17173 .cmp(&cursor_offset_in_rename_range_end)
17174 {
17175 Ordering::Equal => {
17176 editor.select_all(&SelectAll, window, cx);
17177 return editor;
17178 }
17179 Ordering::Less => {
17180 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17181 }
17182 Ordering::Greater => {
17183 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17184 }
17185 };
17186 if rename_selection_range.end > old_name.len() {
17187 editor.select_all(&SelectAll, window, cx);
17188 } else {
17189 editor.change_selections(Default::default(), window, cx, |s| {
17190 s.select_ranges([rename_selection_range]);
17191 });
17192 }
17193 editor
17194 });
17195 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17196 if e == &EditorEvent::Focused {
17197 cx.emit(EditorEvent::FocusedIn)
17198 }
17199 })
17200 .detach();
17201
17202 let write_highlights =
17203 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17204 let read_highlights =
17205 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17206 let ranges = write_highlights
17207 .iter()
17208 .flat_map(|(_, ranges)| ranges.iter())
17209 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17210 .cloned()
17211 .collect();
17212
17213 this.highlight_text::<Rename>(
17214 ranges,
17215 HighlightStyle {
17216 fade_out: Some(0.6),
17217 ..Default::default()
17218 },
17219 cx,
17220 );
17221 let rename_focus_handle = rename_editor.focus_handle(cx);
17222 window.focus(&rename_focus_handle);
17223 let block_id = this.insert_blocks(
17224 [BlockProperties {
17225 style: BlockStyle::Flex,
17226 placement: BlockPlacement::Below(range.start),
17227 height: Some(1),
17228 render: Arc::new({
17229 let rename_editor = rename_editor.clone();
17230 move |cx: &mut BlockContext| {
17231 let mut text_style = cx.editor_style.text.clone();
17232 if let Some(highlight_style) = old_highlight_id
17233 .and_then(|h| h.style(&cx.editor_style.syntax))
17234 {
17235 text_style = text_style.highlight(highlight_style);
17236 }
17237 div()
17238 .block_mouse_except_scroll()
17239 .pl(cx.anchor_x)
17240 .child(EditorElement::new(
17241 &rename_editor,
17242 EditorStyle {
17243 background: cx.theme().system().transparent,
17244 local_player: cx.editor_style.local_player,
17245 text: text_style,
17246 scrollbar_width: cx.editor_style.scrollbar_width,
17247 syntax: cx.editor_style.syntax.clone(),
17248 status: cx.editor_style.status.clone(),
17249 inlay_hints_style: HighlightStyle {
17250 font_weight: Some(FontWeight::BOLD),
17251 ..make_inlay_hints_style(cx.app)
17252 },
17253 edit_prediction_styles: make_suggestion_styles(
17254 cx.app,
17255 ),
17256 ..EditorStyle::default()
17257 },
17258 ))
17259 .into_any_element()
17260 }
17261 }),
17262 priority: 0,
17263 }],
17264 Some(Autoscroll::fit()),
17265 cx,
17266 )[0];
17267 this.pending_rename = Some(RenameState {
17268 range,
17269 old_name,
17270 editor: rename_editor,
17271 block_id,
17272 });
17273 })?;
17274 }
17275
17276 Ok(())
17277 }))
17278 }
17279
17280 pub fn confirm_rename(
17281 &mut self,
17282 _: &ConfirmRename,
17283 window: &mut Window,
17284 cx: &mut Context<Self>,
17285 ) -> Option<Task<Result<()>>> {
17286 let rename = self.take_rename(false, window, cx)?;
17287 let workspace = self.workspace()?.downgrade();
17288 let (buffer, start) = self
17289 .buffer
17290 .read(cx)
17291 .text_anchor_for_position(rename.range.start, cx)?;
17292 let (end_buffer, _) = self
17293 .buffer
17294 .read(cx)
17295 .text_anchor_for_position(rename.range.end, cx)?;
17296 if buffer != end_buffer {
17297 return None;
17298 }
17299
17300 let old_name = rename.old_name;
17301 let new_name = rename.editor.read(cx).text(cx);
17302
17303 let rename = self.semantics_provider.as_ref()?.perform_rename(
17304 &buffer,
17305 start,
17306 new_name.clone(),
17307 cx,
17308 )?;
17309
17310 Some(cx.spawn_in(window, async move |editor, cx| {
17311 let project_transaction = rename.await?;
17312 Self::open_project_transaction(
17313 &editor,
17314 workspace,
17315 project_transaction,
17316 format!("Rename: {} → {}", old_name, new_name),
17317 cx,
17318 )
17319 .await?;
17320
17321 editor.update(cx, |editor, cx| {
17322 editor.refresh_document_highlights(cx);
17323 })?;
17324 Ok(())
17325 }))
17326 }
17327
17328 fn take_rename(
17329 &mut self,
17330 moving_cursor: bool,
17331 window: &mut Window,
17332 cx: &mut Context<Self>,
17333 ) -> Option<RenameState> {
17334 let rename = self.pending_rename.take()?;
17335 if rename.editor.focus_handle(cx).is_focused(window) {
17336 window.focus(&self.focus_handle);
17337 }
17338
17339 self.remove_blocks(
17340 [rename.block_id].into_iter().collect(),
17341 Some(Autoscroll::fit()),
17342 cx,
17343 );
17344 self.clear_highlights::<Rename>(cx);
17345 self.show_local_selections = true;
17346
17347 if moving_cursor {
17348 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17349 editor
17350 .selections
17351 .newest::<usize>(&editor.display_snapshot(cx))
17352 .head()
17353 });
17354
17355 // Update the selection to match the position of the selection inside
17356 // the rename editor.
17357 let snapshot = self.buffer.read(cx).read(cx);
17358 let rename_range = rename.range.to_offset(&snapshot);
17359 let cursor_in_editor = snapshot
17360 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17361 .min(rename_range.end);
17362 drop(snapshot);
17363
17364 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17365 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17366 });
17367 } else {
17368 self.refresh_document_highlights(cx);
17369 }
17370
17371 Some(rename)
17372 }
17373
17374 pub fn pending_rename(&self) -> Option<&RenameState> {
17375 self.pending_rename.as_ref()
17376 }
17377
17378 fn format(
17379 &mut self,
17380 _: &Format,
17381 window: &mut Window,
17382 cx: &mut Context<Self>,
17383 ) -> Option<Task<Result<()>>> {
17384 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17385
17386 let project = match &self.project {
17387 Some(project) => project.clone(),
17388 None => return None,
17389 };
17390
17391 Some(self.perform_format(
17392 project,
17393 FormatTrigger::Manual,
17394 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17395 window,
17396 cx,
17397 ))
17398 }
17399
17400 fn format_selections(
17401 &mut self,
17402 _: &FormatSelections,
17403 window: &mut Window,
17404 cx: &mut Context<Self>,
17405 ) -> Option<Task<Result<()>>> {
17406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17407
17408 let project = match &self.project {
17409 Some(project) => project.clone(),
17410 None => return None,
17411 };
17412
17413 let ranges = self
17414 .selections
17415 .all_adjusted(&self.display_snapshot(cx))
17416 .into_iter()
17417 .map(|selection| selection.range())
17418 .collect_vec();
17419
17420 Some(self.perform_format(
17421 project,
17422 FormatTrigger::Manual,
17423 FormatTarget::Ranges(ranges),
17424 window,
17425 cx,
17426 ))
17427 }
17428
17429 fn perform_format(
17430 &mut self,
17431 project: Entity<Project>,
17432 trigger: FormatTrigger,
17433 target: FormatTarget,
17434 window: &mut Window,
17435 cx: &mut Context<Self>,
17436 ) -> Task<Result<()>> {
17437 let buffer = self.buffer.clone();
17438 let (buffers, target) = match target {
17439 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17440 FormatTarget::Ranges(selection_ranges) => {
17441 let multi_buffer = buffer.read(cx);
17442 let snapshot = multi_buffer.read(cx);
17443 let mut buffers = HashSet::default();
17444 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17445 BTreeMap::new();
17446 for selection_range in selection_ranges {
17447 for (buffer, buffer_range, _) in
17448 snapshot.range_to_buffer_ranges(selection_range)
17449 {
17450 let buffer_id = buffer.remote_id();
17451 let start = buffer.anchor_before(buffer_range.start);
17452 let end = buffer.anchor_after(buffer_range.end);
17453 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17454 buffer_id_to_ranges
17455 .entry(buffer_id)
17456 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17457 .or_insert_with(|| vec![start..end]);
17458 }
17459 }
17460 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17461 }
17462 };
17463
17464 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17465 let selections_prev = transaction_id_prev
17466 .and_then(|transaction_id_prev| {
17467 // default to selections as they were after the last edit, if we have them,
17468 // instead of how they are now.
17469 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17470 // will take you back to where you made the last edit, instead of staying where you scrolled
17471 self.selection_history
17472 .transaction(transaction_id_prev)
17473 .map(|t| t.0.clone())
17474 })
17475 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17476
17477 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17478 let format = project.update(cx, |project, cx| {
17479 project.format(buffers, target, true, trigger, cx)
17480 });
17481
17482 cx.spawn_in(window, async move |editor, cx| {
17483 let transaction = futures::select_biased! {
17484 transaction = format.log_err().fuse() => transaction,
17485 () = timeout => {
17486 log::warn!("timed out waiting for formatting");
17487 None
17488 }
17489 };
17490
17491 buffer
17492 .update(cx, |buffer, cx| {
17493 if let Some(transaction) = transaction
17494 && !buffer.is_singleton()
17495 {
17496 buffer.push_transaction(&transaction.0, cx);
17497 }
17498 cx.notify();
17499 })
17500 .ok();
17501
17502 if let Some(transaction_id_now) =
17503 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17504 {
17505 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17506 if has_new_transaction {
17507 _ = editor.update(cx, |editor, _| {
17508 editor
17509 .selection_history
17510 .insert_transaction(transaction_id_now, selections_prev);
17511 });
17512 }
17513 }
17514
17515 Ok(())
17516 })
17517 }
17518
17519 fn organize_imports(
17520 &mut self,
17521 _: &OrganizeImports,
17522 window: &mut Window,
17523 cx: &mut Context<Self>,
17524 ) -> Option<Task<Result<()>>> {
17525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17526 let project = match &self.project {
17527 Some(project) => project.clone(),
17528 None => return None,
17529 };
17530 Some(self.perform_code_action_kind(
17531 project,
17532 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17533 window,
17534 cx,
17535 ))
17536 }
17537
17538 fn perform_code_action_kind(
17539 &mut self,
17540 project: Entity<Project>,
17541 kind: CodeActionKind,
17542 window: &mut Window,
17543 cx: &mut Context<Self>,
17544 ) -> Task<Result<()>> {
17545 let buffer = self.buffer.clone();
17546 let buffers = buffer.read(cx).all_buffers();
17547 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17548 let apply_action = project.update(cx, |project, cx| {
17549 project.apply_code_action_kind(buffers, kind, true, cx)
17550 });
17551 cx.spawn_in(window, async move |_, cx| {
17552 let transaction = futures::select_biased! {
17553 () = timeout => {
17554 log::warn!("timed out waiting for executing code action");
17555 None
17556 }
17557 transaction = apply_action.log_err().fuse() => transaction,
17558 };
17559 buffer
17560 .update(cx, |buffer, cx| {
17561 // check if we need this
17562 if let Some(transaction) = transaction
17563 && !buffer.is_singleton()
17564 {
17565 buffer.push_transaction(&transaction.0, cx);
17566 }
17567 cx.notify();
17568 })
17569 .ok();
17570 Ok(())
17571 })
17572 }
17573
17574 pub fn restart_language_server(
17575 &mut self,
17576 _: &RestartLanguageServer,
17577 _: &mut Window,
17578 cx: &mut Context<Self>,
17579 ) {
17580 if let Some(project) = self.project.clone() {
17581 self.buffer.update(cx, |multi_buffer, cx| {
17582 project.update(cx, |project, cx| {
17583 project.restart_language_servers_for_buffers(
17584 multi_buffer.all_buffers().into_iter().collect(),
17585 HashSet::default(),
17586 cx,
17587 );
17588 });
17589 })
17590 }
17591 }
17592
17593 pub fn stop_language_server(
17594 &mut self,
17595 _: &StopLanguageServer,
17596 _: &mut Window,
17597 cx: &mut Context<Self>,
17598 ) {
17599 if let Some(project) = self.project.clone() {
17600 self.buffer.update(cx, |multi_buffer, cx| {
17601 project.update(cx, |project, cx| {
17602 project.stop_language_servers_for_buffers(
17603 multi_buffer.all_buffers().into_iter().collect(),
17604 HashSet::default(),
17605 cx,
17606 );
17607 });
17608 });
17609 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17610 }
17611 }
17612
17613 fn cancel_language_server_work(
17614 workspace: &mut Workspace,
17615 _: &actions::CancelLanguageServerWork,
17616 _: &mut Window,
17617 cx: &mut Context<Workspace>,
17618 ) {
17619 let project = workspace.project();
17620 let buffers = workspace
17621 .active_item(cx)
17622 .and_then(|item| item.act_as::<Editor>(cx))
17623 .map_or(HashSet::default(), |editor| {
17624 editor.read(cx).buffer.read(cx).all_buffers()
17625 });
17626 project.update(cx, |project, cx| {
17627 project.cancel_language_server_work_for_buffers(buffers, cx);
17628 });
17629 }
17630
17631 fn show_character_palette(
17632 &mut self,
17633 _: &ShowCharacterPalette,
17634 window: &mut Window,
17635 _: &mut Context<Self>,
17636 ) {
17637 window.show_character_palette();
17638 }
17639
17640 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17641 if !self.diagnostics_enabled() {
17642 return;
17643 }
17644
17645 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17646 let buffer = self.buffer.read(cx).snapshot(cx);
17647 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17648 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17649 let is_valid = buffer
17650 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17651 .any(|entry| {
17652 entry.diagnostic.is_primary
17653 && !entry.range.is_empty()
17654 && entry.range.start == primary_range_start
17655 && entry.diagnostic.message == active_diagnostics.active_message
17656 });
17657
17658 if !is_valid {
17659 self.dismiss_diagnostics(cx);
17660 }
17661 }
17662 }
17663
17664 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17665 match &self.active_diagnostics {
17666 ActiveDiagnostic::Group(group) => Some(group),
17667 _ => None,
17668 }
17669 }
17670
17671 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17672 if !self.diagnostics_enabled() {
17673 return;
17674 }
17675 self.dismiss_diagnostics(cx);
17676 self.active_diagnostics = ActiveDiagnostic::All;
17677 }
17678
17679 fn activate_diagnostics(
17680 &mut self,
17681 buffer_id: BufferId,
17682 diagnostic: DiagnosticEntryRef<'_, usize>,
17683 window: &mut Window,
17684 cx: &mut Context<Self>,
17685 ) {
17686 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17687 return;
17688 }
17689 self.dismiss_diagnostics(cx);
17690 let snapshot = self.snapshot(window, cx);
17691 let buffer = self.buffer.read(cx).snapshot(cx);
17692 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17693 return;
17694 };
17695
17696 let diagnostic_group = buffer
17697 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17698 .collect::<Vec<_>>();
17699
17700 let blocks =
17701 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17702
17703 let blocks = self.display_map.update(cx, |display_map, cx| {
17704 display_map.insert_blocks(blocks, cx).into_iter().collect()
17705 });
17706 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17707 active_range: buffer.anchor_before(diagnostic.range.start)
17708 ..buffer.anchor_after(diagnostic.range.end),
17709 active_message: diagnostic.diagnostic.message.clone(),
17710 group_id: diagnostic.diagnostic.group_id,
17711 blocks,
17712 });
17713 cx.notify();
17714 }
17715
17716 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17717 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17718 return;
17719 };
17720
17721 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17722 if let ActiveDiagnostic::Group(group) = prev {
17723 self.display_map.update(cx, |display_map, cx| {
17724 display_map.remove_blocks(group.blocks, cx);
17725 });
17726 cx.notify();
17727 }
17728 }
17729
17730 /// Disable inline diagnostics rendering for this editor.
17731 pub fn disable_inline_diagnostics(&mut self) {
17732 self.inline_diagnostics_enabled = false;
17733 self.inline_diagnostics_update = Task::ready(());
17734 self.inline_diagnostics.clear();
17735 }
17736
17737 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17738 self.diagnostics_enabled = false;
17739 self.dismiss_diagnostics(cx);
17740 self.inline_diagnostics_update = Task::ready(());
17741 self.inline_diagnostics.clear();
17742 }
17743
17744 pub fn disable_word_completions(&mut self) {
17745 self.word_completions_enabled = false;
17746 }
17747
17748 pub fn diagnostics_enabled(&self) -> bool {
17749 self.diagnostics_enabled && self.mode.is_full()
17750 }
17751
17752 pub fn inline_diagnostics_enabled(&self) -> bool {
17753 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17754 }
17755
17756 pub fn show_inline_diagnostics(&self) -> bool {
17757 self.show_inline_diagnostics
17758 }
17759
17760 pub fn toggle_inline_diagnostics(
17761 &mut self,
17762 _: &ToggleInlineDiagnostics,
17763 window: &mut Window,
17764 cx: &mut Context<Editor>,
17765 ) {
17766 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17767 self.refresh_inline_diagnostics(false, window, cx);
17768 }
17769
17770 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17771 self.diagnostics_max_severity = severity;
17772 self.display_map.update(cx, |display_map, _| {
17773 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17774 });
17775 }
17776
17777 pub fn toggle_diagnostics(
17778 &mut self,
17779 _: &ToggleDiagnostics,
17780 window: &mut Window,
17781 cx: &mut Context<Editor>,
17782 ) {
17783 if !self.diagnostics_enabled() {
17784 return;
17785 }
17786
17787 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17788 EditorSettings::get_global(cx)
17789 .diagnostics_max_severity
17790 .filter(|severity| severity != &DiagnosticSeverity::Off)
17791 .unwrap_or(DiagnosticSeverity::Hint)
17792 } else {
17793 DiagnosticSeverity::Off
17794 };
17795 self.set_max_diagnostics_severity(new_severity, cx);
17796 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17797 self.active_diagnostics = ActiveDiagnostic::None;
17798 self.inline_diagnostics_update = Task::ready(());
17799 self.inline_diagnostics.clear();
17800 } else {
17801 self.refresh_inline_diagnostics(false, window, cx);
17802 }
17803
17804 cx.notify();
17805 }
17806
17807 pub fn toggle_minimap(
17808 &mut self,
17809 _: &ToggleMinimap,
17810 window: &mut Window,
17811 cx: &mut Context<Editor>,
17812 ) {
17813 if self.supports_minimap(cx) {
17814 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17815 }
17816 }
17817
17818 fn refresh_inline_diagnostics(
17819 &mut self,
17820 debounce: bool,
17821 window: &mut Window,
17822 cx: &mut Context<Self>,
17823 ) {
17824 let max_severity = ProjectSettings::get_global(cx)
17825 .diagnostics
17826 .inline
17827 .max_severity
17828 .unwrap_or(self.diagnostics_max_severity);
17829
17830 if !self.inline_diagnostics_enabled()
17831 || !self.show_inline_diagnostics
17832 || max_severity == DiagnosticSeverity::Off
17833 {
17834 self.inline_diagnostics_update = Task::ready(());
17835 self.inline_diagnostics.clear();
17836 return;
17837 }
17838
17839 let debounce_ms = ProjectSettings::get_global(cx)
17840 .diagnostics
17841 .inline
17842 .update_debounce_ms;
17843 let debounce = if debounce && debounce_ms > 0 {
17844 Some(Duration::from_millis(debounce_ms))
17845 } else {
17846 None
17847 };
17848 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17849 if let Some(debounce) = debounce {
17850 cx.background_executor().timer(debounce).await;
17851 }
17852 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17853 editor
17854 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17855 .ok()
17856 }) else {
17857 return;
17858 };
17859
17860 let new_inline_diagnostics = cx
17861 .background_spawn(async move {
17862 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17863 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17864 let message = diagnostic_entry
17865 .diagnostic
17866 .message
17867 .split_once('\n')
17868 .map(|(line, _)| line)
17869 .map(SharedString::new)
17870 .unwrap_or_else(|| {
17871 SharedString::new(&*diagnostic_entry.diagnostic.message)
17872 });
17873 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17874 let (Ok(i) | Err(i)) = inline_diagnostics
17875 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17876 inline_diagnostics.insert(
17877 i,
17878 (
17879 start_anchor,
17880 InlineDiagnostic {
17881 message,
17882 group_id: diagnostic_entry.diagnostic.group_id,
17883 start: diagnostic_entry.range.start.to_point(&snapshot),
17884 is_primary: diagnostic_entry.diagnostic.is_primary,
17885 severity: diagnostic_entry.diagnostic.severity,
17886 },
17887 ),
17888 );
17889 }
17890 inline_diagnostics
17891 })
17892 .await;
17893
17894 editor
17895 .update(cx, |editor, cx| {
17896 editor.inline_diagnostics = new_inline_diagnostics;
17897 cx.notify();
17898 })
17899 .ok();
17900 });
17901 }
17902
17903 fn pull_diagnostics(
17904 &mut self,
17905 buffer_id: Option<BufferId>,
17906 window: &Window,
17907 cx: &mut Context<Self>,
17908 ) -> Option<()> {
17909 if self.ignore_lsp_data() {
17910 return None;
17911 }
17912 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17913 .diagnostics
17914 .lsp_pull_diagnostics;
17915 if !pull_diagnostics_settings.enabled {
17916 return None;
17917 }
17918 let project = self.project()?.downgrade();
17919 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17920 let mut buffers = self.buffer.read(cx).all_buffers();
17921 buffers.retain(|buffer| {
17922 let buffer_id_to_retain = buffer.read(cx).remote_id();
17923 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17924 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17925 });
17926 if buffers.is_empty() {
17927 self.pull_diagnostics_task = Task::ready(());
17928 return None;
17929 }
17930
17931 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17932 cx.background_executor().timer(debounce).await;
17933
17934 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17935 buffers
17936 .into_iter()
17937 .filter_map(|buffer| {
17938 project
17939 .update(cx, |project, cx| {
17940 project.lsp_store().update(cx, |lsp_store, cx| {
17941 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17942 })
17943 })
17944 .ok()
17945 })
17946 .collect::<FuturesUnordered<_>>()
17947 }) else {
17948 return;
17949 };
17950
17951 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17952 match pull_task {
17953 Ok(()) => {
17954 if editor
17955 .update_in(cx, |editor, window, cx| {
17956 editor.update_diagnostics_state(window, cx);
17957 })
17958 .is_err()
17959 {
17960 return;
17961 }
17962 }
17963 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17964 }
17965 }
17966 });
17967
17968 Some(())
17969 }
17970
17971 pub fn set_selections_from_remote(
17972 &mut self,
17973 selections: Vec<Selection<Anchor>>,
17974 pending_selection: Option<Selection<Anchor>>,
17975 window: &mut Window,
17976 cx: &mut Context<Self>,
17977 ) {
17978 let old_cursor_position = self.selections.newest_anchor().head();
17979 self.selections.change_with(cx, |s| {
17980 s.select_anchors(selections);
17981 if let Some(pending_selection) = pending_selection {
17982 s.set_pending(pending_selection, SelectMode::Character);
17983 } else {
17984 s.clear_pending();
17985 }
17986 });
17987 self.selections_did_change(
17988 false,
17989 &old_cursor_position,
17990 SelectionEffects::default(),
17991 window,
17992 cx,
17993 );
17994 }
17995
17996 pub fn transact(
17997 &mut self,
17998 window: &mut Window,
17999 cx: &mut Context<Self>,
18000 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18001 ) -> Option<TransactionId> {
18002 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18003 this.start_transaction_at(Instant::now(), window, cx);
18004 update(this, window, cx);
18005 this.end_transaction_at(Instant::now(), cx)
18006 })
18007 }
18008
18009 pub fn start_transaction_at(
18010 &mut self,
18011 now: Instant,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 ) -> Option<TransactionId> {
18015 self.end_selection(window, cx);
18016 if let Some(tx_id) = self
18017 .buffer
18018 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18019 {
18020 self.selection_history
18021 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18022 cx.emit(EditorEvent::TransactionBegun {
18023 transaction_id: tx_id,
18024 });
18025 Some(tx_id)
18026 } else {
18027 None
18028 }
18029 }
18030
18031 pub fn end_transaction_at(
18032 &mut self,
18033 now: Instant,
18034 cx: &mut Context<Self>,
18035 ) -> Option<TransactionId> {
18036 if let Some(transaction_id) = self
18037 .buffer
18038 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18039 {
18040 if let Some((_, end_selections)) =
18041 self.selection_history.transaction_mut(transaction_id)
18042 {
18043 *end_selections = Some(self.selections.disjoint_anchors_arc());
18044 } else {
18045 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18046 }
18047
18048 cx.emit(EditorEvent::Edited { transaction_id });
18049 Some(transaction_id)
18050 } else {
18051 None
18052 }
18053 }
18054
18055 pub fn modify_transaction_selection_history(
18056 &mut self,
18057 transaction_id: TransactionId,
18058 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18059 ) -> bool {
18060 self.selection_history
18061 .transaction_mut(transaction_id)
18062 .map(modify)
18063 .is_some()
18064 }
18065
18066 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18067 if self.selection_mark_mode {
18068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18069 s.move_with(|_, sel| {
18070 sel.collapse_to(sel.head(), SelectionGoal::None);
18071 });
18072 })
18073 }
18074 self.selection_mark_mode = true;
18075 cx.notify();
18076 }
18077
18078 pub fn swap_selection_ends(
18079 &mut self,
18080 _: &actions::SwapSelectionEnds,
18081 window: &mut Window,
18082 cx: &mut Context<Self>,
18083 ) {
18084 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18085 s.move_with(|_, sel| {
18086 if sel.start != sel.end {
18087 sel.reversed = !sel.reversed
18088 }
18089 });
18090 });
18091 self.request_autoscroll(Autoscroll::newest(), cx);
18092 cx.notify();
18093 }
18094
18095 pub fn toggle_focus(
18096 workspace: &mut Workspace,
18097 _: &actions::ToggleFocus,
18098 window: &mut Window,
18099 cx: &mut Context<Workspace>,
18100 ) {
18101 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18102 return;
18103 };
18104 workspace.activate_item(&item, true, true, window, cx);
18105 }
18106
18107 pub fn toggle_fold(
18108 &mut self,
18109 _: &actions::ToggleFold,
18110 window: &mut Window,
18111 cx: &mut Context<Self>,
18112 ) {
18113 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18115 let selection = self.selections.newest::<Point>(&display_map);
18116
18117 let range = if selection.is_empty() {
18118 let point = selection.head().to_display_point(&display_map);
18119 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18120 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18121 .to_point(&display_map);
18122 start..end
18123 } else {
18124 selection.range()
18125 };
18126 if display_map.folds_in_range(range).next().is_some() {
18127 self.unfold_lines(&Default::default(), window, cx)
18128 } else {
18129 self.fold(&Default::default(), window, cx)
18130 }
18131 } else {
18132 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18133 let buffer_ids: HashSet<_> = self
18134 .selections
18135 .disjoint_anchor_ranges()
18136 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18137 .collect();
18138
18139 let should_unfold = buffer_ids
18140 .iter()
18141 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18142
18143 for buffer_id in buffer_ids {
18144 if should_unfold {
18145 self.unfold_buffer(buffer_id, cx);
18146 } else {
18147 self.fold_buffer(buffer_id, cx);
18148 }
18149 }
18150 }
18151 }
18152
18153 pub fn toggle_fold_recursive(
18154 &mut self,
18155 _: &actions::ToggleFoldRecursive,
18156 window: &mut Window,
18157 cx: &mut Context<Self>,
18158 ) {
18159 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18160
18161 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18162 let range = if selection.is_empty() {
18163 let point = selection.head().to_display_point(&display_map);
18164 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18165 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18166 .to_point(&display_map);
18167 start..end
18168 } else {
18169 selection.range()
18170 };
18171 if display_map.folds_in_range(range).next().is_some() {
18172 self.unfold_recursive(&Default::default(), window, cx)
18173 } else {
18174 self.fold_recursive(&Default::default(), window, cx)
18175 }
18176 }
18177
18178 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18179 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18180 let mut to_fold = Vec::new();
18181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18182 let selections = self.selections.all_adjusted(&display_map);
18183
18184 for selection in selections {
18185 let range = selection.range().sorted();
18186 let buffer_start_row = range.start.row;
18187
18188 if range.start.row != range.end.row {
18189 let mut found = false;
18190 let mut row = range.start.row;
18191 while row <= range.end.row {
18192 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18193 {
18194 found = true;
18195 row = crease.range().end.row + 1;
18196 to_fold.push(crease);
18197 } else {
18198 row += 1
18199 }
18200 }
18201 if found {
18202 continue;
18203 }
18204 }
18205
18206 for row in (0..=range.start.row).rev() {
18207 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18208 && crease.range().end.row >= buffer_start_row
18209 {
18210 to_fold.push(crease);
18211 if row <= range.start.row {
18212 break;
18213 }
18214 }
18215 }
18216 }
18217
18218 self.fold_creases(to_fold, true, window, cx);
18219 } else {
18220 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18221 let buffer_ids = self
18222 .selections
18223 .disjoint_anchor_ranges()
18224 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18225 .collect::<HashSet<_>>();
18226 for buffer_id in buffer_ids {
18227 self.fold_buffer(buffer_id, cx);
18228 }
18229 }
18230 }
18231
18232 pub fn toggle_fold_all(
18233 &mut self,
18234 _: &actions::ToggleFoldAll,
18235 window: &mut Window,
18236 cx: &mut Context<Self>,
18237 ) {
18238 if self.buffer.read(cx).is_singleton() {
18239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18240 let has_folds = display_map
18241 .folds_in_range(0..display_map.buffer_snapshot().len())
18242 .next()
18243 .is_some();
18244
18245 if has_folds {
18246 self.unfold_all(&actions::UnfoldAll, window, cx);
18247 } else {
18248 self.fold_all(&actions::FoldAll, window, cx);
18249 }
18250 } else {
18251 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18252 let should_unfold = buffer_ids
18253 .iter()
18254 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18255
18256 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18257 editor
18258 .update_in(cx, |editor, _, cx| {
18259 for buffer_id in buffer_ids {
18260 if should_unfold {
18261 editor.unfold_buffer(buffer_id, cx);
18262 } else {
18263 editor.fold_buffer(buffer_id, cx);
18264 }
18265 }
18266 })
18267 .ok();
18268 });
18269 }
18270 }
18271
18272 fn fold_at_level(
18273 &mut self,
18274 fold_at: &FoldAtLevel,
18275 window: &mut Window,
18276 cx: &mut Context<Self>,
18277 ) {
18278 if !self.buffer.read(cx).is_singleton() {
18279 return;
18280 }
18281
18282 let fold_at_level = fold_at.0;
18283 let snapshot = self.buffer.read(cx).snapshot(cx);
18284 let mut to_fold = Vec::new();
18285 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18286
18287 let row_ranges_to_keep: Vec<Range<u32>> = self
18288 .selections
18289 .all::<Point>(&self.display_snapshot(cx))
18290 .into_iter()
18291 .map(|sel| sel.start.row..sel.end.row)
18292 .collect();
18293
18294 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18295 while start_row < end_row {
18296 match self
18297 .snapshot(window, cx)
18298 .crease_for_buffer_row(MultiBufferRow(start_row))
18299 {
18300 Some(crease) => {
18301 let nested_start_row = crease.range().start.row + 1;
18302 let nested_end_row = crease.range().end.row;
18303
18304 if current_level < fold_at_level {
18305 stack.push((nested_start_row, nested_end_row, current_level + 1));
18306 } else if current_level == fold_at_level {
18307 // Fold iff there is no selection completely contained within the fold region
18308 if !row_ranges_to_keep.iter().any(|selection| {
18309 selection.end >= nested_start_row
18310 && selection.start <= nested_end_row
18311 }) {
18312 to_fold.push(crease);
18313 }
18314 }
18315
18316 start_row = nested_end_row + 1;
18317 }
18318 None => start_row += 1,
18319 }
18320 }
18321 }
18322
18323 self.fold_creases(to_fold, true, window, cx);
18324 }
18325
18326 pub fn fold_at_level_1(
18327 &mut self,
18328 _: &actions::FoldAtLevel1,
18329 window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18333 }
18334
18335 pub fn fold_at_level_2(
18336 &mut self,
18337 _: &actions::FoldAtLevel2,
18338 window: &mut Window,
18339 cx: &mut Context<Self>,
18340 ) {
18341 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18342 }
18343
18344 pub fn fold_at_level_3(
18345 &mut self,
18346 _: &actions::FoldAtLevel3,
18347 window: &mut Window,
18348 cx: &mut Context<Self>,
18349 ) {
18350 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18351 }
18352
18353 pub fn fold_at_level_4(
18354 &mut self,
18355 _: &actions::FoldAtLevel4,
18356 window: &mut Window,
18357 cx: &mut Context<Self>,
18358 ) {
18359 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18360 }
18361
18362 pub fn fold_at_level_5(
18363 &mut self,
18364 _: &actions::FoldAtLevel5,
18365 window: &mut Window,
18366 cx: &mut Context<Self>,
18367 ) {
18368 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18369 }
18370
18371 pub fn fold_at_level_6(
18372 &mut self,
18373 _: &actions::FoldAtLevel6,
18374 window: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) {
18377 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18378 }
18379
18380 pub fn fold_at_level_7(
18381 &mut self,
18382 _: &actions::FoldAtLevel7,
18383 window: &mut Window,
18384 cx: &mut Context<Self>,
18385 ) {
18386 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18387 }
18388
18389 pub fn fold_at_level_8(
18390 &mut self,
18391 _: &actions::FoldAtLevel8,
18392 window: &mut Window,
18393 cx: &mut Context<Self>,
18394 ) {
18395 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18396 }
18397
18398 pub fn fold_at_level_9(
18399 &mut self,
18400 _: &actions::FoldAtLevel9,
18401 window: &mut Window,
18402 cx: &mut Context<Self>,
18403 ) {
18404 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18405 }
18406
18407 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18408 if self.buffer.read(cx).is_singleton() {
18409 let mut fold_ranges = Vec::new();
18410 let snapshot = self.buffer.read(cx).snapshot(cx);
18411
18412 for row in 0..snapshot.max_row().0 {
18413 if let Some(foldable_range) = self
18414 .snapshot(window, cx)
18415 .crease_for_buffer_row(MultiBufferRow(row))
18416 {
18417 fold_ranges.push(foldable_range);
18418 }
18419 }
18420
18421 self.fold_creases(fold_ranges, true, window, cx);
18422 } else {
18423 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18424 editor
18425 .update_in(cx, |editor, _, cx| {
18426 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18427 editor.fold_buffer(buffer_id, cx);
18428 }
18429 })
18430 .ok();
18431 });
18432 }
18433 }
18434
18435 pub fn fold_function_bodies(
18436 &mut self,
18437 _: &actions::FoldFunctionBodies,
18438 window: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) {
18441 let snapshot = self.buffer.read(cx).snapshot(cx);
18442
18443 let ranges = snapshot
18444 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18445 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18446 .collect::<Vec<_>>();
18447
18448 let creases = ranges
18449 .into_iter()
18450 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18451 .collect();
18452
18453 self.fold_creases(creases, true, window, cx);
18454 }
18455
18456 pub fn fold_recursive(
18457 &mut self,
18458 _: &actions::FoldRecursive,
18459 window: &mut Window,
18460 cx: &mut Context<Self>,
18461 ) {
18462 let mut to_fold = Vec::new();
18463 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18464 let selections = self.selections.all_adjusted(&display_map);
18465
18466 for selection in selections {
18467 let range = selection.range().sorted();
18468 let buffer_start_row = range.start.row;
18469
18470 if range.start.row != range.end.row {
18471 let mut found = false;
18472 for row in range.start.row..=range.end.row {
18473 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18474 found = true;
18475 to_fold.push(crease);
18476 }
18477 }
18478 if found {
18479 continue;
18480 }
18481 }
18482
18483 for row in (0..=range.start.row).rev() {
18484 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18485 if crease.range().end.row >= buffer_start_row {
18486 to_fold.push(crease);
18487 } else {
18488 break;
18489 }
18490 }
18491 }
18492 }
18493
18494 self.fold_creases(to_fold, true, window, cx);
18495 }
18496
18497 pub fn fold_at(
18498 &mut self,
18499 buffer_row: MultiBufferRow,
18500 window: &mut Window,
18501 cx: &mut Context<Self>,
18502 ) {
18503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18504
18505 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18506 let autoscroll = self
18507 .selections
18508 .all::<Point>(&display_map)
18509 .iter()
18510 .any(|selection| crease.range().overlaps(&selection.range()));
18511
18512 self.fold_creases(vec![crease], autoscroll, window, cx);
18513 }
18514 }
18515
18516 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18517 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18518 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18519 let buffer = display_map.buffer_snapshot();
18520 let selections = self.selections.all::<Point>(&display_map);
18521 let ranges = selections
18522 .iter()
18523 .map(|s| {
18524 let range = s.display_range(&display_map).sorted();
18525 let mut start = range.start.to_point(&display_map);
18526 let mut end = range.end.to_point(&display_map);
18527 start.column = 0;
18528 end.column = buffer.line_len(MultiBufferRow(end.row));
18529 start..end
18530 })
18531 .collect::<Vec<_>>();
18532
18533 self.unfold_ranges(&ranges, true, true, cx);
18534 } else {
18535 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18536 let buffer_ids = self
18537 .selections
18538 .disjoint_anchor_ranges()
18539 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18540 .collect::<HashSet<_>>();
18541 for buffer_id in buffer_ids {
18542 self.unfold_buffer(buffer_id, cx);
18543 }
18544 }
18545 }
18546
18547 pub fn unfold_recursive(
18548 &mut self,
18549 _: &UnfoldRecursive,
18550 _window: &mut Window,
18551 cx: &mut Context<Self>,
18552 ) {
18553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18554 let selections = self.selections.all::<Point>(&display_map);
18555 let ranges = selections
18556 .iter()
18557 .map(|s| {
18558 let mut range = s.display_range(&display_map).sorted();
18559 *range.start.column_mut() = 0;
18560 *range.end.column_mut() = display_map.line_len(range.end.row());
18561 let start = range.start.to_point(&display_map);
18562 let end = range.end.to_point(&display_map);
18563 start..end
18564 })
18565 .collect::<Vec<_>>();
18566
18567 self.unfold_ranges(&ranges, true, true, cx);
18568 }
18569
18570 pub fn unfold_at(
18571 &mut self,
18572 buffer_row: MultiBufferRow,
18573 _window: &mut Window,
18574 cx: &mut Context<Self>,
18575 ) {
18576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18577
18578 let intersection_range = Point::new(buffer_row.0, 0)
18579 ..Point::new(
18580 buffer_row.0,
18581 display_map.buffer_snapshot().line_len(buffer_row),
18582 );
18583
18584 let autoscroll = self
18585 .selections
18586 .all::<Point>(&display_map)
18587 .iter()
18588 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18589
18590 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18591 }
18592
18593 pub fn unfold_all(
18594 &mut self,
18595 _: &actions::UnfoldAll,
18596 _window: &mut Window,
18597 cx: &mut Context<Self>,
18598 ) {
18599 if self.buffer.read(cx).is_singleton() {
18600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18601 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18602 } else {
18603 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18604 editor
18605 .update(cx, |editor, cx| {
18606 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18607 editor.unfold_buffer(buffer_id, cx);
18608 }
18609 })
18610 .ok();
18611 });
18612 }
18613 }
18614
18615 pub fn fold_selected_ranges(
18616 &mut self,
18617 _: &FoldSelectedRanges,
18618 window: &mut Window,
18619 cx: &mut Context<Self>,
18620 ) {
18621 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18622 let selections = self.selections.all_adjusted(&display_map);
18623 let ranges = selections
18624 .into_iter()
18625 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18626 .collect::<Vec<_>>();
18627 self.fold_creases(ranges, true, window, cx);
18628 }
18629
18630 pub fn fold_ranges<T: ToOffset + Clone>(
18631 &mut self,
18632 ranges: Vec<Range<T>>,
18633 auto_scroll: bool,
18634 window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) {
18637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18638 let ranges = ranges
18639 .into_iter()
18640 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18641 .collect::<Vec<_>>();
18642 self.fold_creases(ranges, auto_scroll, window, cx);
18643 }
18644
18645 pub fn fold_creases<T: ToOffset + Clone>(
18646 &mut self,
18647 creases: Vec<Crease<T>>,
18648 auto_scroll: bool,
18649 _window: &mut Window,
18650 cx: &mut Context<Self>,
18651 ) {
18652 if creases.is_empty() {
18653 return;
18654 }
18655
18656 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18657
18658 if auto_scroll {
18659 self.request_autoscroll(Autoscroll::fit(), cx);
18660 }
18661
18662 cx.notify();
18663
18664 self.scrollbar_marker_state.dirty = true;
18665 self.folds_did_change(cx);
18666 }
18667
18668 /// Removes any folds whose ranges intersect any of the given ranges.
18669 pub fn unfold_ranges<T: ToOffset + Clone>(
18670 &mut self,
18671 ranges: &[Range<T>],
18672 inclusive: bool,
18673 auto_scroll: bool,
18674 cx: &mut Context<Self>,
18675 ) {
18676 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18677 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18678 });
18679 self.folds_did_change(cx);
18680 }
18681
18682 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18683 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18684 return;
18685 }
18686 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18687 self.display_map.update(cx, |display_map, cx| {
18688 display_map.fold_buffers([buffer_id], cx)
18689 });
18690 cx.emit(EditorEvent::BufferFoldToggled {
18691 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18692 folded: true,
18693 });
18694 cx.notify();
18695 }
18696
18697 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18698 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18699 return;
18700 }
18701 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18702 self.display_map.update(cx, |display_map, cx| {
18703 display_map.unfold_buffers([buffer_id], cx);
18704 });
18705 cx.emit(EditorEvent::BufferFoldToggled {
18706 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18707 folded: false,
18708 });
18709 cx.notify();
18710 }
18711
18712 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18713 self.display_map.read(cx).is_buffer_folded(buffer)
18714 }
18715
18716 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18717 self.display_map.read(cx).folded_buffers()
18718 }
18719
18720 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18721 self.display_map.update(cx, |display_map, cx| {
18722 display_map.disable_header_for_buffer(buffer_id, cx);
18723 });
18724 cx.notify();
18725 }
18726
18727 /// Removes any folds with the given ranges.
18728 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18729 &mut self,
18730 ranges: &[Range<T>],
18731 type_id: TypeId,
18732 auto_scroll: bool,
18733 cx: &mut Context<Self>,
18734 ) {
18735 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18736 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18737 });
18738 self.folds_did_change(cx);
18739 }
18740
18741 fn remove_folds_with<T: ToOffset + Clone>(
18742 &mut self,
18743 ranges: &[Range<T>],
18744 auto_scroll: bool,
18745 cx: &mut Context<Self>,
18746 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18747 ) {
18748 if ranges.is_empty() {
18749 return;
18750 }
18751
18752 let mut buffers_affected = HashSet::default();
18753 let multi_buffer = self.buffer().read(cx);
18754 for range in ranges {
18755 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18756 buffers_affected.insert(buffer.read(cx).remote_id());
18757 };
18758 }
18759
18760 self.display_map.update(cx, update);
18761
18762 if auto_scroll {
18763 self.request_autoscroll(Autoscroll::fit(), cx);
18764 }
18765
18766 cx.notify();
18767 self.scrollbar_marker_state.dirty = true;
18768 self.active_indent_guides_state.dirty = true;
18769 }
18770
18771 pub fn update_renderer_widths(
18772 &mut self,
18773 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18774 cx: &mut Context<Self>,
18775 ) -> bool {
18776 self.display_map
18777 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18778 }
18779
18780 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18781 self.display_map.read(cx).fold_placeholder.clone()
18782 }
18783
18784 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18785 self.buffer.update(cx, |buffer, cx| {
18786 buffer.set_all_diff_hunks_expanded(cx);
18787 });
18788 }
18789
18790 pub fn expand_all_diff_hunks(
18791 &mut self,
18792 _: &ExpandAllDiffHunks,
18793 _window: &mut Window,
18794 cx: &mut Context<Self>,
18795 ) {
18796 self.buffer.update(cx, |buffer, cx| {
18797 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18798 });
18799 }
18800
18801 pub fn collapse_all_diff_hunks(
18802 &mut self,
18803 _: &CollapseAllDiffHunks,
18804 _window: &mut Window,
18805 cx: &mut Context<Self>,
18806 ) {
18807 self.buffer.update(cx, |buffer, cx| {
18808 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18809 });
18810 }
18811
18812 pub fn toggle_selected_diff_hunks(
18813 &mut self,
18814 _: &ToggleSelectedDiffHunks,
18815 _window: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 let ranges: Vec<_> = self
18819 .selections
18820 .disjoint_anchors()
18821 .iter()
18822 .map(|s| s.range())
18823 .collect();
18824 self.toggle_diff_hunks_in_ranges(ranges, cx);
18825 }
18826
18827 pub fn diff_hunks_in_ranges<'a>(
18828 &'a self,
18829 ranges: &'a [Range<Anchor>],
18830 buffer: &'a MultiBufferSnapshot,
18831 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18832 ranges.iter().flat_map(move |range| {
18833 let end_excerpt_id = range.end.excerpt_id;
18834 let range = range.to_point(buffer);
18835 let mut peek_end = range.end;
18836 if range.end.row < buffer.max_row().0 {
18837 peek_end = Point::new(range.end.row + 1, 0);
18838 }
18839 buffer
18840 .diff_hunks_in_range(range.start..peek_end)
18841 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18842 })
18843 }
18844
18845 pub fn has_stageable_diff_hunks_in_ranges(
18846 &self,
18847 ranges: &[Range<Anchor>],
18848 snapshot: &MultiBufferSnapshot,
18849 ) -> bool {
18850 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18851 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18852 }
18853
18854 pub fn toggle_staged_selected_diff_hunks(
18855 &mut self,
18856 _: &::git::ToggleStaged,
18857 _: &mut Window,
18858 cx: &mut Context<Self>,
18859 ) {
18860 let snapshot = self.buffer.read(cx).snapshot(cx);
18861 let ranges: Vec<_> = self
18862 .selections
18863 .disjoint_anchors()
18864 .iter()
18865 .map(|s| s.range())
18866 .collect();
18867 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18868 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18869 }
18870
18871 pub fn set_render_diff_hunk_controls(
18872 &mut self,
18873 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18874 cx: &mut Context<Self>,
18875 ) {
18876 self.render_diff_hunk_controls = render_diff_hunk_controls;
18877 cx.notify();
18878 }
18879
18880 pub fn stage_and_next(
18881 &mut self,
18882 _: &::git::StageAndNext,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 self.do_stage_or_unstage_and_next(true, window, cx);
18887 }
18888
18889 pub fn unstage_and_next(
18890 &mut self,
18891 _: &::git::UnstageAndNext,
18892 window: &mut Window,
18893 cx: &mut Context<Self>,
18894 ) {
18895 self.do_stage_or_unstage_and_next(false, window, cx);
18896 }
18897
18898 pub fn stage_or_unstage_diff_hunks(
18899 &mut self,
18900 stage: bool,
18901 ranges: Vec<Range<Anchor>>,
18902 cx: &mut Context<Self>,
18903 ) {
18904 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18905 cx.spawn(async move |this, cx| {
18906 task.await?;
18907 this.update(cx, |this, cx| {
18908 let snapshot = this.buffer.read(cx).snapshot(cx);
18909 let chunk_by = this
18910 .diff_hunks_in_ranges(&ranges, &snapshot)
18911 .chunk_by(|hunk| hunk.buffer_id);
18912 for (buffer_id, hunks) in &chunk_by {
18913 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18914 }
18915 })
18916 })
18917 .detach_and_log_err(cx);
18918 }
18919
18920 fn save_buffers_for_ranges_if_needed(
18921 &mut self,
18922 ranges: &[Range<Anchor>],
18923 cx: &mut Context<Editor>,
18924 ) -> Task<Result<()>> {
18925 let multibuffer = self.buffer.read(cx);
18926 let snapshot = multibuffer.read(cx);
18927 let buffer_ids: HashSet<_> = ranges
18928 .iter()
18929 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18930 .collect();
18931 drop(snapshot);
18932
18933 let mut buffers = HashSet::default();
18934 for buffer_id in buffer_ids {
18935 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18936 let buffer = buffer_entity.read(cx);
18937 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18938 {
18939 buffers.insert(buffer_entity);
18940 }
18941 }
18942 }
18943
18944 if let Some(project) = &self.project {
18945 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18946 } else {
18947 Task::ready(Ok(()))
18948 }
18949 }
18950
18951 fn do_stage_or_unstage_and_next(
18952 &mut self,
18953 stage: bool,
18954 window: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) {
18957 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18958
18959 if ranges.iter().any(|range| range.start != range.end) {
18960 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18961 return;
18962 }
18963
18964 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18965 let snapshot = self.snapshot(window, cx);
18966 let position = self
18967 .selections
18968 .newest::<Point>(&snapshot.display_snapshot)
18969 .head();
18970 let mut row = snapshot
18971 .buffer_snapshot()
18972 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18973 .find(|hunk| hunk.row_range.start.0 > position.row)
18974 .map(|hunk| hunk.row_range.start);
18975
18976 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18977 // Outside of the project diff editor, wrap around to the beginning.
18978 if !all_diff_hunks_expanded {
18979 row = row.or_else(|| {
18980 snapshot
18981 .buffer_snapshot()
18982 .diff_hunks_in_range(Point::zero()..position)
18983 .find(|hunk| hunk.row_range.end.0 < position.row)
18984 .map(|hunk| hunk.row_range.start)
18985 });
18986 }
18987
18988 if let Some(row) = row {
18989 let destination = Point::new(row.0, 0);
18990 let autoscroll = Autoscroll::center();
18991
18992 self.unfold_ranges(&[destination..destination], false, false, cx);
18993 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18994 s.select_ranges([destination..destination]);
18995 });
18996 }
18997 }
18998
18999 fn do_stage_or_unstage(
19000 &self,
19001 stage: bool,
19002 buffer_id: BufferId,
19003 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19004 cx: &mut App,
19005 ) -> Option<()> {
19006 let project = self.project()?;
19007 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19008 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19009 let buffer_snapshot = buffer.read(cx).snapshot();
19010 let file_exists = buffer_snapshot
19011 .file()
19012 .is_some_and(|file| file.disk_state().exists());
19013 diff.update(cx, |diff, cx| {
19014 diff.stage_or_unstage_hunks(
19015 stage,
19016 &hunks
19017 .map(|hunk| buffer_diff::DiffHunk {
19018 buffer_range: hunk.buffer_range,
19019 diff_base_byte_range: hunk.diff_base_byte_range,
19020 secondary_status: hunk.secondary_status,
19021 range: Point::zero()..Point::zero(), // unused
19022 })
19023 .collect::<Vec<_>>(),
19024 &buffer_snapshot,
19025 file_exists,
19026 cx,
19027 )
19028 });
19029 None
19030 }
19031
19032 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19033 let ranges: Vec<_> = self
19034 .selections
19035 .disjoint_anchors()
19036 .iter()
19037 .map(|s| s.range())
19038 .collect();
19039 self.buffer
19040 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19041 }
19042
19043 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19044 self.buffer.update(cx, |buffer, cx| {
19045 let ranges = vec![Anchor::min()..Anchor::max()];
19046 if !buffer.all_diff_hunks_expanded()
19047 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19048 {
19049 buffer.collapse_diff_hunks(ranges, cx);
19050 true
19051 } else {
19052 false
19053 }
19054 })
19055 }
19056
19057 fn toggle_diff_hunks_in_ranges(
19058 &mut self,
19059 ranges: Vec<Range<Anchor>>,
19060 cx: &mut Context<Editor>,
19061 ) {
19062 self.buffer.update(cx, |buffer, cx| {
19063 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19064 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19065 })
19066 }
19067
19068 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19069 self.buffer.update(cx, |buffer, cx| {
19070 let snapshot = buffer.snapshot(cx);
19071 let excerpt_id = range.end.excerpt_id;
19072 let point_range = range.to_point(&snapshot);
19073 let expand = !buffer.single_hunk_is_expanded(range, cx);
19074 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19075 })
19076 }
19077
19078 pub(crate) fn apply_all_diff_hunks(
19079 &mut self,
19080 _: &ApplyAllDiffHunks,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19085
19086 let buffers = self.buffer.read(cx).all_buffers();
19087 for branch_buffer in buffers {
19088 branch_buffer.update(cx, |branch_buffer, cx| {
19089 branch_buffer.merge_into_base(Vec::new(), cx);
19090 });
19091 }
19092
19093 if let Some(project) = self.project.clone() {
19094 self.save(
19095 SaveOptions {
19096 format: true,
19097 autosave: false,
19098 },
19099 project,
19100 window,
19101 cx,
19102 )
19103 .detach_and_log_err(cx);
19104 }
19105 }
19106
19107 pub(crate) fn apply_selected_diff_hunks(
19108 &mut self,
19109 _: &ApplyDiffHunk,
19110 window: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19114 let snapshot = self.snapshot(window, cx);
19115 let hunks = snapshot.hunks_for_ranges(
19116 self.selections
19117 .all(&snapshot.display_snapshot)
19118 .into_iter()
19119 .map(|selection| selection.range()),
19120 );
19121 let mut ranges_by_buffer = HashMap::default();
19122 self.transact(window, cx, |editor, _window, cx| {
19123 for hunk in hunks {
19124 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19125 ranges_by_buffer
19126 .entry(buffer.clone())
19127 .or_insert_with(Vec::new)
19128 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19129 }
19130 }
19131
19132 for (buffer, ranges) in ranges_by_buffer {
19133 buffer.update(cx, |buffer, cx| {
19134 buffer.merge_into_base(ranges, cx);
19135 });
19136 }
19137 });
19138
19139 if let Some(project) = self.project.clone() {
19140 self.save(
19141 SaveOptions {
19142 format: true,
19143 autosave: false,
19144 },
19145 project,
19146 window,
19147 cx,
19148 )
19149 .detach_and_log_err(cx);
19150 }
19151 }
19152
19153 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19154 if hovered != self.gutter_hovered {
19155 self.gutter_hovered = hovered;
19156 cx.notify();
19157 }
19158 }
19159
19160 pub fn insert_blocks(
19161 &mut self,
19162 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19163 autoscroll: Option<Autoscroll>,
19164 cx: &mut Context<Self>,
19165 ) -> Vec<CustomBlockId> {
19166 let blocks = self
19167 .display_map
19168 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19169 if let Some(autoscroll) = autoscroll {
19170 self.request_autoscroll(autoscroll, cx);
19171 }
19172 cx.notify();
19173 blocks
19174 }
19175
19176 pub fn resize_blocks(
19177 &mut self,
19178 heights: HashMap<CustomBlockId, u32>,
19179 autoscroll: Option<Autoscroll>,
19180 cx: &mut Context<Self>,
19181 ) {
19182 self.display_map
19183 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19184 if let Some(autoscroll) = autoscroll {
19185 self.request_autoscroll(autoscroll, cx);
19186 }
19187 cx.notify();
19188 }
19189
19190 pub fn replace_blocks(
19191 &mut self,
19192 renderers: HashMap<CustomBlockId, RenderBlock>,
19193 autoscroll: Option<Autoscroll>,
19194 cx: &mut Context<Self>,
19195 ) {
19196 self.display_map
19197 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19198 if let Some(autoscroll) = autoscroll {
19199 self.request_autoscroll(autoscroll, cx);
19200 }
19201 cx.notify();
19202 }
19203
19204 pub fn remove_blocks(
19205 &mut self,
19206 block_ids: HashSet<CustomBlockId>,
19207 autoscroll: Option<Autoscroll>,
19208 cx: &mut Context<Self>,
19209 ) {
19210 self.display_map.update(cx, |display_map, cx| {
19211 display_map.remove_blocks(block_ids, cx)
19212 });
19213 if let Some(autoscroll) = autoscroll {
19214 self.request_autoscroll(autoscroll, cx);
19215 }
19216 cx.notify();
19217 }
19218
19219 pub fn row_for_block(
19220 &self,
19221 block_id: CustomBlockId,
19222 cx: &mut Context<Self>,
19223 ) -> Option<DisplayRow> {
19224 self.display_map
19225 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19226 }
19227
19228 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19229 self.focused_block = Some(focused_block);
19230 }
19231
19232 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19233 self.focused_block.take()
19234 }
19235
19236 pub fn insert_creases(
19237 &mut self,
19238 creases: impl IntoIterator<Item = Crease<Anchor>>,
19239 cx: &mut Context<Self>,
19240 ) -> Vec<CreaseId> {
19241 self.display_map
19242 .update(cx, |map, cx| map.insert_creases(creases, cx))
19243 }
19244
19245 pub fn remove_creases(
19246 &mut self,
19247 ids: impl IntoIterator<Item = CreaseId>,
19248 cx: &mut Context<Self>,
19249 ) -> Vec<(CreaseId, Range<Anchor>)> {
19250 self.display_map
19251 .update(cx, |map, cx| map.remove_creases(ids, cx))
19252 }
19253
19254 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19255 self.display_map
19256 .update(cx, |map, cx| map.snapshot(cx))
19257 .longest_row()
19258 }
19259
19260 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19261 self.display_map
19262 .update(cx, |map, cx| map.snapshot(cx))
19263 .max_point()
19264 }
19265
19266 pub fn text(&self, cx: &App) -> String {
19267 self.buffer.read(cx).read(cx).text()
19268 }
19269
19270 pub fn is_empty(&self, cx: &App) -> bool {
19271 self.buffer.read(cx).read(cx).is_empty()
19272 }
19273
19274 pub fn text_option(&self, cx: &App) -> Option<String> {
19275 let text = self.text(cx);
19276 let text = text.trim();
19277
19278 if text.is_empty() {
19279 return None;
19280 }
19281
19282 Some(text.to_string())
19283 }
19284
19285 pub fn set_text(
19286 &mut self,
19287 text: impl Into<Arc<str>>,
19288 window: &mut Window,
19289 cx: &mut Context<Self>,
19290 ) {
19291 self.transact(window, cx, |this, _, cx| {
19292 this.buffer
19293 .read(cx)
19294 .as_singleton()
19295 .expect("you can only call set_text on editors for singleton buffers")
19296 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19297 });
19298 }
19299
19300 pub fn display_text(&self, cx: &mut App) -> String {
19301 self.display_map
19302 .update(cx, |map, cx| map.snapshot(cx))
19303 .text()
19304 }
19305
19306 fn create_minimap(
19307 &self,
19308 minimap_settings: MinimapSettings,
19309 window: &mut Window,
19310 cx: &mut Context<Self>,
19311 ) -> Option<Entity<Self>> {
19312 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19313 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19314 }
19315
19316 fn initialize_new_minimap(
19317 &self,
19318 minimap_settings: MinimapSettings,
19319 window: &mut Window,
19320 cx: &mut Context<Self>,
19321 ) -> Entity<Self> {
19322 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19323
19324 let mut minimap = Editor::new_internal(
19325 EditorMode::Minimap {
19326 parent: cx.weak_entity(),
19327 },
19328 self.buffer.clone(),
19329 None,
19330 Some(self.display_map.clone()),
19331 window,
19332 cx,
19333 );
19334 minimap.scroll_manager.clone_state(&self.scroll_manager);
19335 minimap.set_text_style_refinement(TextStyleRefinement {
19336 font_size: Some(MINIMAP_FONT_SIZE),
19337 font_weight: Some(MINIMAP_FONT_WEIGHT),
19338 ..Default::default()
19339 });
19340 minimap.update_minimap_configuration(minimap_settings, cx);
19341 cx.new(|_| minimap)
19342 }
19343
19344 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19345 let current_line_highlight = minimap_settings
19346 .current_line_highlight
19347 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19348 self.set_current_line_highlight(Some(current_line_highlight));
19349 }
19350
19351 pub fn minimap(&self) -> Option<&Entity<Self>> {
19352 self.minimap
19353 .as_ref()
19354 .filter(|_| self.minimap_visibility.visible())
19355 }
19356
19357 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19358 let mut wrap_guides = smallvec![];
19359
19360 if self.show_wrap_guides == Some(false) {
19361 return wrap_guides;
19362 }
19363
19364 let settings = self.buffer.read(cx).language_settings(cx);
19365 if settings.show_wrap_guides {
19366 match self.soft_wrap_mode(cx) {
19367 SoftWrap::Column(soft_wrap) => {
19368 wrap_guides.push((soft_wrap as usize, true));
19369 }
19370 SoftWrap::Bounded(soft_wrap) => {
19371 wrap_guides.push((soft_wrap as usize, true));
19372 }
19373 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19374 }
19375 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19376 }
19377
19378 wrap_guides
19379 }
19380
19381 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19382 let settings = self.buffer.read(cx).language_settings(cx);
19383 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19384 match mode {
19385 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19386 SoftWrap::None
19387 }
19388 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19389 language_settings::SoftWrap::PreferredLineLength => {
19390 SoftWrap::Column(settings.preferred_line_length)
19391 }
19392 language_settings::SoftWrap::Bounded => {
19393 SoftWrap::Bounded(settings.preferred_line_length)
19394 }
19395 }
19396 }
19397
19398 pub fn set_soft_wrap_mode(
19399 &mut self,
19400 mode: language_settings::SoftWrap,
19401
19402 cx: &mut Context<Self>,
19403 ) {
19404 self.soft_wrap_mode_override = Some(mode);
19405 cx.notify();
19406 }
19407
19408 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19409 self.hard_wrap = hard_wrap;
19410 cx.notify();
19411 }
19412
19413 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19414 self.text_style_refinement = Some(style);
19415 }
19416
19417 /// called by the Element so we know what style we were most recently rendered with.
19418 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19419 // We intentionally do not inform the display map about the minimap style
19420 // so that wrapping is not recalculated and stays consistent for the editor
19421 // and its linked minimap.
19422 if !self.mode.is_minimap() {
19423 let font = style.text.font();
19424 let font_size = style.text.font_size.to_pixels(window.rem_size());
19425 let display_map = self
19426 .placeholder_display_map
19427 .as_ref()
19428 .filter(|_| self.is_empty(cx))
19429 .unwrap_or(&self.display_map);
19430
19431 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19432 }
19433 self.style = Some(style);
19434 }
19435
19436 pub fn style(&self) -> Option<&EditorStyle> {
19437 self.style.as_ref()
19438 }
19439
19440 // Called by the element. This method is not designed to be called outside of the editor
19441 // element's layout code because it does not notify when rewrapping is computed synchronously.
19442 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19443 if self.is_empty(cx) {
19444 self.placeholder_display_map
19445 .as_ref()
19446 .map_or(false, |display_map| {
19447 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19448 })
19449 } else {
19450 self.display_map
19451 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19452 }
19453 }
19454
19455 pub fn set_soft_wrap(&mut self) {
19456 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19457 }
19458
19459 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19460 if self.soft_wrap_mode_override.is_some() {
19461 self.soft_wrap_mode_override.take();
19462 } else {
19463 let soft_wrap = match self.soft_wrap_mode(cx) {
19464 SoftWrap::GitDiff => return,
19465 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19466 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19467 language_settings::SoftWrap::None
19468 }
19469 };
19470 self.soft_wrap_mode_override = Some(soft_wrap);
19471 }
19472 cx.notify();
19473 }
19474
19475 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19476 let Some(workspace) = self.workspace() else {
19477 return;
19478 };
19479 let fs = workspace.read(cx).app_state().fs.clone();
19480 let current_show = TabBarSettings::get_global(cx).show;
19481 update_settings_file(fs, cx, move |setting, _| {
19482 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19483 });
19484 }
19485
19486 pub fn toggle_indent_guides(
19487 &mut self,
19488 _: &ToggleIndentGuides,
19489 _: &mut Window,
19490 cx: &mut Context<Self>,
19491 ) {
19492 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19493 self.buffer
19494 .read(cx)
19495 .language_settings(cx)
19496 .indent_guides
19497 .enabled
19498 });
19499 self.show_indent_guides = Some(!currently_enabled);
19500 cx.notify();
19501 }
19502
19503 fn should_show_indent_guides(&self) -> Option<bool> {
19504 self.show_indent_guides
19505 }
19506
19507 pub fn toggle_line_numbers(
19508 &mut self,
19509 _: &ToggleLineNumbers,
19510 _: &mut Window,
19511 cx: &mut Context<Self>,
19512 ) {
19513 let mut editor_settings = EditorSettings::get_global(cx).clone();
19514 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19515 EditorSettings::override_global(editor_settings, cx);
19516 }
19517
19518 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19519 if let Some(show_line_numbers) = self.show_line_numbers {
19520 return show_line_numbers;
19521 }
19522 EditorSettings::get_global(cx).gutter.line_numbers
19523 }
19524
19525 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19526 self.use_relative_line_numbers
19527 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19528 }
19529
19530 pub fn toggle_relative_line_numbers(
19531 &mut self,
19532 _: &ToggleRelativeLineNumbers,
19533 _: &mut Window,
19534 cx: &mut Context<Self>,
19535 ) {
19536 let is_relative = self.should_use_relative_line_numbers(cx);
19537 self.set_relative_line_number(Some(!is_relative), cx)
19538 }
19539
19540 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19541 self.use_relative_line_numbers = is_relative;
19542 cx.notify();
19543 }
19544
19545 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19546 self.show_gutter = show_gutter;
19547 cx.notify();
19548 }
19549
19550 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19551 self.show_scrollbars = ScrollbarAxes {
19552 horizontal: show,
19553 vertical: show,
19554 };
19555 cx.notify();
19556 }
19557
19558 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19559 self.show_scrollbars.vertical = show;
19560 cx.notify();
19561 }
19562
19563 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19564 self.show_scrollbars.horizontal = show;
19565 cx.notify();
19566 }
19567
19568 pub fn set_minimap_visibility(
19569 &mut self,
19570 minimap_visibility: MinimapVisibility,
19571 window: &mut Window,
19572 cx: &mut Context<Self>,
19573 ) {
19574 if self.minimap_visibility != minimap_visibility {
19575 if minimap_visibility.visible() && self.minimap.is_none() {
19576 let minimap_settings = EditorSettings::get_global(cx).minimap;
19577 self.minimap =
19578 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19579 }
19580 self.minimap_visibility = minimap_visibility;
19581 cx.notify();
19582 }
19583 }
19584
19585 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19586 self.set_show_scrollbars(false, cx);
19587 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19588 }
19589
19590 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19591 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19592 }
19593
19594 /// Normally the text in full mode and auto height editors is padded on the
19595 /// left side by roughly half a character width for improved hit testing.
19596 ///
19597 /// Use this method to disable this for cases where this is not wanted (e.g.
19598 /// if you want to align the editor text with some other text above or below)
19599 /// or if you want to add this padding to single-line editors.
19600 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19601 self.offset_content = offset_content;
19602 cx.notify();
19603 }
19604
19605 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19606 self.show_line_numbers = Some(show_line_numbers);
19607 cx.notify();
19608 }
19609
19610 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19611 self.disable_expand_excerpt_buttons = true;
19612 cx.notify();
19613 }
19614
19615 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19616 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19617 cx.notify();
19618 }
19619
19620 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19621 self.show_code_actions = Some(show_code_actions);
19622 cx.notify();
19623 }
19624
19625 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19626 self.show_runnables = Some(show_runnables);
19627 cx.notify();
19628 }
19629
19630 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19631 self.show_breakpoints = Some(show_breakpoints);
19632 cx.notify();
19633 }
19634
19635 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19636 if self.display_map.read(cx).masked != masked {
19637 self.display_map.update(cx, |map, _| map.masked = masked);
19638 }
19639 cx.notify()
19640 }
19641
19642 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19643 self.show_wrap_guides = Some(show_wrap_guides);
19644 cx.notify();
19645 }
19646
19647 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19648 self.show_indent_guides = Some(show_indent_guides);
19649 cx.notify();
19650 }
19651
19652 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19653 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19654 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19655 && let Some(dir) = file.abs_path(cx).parent()
19656 {
19657 return Some(dir.to_owned());
19658 }
19659 }
19660
19661 None
19662 }
19663
19664 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19665 self.active_excerpt(cx)?
19666 .1
19667 .read(cx)
19668 .file()
19669 .and_then(|f| f.as_local())
19670 }
19671
19672 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19673 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19674 let buffer = buffer.read(cx);
19675 if let Some(project_path) = buffer.project_path(cx) {
19676 let project = self.project()?.read(cx);
19677 project.absolute_path(&project_path, cx)
19678 } else {
19679 buffer
19680 .file()
19681 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19682 }
19683 })
19684 }
19685
19686 pub fn reveal_in_finder(
19687 &mut self,
19688 _: &RevealInFileManager,
19689 _window: &mut Window,
19690 cx: &mut Context<Self>,
19691 ) {
19692 if let Some(target) = self.target_file(cx) {
19693 cx.reveal_path(&target.abs_path(cx));
19694 }
19695 }
19696
19697 pub fn copy_path(
19698 &mut self,
19699 _: &zed_actions::workspace::CopyPath,
19700 _window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 if let Some(path) = self.target_file_abs_path(cx)
19704 && let Some(path) = path.to_str()
19705 {
19706 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19707 } else {
19708 cx.propagate();
19709 }
19710 }
19711
19712 pub fn copy_relative_path(
19713 &mut self,
19714 _: &zed_actions::workspace::CopyRelativePath,
19715 _window: &mut Window,
19716 cx: &mut Context<Self>,
19717 ) {
19718 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19719 let project = self.project()?.read(cx);
19720 let path = buffer.read(cx).file()?.path();
19721 let path = path.display(project.path_style(cx));
19722 Some(path)
19723 }) {
19724 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19725 } else {
19726 cx.propagate();
19727 }
19728 }
19729
19730 /// Returns the project path for the editor's buffer, if any buffer is
19731 /// opened in the editor.
19732 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19733 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19734 buffer.read(cx).project_path(cx)
19735 } else {
19736 None
19737 }
19738 }
19739
19740 // Returns true if the editor handled a go-to-line request
19741 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19742 maybe!({
19743 let breakpoint_store = self.breakpoint_store.as_ref()?;
19744
19745 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19746 else {
19747 self.clear_row_highlights::<ActiveDebugLine>();
19748 return None;
19749 };
19750
19751 let position = active_stack_frame.position;
19752 let buffer_id = position.buffer_id?;
19753 let snapshot = self
19754 .project
19755 .as_ref()?
19756 .read(cx)
19757 .buffer_for_id(buffer_id, cx)?
19758 .read(cx)
19759 .snapshot();
19760
19761 let mut handled = false;
19762 for (id, ExcerptRange { context, .. }) in
19763 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19764 {
19765 if context.start.cmp(&position, &snapshot).is_ge()
19766 || context.end.cmp(&position, &snapshot).is_lt()
19767 {
19768 continue;
19769 }
19770 let snapshot = self.buffer.read(cx).snapshot(cx);
19771 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19772
19773 handled = true;
19774 self.clear_row_highlights::<ActiveDebugLine>();
19775
19776 self.go_to_line::<ActiveDebugLine>(
19777 multibuffer_anchor,
19778 Some(cx.theme().colors().editor_debugger_active_line_background),
19779 window,
19780 cx,
19781 );
19782
19783 cx.notify();
19784 }
19785
19786 handled.then_some(())
19787 })
19788 .is_some()
19789 }
19790
19791 pub fn copy_file_name_without_extension(
19792 &mut self,
19793 _: &CopyFileNameWithoutExtension,
19794 _: &mut Window,
19795 cx: &mut Context<Self>,
19796 ) {
19797 if let Some(file) = self.target_file(cx)
19798 && let Some(file_stem) = file.path().file_stem()
19799 {
19800 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19801 }
19802 }
19803
19804 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19805 if let Some(file) = self.target_file(cx)
19806 && let Some(name) = file.path().file_name()
19807 {
19808 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19809 }
19810 }
19811
19812 pub fn toggle_git_blame(
19813 &mut self,
19814 _: &::git::Blame,
19815 window: &mut Window,
19816 cx: &mut Context<Self>,
19817 ) {
19818 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19819
19820 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19821 self.start_git_blame(true, window, cx);
19822 }
19823
19824 cx.notify();
19825 }
19826
19827 pub fn toggle_git_blame_inline(
19828 &mut self,
19829 _: &ToggleGitBlameInline,
19830 window: &mut Window,
19831 cx: &mut Context<Self>,
19832 ) {
19833 self.toggle_git_blame_inline_internal(true, window, cx);
19834 cx.notify();
19835 }
19836
19837 pub fn open_git_blame_commit(
19838 &mut self,
19839 _: &OpenGitBlameCommit,
19840 window: &mut Window,
19841 cx: &mut Context<Self>,
19842 ) {
19843 self.open_git_blame_commit_internal(window, cx);
19844 }
19845
19846 fn open_git_blame_commit_internal(
19847 &mut self,
19848 window: &mut Window,
19849 cx: &mut Context<Self>,
19850 ) -> Option<()> {
19851 let blame = self.blame.as_ref()?;
19852 let snapshot = self.snapshot(window, cx);
19853 let cursor = self
19854 .selections
19855 .newest::<Point>(&snapshot.display_snapshot)
19856 .head();
19857 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19858 let (_, blame_entry) = blame
19859 .update(cx, |blame, cx| {
19860 blame
19861 .blame_for_rows(
19862 &[RowInfo {
19863 buffer_id: Some(buffer.remote_id()),
19864 buffer_row: Some(point.row),
19865 ..Default::default()
19866 }],
19867 cx,
19868 )
19869 .next()
19870 })
19871 .flatten()?;
19872 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19873 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19874 let workspace = self.workspace()?.downgrade();
19875 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19876 None
19877 }
19878
19879 pub fn git_blame_inline_enabled(&self) -> bool {
19880 self.git_blame_inline_enabled
19881 }
19882
19883 pub fn toggle_selection_menu(
19884 &mut self,
19885 _: &ToggleSelectionMenu,
19886 _: &mut Window,
19887 cx: &mut Context<Self>,
19888 ) {
19889 self.show_selection_menu = self
19890 .show_selection_menu
19891 .map(|show_selections_menu| !show_selections_menu)
19892 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19893
19894 cx.notify();
19895 }
19896
19897 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19898 self.show_selection_menu
19899 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19900 }
19901
19902 fn start_git_blame(
19903 &mut self,
19904 user_triggered: bool,
19905 window: &mut Window,
19906 cx: &mut Context<Self>,
19907 ) {
19908 if let Some(project) = self.project() {
19909 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19910 && buffer.read(cx).file().is_none()
19911 {
19912 return;
19913 }
19914
19915 let focused = self.focus_handle(cx).contains_focused(window, cx);
19916
19917 let project = project.clone();
19918 let blame = cx
19919 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19920 self.blame_subscription =
19921 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19922 self.blame = Some(blame);
19923 }
19924 }
19925
19926 fn toggle_git_blame_inline_internal(
19927 &mut self,
19928 user_triggered: bool,
19929 window: &mut Window,
19930 cx: &mut Context<Self>,
19931 ) {
19932 if self.git_blame_inline_enabled {
19933 self.git_blame_inline_enabled = false;
19934 self.show_git_blame_inline = false;
19935 self.show_git_blame_inline_delay_task.take();
19936 } else {
19937 self.git_blame_inline_enabled = true;
19938 self.start_git_blame_inline(user_triggered, window, cx);
19939 }
19940
19941 cx.notify();
19942 }
19943
19944 fn start_git_blame_inline(
19945 &mut self,
19946 user_triggered: bool,
19947 window: &mut Window,
19948 cx: &mut Context<Self>,
19949 ) {
19950 self.start_git_blame(user_triggered, window, cx);
19951
19952 if ProjectSettings::get_global(cx)
19953 .git
19954 .inline_blame_delay()
19955 .is_some()
19956 {
19957 self.start_inline_blame_timer(window, cx);
19958 } else {
19959 self.show_git_blame_inline = true
19960 }
19961 }
19962
19963 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19964 self.blame.as_ref()
19965 }
19966
19967 pub fn show_git_blame_gutter(&self) -> bool {
19968 self.show_git_blame_gutter
19969 }
19970
19971 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19972 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19973 }
19974
19975 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19976 self.show_git_blame_inline
19977 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19978 && !self.newest_selection_head_on_empty_line(cx)
19979 && self.has_blame_entries(cx)
19980 }
19981
19982 fn has_blame_entries(&self, cx: &App) -> bool {
19983 self.blame()
19984 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19985 }
19986
19987 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19988 let cursor_anchor = self.selections.newest_anchor().head();
19989
19990 let snapshot = self.buffer.read(cx).snapshot(cx);
19991 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19992
19993 snapshot.line_len(buffer_row) == 0
19994 }
19995
19996 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19997 let buffer_and_selection = maybe!({
19998 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19999 let selection_range = selection.range();
20000
20001 let multi_buffer = self.buffer().read(cx);
20002 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20003 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20004
20005 let (buffer, range, _) = if selection.reversed {
20006 buffer_ranges.first()
20007 } else {
20008 buffer_ranges.last()
20009 }?;
20010
20011 let selection = text::ToPoint::to_point(&range.start, buffer).row
20012 ..text::ToPoint::to_point(&range.end, buffer).row;
20013 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20014 });
20015
20016 let Some((buffer, selection)) = buffer_and_selection else {
20017 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20018 };
20019
20020 let Some(project) = self.project() else {
20021 return Task::ready(Err(anyhow!("editor does not have project")));
20022 };
20023
20024 project.update(cx, |project, cx| {
20025 project.get_permalink_to_line(&buffer, selection, cx)
20026 })
20027 }
20028
20029 pub fn copy_permalink_to_line(
20030 &mut self,
20031 _: &CopyPermalinkToLine,
20032 window: &mut Window,
20033 cx: &mut Context<Self>,
20034 ) {
20035 let permalink_task = self.get_permalink_to_line(cx);
20036 let workspace = self.workspace();
20037
20038 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20039 Ok(permalink) => {
20040 cx.update(|_, cx| {
20041 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20042 })
20043 .ok();
20044 }
20045 Err(err) => {
20046 let message = format!("Failed to copy permalink: {err}");
20047
20048 anyhow::Result::<()>::Err(err).log_err();
20049
20050 if let Some(workspace) = workspace {
20051 workspace
20052 .update_in(cx, |workspace, _, cx| {
20053 struct CopyPermalinkToLine;
20054
20055 workspace.show_toast(
20056 Toast::new(
20057 NotificationId::unique::<CopyPermalinkToLine>(),
20058 message,
20059 ),
20060 cx,
20061 )
20062 })
20063 .ok();
20064 }
20065 }
20066 })
20067 .detach();
20068 }
20069
20070 pub fn copy_file_location(
20071 &mut self,
20072 _: &CopyFileLocation,
20073 _: &mut Window,
20074 cx: &mut Context<Self>,
20075 ) {
20076 let selection = self
20077 .selections
20078 .newest::<Point>(&self.display_snapshot(cx))
20079 .start
20080 .row
20081 + 1;
20082 if let Some(file) = self.target_file(cx) {
20083 let path = file.path().display(file.path_style(cx));
20084 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20085 }
20086 }
20087
20088 pub fn open_permalink_to_line(
20089 &mut self,
20090 _: &OpenPermalinkToLine,
20091 window: &mut Window,
20092 cx: &mut Context<Self>,
20093 ) {
20094 let permalink_task = self.get_permalink_to_line(cx);
20095 let workspace = self.workspace();
20096
20097 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20098 Ok(permalink) => {
20099 cx.update(|_, cx| {
20100 cx.open_url(permalink.as_ref());
20101 })
20102 .ok();
20103 }
20104 Err(err) => {
20105 let message = format!("Failed to open permalink: {err}");
20106
20107 anyhow::Result::<()>::Err(err).log_err();
20108
20109 if let Some(workspace) = workspace {
20110 workspace
20111 .update(cx, |workspace, cx| {
20112 struct OpenPermalinkToLine;
20113
20114 workspace.show_toast(
20115 Toast::new(
20116 NotificationId::unique::<OpenPermalinkToLine>(),
20117 message,
20118 ),
20119 cx,
20120 )
20121 })
20122 .ok();
20123 }
20124 }
20125 })
20126 .detach();
20127 }
20128
20129 pub fn insert_uuid_v4(
20130 &mut self,
20131 _: &InsertUuidV4,
20132 window: &mut Window,
20133 cx: &mut Context<Self>,
20134 ) {
20135 self.insert_uuid(UuidVersion::V4, window, cx);
20136 }
20137
20138 pub fn insert_uuid_v7(
20139 &mut self,
20140 _: &InsertUuidV7,
20141 window: &mut Window,
20142 cx: &mut Context<Self>,
20143 ) {
20144 self.insert_uuid(UuidVersion::V7, window, cx);
20145 }
20146
20147 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20149 self.transact(window, cx, |this, window, cx| {
20150 let edits = this
20151 .selections
20152 .all::<Point>(&this.display_snapshot(cx))
20153 .into_iter()
20154 .map(|selection| {
20155 let uuid = match version {
20156 UuidVersion::V4 => uuid::Uuid::new_v4(),
20157 UuidVersion::V7 => uuid::Uuid::now_v7(),
20158 };
20159
20160 (selection.range(), uuid.to_string())
20161 });
20162 this.edit(edits, cx);
20163 this.refresh_edit_prediction(true, false, window, cx);
20164 });
20165 }
20166
20167 pub fn open_selections_in_multibuffer(
20168 &mut self,
20169 _: &OpenSelectionsInMultibuffer,
20170 window: &mut Window,
20171 cx: &mut Context<Self>,
20172 ) {
20173 let multibuffer = self.buffer.read(cx);
20174
20175 let Some(buffer) = multibuffer.as_singleton() else {
20176 return;
20177 };
20178
20179 let Some(workspace) = self.workspace() else {
20180 return;
20181 };
20182
20183 let title = multibuffer.title(cx).to_string();
20184
20185 let locations = self
20186 .selections
20187 .all_anchors(cx)
20188 .iter()
20189 .map(|selection| {
20190 (
20191 buffer.clone(),
20192 (selection.start.text_anchor..selection.end.text_anchor)
20193 .to_point(buffer.read(cx)),
20194 )
20195 })
20196 .into_group_map();
20197
20198 cx.spawn_in(window, async move |_, cx| {
20199 workspace.update_in(cx, |workspace, window, cx| {
20200 Self::open_locations_in_multibuffer(
20201 workspace,
20202 locations,
20203 format!("Selections for '{title}'"),
20204 false,
20205 MultibufferSelectionMode::All,
20206 window,
20207 cx,
20208 );
20209 })
20210 })
20211 .detach();
20212 }
20213
20214 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20215 /// last highlight added will be used.
20216 ///
20217 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20218 pub fn highlight_rows<T: 'static>(
20219 &mut self,
20220 range: Range<Anchor>,
20221 color: Hsla,
20222 options: RowHighlightOptions,
20223 cx: &mut Context<Self>,
20224 ) {
20225 let snapshot = self.buffer().read(cx).snapshot(cx);
20226 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20227 let ix = row_highlights.binary_search_by(|highlight| {
20228 Ordering::Equal
20229 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20230 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20231 });
20232
20233 if let Err(mut ix) = ix {
20234 let index = post_inc(&mut self.highlight_order);
20235
20236 // If this range intersects with the preceding highlight, then merge it with
20237 // the preceding highlight. Otherwise insert a new highlight.
20238 let mut merged = false;
20239 if ix > 0 {
20240 let prev_highlight = &mut row_highlights[ix - 1];
20241 if prev_highlight
20242 .range
20243 .end
20244 .cmp(&range.start, &snapshot)
20245 .is_ge()
20246 {
20247 ix -= 1;
20248 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20249 prev_highlight.range.end = range.end;
20250 }
20251 merged = true;
20252 prev_highlight.index = index;
20253 prev_highlight.color = color;
20254 prev_highlight.options = options;
20255 }
20256 }
20257
20258 if !merged {
20259 row_highlights.insert(
20260 ix,
20261 RowHighlight {
20262 range,
20263 index,
20264 color,
20265 options,
20266 type_id: TypeId::of::<T>(),
20267 },
20268 );
20269 }
20270
20271 // If any of the following highlights intersect with this one, merge them.
20272 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20273 let highlight = &row_highlights[ix];
20274 if next_highlight
20275 .range
20276 .start
20277 .cmp(&highlight.range.end, &snapshot)
20278 .is_le()
20279 {
20280 if next_highlight
20281 .range
20282 .end
20283 .cmp(&highlight.range.end, &snapshot)
20284 .is_gt()
20285 {
20286 row_highlights[ix].range.end = next_highlight.range.end;
20287 }
20288 row_highlights.remove(ix + 1);
20289 } else {
20290 break;
20291 }
20292 }
20293 }
20294 }
20295
20296 /// Remove any highlighted row ranges of the given type that intersect the
20297 /// given ranges.
20298 pub fn remove_highlighted_rows<T: 'static>(
20299 &mut self,
20300 ranges_to_remove: Vec<Range<Anchor>>,
20301 cx: &mut Context<Self>,
20302 ) {
20303 let snapshot = self.buffer().read(cx).snapshot(cx);
20304 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20305 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20306 row_highlights.retain(|highlight| {
20307 while let Some(range_to_remove) = ranges_to_remove.peek() {
20308 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20309 Ordering::Less | Ordering::Equal => {
20310 ranges_to_remove.next();
20311 }
20312 Ordering::Greater => {
20313 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20314 Ordering::Less | Ordering::Equal => {
20315 return false;
20316 }
20317 Ordering::Greater => break,
20318 }
20319 }
20320 }
20321 }
20322
20323 true
20324 })
20325 }
20326
20327 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20328 pub fn clear_row_highlights<T: 'static>(&mut self) {
20329 self.highlighted_rows.remove(&TypeId::of::<T>());
20330 }
20331
20332 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20333 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20334 self.highlighted_rows
20335 .get(&TypeId::of::<T>())
20336 .map_or(&[] as &[_], |vec| vec.as_slice())
20337 .iter()
20338 .map(|highlight| (highlight.range.clone(), highlight.color))
20339 }
20340
20341 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20342 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20343 /// Allows to ignore certain kinds of highlights.
20344 pub fn highlighted_display_rows(
20345 &self,
20346 window: &mut Window,
20347 cx: &mut App,
20348 ) -> BTreeMap<DisplayRow, LineHighlight> {
20349 let snapshot = self.snapshot(window, cx);
20350 let mut used_highlight_orders = HashMap::default();
20351 self.highlighted_rows
20352 .iter()
20353 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20354 .fold(
20355 BTreeMap::<DisplayRow, LineHighlight>::new(),
20356 |mut unique_rows, highlight| {
20357 let start = highlight.range.start.to_display_point(&snapshot);
20358 let end = highlight.range.end.to_display_point(&snapshot);
20359 let start_row = start.row().0;
20360 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20361 && end.column() == 0
20362 {
20363 end.row().0.saturating_sub(1)
20364 } else {
20365 end.row().0
20366 };
20367 for row in start_row..=end_row {
20368 let used_index =
20369 used_highlight_orders.entry(row).or_insert(highlight.index);
20370 if highlight.index >= *used_index {
20371 *used_index = highlight.index;
20372 unique_rows.insert(
20373 DisplayRow(row),
20374 LineHighlight {
20375 include_gutter: highlight.options.include_gutter,
20376 border: None,
20377 background: highlight.color.into(),
20378 type_id: Some(highlight.type_id),
20379 },
20380 );
20381 }
20382 }
20383 unique_rows
20384 },
20385 )
20386 }
20387
20388 pub fn highlighted_display_row_for_autoscroll(
20389 &self,
20390 snapshot: &DisplaySnapshot,
20391 ) -> Option<DisplayRow> {
20392 self.highlighted_rows
20393 .values()
20394 .flat_map(|highlighted_rows| highlighted_rows.iter())
20395 .filter_map(|highlight| {
20396 if highlight.options.autoscroll {
20397 Some(highlight.range.start.to_display_point(snapshot).row())
20398 } else {
20399 None
20400 }
20401 })
20402 .min()
20403 }
20404
20405 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20406 self.highlight_background::<SearchWithinRange>(
20407 ranges,
20408 |colors| colors.colors().editor_document_highlight_read_background,
20409 cx,
20410 )
20411 }
20412
20413 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20414 self.breadcrumb_header = Some(new_header);
20415 }
20416
20417 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20418 self.clear_background_highlights::<SearchWithinRange>(cx);
20419 }
20420
20421 pub fn highlight_background<T: 'static>(
20422 &mut self,
20423 ranges: &[Range<Anchor>],
20424 color_fetcher: fn(&Theme) -> Hsla,
20425 cx: &mut Context<Self>,
20426 ) {
20427 self.background_highlights.insert(
20428 HighlightKey::Type(TypeId::of::<T>()),
20429 (color_fetcher, Arc::from(ranges)),
20430 );
20431 self.scrollbar_marker_state.dirty = true;
20432 cx.notify();
20433 }
20434
20435 pub fn highlight_background_key<T: 'static>(
20436 &mut self,
20437 key: usize,
20438 ranges: &[Range<Anchor>],
20439 color_fetcher: fn(&Theme) -> Hsla,
20440 cx: &mut Context<Self>,
20441 ) {
20442 self.background_highlights.insert(
20443 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20444 (color_fetcher, Arc::from(ranges)),
20445 );
20446 self.scrollbar_marker_state.dirty = true;
20447 cx.notify();
20448 }
20449
20450 pub fn clear_background_highlights<T: 'static>(
20451 &mut self,
20452 cx: &mut Context<Self>,
20453 ) -> Option<BackgroundHighlight> {
20454 let text_highlights = self
20455 .background_highlights
20456 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20457 if !text_highlights.1.is_empty() {
20458 self.scrollbar_marker_state.dirty = true;
20459 cx.notify();
20460 }
20461 Some(text_highlights)
20462 }
20463
20464 pub fn highlight_gutter<T: 'static>(
20465 &mut self,
20466 ranges: impl Into<Vec<Range<Anchor>>>,
20467 color_fetcher: fn(&App) -> Hsla,
20468 cx: &mut Context<Self>,
20469 ) {
20470 self.gutter_highlights
20471 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20472 cx.notify();
20473 }
20474
20475 pub fn clear_gutter_highlights<T: 'static>(
20476 &mut self,
20477 cx: &mut Context<Self>,
20478 ) -> Option<GutterHighlight> {
20479 cx.notify();
20480 self.gutter_highlights.remove(&TypeId::of::<T>())
20481 }
20482
20483 pub fn insert_gutter_highlight<T: 'static>(
20484 &mut self,
20485 range: Range<Anchor>,
20486 color_fetcher: fn(&App) -> Hsla,
20487 cx: &mut Context<Self>,
20488 ) {
20489 let snapshot = self.buffer().read(cx).snapshot(cx);
20490 let mut highlights = self
20491 .gutter_highlights
20492 .remove(&TypeId::of::<T>())
20493 .map(|(_, highlights)| highlights)
20494 .unwrap_or_default();
20495 let ix = highlights.binary_search_by(|highlight| {
20496 Ordering::Equal
20497 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20498 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20499 });
20500 if let Err(ix) = ix {
20501 highlights.insert(ix, range);
20502 }
20503 self.gutter_highlights
20504 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20505 }
20506
20507 pub fn remove_gutter_highlights<T: 'static>(
20508 &mut self,
20509 ranges_to_remove: Vec<Range<Anchor>>,
20510 cx: &mut Context<Self>,
20511 ) {
20512 let snapshot = self.buffer().read(cx).snapshot(cx);
20513 let Some((color_fetcher, mut gutter_highlights)) =
20514 self.gutter_highlights.remove(&TypeId::of::<T>())
20515 else {
20516 return;
20517 };
20518 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20519 gutter_highlights.retain(|highlight| {
20520 while let Some(range_to_remove) = ranges_to_remove.peek() {
20521 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20522 Ordering::Less | Ordering::Equal => {
20523 ranges_to_remove.next();
20524 }
20525 Ordering::Greater => {
20526 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20527 Ordering::Less | Ordering::Equal => {
20528 return false;
20529 }
20530 Ordering::Greater => break,
20531 }
20532 }
20533 }
20534 }
20535
20536 true
20537 });
20538 self.gutter_highlights
20539 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20540 }
20541
20542 #[cfg(feature = "test-support")]
20543 pub fn all_text_highlights(
20544 &self,
20545 window: &mut Window,
20546 cx: &mut Context<Self>,
20547 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20548 let snapshot = self.snapshot(window, cx);
20549 self.display_map.update(cx, |display_map, _| {
20550 display_map
20551 .all_text_highlights()
20552 .map(|highlight| {
20553 let (style, ranges) = highlight.as_ref();
20554 (
20555 *style,
20556 ranges
20557 .iter()
20558 .map(|range| range.clone().to_display_points(&snapshot))
20559 .collect(),
20560 )
20561 })
20562 .collect()
20563 })
20564 }
20565
20566 #[cfg(feature = "test-support")]
20567 pub fn all_text_background_highlights(
20568 &self,
20569 window: &mut Window,
20570 cx: &mut Context<Self>,
20571 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20572 let snapshot = self.snapshot(window, cx);
20573 let buffer = &snapshot.buffer_snapshot();
20574 let start = buffer.anchor_before(0);
20575 let end = buffer.anchor_after(buffer.len());
20576 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20577 }
20578
20579 #[cfg(any(test, feature = "test-support"))]
20580 pub fn sorted_background_highlights_in_range(
20581 &self,
20582 search_range: Range<Anchor>,
20583 display_snapshot: &DisplaySnapshot,
20584 theme: &Theme,
20585 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20586 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20587 res.sort_by(|a, b| {
20588 a.0.start
20589 .cmp(&b.0.start)
20590 .then_with(|| a.0.end.cmp(&b.0.end))
20591 .then_with(|| a.1.cmp(&b.1))
20592 });
20593 res
20594 }
20595
20596 #[cfg(feature = "test-support")]
20597 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20598 let snapshot = self.buffer().read(cx).snapshot(cx);
20599
20600 let highlights = self
20601 .background_highlights
20602 .get(&HighlightKey::Type(TypeId::of::<
20603 items::BufferSearchHighlights,
20604 >()));
20605
20606 if let Some((_color, ranges)) = highlights {
20607 ranges
20608 .iter()
20609 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20610 .collect_vec()
20611 } else {
20612 vec![]
20613 }
20614 }
20615
20616 fn document_highlights_for_position<'a>(
20617 &'a self,
20618 position: Anchor,
20619 buffer: &'a MultiBufferSnapshot,
20620 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20621 let read_highlights = self
20622 .background_highlights
20623 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20624 .map(|h| &h.1);
20625 let write_highlights = self
20626 .background_highlights
20627 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20628 .map(|h| &h.1);
20629 let left_position = position.bias_left(buffer);
20630 let right_position = position.bias_right(buffer);
20631 read_highlights
20632 .into_iter()
20633 .chain(write_highlights)
20634 .flat_map(move |ranges| {
20635 let start_ix = match ranges.binary_search_by(|probe| {
20636 let cmp = probe.end.cmp(&left_position, buffer);
20637 if cmp.is_ge() {
20638 Ordering::Greater
20639 } else {
20640 Ordering::Less
20641 }
20642 }) {
20643 Ok(i) | Err(i) => i,
20644 };
20645
20646 ranges[start_ix..]
20647 .iter()
20648 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20649 })
20650 }
20651
20652 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20653 self.background_highlights
20654 .get(&HighlightKey::Type(TypeId::of::<T>()))
20655 .is_some_and(|(_, highlights)| !highlights.is_empty())
20656 }
20657
20658 /// Returns all background highlights for a given range.
20659 ///
20660 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20661 pub fn background_highlights_in_range(
20662 &self,
20663 search_range: Range<Anchor>,
20664 display_snapshot: &DisplaySnapshot,
20665 theme: &Theme,
20666 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20667 let mut results = Vec::new();
20668 for (color_fetcher, ranges) in self.background_highlights.values() {
20669 let color = color_fetcher(theme);
20670 let start_ix = match ranges.binary_search_by(|probe| {
20671 let cmp = probe
20672 .end
20673 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20674 if cmp.is_gt() {
20675 Ordering::Greater
20676 } else {
20677 Ordering::Less
20678 }
20679 }) {
20680 Ok(i) | Err(i) => i,
20681 };
20682 for range in &ranges[start_ix..] {
20683 if range
20684 .start
20685 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20686 .is_ge()
20687 {
20688 break;
20689 }
20690
20691 let start = range.start.to_display_point(display_snapshot);
20692 let end = range.end.to_display_point(display_snapshot);
20693 results.push((start..end, color))
20694 }
20695 }
20696 results
20697 }
20698
20699 pub fn gutter_highlights_in_range(
20700 &self,
20701 search_range: Range<Anchor>,
20702 display_snapshot: &DisplaySnapshot,
20703 cx: &App,
20704 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20705 let mut results = Vec::new();
20706 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20707 let color = color_fetcher(cx);
20708 let start_ix = match ranges.binary_search_by(|probe| {
20709 let cmp = probe
20710 .end
20711 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20712 if cmp.is_gt() {
20713 Ordering::Greater
20714 } else {
20715 Ordering::Less
20716 }
20717 }) {
20718 Ok(i) | Err(i) => i,
20719 };
20720 for range in &ranges[start_ix..] {
20721 if range
20722 .start
20723 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20724 .is_ge()
20725 {
20726 break;
20727 }
20728
20729 let start = range.start.to_display_point(display_snapshot);
20730 let end = range.end.to_display_point(display_snapshot);
20731 results.push((start..end, color))
20732 }
20733 }
20734 results
20735 }
20736
20737 /// Get the text ranges corresponding to the redaction query
20738 pub fn redacted_ranges(
20739 &self,
20740 search_range: Range<Anchor>,
20741 display_snapshot: &DisplaySnapshot,
20742 cx: &App,
20743 ) -> Vec<Range<DisplayPoint>> {
20744 display_snapshot
20745 .buffer_snapshot()
20746 .redacted_ranges(search_range, |file| {
20747 if let Some(file) = file {
20748 file.is_private()
20749 && EditorSettings::get(
20750 Some(SettingsLocation {
20751 worktree_id: file.worktree_id(cx),
20752 path: file.path().as_ref(),
20753 }),
20754 cx,
20755 )
20756 .redact_private_values
20757 } else {
20758 false
20759 }
20760 })
20761 .map(|range| {
20762 range.start.to_display_point(display_snapshot)
20763 ..range.end.to_display_point(display_snapshot)
20764 })
20765 .collect()
20766 }
20767
20768 pub fn highlight_text_key<T: 'static>(
20769 &mut self,
20770 key: usize,
20771 ranges: Vec<Range<Anchor>>,
20772 style: HighlightStyle,
20773 cx: &mut Context<Self>,
20774 ) {
20775 self.display_map.update(cx, |map, _| {
20776 map.highlight_text(
20777 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20778 ranges,
20779 style,
20780 );
20781 });
20782 cx.notify();
20783 }
20784
20785 pub fn highlight_text<T: 'static>(
20786 &mut self,
20787 ranges: Vec<Range<Anchor>>,
20788 style: HighlightStyle,
20789 cx: &mut Context<Self>,
20790 ) {
20791 self.display_map.update(cx, |map, _| {
20792 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20793 });
20794 cx.notify();
20795 }
20796
20797 pub fn text_highlights<'a, T: 'static>(
20798 &'a self,
20799 cx: &'a App,
20800 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20801 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20802 }
20803
20804 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20805 let cleared = self
20806 .display_map
20807 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20808 if cleared {
20809 cx.notify();
20810 }
20811 }
20812
20813 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20814 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20815 && self.focus_handle.is_focused(window)
20816 }
20817
20818 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20819 self.show_cursor_when_unfocused = is_enabled;
20820 cx.notify();
20821 }
20822
20823 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20824 cx.notify();
20825 }
20826
20827 fn on_debug_session_event(
20828 &mut self,
20829 _session: Entity<Session>,
20830 event: &SessionEvent,
20831 cx: &mut Context<Self>,
20832 ) {
20833 if let SessionEvent::InvalidateInlineValue = event {
20834 self.refresh_inline_values(cx);
20835 }
20836 }
20837
20838 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20839 let Some(project) = self.project.clone() else {
20840 return;
20841 };
20842
20843 if !self.inline_value_cache.enabled {
20844 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20845 self.splice_inlays(&inlays, Vec::new(), cx);
20846 return;
20847 }
20848
20849 let current_execution_position = self
20850 .highlighted_rows
20851 .get(&TypeId::of::<ActiveDebugLine>())
20852 .and_then(|lines| lines.last().map(|line| line.range.end));
20853
20854 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20855 let inline_values = editor
20856 .update(cx, |editor, cx| {
20857 let Some(current_execution_position) = current_execution_position else {
20858 return Some(Task::ready(Ok(Vec::new())));
20859 };
20860
20861 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20862 let snapshot = buffer.snapshot(cx);
20863
20864 let excerpt = snapshot.excerpt_containing(
20865 current_execution_position..current_execution_position,
20866 )?;
20867
20868 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20869 })?;
20870
20871 let range =
20872 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20873
20874 project.inline_values(buffer, range, cx)
20875 })
20876 .ok()
20877 .flatten()?
20878 .await
20879 .context("refreshing debugger inlays")
20880 .log_err()?;
20881
20882 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20883
20884 for (buffer_id, inline_value) in inline_values
20885 .into_iter()
20886 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20887 {
20888 buffer_inline_values
20889 .entry(buffer_id)
20890 .or_default()
20891 .push(inline_value);
20892 }
20893
20894 editor
20895 .update(cx, |editor, cx| {
20896 let snapshot = editor.buffer.read(cx).snapshot(cx);
20897 let mut new_inlays = Vec::default();
20898
20899 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20900 let buffer_id = buffer_snapshot.remote_id();
20901 buffer_inline_values
20902 .get(&buffer_id)
20903 .into_iter()
20904 .flatten()
20905 .for_each(|hint| {
20906 let inlay = Inlay::debugger(
20907 post_inc(&mut editor.next_inlay_id),
20908 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20909 hint.text(),
20910 );
20911 if !inlay.text().chars().contains(&'\n') {
20912 new_inlays.push(inlay);
20913 }
20914 });
20915 }
20916
20917 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20918 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20919
20920 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20921 })
20922 .ok()?;
20923 Some(())
20924 });
20925 }
20926
20927 fn on_buffer_event(
20928 &mut self,
20929 multibuffer: &Entity<MultiBuffer>,
20930 event: &multi_buffer::Event,
20931 window: &mut Window,
20932 cx: &mut Context<Self>,
20933 ) {
20934 match event {
20935 multi_buffer::Event::Edited { edited_buffer } => {
20936 self.scrollbar_marker_state.dirty = true;
20937 self.active_indent_guides_state.dirty = true;
20938 self.refresh_active_diagnostics(cx);
20939 self.refresh_code_actions(window, cx);
20940 self.refresh_selected_text_highlights(true, window, cx);
20941 self.refresh_single_line_folds(window, cx);
20942 self.refresh_matching_bracket_highlights(window, cx);
20943 if self.has_active_edit_prediction() {
20944 self.update_visible_edit_prediction(window, cx);
20945 }
20946
20947 if let Some(buffer) = edited_buffer {
20948 if buffer.read(cx).file().is_none() {
20949 cx.emit(EditorEvent::TitleChanged);
20950 }
20951
20952 if self.project.is_some() {
20953 let buffer_id = buffer.read(cx).remote_id();
20954 self.register_buffer(buffer_id, cx);
20955 self.update_lsp_data(Some(buffer_id), window, cx);
20956 self.refresh_inlay_hints(
20957 InlayHintRefreshReason::BufferEdited(buffer_id),
20958 cx,
20959 );
20960 }
20961 }
20962
20963 cx.emit(EditorEvent::BufferEdited);
20964 cx.emit(SearchEvent::MatchesInvalidated);
20965
20966 let Some(project) = &self.project else { return };
20967 let (telemetry, is_via_ssh) = {
20968 let project = project.read(cx);
20969 let telemetry = project.client().telemetry().clone();
20970 let is_via_ssh = project.is_via_remote_server();
20971 (telemetry, is_via_ssh)
20972 };
20973 telemetry.log_edit_event("editor", is_via_ssh);
20974 }
20975 multi_buffer::Event::ExcerptsAdded {
20976 buffer,
20977 predecessor,
20978 excerpts,
20979 } => {
20980 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20981 let buffer_id = buffer.read(cx).remote_id();
20982 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20983 && let Some(project) = &self.project
20984 {
20985 update_uncommitted_diff_for_buffer(
20986 cx.entity(),
20987 project,
20988 [buffer.clone()],
20989 self.buffer.clone(),
20990 cx,
20991 )
20992 .detach();
20993 }
20994 self.update_lsp_data(Some(buffer_id), window, cx);
20995 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20996 cx.emit(EditorEvent::ExcerptsAdded {
20997 buffer: buffer.clone(),
20998 predecessor: *predecessor,
20999 excerpts: excerpts.clone(),
21000 });
21001 }
21002 multi_buffer::Event::ExcerptsRemoved {
21003 ids,
21004 removed_buffer_ids,
21005 } => {
21006 if let Some(inlay_hints) = &mut self.inlay_hints {
21007 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21008 }
21009 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21010 for buffer_id in removed_buffer_ids {
21011 self.registered_buffers.remove(buffer_id);
21012 }
21013 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21014 cx.emit(EditorEvent::ExcerptsRemoved {
21015 ids: ids.clone(),
21016 removed_buffer_ids: removed_buffer_ids.clone(),
21017 });
21018 }
21019 multi_buffer::Event::ExcerptsEdited {
21020 excerpt_ids,
21021 buffer_ids,
21022 } => {
21023 self.display_map.update(cx, |map, cx| {
21024 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21025 });
21026 cx.emit(EditorEvent::ExcerptsEdited {
21027 ids: excerpt_ids.clone(),
21028 });
21029 }
21030 multi_buffer::Event::ExcerptsExpanded { ids } => {
21031 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21032 self.refresh_document_highlights(cx);
21033 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21034 }
21035 multi_buffer::Event::Reparsed(buffer_id) => {
21036 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21037 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21038
21039 cx.emit(EditorEvent::Reparsed(*buffer_id));
21040 }
21041 multi_buffer::Event::DiffHunksToggled => {
21042 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21043 }
21044 multi_buffer::Event::LanguageChanged(buffer_id) => {
21045 self.registered_buffers.remove(&buffer_id);
21046 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21047 cx.emit(EditorEvent::Reparsed(*buffer_id));
21048 cx.notify();
21049 }
21050 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21051 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21052 multi_buffer::Event::FileHandleChanged
21053 | multi_buffer::Event::Reloaded
21054 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21055 multi_buffer::Event::DiagnosticsUpdated => {
21056 self.update_diagnostics_state(window, cx);
21057 }
21058 _ => {}
21059 };
21060 }
21061
21062 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21063 if !self.diagnostics_enabled() {
21064 return;
21065 }
21066 self.refresh_active_diagnostics(cx);
21067 self.refresh_inline_diagnostics(true, window, cx);
21068 self.scrollbar_marker_state.dirty = true;
21069 cx.notify();
21070 }
21071
21072 pub fn start_temporary_diff_override(&mut self) {
21073 self.load_diff_task.take();
21074 self.temporary_diff_override = true;
21075 }
21076
21077 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21078 self.temporary_diff_override = false;
21079 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21080 self.buffer.update(cx, |buffer, cx| {
21081 buffer.set_all_diff_hunks_collapsed(cx);
21082 });
21083
21084 if let Some(project) = self.project.clone() {
21085 self.load_diff_task = Some(
21086 update_uncommitted_diff_for_buffer(
21087 cx.entity(),
21088 &project,
21089 self.buffer.read(cx).all_buffers(),
21090 self.buffer.clone(),
21091 cx,
21092 )
21093 .shared(),
21094 );
21095 }
21096 }
21097
21098 fn on_display_map_changed(
21099 &mut self,
21100 _: Entity<DisplayMap>,
21101 _: &mut Window,
21102 cx: &mut Context<Self>,
21103 ) {
21104 cx.notify();
21105 }
21106
21107 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21108 if self.diagnostics_enabled() {
21109 let new_severity = EditorSettings::get_global(cx)
21110 .diagnostics_max_severity
21111 .unwrap_or(DiagnosticSeverity::Hint);
21112 self.set_max_diagnostics_severity(new_severity, cx);
21113 }
21114 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21115 self.update_edit_prediction_settings(cx);
21116 self.refresh_edit_prediction(true, false, window, cx);
21117 self.refresh_inline_values(cx);
21118 self.refresh_inlay_hints(
21119 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21120 self.selections.newest_anchor().head(),
21121 &self.buffer.read(cx).snapshot(cx),
21122 cx,
21123 )),
21124 cx,
21125 );
21126
21127 let old_cursor_shape = self.cursor_shape;
21128 let old_show_breadcrumbs = self.show_breadcrumbs;
21129
21130 {
21131 let editor_settings = EditorSettings::get_global(cx);
21132 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21133 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21134 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21135 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21136 }
21137
21138 if old_cursor_shape != self.cursor_shape {
21139 cx.emit(EditorEvent::CursorShapeChanged);
21140 }
21141
21142 if old_show_breadcrumbs != self.show_breadcrumbs {
21143 cx.emit(EditorEvent::BreadcrumbsChanged);
21144 }
21145
21146 let project_settings = ProjectSettings::get_global(cx);
21147 self.serialize_dirty_buffers =
21148 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21149
21150 if self.mode.is_full() {
21151 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21152 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21153 if self.show_inline_diagnostics != show_inline_diagnostics {
21154 self.show_inline_diagnostics = show_inline_diagnostics;
21155 self.refresh_inline_diagnostics(false, window, cx);
21156 }
21157
21158 if self.git_blame_inline_enabled != inline_blame_enabled {
21159 self.toggle_git_blame_inline_internal(false, window, cx);
21160 }
21161
21162 let minimap_settings = EditorSettings::get_global(cx).minimap;
21163 if self.minimap_visibility != MinimapVisibility::Disabled {
21164 if self.minimap_visibility.settings_visibility()
21165 != minimap_settings.minimap_enabled()
21166 {
21167 self.set_minimap_visibility(
21168 MinimapVisibility::for_mode(self.mode(), cx),
21169 window,
21170 cx,
21171 );
21172 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21173 minimap_entity.update(cx, |minimap_editor, cx| {
21174 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21175 })
21176 }
21177 }
21178 }
21179
21180 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21181 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21182 }) {
21183 if !inlay_splice.is_empty() {
21184 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21185 }
21186 self.refresh_colors_for_visible_range(None, window, cx);
21187 }
21188
21189 cx.notify();
21190 }
21191
21192 pub fn set_searchable(&mut self, searchable: bool) {
21193 self.searchable = searchable;
21194 }
21195
21196 pub fn searchable(&self) -> bool {
21197 self.searchable
21198 }
21199
21200 pub fn open_excerpts_in_split(
21201 &mut self,
21202 _: &OpenExcerptsSplit,
21203 window: &mut Window,
21204 cx: &mut Context<Self>,
21205 ) {
21206 self.open_excerpts_common(None, true, window, cx)
21207 }
21208
21209 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21210 self.open_excerpts_common(None, false, window, cx)
21211 }
21212
21213 fn open_excerpts_common(
21214 &mut self,
21215 jump_data: Option<JumpData>,
21216 split: bool,
21217 window: &mut Window,
21218 cx: &mut Context<Self>,
21219 ) {
21220 let Some(workspace) = self.workspace() else {
21221 cx.propagate();
21222 return;
21223 };
21224
21225 if self.buffer.read(cx).is_singleton() {
21226 cx.propagate();
21227 return;
21228 }
21229
21230 let mut new_selections_by_buffer = HashMap::default();
21231 match &jump_data {
21232 Some(JumpData::MultiBufferPoint {
21233 excerpt_id,
21234 position,
21235 anchor,
21236 line_offset_from_top,
21237 }) => {
21238 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21239 if let Some(buffer) = multi_buffer_snapshot
21240 .buffer_id_for_excerpt(*excerpt_id)
21241 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21242 {
21243 let buffer_snapshot = buffer.read(cx).snapshot();
21244 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21245 language::ToPoint::to_point(anchor, &buffer_snapshot)
21246 } else {
21247 buffer_snapshot.clip_point(*position, Bias::Left)
21248 };
21249 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21250 new_selections_by_buffer.insert(
21251 buffer,
21252 (
21253 vec![jump_to_offset..jump_to_offset],
21254 Some(*line_offset_from_top),
21255 ),
21256 );
21257 }
21258 }
21259 Some(JumpData::MultiBufferRow {
21260 row,
21261 line_offset_from_top,
21262 }) => {
21263 let point = MultiBufferPoint::new(row.0, 0);
21264 if let Some((buffer, buffer_point, _)) =
21265 self.buffer.read(cx).point_to_buffer_point(point, cx)
21266 {
21267 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21268 new_selections_by_buffer
21269 .entry(buffer)
21270 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21271 .0
21272 .push(buffer_offset..buffer_offset)
21273 }
21274 }
21275 None => {
21276 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21277 let multi_buffer = self.buffer.read(cx);
21278 for selection in selections {
21279 for (snapshot, range, _, anchor) in multi_buffer
21280 .snapshot(cx)
21281 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21282 {
21283 if let Some(anchor) = anchor {
21284 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21285 else {
21286 continue;
21287 };
21288 let offset = text::ToOffset::to_offset(
21289 &anchor.text_anchor,
21290 &buffer_handle.read(cx).snapshot(),
21291 );
21292 let range = offset..offset;
21293 new_selections_by_buffer
21294 .entry(buffer_handle)
21295 .or_insert((Vec::new(), None))
21296 .0
21297 .push(range)
21298 } else {
21299 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21300 else {
21301 continue;
21302 };
21303 new_selections_by_buffer
21304 .entry(buffer_handle)
21305 .or_insert((Vec::new(), None))
21306 .0
21307 .push(range)
21308 }
21309 }
21310 }
21311 }
21312 }
21313
21314 new_selections_by_buffer
21315 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21316
21317 if new_selections_by_buffer.is_empty() {
21318 return;
21319 }
21320
21321 // We defer the pane interaction because we ourselves are a workspace item
21322 // and activating a new item causes the pane to call a method on us reentrantly,
21323 // which panics if we're on the stack.
21324 window.defer(cx, move |window, cx| {
21325 workspace.update(cx, |workspace, cx| {
21326 let pane = if split {
21327 workspace.adjacent_pane(window, cx)
21328 } else {
21329 workspace.active_pane().clone()
21330 };
21331
21332 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21333 let editor = buffer
21334 .read(cx)
21335 .file()
21336 .is_none()
21337 .then(|| {
21338 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21339 // so `workspace.open_project_item` will never find them, always opening a new editor.
21340 // Instead, we try to activate the existing editor in the pane first.
21341 let (editor, pane_item_index) =
21342 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21343 let editor = item.downcast::<Editor>()?;
21344 let singleton_buffer =
21345 editor.read(cx).buffer().read(cx).as_singleton()?;
21346 if singleton_buffer == buffer {
21347 Some((editor, i))
21348 } else {
21349 None
21350 }
21351 })?;
21352 pane.update(cx, |pane, cx| {
21353 pane.activate_item(pane_item_index, true, true, window, cx)
21354 });
21355 Some(editor)
21356 })
21357 .flatten()
21358 .unwrap_or_else(|| {
21359 workspace.open_project_item::<Self>(
21360 pane.clone(),
21361 buffer,
21362 true,
21363 true,
21364 window,
21365 cx,
21366 )
21367 });
21368
21369 editor.update(cx, |editor, cx| {
21370 let autoscroll = match scroll_offset {
21371 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21372 None => Autoscroll::newest(),
21373 };
21374 let nav_history = editor.nav_history.take();
21375 editor.change_selections(
21376 SelectionEffects::scroll(autoscroll),
21377 window,
21378 cx,
21379 |s| {
21380 s.select_ranges(ranges);
21381 },
21382 );
21383 editor.nav_history = nav_history;
21384 });
21385 }
21386 })
21387 });
21388 }
21389
21390 // For now, don't allow opening excerpts in buffers that aren't backed by
21391 // regular project files.
21392 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21393 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21394 }
21395
21396 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21397 let snapshot = self.buffer.read(cx).read(cx);
21398 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21399 Some(
21400 ranges
21401 .iter()
21402 .map(move |range| {
21403 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21404 })
21405 .collect(),
21406 )
21407 }
21408
21409 fn selection_replacement_ranges(
21410 &self,
21411 range: Range<OffsetUtf16>,
21412 cx: &mut App,
21413 ) -> Vec<Range<OffsetUtf16>> {
21414 let selections = self
21415 .selections
21416 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21417 let newest_selection = selections
21418 .iter()
21419 .max_by_key(|selection| selection.id)
21420 .unwrap();
21421 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21422 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21423 let snapshot = self.buffer.read(cx).read(cx);
21424 selections
21425 .into_iter()
21426 .map(|mut selection| {
21427 selection.start.0 =
21428 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21429 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21430 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21431 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21432 })
21433 .collect()
21434 }
21435
21436 fn report_editor_event(
21437 &self,
21438 reported_event: ReportEditorEvent,
21439 file_extension: Option<String>,
21440 cx: &App,
21441 ) {
21442 if cfg!(any(test, feature = "test-support")) {
21443 return;
21444 }
21445
21446 let Some(project) = &self.project else { return };
21447
21448 // If None, we are in a file without an extension
21449 let file = self
21450 .buffer
21451 .read(cx)
21452 .as_singleton()
21453 .and_then(|b| b.read(cx).file());
21454 let file_extension = file_extension.or(file
21455 .as_ref()
21456 .and_then(|file| Path::new(file.file_name(cx)).extension())
21457 .and_then(|e| e.to_str())
21458 .map(|a| a.to_string()));
21459
21460 let vim_mode = vim_enabled(cx);
21461
21462 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21463 let copilot_enabled = edit_predictions_provider
21464 == language::language_settings::EditPredictionProvider::Copilot;
21465 let copilot_enabled_for_language = self
21466 .buffer
21467 .read(cx)
21468 .language_settings(cx)
21469 .show_edit_predictions;
21470
21471 let project = project.read(cx);
21472 let event_type = reported_event.event_type();
21473
21474 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21475 telemetry::event!(
21476 event_type,
21477 type = if auto_saved {"autosave"} else {"manual"},
21478 file_extension,
21479 vim_mode,
21480 copilot_enabled,
21481 copilot_enabled_for_language,
21482 edit_predictions_provider,
21483 is_via_ssh = project.is_via_remote_server(),
21484 );
21485 } else {
21486 telemetry::event!(
21487 event_type,
21488 file_extension,
21489 vim_mode,
21490 copilot_enabled,
21491 copilot_enabled_for_language,
21492 edit_predictions_provider,
21493 is_via_ssh = project.is_via_remote_server(),
21494 );
21495 };
21496 }
21497
21498 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21499 /// with each line being an array of {text, highlight} objects.
21500 fn copy_highlight_json(
21501 &mut self,
21502 _: &CopyHighlightJson,
21503 window: &mut Window,
21504 cx: &mut Context<Self>,
21505 ) {
21506 #[derive(Serialize)]
21507 struct Chunk<'a> {
21508 text: String,
21509 highlight: Option<&'a str>,
21510 }
21511
21512 let snapshot = self.buffer.read(cx).snapshot(cx);
21513 let range = self
21514 .selected_text_range(false, window, cx)
21515 .and_then(|selection| {
21516 if selection.range.is_empty() {
21517 None
21518 } else {
21519 Some(
21520 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21521 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21522 )
21523 }
21524 })
21525 .unwrap_or_else(|| 0..snapshot.len());
21526
21527 let chunks = snapshot.chunks(range, true);
21528 let mut lines = Vec::new();
21529 let mut line: VecDeque<Chunk> = VecDeque::new();
21530
21531 let Some(style) = self.style.as_ref() else {
21532 return;
21533 };
21534
21535 for chunk in chunks {
21536 let highlight = chunk
21537 .syntax_highlight_id
21538 .and_then(|id| id.name(&style.syntax));
21539 let mut chunk_lines = chunk.text.split('\n').peekable();
21540 while let Some(text) = chunk_lines.next() {
21541 let mut merged_with_last_token = false;
21542 if let Some(last_token) = line.back_mut()
21543 && last_token.highlight == highlight
21544 {
21545 last_token.text.push_str(text);
21546 merged_with_last_token = true;
21547 }
21548
21549 if !merged_with_last_token {
21550 line.push_back(Chunk {
21551 text: text.into(),
21552 highlight,
21553 });
21554 }
21555
21556 if chunk_lines.peek().is_some() {
21557 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21558 line.pop_front();
21559 }
21560 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21561 line.pop_back();
21562 }
21563
21564 lines.push(mem::take(&mut line));
21565 }
21566 }
21567 }
21568
21569 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21570 return;
21571 };
21572 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21573 }
21574
21575 pub fn open_context_menu(
21576 &mut self,
21577 _: &OpenContextMenu,
21578 window: &mut Window,
21579 cx: &mut Context<Self>,
21580 ) {
21581 self.request_autoscroll(Autoscroll::newest(), cx);
21582 let position = self
21583 .selections
21584 .newest_display(&self.display_snapshot(cx))
21585 .start;
21586 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21587 }
21588
21589 pub fn replay_insert_event(
21590 &mut self,
21591 text: &str,
21592 relative_utf16_range: Option<Range<isize>>,
21593 window: &mut Window,
21594 cx: &mut Context<Self>,
21595 ) {
21596 if !self.input_enabled {
21597 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21598 return;
21599 }
21600 if let Some(relative_utf16_range) = relative_utf16_range {
21601 let selections = self
21602 .selections
21603 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21604 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21605 let new_ranges = selections.into_iter().map(|range| {
21606 let start = OffsetUtf16(
21607 range
21608 .head()
21609 .0
21610 .saturating_add_signed(relative_utf16_range.start),
21611 );
21612 let end = OffsetUtf16(
21613 range
21614 .head()
21615 .0
21616 .saturating_add_signed(relative_utf16_range.end),
21617 );
21618 start..end
21619 });
21620 s.select_ranges(new_ranges);
21621 });
21622 }
21623
21624 self.handle_input(text, window, cx);
21625 }
21626
21627 pub fn is_focused(&self, window: &Window) -> bool {
21628 self.focus_handle.is_focused(window)
21629 }
21630
21631 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21632 cx.emit(EditorEvent::Focused);
21633
21634 if let Some(descendant) = self
21635 .last_focused_descendant
21636 .take()
21637 .and_then(|descendant| descendant.upgrade())
21638 {
21639 window.focus(&descendant);
21640 } else {
21641 if let Some(blame) = self.blame.as_ref() {
21642 blame.update(cx, GitBlame::focus)
21643 }
21644
21645 self.blink_manager.update(cx, BlinkManager::enable);
21646 self.show_cursor_names(window, cx);
21647 self.buffer.update(cx, |buffer, cx| {
21648 buffer.finalize_last_transaction(cx);
21649 if self.leader_id.is_none() {
21650 buffer.set_active_selections(
21651 &self.selections.disjoint_anchors_arc(),
21652 self.selections.line_mode(),
21653 self.cursor_shape,
21654 cx,
21655 );
21656 }
21657 });
21658 }
21659 }
21660
21661 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21662 cx.emit(EditorEvent::FocusedIn)
21663 }
21664
21665 fn handle_focus_out(
21666 &mut self,
21667 event: FocusOutEvent,
21668 _window: &mut Window,
21669 cx: &mut Context<Self>,
21670 ) {
21671 if event.blurred != self.focus_handle {
21672 self.last_focused_descendant = Some(event.blurred);
21673 }
21674 self.selection_drag_state = SelectionDragState::None;
21675 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21676 }
21677
21678 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21679 self.blink_manager.update(cx, BlinkManager::disable);
21680 self.buffer
21681 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21682
21683 if let Some(blame) = self.blame.as_ref() {
21684 blame.update(cx, GitBlame::blur)
21685 }
21686 if !self.hover_state.focused(window, cx) {
21687 hide_hover(self, cx);
21688 }
21689 if !self
21690 .context_menu
21691 .borrow()
21692 .as_ref()
21693 .is_some_and(|context_menu| context_menu.focused(window, cx))
21694 {
21695 self.hide_context_menu(window, cx);
21696 }
21697 self.take_active_edit_prediction(cx);
21698 cx.emit(EditorEvent::Blurred);
21699 cx.notify();
21700 }
21701
21702 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21703 let mut pending: String = window
21704 .pending_input_keystrokes()
21705 .into_iter()
21706 .flatten()
21707 .filter_map(|keystroke| {
21708 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21709 keystroke.key_char.clone()
21710 } else {
21711 None
21712 }
21713 })
21714 .collect();
21715
21716 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21717 pending = "".to_string();
21718 }
21719
21720 let existing_pending = self
21721 .text_highlights::<PendingInput>(cx)
21722 .map(|(_, ranges)| ranges.to_vec());
21723 if existing_pending.is_none() && pending.is_empty() {
21724 return;
21725 }
21726 let transaction =
21727 self.transact(window, cx, |this, window, cx| {
21728 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21729 let edits = selections
21730 .iter()
21731 .map(|selection| (selection.end..selection.end, pending.clone()));
21732 this.edit(edits, cx);
21733 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21734 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21735 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21736 }));
21737 });
21738 if let Some(existing_ranges) = existing_pending {
21739 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21740 this.edit(edits, cx);
21741 }
21742 });
21743
21744 let snapshot = self.snapshot(window, cx);
21745 let ranges = self
21746 .selections
21747 .all::<usize>(&snapshot.display_snapshot)
21748 .into_iter()
21749 .map(|selection| {
21750 snapshot.buffer_snapshot().anchor_after(selection.end)
21751 ..snapshot
21752 .buffer_snapshot()
21753 .anchor_before(selection.end + pending.len())
21754 })
21755 .collect();
21756
21757 if pending.is_empty() {
21758 self.clear_highlights::<PendingInput>(cx);
21759 } else {
21760 self.highlight_text::<PendingInput>(
21761 ranges,
21762 HighlightStyle {
21763 underline: Some(UnderlineStyle {
21764 thickness: px(1.),
21765 color: None,
21766 wavy: false,
21767 }),
21768 ..Default::default()
21769 },
21770 cx,
21771 );
21772 }
21773
21774 self.ime_transaction = self.ime_transaction.or(transaction);
21775 if let Some(transaction) = self.ime_transaction {
21776 self.buffer.update(cx, |buffer, cx| {
21777 buffer.group_until_transaction(transaction, cx);
21778 });
21779 }
21780
21781 if self.text_highlights::<PendingInput>(cx).is_none() {
21782 self.ime_transaction.take();
21783 }
21784 }
21785
21786 pub fn register_action_renderer(
21787 &mut self,
21788 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21789 ) -> Subscription {
21790 let id = self.next_editor_action_id.post_inc();
21791 self.editor_actions
21792 .borrow_mut()
21793 .insert(id, Box::new(listener));
21794
21795 let editor_actions = self.editor_actions.clone();
21796 Subscription::new(move || {
21797 editor_actions.borrow_mut().remove(&id);
21798 })
21799 }
21800
21801 pub fn register_action<A: Action>(
21802 &mut self,
21803 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21804 ) -> Subscription {
21805 let id = self.next_editor_action_id.post_inc();
21806 let listener = Arc::new(listener);
21807 self.editor_actions.borrow_mut().insert(
21808 id,
21809 Box::new(move |_, window, _| {
21810 let listener = listener.clone();
21811 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21812 let action = action.downcast_ref().unwrap();
21813 if phase == DispatchPhase::Bubble {
21814 listener(action, window, cx)
21815 }
21816 })
21817 }),
21818 );
21819
21820 let editor_actions = self.editor_actions.clone();
21821 Subscription::new(move || {
21822 editor_actions.borrow_mut().remove(&id);
21823 })
21824 }
21825
21826 pub fn file_header_size(&self) -> u32 {
21827 FILE_HEADER_HEIGHT
21828 }
21829
21830 pub fn restore(
21831 &mut self,
21832 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21833 window: &mut Window,
21834 cx: &mut Context<Self>,
21835 ) {
21836 let workspace = self.workspace();
21837 let project = self.project();
21838 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21839 let mut tasks = Vec::new();
21840 for (buffer_id, changes) in revert_changes {
21841 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21842 buffer.update(cx, |buffer, cx| {
21843 buffer.edit(
21844 changes
21845 .into_iter()
21846 .map(|(range, text)| (range, text.to_string())),
21847 None,
21848 cx,
21849 );
21850 });
21851
21852 if let Some(project) =
21853 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21854 {
21855 project.update(cx, |project, cx| {
21856 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21857 })
21858 }
21859 }
21860 }
21861 tasks
21862 });
21863 cx.spawn_in(window, async move |_, cx| {
21864 for (buffer, task) in save_tasks {
21865 let result = task.await;
21866 if result.is_err() {
21867 let Some(path) = buffer
21868 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21869 .ok()
21870 else {
21871 continue;
21872 };
21873 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21874 let Some(task) = cx
21875 .update_window_entity(workspace, |workspace, window, cx| {
21876 workspace
21877 .open_path_preview(path, None, false, false, false, window, cx)
21878 })
21879 .ok()
21880 else {
21881 continue;
21882 };
21883 task.await.log_err();
21884 }
21885 }
21886 }
21887 })
21888 .detach();
21889 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21890 selections.refresh()
21891 });
21892 }
21893
21894 pub fn to_pixel_point(
21895 &self,
21896 source: multi_buffer::Anchor,
21897 editor_snapshot: &EditorSnapshot,
21898 window: &mut Window,
21899 ) -> Option<gpui::Point<Pixels>> {
21900 let source_point = source.to_display_point(editor_snapshot);
21901 self.display_to_pixel_point(source_point, editor_snapshot, window)
21902 }
21903
21904 pub fn display_to_pixel_point(
21905 &self,
21906 source: DisplayPoint,
21907 editor_snapshot: &EditorSnapshot,
21908 window: &mut Window,
21909 ) -> Option<gpui::Point<Pixels>> {
21910 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21911 let text_layout_details = self.text_layout_details(window);
21912 let scroll_top = text_layout_details
21913 .scroll_anchor
21914 .scroll_position(editor_snapshot)
21915 .y;
21916
21917 if source.row().as_f64() < scroll_top.floor() {
21918 return None;
21919 }
21920 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21921 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21922 Some(gpui::Point::new(source_x, source_y))
21923 }
21924
21925 pub fn has_visible_completions_menu(&self) -> bool {
21926 !self.edit_prediction_preview_is_active()
21927 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21928 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21929 })
21930 }
21931
21932 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21933 if self.mode.is_minimap() {
21934 return;
21935 }
21936 self.addons
21937 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21938 }
21939
21940 pub fn unregister_addon<T: Addon>(&mut self) {
21941 self.addons.remove(&std::any::TypeId::of::<T>());
21942 }
21943
21944 pub fn addon<T: Addon>(&self) -> Option<&T> {
21945 let type_id = std::any::TypeId::of::<T>();
21946 self.addons
21947 .get(&type_id)
21948 .and_then(|item| item.to_any().downcast_ref::<T>())
21949 }
21950
21951 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21952 let type_id = std::any::TypeId::of::<T>();
21953 self.addons
21954 .get_mut(&type_id)
21955 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21956 }
21957
21958 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21959 let text_layout_details = self.text_layout_details(window);
21960 let style = &text_layout_details.editor_style;
21961 let font_id = window.text_system().resolve_font(&style.text.font());
21962 let font_size = style.text.font_size.to_pixels(window.rem_size());
21963 let line_height = style.text.line_height_in_pixels(window.rem_size());
21964 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21965 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21966
21967 CharacterDimensions {
21968 em_width,
21969 em_advance,
21970 line_height,
21971 }
21972 }
21973
21974 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21975 self.load_diff_task.clone()
21976 }
21977
21978 fn read_metadata_from_db(
21979 &mut self,
21980 item_id: u64,
21981 workspace_id: WorkspaceId,
21982 window: &mut Window,
21983 cx: &mut Context<Editor>,
21984 ) {
21985 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21986 && !self.mode.is_minimap()
21987 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21988 {
21989 let buffer_snapshot = OnceCell::new();
21990
21991 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21992 && !folds.is_empty()
21993 {
21994 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21995 self.fold_ranges(
21996 folds
21997 .into_iter()
21998 .map(|(start, end)| {
21999 snapshot.clip_offset(start, Bias::Left)
22000 ..snapshot.clip_offset(end, Bias::Right)
22001 })
22002 .collect(),
22003 false,
22004 window,
22005 cx,
22006 );
22007 }
22008
22009 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22010 && !selections.is_empty()
22011 {
22012 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22013 // skip adding the initial selection to selection history
22014 self.selection_history.mode = SelectionHistoryMode::Skipping;
22015 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22016 s.select_ranges(selections.into_iter().map(|(start, end)| {
22017 snapshot.clip_offset(start, Bias::Left)
22018 ..snapshot.clip_offset(end, Bias::Right)
22019 }));
22020 });
22021 self.selection_history.mode = SelectionHistoryMode::Normal;
22022 };
22023 }
22024
22025 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22026 }
22027
22028 fn update_lsp_data(
22029 &mut self,
22030 for_buffer: Option<BufferId>,
22031 window: &mut Window,
22032 cx: &mut Context<'_, Self>,
22033 ) {
22034 self.pull_diagnostics(for_buffer, window, cx);
22035 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22036 }
22037
22038 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22039 if self.ignore_lsp_data() {
22040 return;
22041 }
22042 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22043 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22044 }
22045 }
22046
22047 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22048 if !self.registered_buffers.contains_key(&buffer_id)
22049 && let Some(project) = self.project.as_ref()
22050 {
22051 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22052 project.update(cx, |project, cx| {
22053 self.registered_buffers.insert(
22054 buffer_id,
22055 project.register_buffer_with_language_servers(&buffer, cx),
22056 );
22057 });
22058 } else {
22059 self.registered_buffers.remove(&buffer_id);
22060 }
22061 }
22062 }
22063
22064 fn ignore_lsp_data(&self) -> bool {
22065 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22066 // skip any LSP updates for it.
22067 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22068 }
22069}
22070
22071fn edit_for_markdown_paste<'a>(
22072 buffer: &MultiBufferSnapshot,
22073 range: Range<usize>,
22074 to_insert: &'a str,
22075 url: Option<url::Url>,
22076) -> (Range<usize>, Cow<'a, str>) {
22077 if url.is_none() {
22078 return (range, Cow::Borrowed(to_insert));
22079 };
22080
22081 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22082
22083 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22084 Cow::Borrowed(to_insert)
22085 } else {
22086 Cow::Owned(format!("[{old_text}]({to_insert})"))
22087 };
22088 (range, new_text)
22089}
22090
22091fn vim_enabled(cx: &App) -> bool {
22092 vim_mode_setting::VimModeSetting::try_get(cx)
22093 .map(|vim_mode| vim_mode.0)
22094 .unwrap_or(false)
22095}
22096
22097fn process_completion_for_edit(
22098 completion: &Completion,
22099 intent: CompletionIntent,
22100 buffer: &Entity<Buffer>,
22101 cursor_position: &text::Anchor,
22102 cx: &mut Context<Editor>,
22103) -> CompletionEdit {
22104 let buffer = buffer.read(cx);
22105 let buffer_snapshot = buffer.snapshot();
22106 let (snippet, new_text) = if completion.is_snippet() {
22107 let mut snippet_source = completion.new_text.clone();
22108 // Workaround for typescript language server issues so that methods don't expand within
22109 // strings and functions with type expressions. The previous point is used because the query
22110 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22111 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22112 let previous_point = if previous_point.column > 0 {
22113 cursor_position.to_previous_offset(&buffer_snapshot)
22114 } else {
22115 cursor_position.to_offset(&buffer_snapshot)
22116 };
22117 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22118 && scope.prefers_label_for_snippet_in_completion()
22119 && let Some(label) = completion.label()
22120 && matches!(
22121 completion.kind(),
22122 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22123 )
22124 {
22125 snippet_source = label;
22126 }
22127 match Snippet::parse(&snippet_source).log_err() {
22128 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22129 None => (None, completion.new_text.clone()),
22130 }
22131 } else {
22132 (None, completion.new_text.clone())
22133 };
22134
22135 let mut range_to_replace = {
22136 let replace_range = &completion.replace_range;
22137 if let CompletionSource::Lsp {
22138 insert_range: Some(insert_range),
22139 ..
22140 } = &completion.source
22141 {
22142 debug_assert_eq!(
22143 insert_range.start, replace_range.start,
22144 "insert_range and replace_range should start at the same position"
22145 );
22146 debug_assert!(
22147 insert_range
22148 .start
22149 .cmp(cursor_position, &buffer_snapshot)
22150 .is_le(),
22151 "insert_range should start before or at cursor position"
22152 );
22153 debug_assert!(
22154 replace_range
22155 .start
22156 .cmp(cursor_position, &buffer_snapshot)
22157 .is_le(),
22158 "replace_range should start before or at cursor position"
22159 );
22160
22161 let should_replace = match intent {
22162 CompletionIntent::CompleteWithInsert => false,
22163 CompletionIntent::CompleteWithReplace => true,
22164 CompletionIntent::Complete | CompletionIntent::Compose => {
22165 let insert_mode =
22166 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22167 .completions
22168 .lsp_insert_mode;
22169 match insert_mode {
22170 LspInsertMode::Insert => false,
22171 LspInsertMode::Replace => true,
22172 LspInsertMode::ReplaceSubsequence => {
22173 let mut text_to_replace = buffer.chars_for_range(
22174 buffer.anchor_before(replace_range.start)
22175 ..buffer.anchor_after(replace_range.end),
22176 );
22177 let mut current_needle = text_to_replace.next();
22178 for haystack_ch in completion.label.text.chars() {
22179 if let Some(needle_ch) = current_needle
22180 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22181 {
22182 current_needle = text_to_replace.next();
22183 }
22184 }
22185 current_needle.is_none()
22186 }
22187 LspInsertMode::ReplaceSuffix => {
22188 if replace_range
22189 .end
22190 .cmp(cursor_position, &buffer_snapshot)
22191 .is_gt()
22192 {
22193 let range_after_cursor = *cursor_position..replace_range.end;
22194 let text_after_cursor = buffer
22195 .text_for_range(
22196 buffer.anchor_before(range_after_cursor.start)
22197 ..buffer.anchor_after(range_after_cursor.end),
22198 )
22199 .collect::<String>()
22200 .to_ascii_lowercase();
22201 completion
22202 .label
22203 .text
22204 .to_ascii_lowercase()
22205 .ends_with(&text_after_cursor)
22206 } else {
22207 true
22208 }
22209 }
22210 }
22211 }
22212 };
22213
22214 if should_replace {
22215 replace_range.clone()
22216 } else {
22217 insert_range.clone()
22218 }
22219 } else {
22220 replace_range.clone()
22221 }
22222 };
22223
22224 if range_to_replace
22225 .end
22226 .cmp(cursor_position, &buffer_snapshot)
22227 .is_lt()
22228 {
22229 range_to_replace.end = *cursor_position;
22230 }
22231
22232 CompletionEdit {
22233 new_text,
22234 replace_range: range_to_replace.to_offset(buffer),
22235 snippet,
22236 }
22237}
22238
22239struct CompletionEdit {
22240 new_text: String,
22241 replace_range: Range<usize>,
22242 snippet: Option<Snippet>,
22243}
22244
22245fn insert_extra_newline_brackets(
22246 buffer: &MultiBufferSnapshot,
22247 range: Range<usize>,
22248 language: &language::LanguageScope,
22249) -> bool {
22250 let leading_whitespace_len = buffer
22251 .reversed_chars_at(range.start)
22252 .take_while(|c| c.is_whitespace() && *c != '\n')
22253 .map(|c| c.len_utf8())
22254 .sum::<usize>();
22255 let trailing_whitespace_len = buffer
22256 .chars_at(range.end)
22257 .take_while(|c| c.is_whitespace() && *c != '\n')
22258 .map(|c| c.len_utf8())
22259 .sum::<usize>();
22260 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22261
22262 language.brackets().any(|(pair, enabled)| {
22263 let pair_start = pair.start.trim_end();
22264 let pair_end = pair.end.trim_start();
22265
22266 enabled
22267 && pair.newline
22268 && buffer.contains_str_at(range.end, pair_end)
22269 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22270 })
22271}
22272
22273fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22274 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22275 [(buffer, range, _)] => (*buffer, range.clone()),
22276 _ => return false,
22277 };
22278 let pair = {
22279 let mut result: Option<BracketMatch> = None;
22280
22281 for pair in buffer
22282 .all_bracket_ranges(range.clone())
22283 .filter(move |pair| {
22284 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22285 })
22286 {
22287 let len = pair.close_range.end - pair.open_range.start;
22288
22289 if let Some(existing) = &result {
22290 let existing_len = existing.close_range.end - existing.open_range.start;
22291 if len > existing_len {
22292 continue;
22293 }
22294 }
22295
22296 result = Some(pair);
22297 }
22298
22299 result
22300 };
22301 let Some(pair) = pair else {
22302 return false;
22303 };
22304 pair.newline_only
22305 && buffer
22306 .chars_for_range(pair.open_range.end..range.start)
22307 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22308 .all(|c| c.is_whitespace() && c != '\n')
22309}
22310
22311fn update_uncommitted_diff_for_buffer(
22312 editor: Entity<Editor>,
22313 project: &Entity<Project>,
22314 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22315 buffer: Entity<MultiBuffer>,
22316 cx: &mut App,
22317) -> Task<()> {
22318 let mut tasks = Vec::new();
22319 project.update(cx, |project, cx| {
22320 for buffer in buffers {
22321 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22322 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22323 }
22324 }
22325 });
22326 cx.spawn(async move |cx| {
22327 let diffs = future::join_all(tasks).await;
22328 if editor
22329 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22330 .unwrap_or(false)
22331 {
22332 return;
22333 }
22334
22335 buffer
22336 .update(cx, |buffer, cx| {
22337 for diff in diffs.into_iter().flatten() {
22338 buffer.add_diff(diff, cx);
22339 }
22340 })
22341 .ok();
22342 })
22343}
22344
22345fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22346 let tab_size = tab_size.get() as usize;
22347 let mut width = offset;
22348
22349 for ch in text.chars() {
22350 width += if ch == '\t' {
22351 tab_size - (width % tab_size)
22352 } else {
22353 1
22354 };
22355 }
22356
22357 width - offset
22358}
22359
22360#[cfg(test)]
22361mod tests {
22362 use super::*;
22363
22364 #[test]
22365 fn test_string_size_with_expanded_tabs() {
22366 let nz = |val| NonZeroU32::new(val).unwrap();
22367 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22368 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22369 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22370 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22371 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22372 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22373 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22374 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22375 }
22376}
22377
22378/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22379struct WordBreakingTokenizer<'a> {
22380 input: &'a str,
22381}
22382
22383impl<'a> WordBreakingTokenizer<'a> {
22384 fn new(input: &'a str) -> Self {
22385 Self { input }
22386 }
22387}
22388
22389fn is_char_ideographic(ch: char) -> bool {
22390 use unicode_script::Script::*;
22391 use unicode_script::UnicodeScript;
22392 matches!(ch.script(), Han | Tangut | Yi)
22393}
22394
22395fn is_grapheme_ideographic(text: &str) -> bool {
22396 text.chars().any(is_char_ideographic)
22397}
22398
22399fn is_grapheme_whitespace(text: &str) -> bool {
22400 text.chars().any(|x| x.is_whitespace())
22401}
22402
22403fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22404 text.chars()
22405 .next()
22406 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22407}
22408
22409#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22410enum WordBreakToken<'a> {
22411 Word { token: &'a str, grapheme_len: usize },
22412 InlineWhitespace { token: &'a str, grapheme_len: usize },
22413 Newline,
22414}
22415
22416impl<'a> Iterator for WordBreakingTokenizer<'a> {
22417 /// Yields a span, the count of graphemes in the token, and whether it was
22418 /// whitespace. Note that it also breaks at word boundaries.
22419 type Item = WordBreakToken<'a>;
22420
22421 fn next(&mut self) -> Option<Self::Item> {
22422 use unicode_segmentation::UnicodeSegmentation;
22423 if self.input.is_empty() {
22424 return None;
22425 }
22426
22427 let mut iter = self.input.graphemes(true).peekable();
22428 let mut offset = 0;
22429 let mut grapheme_len = 0;
22430 if let Some(first_grapheme) = iter.next() {
22431 let is_newline = first_grapheme == "\n";
22432 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22433 offset += first_grapheme.len();
22434 grapheme_len += 1;
22435 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22436 if let Some(grapheme) = iter.peek().copied()
22437 && should_stay_with_preceding_ideograph(grapheme)
22438 {
22439 offset += grapheme.len();
22440 grapheme_len += 1;
22441 }
22442 } else {
22443 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22444 let mut next_word_bound = words.peek().copied();
22445 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22446 next_word_bound = words.next();
22447 }
22448 while let Some(grapheme) = iter.peek().copied() {
22449 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22450 break;
22451 };
22452 if is_grapheme_whitespace(grapheme) != is_whitespace
22453 || (grapheme == "\n") != is_newline
22454 {
22455 break;
22456 };
22457 offset += grapheme.len();
22458 grapheme_len += 1;
22459 iter.next();
22460 }
22461 }
22462 let token = &self.input[..offset];
22463 self.input = &self.input[offset..];
22464 if token == "\n" {
22465 Some(WordBreakToken::Newline)
22466 } else if is_whitespace {
22467 Some(WordBreakToken::InlineWhitespace {
22468 token,
22469 grapheme_len,
22470 })
22471 } else {
22472 Some(WordBreakToken::Word {
22473 token,
22474 grapheme_len,
22475 })
22476 }
22477 } else {
22478 None
22479 }
22480 }
22481}
22482
22483#[test]
22484fn test_word_breaking_tokenizer() {
22485 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22486 ("", &[]),
22487 (" ", &[whitespace(" ", 2)]),
22488 ("Ʒ", &[word("Ʒ", 1)]),
22489 ("Ǽ", &[word("Ǽ", 1)]),
22490 ("⋑", &[word("⋑", 1)]),
22491 ("⋑⋑", &[word("⋑⋑", 2)]),
22492 (
22493 "原理,进而",
22494 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22495 ),
22496 (
22497 "hello world",
22498 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22499 ),
22500 (
22501 "hello, world",
22502 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22503 ),
22504 (
22505 " hello world",
22506 &[
22507 whitespace(" ", 2),
22508 word("hello", 5),
22509 whitespace(" ", 1),
22510 word("world", 5),
22511 ],
22512 ),
22513 (
22514 "这是什么 \n 钢笔",
22515 &[
22516 word("这", 1),
22517 word("是", 1),
22518 word("什", 1),
22519 word("么", 1),
22520 whitespace(" ", 1),
22521 newline(),
22522 whitespace(" ", 1),
22523 word("钢", 1),
22524 word("笔", 1),
22525 ],
22526 ),
22527 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22528 ];
22529
22530 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22531 WordBreakToken::Word {
22532 token,
22533 grapheme_len,
22534 }
22535 }
22536
22537 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22538 WordBreakToken::InlineWhitespace {
22539 token,
22540 grapheme_len,
22541 }
22542 }
22543
22544 fn newline() -> WordBreakToken<'static> {
22545 WordBreakToken::Newline
22546 }
22547
22548 for (input, result) in tests {
22549 assert_eq!(
22550 WordBreakingTokenizer::new(input)
22551 .collect::<Vec<_>>()
22552 .as_slice(),
22553 *result,
22554 );
22555 }
22556}
22557
22558fn wrap_with_prefix(
22559 first_line_prefix: String,
22560 subsequent_lines_prefix: String,
22561 unwrapped_text: String,
22562 wrap_column: usize,
22563 tab_size: NonZeroU32,
22564 preserve_existing_whitespace: bool,
22565) -> String {
22566 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22567 let subsequent_lines_prefix_len =
22568 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22569 let mut wrapped_text = String::new();
22570 let mut current_line = first_line_prefix;
22571 let mut is_first_line = true;
22572
22573 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22574 let mut current_line_len = first_line_prefix_len;
22575 let mut in_whitespace = false;
22576 for token in tokenizer {
22577 let have_preceding_whitespace = in_whitespace;
22578 match token {
22579 WordBreakToken::Word {
22580 token,
22581 grapheme_len,
22582 } => {
22583 in_whitespace = false;
22584 let current_prefix_len = if is_first_line {
22585 first_line_prefix_len
22586 } else {
22587 subsequent_lines_prefix_len
22588 };
22589 if current_line_len + grapheme_len > wrap_column
22590 && current_line_len != current_prefix_len
22591 {
22592 wrapped_text.push_str(current_line.trim_end());
22593 wrapped_text.push('\n');
22594 is_first_line = false;
22595 current_line = subsequent_lines_prefix.clone();
22596 current_line_len = subsequent_lines_prefix_len;
22597 }
22598 current_line.push_str(token);
22599 current_line_len += grapheme_len;
22600 }
22601 WordBreakToken::InlineWhitespace {
22602 mut token,
22603 mut grapheme_len,
22604 } => {
22605 in_whitespace = true;
22606 if have_preceding_whitespace && !preserve_existing_whitespace {
22607 continue;
22608 }
22609 if !preserve_existing_whitespace {
22610 // Keep a single whitespace grapheme as-is
22611 if let Some(first) =
22612 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22613 {
22614 token = first;
22615 } else {
22616 token = " ";
22617 }
22618 grapheme_len = 1;
22619 }
22620 let current_prefix_len = if is_first_line {
22621 first_line_prefix_len
22622 } else {
22623 subsequent_lines_prefix_len
22624 };
22625 if current_line_len + grapheme_len > wrap_column {
22626 wrapped_text.push_str(current_line.trim_end());
22627 wrapped_text.push('\n');
22628 is_first_line = false;
22629 current_line = subsequent_lines_prefix.clone();
22630 current_line_len = subsequent_lines_prefix_len;
22631 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22632 current_line.push_str(token);
22633 current_line_len += grapheme_len;
22634 }
22635 }
22636 WordBreakToken::Newline => {
22637 in_whitespace = true;
22638 let current_prefix_len = if is_first_line {
22639 first_line_prefix_len
22640 } else {
22641 subsequent_lines_prefix_len
22642 };
22643 if preserve_existing_whitespace {
22644 wrapped_text.push_str(current_line.trim_end());
22645 wrapped_text.push('\n');
22646 is_first_line = false;
22647 current_line = subsequent_lines_prefix.clone();
22648 current_line_len = subsequent_lines_prefix_len;
22649 } else if have_preceding_whitespace {
22650 continue;
22651 } else if current_line_len + 1 > wrap_column
22652 && current_line_len != current_prefix_len
22653 {
22654 wrapped_text.push_str(current_line.trim_end());
22655 wrapped_text.push('\n');
22656 is_first_line = false;
22657 current_line = subsequent_lines_prefix.clone();
22658 current_line_len = subsequent_lines_prefix_len;
22659 } else if current_line_len != current_prefix_len {
22660 current_line.push(' ');
22661 current_line_len += 1;
22662 }
22663 }
22664 }
22665 }
22666
22667 if !current_line.is_empty() {
22668 wrapped_text.push_str(¤t_line);
22669 }
22670 wrapped_text
22671}
22672
22673#[test]
22674fn test_wrap_with_prefix() {
22675 assert_eq!(
22676 wrap_with_prefix(
22677 "# ".to_string(),
22678 "# ".to_string(),
22679 "abcdefg".to_string(),
22680 4,
22681 NonZeroU32::new(4).unwrap(),
22682 false,
22683 ),
22684 "# abcdefg"
22685 );
22686 assert_eq!(
22687 wrap_with_prefix(
22688 "".to_string(),
22689 "".to_string(),
22690 "\thello world".to_string(),
22691 8,
22692 NonZeroU32::new(4).unwrap(),
22693 false,
22694 ),
22695 "hello\nworld"
22696 );
22697 assert_eq!(
22698 wrap_with_prefix(
22699 "// ".to_string(),
22700 "// ".to_string(),
22701 "xx \nyy zz aa bb cc".to_string(),
22702 12,
22703 NonZeroU32::new(4).unwrap(),
22704 false,
22705 ),
22706 "// xx yy zz\n// aa bb cc"
22707 );
22708 assert_eq!(
22709 wrap_with_prefix(
22710 String::new(),
22711 String::new(),
22712 "这是什么 \n 钢笔".to_string(),
22713 3,
22714 NonZeroU32::new(4).unwrap(),
22715 false,
22716 ),
22717 "这是什\n么 钢\n笔"
22718 );
22719 assert_eq!(
22720 wrap_with_prefix(
22721 String::new(),
22722 String::new(),
22723 format!("foo{}bar", '\u{2009}'), // thin space
22724 80,
22725 NonZeroU32::new(4).unwrap(),
22726 false,
22727 ),
22728 format!("foo{}bar", '\u{2009}')
22729 );
22730}
22731
22732pub trait CollaborationHub {
22733 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22734 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22735 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22736}
22737
22738impl CollaborationHub for Entity<Project> {
22739 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22740 self.read(cx).collaborators()
22741 }
22742
22743 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22744 self.read(cx).user_store().read(cx).participant_indices()
22745 }
22746
22747 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22748 let this = self.read(cx);
22749 let user_ids = this.collaborators().values().map(|c| c.user_id);
22750 this.user_store().read(cx).participant_names(user_ids, cx)
22751 }
22752}
22753
22754pub trait SemanticsProvider {
22755 fn hover(
22756 &self,
22757 buffer: &Entity<Buffer>,
22758 position: text::Anchor,
22759 cx: &mut App,
22760 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22761
22762 fn inline_values(
22763 &self,
22764 buffer_handle: Entity<Buffer>,
22765 range: Range<text::Anchor>,
22766 cx: &mut App,
22767 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22768
22769 fn applicable_inlay_chunks(
22770 &self,
22771 buffer: &Entity<Buffer>,
22772 ranges: &[Range<text::Anchor>],
22773 cx: &mut App,
22774 ) -> Vec<Range<BufferRow>>;
22775
22776 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22777
22778 fn inlay_hints(
22779 &self,
22780 invalidate: InvalidationStrategy,
22781 buffer: Entity<Buffer>,
22782 ranges: Vec<Range<text::Anchor>>,
22783 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22784 cx: &mut App,
22785 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22786
22787 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22788
22789 fn document_highlights(
22790 &self,
22791 buffer: &Entity<Buffer>,
22792 position: text::Anchor,
22793 cx: &mut App,
22794 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22795
22796 fn definitions(
22797 &self,
22798 buffer: &Entity<Buffer>,
22799 position: text::Anchor,
22800 kind: GotoDefinitionKind,
22801 cx: &mut App,
22802 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22803
22804 fn range_for_rename(
22805 &self,
22806 buffer: &Entity<Buffer>,
22807 position: text::Anchor,
22808 cx: &mut App,
22809 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22810
22811 fn perform_rename(
22812 &self,
22813 buffer: &Entity<Buffer>,
22814 position: text::Anchor,
22815 new_name: String,
22816 cx: &mut App,
22817 ) -> Option<Task<Result<ProjectTransaction>>>;
22818}
22819
22820pub trait CompletionProvider {
22821 fn completions(
22822 &self,
22823 excerpt_id: ExcerptId,
22824 buffer: &Entity<Buffer>,
22825 buffer_position: text::Anchor,
22826 trigger: CompletionContext,
22827 window: &mut Window,
22828 cx: &mut Context<Editor>,
22829 ) -> Task<Result<Vec<CompletionResponse>>>;
22830
22831 fn resolve_completions(
22832 &self,
22833 _buffer: Entity<Buffer>,
22834 _completion_indices: Vec<usize>,
22835 _completions: Rc<RefCell<Box<[Completion]>>>,
22836 _cx: &mut Context<Editor>,
22837 ) -> Task<Result<bool>> {
22838 Task::ready(Ok(false))
22839 }
22840
22841 fn apply_additional_edits_for_completion(
22842 &self,
22843 _buffer: Entity<Buffer>,
22844 _completions: Rc<RefCell<Box<[Completion]>>>,
22845 _completion_index: usize,
22846 _push_to_history: bool,
22847 _cx: &mut Context<Editor>,
22848 ) -> Task<Result<Option<language::Transaction>>> {
22849 Task::ready(Ok(None))
22850 }
22851
22852 fn is_completion_trigger(
22853 &self,
22854 buffer: &Entity<Buffer>,
22855 position: language::Anchor,
22856 text: &str,
22857 trigger_in_words: bool,
22858 menu_is_open: bool,
22859 cx: &mut Context<Editor>,
22860 ) -> bool;
22861
22862 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22863
22864 fn sort_completions(&self) -> bool {
22865 true
22866 }
22867
22868 fn filter_completions(&self) -> bool {
22869 true
22870 }
22871}
22872
22873pub trait CodeActionProvider {
22874 fn id(&self) -> Arc<str>;
22875
22876 fn code_actions(
22877 &self,
22878 buffer: &Entity<Buffer>,
22879 range: Range<text::Anchor>,
22880 window: &mut Window,
22881 cx: &mut App,
22882 ) -> Task<Result<Vec<CodeAction>>>;
22883
22884 fn apply_code_action(
22885 &self,
22886 buffer_handle: Entity<Buffer>,
22887 action: CodeAction,
22888 excerpt_id: ExcerptId,
22889 push_to_history: bool,
22890 window: &mut Window,
22891 cx: &mut App,
22892 ) -> Task<Result<ProjectTransaction>>;
22893}
22894
22895impl CodeActionProvider for Entity<Project> {
22896 fn id(&self) -> Arc<str> {
22897 "project".into()
22898 }
22899
22900 fn code_actions(
22901 &self,
22902 buffer: &Entity<Buffer>,
22903 range: Range<text::Anchor>,
22904 _window: &mut Window,
22905 cx: &mut App,
22906 ) -> Task<Result<Vec<CodeAction>>> {
22907 self.update(cx, |project, cx| {
22908 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22909 let code_actions = project.code_actions(buffer, range, None, cx);
22910 cx.background_spawn(async move {
22911 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22912 Ok(code_lens_actions
22913 .context("code lens fetch")?
22914 .into_iter()
22915 .flatten()
22916 .chain(
22917 code_actions
22918 .context("code action fetch")?
22919 .into_iter()
22920 .flatten(),
22921 )
22922 .collect())
22923 })
22924 })
22925 }
22926
22927 fn apply_code_action(
22928 &self,
22929 buffer_handle: Entity<Buffer>,
22930 action: CodeAction,
22931 _excerpt_id: ExcerptId,
22932 push_to_history: bool,
22933 _window: &mut Window,
22934 cx: &mut App,
22935 ) -> Task<Result<ProjectTransaction>> {
22936 self.update(cx, |project, cx| {
22937 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22938 })
22939 }
22940}
22941
22942fn snippet_completions(
22943 project: &Project,
22944 buffer: &Entity<Buffer>,
22945 buffer_position: text::Anchor,
22946 cx: &mut App,
22947) -> Task<Result<CompletionResponse>> {
22948 let languages = buffer.read(cx).languages_at(buffer_position);
22949 let snippet_store = project.snippets().read(cx);
22950
22951 let scopes: Vec<_> = languages
22952 .iter()
22953 .filter_map(|language| {
22954 let language_name = language.lsp_id();
22955 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22956
22957 if snippets.is_empty() {
22958 None
22959 } else {
22960 Some((language.default_scope(), snippets))
22961 }
22962 })
22963 .collect();
22964
22965 if scopes.is_empty() {
22966 return Task::ready(Ok(CompletionResponse {
22967 completions: vec![],
22968 display_options: CompletionDisplayOptions::default(),
22969 is_incomplete: false,
22970 }));
22971 }
22972
22973 let snapshot = buffer.read(cx).text_snapshot();
22974 let executor = cx.background_executor().clone();
22975
22976 cx.background_spawn(async move {
22977 let mut is_incomplete = false;
22978 let mut completions: Vec<Completion> = Vec::new();
22979 for (scope, snippets) in scopes.into_iter() {
22980 let classifier =
22981 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22982
22983 const MAX_WORD_PREFIX_LEN: usize = 128;
22984 let last_word: String = snapshot
22985 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22986 .take(MAX_WORD_PREFIX_LEN)
22987 .take_while(|c| classifier.is_word(*c))
22988 .collect::<String>()
22989 .chars()
22990 .rev()
22991 .collect();
22992
22993 if last_word.is_empty() {
22994 return Ok(CompletionResponse {
22995 completions: vec![],
22996 display_options: CompletionDisplayOptions::default(),
22997 is_incomplete: true,
22998 });
22999 }
23000
23001 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23002 let to_lsp = |point: &text::Anchor| {
23003 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23004 point_to_lsp(end)
23005 };
23006 let lsp_end = to_lsp(&buffer_position);
23007
23008 let candidates = snippets
23009 .iter()
23010 .enumerate()
23011 .flat_map(|(ix, snippet)| {
23012 snippet
23013 .prefix
23014 .iter()
23015 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23016 })
23017 .collect::<Vec<StringMatchCandidate>>();
23018
23019 const MAX_RESULTS: usize = 100;
23020 let mut matches = fuzzy::match_strings(
23021 &candidates,
23022 &last_word,
23023 last_word.chars().any(|c| c.is_uppercase()),
23024 true,
23025 MAX_RESULTS,
23026 &Default::default(),
23027 executor.clone(),
23028 )
23029 .await;
23030
23031 if matches.len() >= MAX_RESULTS {
23032 is_incomplete = true;
23033 }
23034
23035 // Remove all candidates where the query's start does not match the start of any word in the candidate
23036 if let Some(query_start) = last_word.chars().next() {
23037 matches.retain(|string_match| {
23038 split_words(&string_match.string).any(|word| {
23039 // Check that the first codepoint of the word as lowercase matches the first
23040 // codepoint of the query as lowercase
23041 word.chars()
23042 .flat_map(|codepoint| codepoint.to_lowercase())
23043 .zip(query_start.to_lowercase())
23044 .all(|(word_cp, query_cp)| word_cp == query_cp)
23045 })
23046 });
23047 }
23048
23049 let matched_strings = matches
23050 .into_iter()
23051 .map(|m| m.string)
23052 .collect::<HashSet<_>>();
23053
23054 completions.extend(snippets.iter().filter_map(|snippet| {
23055 let matching_prefix = snippet
23056 .prefix
23057 .iter()
23058 .find(|prefix| matched_strings.contains(*prefix))?;
23059 let start = as_offset - last_word.len();
23060 let start = snapshot.anchor_before(start);
23061 let range = start..buffer_position;
23062 let lsp_start = to_lsp(&start);
23063 let lsp_range = lsp::Range {
23064 start: lsp_start,
23065 end: lsp_end,
23066 };
23067 Some(Completion {
23068 replace_range: range,
23069 new_text: snippet.body.clone(),
23070 source: CompletionSource::Lsp {
23071 insert_range: None,
23072 server_id: LanguageServerId(usize::MAX),
23073 resolved: true,
23074 lsp_completion: Box::new(lsp::CompletionItem {
23075 label: snippet.prefix.first().unwrap().clone(),
23076 kind: Some(CompletionItemKind::SNIPPET),
23077 label_details: snippet.description.as_ref().map(|description| {
23078 lsp::CompletionItemLabelDetails {
23079 detail: Some(description.clone()),
23080 description: None,
23081 }
23082 }),
23083 insert_text_format: Some(InsertTextFormat::SNIPPET),
23084 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23085 lsp::InsertReplaceEdit {
23086 new_text: snippet.body.clone(),
23087 insert: lsp_range,
23088 replace: lsp_range,
23089 },
23090 )),
23091 filter_text: Some(snippet.body.clone()),
23092 sort_text: Some(char::MAX.to_string()),
23093 ..lsp::CompletionItem::default()
23094 }),
23095 lsp_defaults: None,
23096 },
23097 label: CodeLabel::plain(matching_prefix.clone(), None),
23098 icon_path: None,
23099 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23100 single_line: snippet.name.clone().into(),
23101 plain_text: snippet
23102 .description
23103 .clone()
23104 .map(|description| description.into()),
23105 }),
23106 insert_text_mode: None,
23107 confirm: None,
23108 })
23109 }))
23110 }
23111
23112 Ok(CompletionResponse {
23113 completions,
23114 display_options: CompletionDisplayOptions::default(),
23115 is_incomplete,
23116 })
23117 })
23118}
23119
23120impl CompletionProvider for Entity<Project> {
23121 fn completions(
23122 &self,
23123 _excerpt_id: ExcerptId,
23124 buffer: &Entity<Buffer>,
23125 buffer_position: text::Anchor,
23126 options: CompletionContext,
23127 _window: &mut Window,
23128 cx: &mut Context<Editor>,
23129 ) -> Task<Result<Vec<CompletionResponse>>> {
23130 self.update(cx, |project, cx| {
23131 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23132 let project_completions = project.completions(buffer, buffer_position, options, cx);
23133 cx.background_spawn(async move {
23134 let mut responses = project_completions.await?;
23135 let snippets = snippets.await?;
23136 if !snippets.completions.is_empty() {
23137 responses.push(snippets);
23138 }
23139 Ok(responses)
23140 })
23141 })
23142 }
23143
23144 fn resolve_completions(
23145 &self,
23146 buffer: Entity<Buffer>,
23147 completion_indices: Vec<usize>,
23148 completions: Rc<RefCell<Box<[Completion]>>>,
23149 cx: &mut Context<Editor>,
23150 ) -> Task<Result<bool>> {
23151 self.update(cx, |project, cx| {
23152 project.lsp_store().update(cx, |lsp_store, cx| {
23153 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23154 })
23155 })
23156 }
23157
23158 fn apply_additional_edits_for_completion(
23159 &self,
23160 buffer: Entity<Buffer>,
23161 completions: Rc<RefCell<Box<[Completion]>>>,
23162 completion_index: usize,
23163 push_to_history: bool,
23164 cx: &mut Context<Editor>,
23165 ) -> Task<Result<Option<language::Transaction>>> {
23166 self.update(cx, |project, cx| {
23167 project.lsp_store().update(cx, |lsp_store, cx| {
23168 lsp_store.apply_additional_edits_for_completion(
23169 buffer,
23170 completions,
23171 completion_index,
23172 push_to_history,
23173 cx,
23174 )
23175 })
23176 })
23177 }
23178
23179 fn is_completion_trigger(
23180 &self,
23181 buffer: &Entity<Buffer>,
23182 position: language::Anchor,
23183 text: &str,
23184 trigger_in_words: bool,
23185 menu_is_open: bool,
23186 cx: &mut Context<Editor>,
23187 ) -> bool {
23188 let mut chars = text.chars();
23189 let char = if let Some(char) = chars.next() {
23190 char
23191 } else {
23192 return false;
23193 };
23194 if chars.next().is_some() {
23195 return false;
23196 }
23197
23198 let buffer = buffer.read(cx);
23199 let snapshot = buffer.snapshot();
23200 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23201 return false;
23202 }
23203 let classifier = snapshot
23204 .char_classifier_at(position)
23205 .scope_context(Some(CharScopeContext::Completion));
23206 if trigger_in_words && classifier.is_word(char) {
23207 return true;
23208 }
23209
23210 buffer.completion_triggers().contains(text)
23211 }
23212}
23213
23214impl SemanticsProvider for Entity<Project> {
23215 fn hover(
23216 &self,
23217 buffer: &Entity<Buffer>,
23218 position: text::Anchor,
23219 cx: &mut App,
23220 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23221 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23222 }
23223
23224 fn document_highlights(
23225 &self,
23226 buffer: &Entity<Buffer>,
23227 position: text::Anchor,
23228 cx: &mut App,
23229 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23230 Some(self.update(cx, |project, cx| {
23231 project.document_highlights(buffer, position, cx)
23232 }))
23233 }
23234
23235 fn definitions(
23236 &self,
23237 buffer: &Entity<Buffer>,
23238 position: text::Anchor,
23239 kind: GotoDefinitionKind,
23240 cx: &mut App,
23241 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23242 Some(self.update(cx, |project, cx| match kind {
23243 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23244 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23245 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23246 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23247 }))
23248 }
23249
23250 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23251 self.update(cx, |project, cx| {
23252 if project
23253 .active_debug_session(cx)
23254 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23255 {
23256 return true;
23257 }
23258
23259 buffer.update(cx, |buffer, cx| {
23260 project.any_language_server_supports_inlay_hints(buffer, cx)
23261 })
23262 })
23263 }
23264
23265 fn inline_values(
23266 &self,
23267 buffer_handle: Entity<Buffer>,
23268 range: Range<text::Anchor>,
23269 cx: &mut App,
23270 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23271 self.update(cx, |project, cx| {
23272 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23273
23274 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23275 })
23276 }
23277
23278 fn applicable_inlay_chunks(
23279 &self,
23280 buffer: &Entity<Buffer>,
23281 ranges: &[Range<text::Anchor>],
23282 cx: &mut App,
23283 ) -> Vec<Range<BufferRow>> {
23284 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23285 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23286 })
23287 }
23288
23289 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23290 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23291 lsp_store.invalidate_inlay_hints(for_buffers)
23292 });
23293 }
23294
23295 fn inlay_hints(
23296 &self,
23297 invalidate: InvalidationStrategy,
23298 buffer: Entity<Buffer>,
23299 ranges: Vec<Range<text::Anchor>>,
23300 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23301 cx: &mut App,
23302 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23303 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23304 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23305 }))
23306 }
23307
23308 fn range_for_rename(
23309 &self,
23310 buffer: &Entity<Buffer>,
23311 position: text::Anchor,
23312 cx: &mut App,
23313 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23314 Some(self.update(cx, |project, cx| {
23315 let buffer = buffer.clone();
23316 let task = project.prepare_rename(buffer.clone(), position, cx);
23317 cx.spawn(async move |_, cx| {
23318 Ok(match task.await? {
23319 PrepareRenameResponse::Success(range) => Some(range),
23320 PrepareRenameResponse::InvalidPosition => None,
23321 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23322 // Fallback on using TreeSitter info to determine identifier range
23323 buffer.read_with(cx, |buffer, _| {
23324 let snapshot = buffer.snapshot();
23325 let (range, kind) = snapshot.surrounding_word(position, None);
23326 if kind != Some(CharKind::Word) {
23327 return None;
23328 }
23329 Some(
23330 snapshot.anchor_before(range.start)
23331 ..snapshot.anchor_after(range.end),
23332 )
23333 })?
23334 }
23335 })
23336 })
23337 }))
23338 }
23339
23340 fn perform_rename(
23341 &self,
23342 buffer: &Entity<Buffer>,
23343 position: text::Anchor,
23344 new_name: String,
23345 cx: &mut App,
23346 ) -> Option<Task<Result<ProjectTransaction>>> {
23347 Some(self.update(cx, |project, cx| {
23348 project.perform_rename(buffer.clone(), position, new_name, cx)
23349 }))
23350 }
23351}
23352
23353fn consume_contiguous_rows(
23354 contiguous_row_selections: &mut Vec<Selection<Point>>,
23355 selection: &Selection<Point>,
23356 display_map: &DisplaySnapshot,
23357 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23358) -> (MultiBufferRow, MultiBufferRow) {
23359 contiguous_row_selections.push(selection.clone());
23360 let start_row = starting_row(selection, display_map);
23361 let mut end_row = ending_row(selection, display_map);
23362
23363 while let Some(next_selection) = selections.peek() {
23364 if next_selection.start.row <= end_row.0 {
23365 end_row = ending_row(next_selection, display_map);
23366 contiguous_row_selections.push(selections.next().unwrap().clone());
23367 } else {
23368 break;
23369 }
23370 }
23371 (start_row, end_row)
23372}
23373
23374fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23375 if selection.start.column > 0 {
23376 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23377 } else {
23378 MultiBufferRow(selection.start.row)
23379 }
23380}
23381
23382fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23383 if next_selection.end.column > 0 || next_selection.is_empty() {
23384 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23385 } else {
23386 MultiBufferRow(next_selection.end.row)
23387 }
23388}
23389
23390impl EditorSnapshot {
23391 pub fn remote_selections_in_range<'a>(
23392 &'a self,
23393 range: &'a Range<Anchor>,
23394 collaboration_hub: &dyn CollaborationHub,
23395 cx: &'a App,
23396 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23397 let participant_names = collaboration_hub.user_names(cx);
23398 let participant_indices = collaboration_hub.user_participant_indices(cx);
23399 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23400 let collaborators_by_replica_id = collaborators_by_peer_id
23401 .values()
23402 .map(|collaborator| (collaborator.replica_id, collaborator))
23403 .collect::<HashMap<_, _>>();
23404 self.buffer_snapshot()
23405 .selections_in_range(range, false)
23406 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23407 if replica_id == ReplicaId::AGENT {
23408 Some(RemoteSelection {
23409 replica_id,
23410 selection,
23411 cursor_shape,
23412 line_mode,
23413 collaborator_id: CollaboratorId::Agent,
23414 user_name: Some("Agent".into()),
23415 color: cx.theme().players().agent(),
23416 })
23417 } else {
23418 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23419 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23420 let user_name = participant_names.get(&collaborator.user_id).cloned();
23421 Some(RemoteSelection {
23422 replica_id,
23423 selection,
23424 cursor_shape,
23425 line_mode,
23426 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23427 user_name,
23428 color: if let Some(index) = participant_index {
23429 cx.theme().players().color_for_participant(index.0)
23430 } else {
23431 cx.theme().players().absent()
23432 },
23433 })
23434 }
23435 })
23436 }
23437
23438 pub fn hunks_for_ranges(
23439 &self,
23440 ranges: impl IntoIterator<Item = Range<Point>>,
23441 ) -> Vec<MultiBufferDiffHunk> {
23442 let mut hunks = Vec::new();
23443 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23444 HashMap::default();
23445 for query_range in ranges {
23446 let query_rows =
23447 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23448 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23449 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23450 ) {
23451 // Include deleted hunks that are adjacent to the query range, because
23452 // otherwise they would be missed.
23453 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23454 if hunk.status().is_deleted() {
23455 intersects_range |= hunk.row_range.start == query_rows.end;
23456 intersects_range |= hunk.row_range.end == query_rows.start;
23457 }
23458 if intersects_range {
23459 if !processed_buffer_rows
23460 .entry(hunk.buffer_id)
23461 .or_default()
23462 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23463 {
23464 continue;
23465 }
23466 hunks.push(hunk);
23467 }
23468 }
23469 }
23470
23471 hunks
23472 }
23473
23474 fn display_diff_hunks_for_rows<'a>(
23475 &'a self,
23476 display_rows: Range<DisplayRow>,
23477 folded_buffers: &'a HashSet<BufferId>,
23478 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23479 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23480 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23481
23482 self.buffer_snapshot()
23483 .diff_hunks_in_range(buffer_start..buffer_end)
23484 .filter_map(|hunk| {
23485 if folded_buffers.contains(&hunk.buffer_id) {
23486 return None;
23487 }
23488
23489 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23490 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23491
23492 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23493 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23494
23495 let display_hunk = if hunk_display_start.column() != 0 {
23496 DisplayDiffHunk::Folded {
23497 display_row: hunk_display_start.row(),
23498 }
23499 } else {
23500 let mut end_row = hunk_display_end.row();
23501 if hunk_display_end.column() > 0 {
23502 end_row.0 += 1;
23503 }
23504 let is_created_file = hunk.is_created_file();
23505 DisplayDiffHunk::Unfolded {
23506 status: hunk.status(),
23507 diff_base_byte_range: hunk.diff_base_byte_range,
23508 display_row_range: hunk_display_start.row()..end_row,
23509 multi_buffer_range: Anchor::range_in_buffer(
23510 hunk.excerpt_id,
23511 hunk.buffer_id,
23512 hunk.buffer_range,
23513 ),
23514 is_created_file,
23515 }
23516 };
23517
23518 Some(display_hunk)
23519 })
23520 }
23521
23522 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23523 self.display_snapshot
23524 .buffer_snapshot()
23525 .language_at(position)
23526 }
23527
23528 pub fn is_focused(&self) -> bool {
23529 self.is_focused
23530 }
23531
23532 pub fn placeholder_text(&self) -> Option<String> {
23533 self.placeholder_display_snapshot
23534 .as_ref()
23535 .map(|display_map| display_map.text())
23536 }
23537
23538 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23539 self.scroll_anchor.scroll_position(&self.display_snapshot)
23540 }
23541
23542 fn gutter_dimensions(
23543 &self,
23544 font_id: FontId,
23545 font_size: Pixels,
23546 max_line_number_width: Pixels,
23547 cx: &App,
23548 ) -> Option<GutterDimensions> {
23549 if !self.show_gutter {
23550 return None;
23551 }
23552
23553 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23554 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23555
23556 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23557 matches!(
23558 ProjectSettings::get_global(cx).git.git_gutter,
23559 GitGutterSetting::TrackedFiles
23560 )
23561 });
23562 let gutter_settings = EditorSettings::get_global(cx).gutter;
23563 let show_line_numbers = self
23564 .show_line_numbers
23565 .unwrap_or(gutter_settings.line_numbers);
23566 let line_gutter_width = if show_line_numbers {
23567 // Avoid flicker-like gutter resizes when the line number gains another digit by
23568 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23569 let min_width_for_number_on_gutter =
23570 ch_advance * gutter_settings.min_line_number_digits as f32;
23571 max_line_number_width.max(min_width_for_number_on_gutter)
23572 } else {
23573 0.0.into()
23574 };
23575
23576 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23577 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23578
23579 let git_blame_entries_width =
23580 self.git_blame_gutter_max_author_length
23581 .map(|max_author_length| {
23582 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23583 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23584
23585 /// The number of characters to dedicate to gaps and margins.
23586 const SPACING_WIDTH: usize = 4;
23587
23588 let max_char_count = max_author_length.min(renderer.max_author_length())
23589 + ::git::SHORT_SHA_LENGTH
23590 + MAX_RELATIVE_TIMESTAMP.len()
23591 + SPACING_WIDTH;
23592
23593 ch_advance * max_char_count
23594 });
23595
23596 let is_singleton = self.buffer_snapshot().is_singleton();
23597
23598 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23599 left_padding += if !is_singleton {
23600 ch_width * 4.0
23601 } else if show_runnables || show_breakpoints {
23602 ch_width * 3.0
23603 } else if show_git_gutter && show_line_numbers {
23604 ch_width * 2.0
23605 } else if show_git_gutter || show_line_numbers {
23606 ch_width
23607 } else {
23608 px(0.)
23609 };
23610
23611 let shows_folds = is_singleton && gutter_settings.folds;
23612
23613 let right_padding = if shows_folds && show_line_numbers {
23614 ch_width * 4.0
23615 } else if shows_folds || (!is_singleton && show_line_numbers) {
23616 ch_width * 3.0
23617 } else if show_line_numbers {
23618 ch_width
23619 } else {
23620 px(0.)
23621 };
23622
23623 Some(GutterDimensions {
23624 left_padding,
23625 right_padding,
23626 width: line_gutter_width + left_padding + right_padding,
23627 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23628 git_blame_entries_width,
23629 })
23630 }
23631
23632 pub fn render_crease_toggle(
23633 &self,
23634 buffer_row: MultiBufferRow,
23635 row_contains_cursor: bool,
23636 editor: Entity<Editor>,
23637 window: &mut Window,
23638 cx: &mut App,
23639 ) -> Option<AnyElement> {
23640 let folded = self.is_line_folded(buffer_row);
23641 let mut is_foldable = false;
23642
23643 if let Some(crease) = self
23644 .crease_snapshot
23645 .query_row(buffer_row, self.buffer_snapshot())
23646 {
23647 is_foldable = true;
23648 match crease {
23649 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23650 if let Some(render_toggle) = render_toggle {
23651 let toggle_callback =
23652 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23653 if folded {
23654 editor.update(cx, |editor, cx| {
23655 editor.fold_at(buffer_row, window, cx)
23656 });
23657 } else {
23658 editor.update(cx, |editor, cx| {
23659 editor.unfold_at(buffer_row, window, cx)
23660 });
23661 }
23662 });
23663 return Some((render_toggle)(
23664 buffer_row,
23665 folded,
23666 toggle_callback,
23667 window,
23668 cx,
23669 ));
23670 }
23671 }
23672 }
23673 }
23674
23675 is_foldable |= self.starts_indent(buffer_row);
23676
23677 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23678 Some(
23679 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23680 .toggle_state(folded)
23681 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23682 if folded {
23683 this.unfold_at(buffer_row, window, cx);
23684 } else {
23685 this.fold_at(buffer_row, window, cx);
23686 }
23687 }))
23688 .into_any_element(),
23689 )
23690 } else {
23691 None
23692 }
23693 }
23694
23695 pub fn render_crease_trailer(
23696 &self,
23697 buffer_row: MultiBufferRow,
23698 window: &mut Window,
23699 cx: &mut App,
23700 ) -> Option<AnyElement> {
23701 let folded = self.is_line_folded(buffer_row);
23702 if let Crease::Inline { render_trailer, .. } = self
23703 .crease_snapshot
23704 .query_row(buffer_row, self.buffer_snapshot())?
23705 {
23706 let render_trailer = render_trailer.as_ref()?;
23707 Some(render_trailer(buffer_row, folded, window, cx))
23708 } else {
23709 None
23710 }
23711 }
23712}
23713
23714impl Deref for EditorSnapshot {
23715 type Target = DisplaySnapshot;
23716
23717 fn deref(&self) -> &Self::Target {
23718 &self.display_snapshot
23719 }
23720}
23721
23722#[derive(Clone, Debug, PartialEq, Eq)]
23723pub enum EditorEvent {
23724 InputIgnored {
23725 text: Arc<str>,
23726 },
23727 InputHandled {
23728 utf16_range_to_replace: Option<Range<isize>>,
23729 text: Arc<str>,
23730 },
23731 ExcerptsAdded {
23732 buffer: Entity<Buffer>,
23733 predecessor: ExcerptId,
23734 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23735 },
23736 ExcerptsRemoved {
23737 ids: Vec<ExcerptId>,
23738 removed_buffer_ids: Vec<BufferId>,
23739 },
23740 BufferFoldToggled {
23741 ids: Vec<ExcerptId>,
23742 folded: bool,
23743 },
23744 ExcerptsEdited {
23745 ids: Vec<ExcerptId>,
23746 },
23747 ExcerptsExpanded {
23748 ids: Vec<ExcerptId>,
23749 },
23750 BufferEdited,
23751 Edited {
23752 transaction_id: clock::Lamport,
23753 },
23754 Reparsed(BufferId),
23755 Focused,
23756 FocusedIn,
23757 Blurred,
23758 DirtyChanged,
23759 Saved,
23760 TitleChanged,
23761 SelectionsChanged {
23762 local: bool,
23763 },
23764 ScrollPositionChanged {
23765 local: bool,
23766 autoscroll: bool,
23767 },
23768 TransactionUndone {
23769 transaction_id: clock::Lamport,
23770 },
23771 TransactionBegun {
23772 transaction_id: clock::Lamport,
23773 },
23774 CursorShapeChanged,
23775 BreadcrumbsChanged,
23776 PushedToNavHistory {
23777 anchor: Anchor,
23778 is_deactivate: bool,
23779 },
23780}
23781
23782impl EventEmitter<EditorEvent> for Editor {}
23783
23784impl Focusable for Editor {
23785 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23786 self.focus_handle.clone()
23787 }
23788}
23789
23790impl Render for Editor {
23791 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23792 let settings = ThemeSettings::get_global(cx);
23793
23794 let mut text_style = match self.mode {
23795 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23796 color: cx.theme().colors().editor_foreground,
23797 font_family: settings.ui_font.family.clone(),
23798 font_features: settings.ui_font.features.clone(),
23799 font_fallbacks: settings.ui_font.fallbacks.clone(),
23800 font_size: rems(0.875).into(),
23801 font_weight: settings.ui_font.weight,
23802 line_height: relative(settings.buffer_line_height.value()),
23803 ..Default::default()
23804 },
23805 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23806 color: cx.theme().colors().editor_foreground,
23807 font_family: settings.buffer_font.family.clone(),
23808 font_features: settings.buffer_font.features.clone(),
23809 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23810 font_size: settings.buffer_font_size(cx).into(),
23811 font_weight: settings.buffer_font.weight,
23812 line_height: relative(settings.buffer_line_height.value()),
23813 ..Default::default()
23814 },
23815 };
23816 if let Some(text_style_refinement) = &self.text_style_refinement {
23817 text_style.refine(text_style_refinement)
23818 }
23819
23820 let background = match self.mode {
23821 EditorMode::SingleLine => cx.theme().system().transparent,
23822 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23823 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23824 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23825 };
23826
23827 EditorElement::new(
23828 &cx.entity(),
23829 EditorStyle {
23830 background,
23831 border: cx.theme().colors().border,
23832 local_player: cx.theme().players().local(),
23833 text: text_style,
23834 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23835 syntax: cx.theme().syntax().clone(),
23836 status: cx.theme().status().clone(),
23837 inlay_hints_style: make_inlay_hints_style(cx),
23838 edit_prediction_styles: make_suggestion_styles(cx),
23839 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23840 show_underlines: self.diagnostics_enabled(),
23841 },
23842 )
23843 }
23844}
23845
23846impl EntityInputHandler for Editor {
23847 fn text_for_range(
23848 &mut self,
23849 range_utf16: Range<usize>,
23850 adjusted_range: &mut Option<Range<usize>>,
23851 _: &mut Window,
23852 cx: &mut Context<Self>,
23853 ) -> Option<String> {
23854 let snapshot = self.buffer.read(cx).read(cx);
23855 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23856 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23857 if (start.0..end.0) != range_utf16 {
23858 adjusted_range.replace(start.0..end.0);
23859 }
23860 Some(snapshot.text_for_range(start..end).collect())
23861 }
23862
23863 fn selected_text_range(
23864 &mut self,
23865 ignore_disabled_input: bool,
23866 _: &mut Window,
23867 cx: &mut Context<Self>,
23868 ) -> Option<UTF16Selection> {
23869 // Prevent the IME menu from appearing when holding down an alphabetic key
23870 // while input is disabled.
23871 if !ignore_disabled_input && !self.input_enabled {
23872 return None;
23873 }
23874
23875 let selection = self
23876 .selections
23877 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23878 let range = selection.range();
23879
23880 Some(UTF16Selection {
23881 range: range.start.0..range.end.0,
23882 reversed: selection.reversed,
23883 })
23884 }
23885
23886 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23887 let snapshot = self.buffer.read(cx).read(cx);
23888 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23889 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23890 }
23891
23892 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23893 self.clear_highlights::<InputComposition>(cx);
23894 self.ime_transaction.take();
23895 }
23896
23897 fn replace_text_in_range(
23898 &mut self,
23899 range_utf16: Option<Range<usize>>,
23900 text: &str,
23901 window: &mut Window,
23902 cx: &mut Context<Self>,
23903 ) {
23904 if !self.input_enabled {
23905 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23906 return;
23907 }
23908
23909 self.transact(window, cx, |this, window, cx| {
23910 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23911 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23912 Some(this.selection_replacement_ranges(range_utf16, cx))
23913 } else {
23914 this.marked_text_ranges(cx)
23915 };
23916
23917 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23918 let newest_selection_id = this.selections.newest_anchor().id;
23919 this.selections
23920 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23921 .iter()
23922 .zip(ranges_to_replace.iter())
23923 .find_map(|(selection, range)| {
23924 if selection.id == newest_selection_id {
23925 Some(
23926 (range.start.0 as isize - selection.head().0 as isize)
23927 ..(range.end.0 as isize - selection.head().0 as isize),
23928 )
23929 } else {
23930 None
23931 }
23932 })
23933 });
23934
23935 cx.emit(EditorEvent::InputHandled {
23936 utf16_range_to_replace: range_to_replace,
23937 text: text.into(),
23938 });
23939
23940 if let Some(new_selected_ranges) = new_selected_ranges {
23941 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23942 selections.select_ranges(new_selected_ranges)
23943 });
23944 this.backspace(&Default::default(), window, cx);
23945 }
23946
23947 this.handle_input(text, window, cx);
23948 });
23949
23950 if let Some(transaction) = self.ime_transaction {
23951 self.buffer.update(cx, |buffer, cx| {
23952 buffer.group_until_transaction(transaction, cx);
23953 });
23954 }
23955
23956 self.unmark_text(window, cx);
23957 }
23958
23959 fn replace_and_mark_text_in_range(
23960 &mut self,
23961 range_utf16: Option<Range<usize>>,
23962 text: &str,
23963 new_selected_range_utf16: Option<Range<usize>>,
23964 window: &mut Window,
23965 cx: &mut Context<Self>,
23966 ) {
23967 if !self.input_enabled {
23968 return;
23969 }
23970
23971 let transaction = self.transact(window, cx, |this, window, cx| {
23972 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23973 let snapshot = this.buffer.read(cx).read(cx);
23974 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23975 for marked_range in &mut marked_ranges {
23976 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23977 marked_range.start.0 += relative_range_utf16.start;
23978 marked_range.start =
23979 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23980 marked_range.end =
23981 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23982 }
23983 }
23984 Some(marked_ranges)
23985 } else if let Some(range_utf16) = range_utf16 {
23986 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23987 Some(this.selection_replacement_ranges(range_utf16, cx))
23988 } else {
23989 None
23990 };
23991
23992 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23993 let newest_selection_id = this.selections.newest_anchor().id;
23994 this.selections
23995 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23996 .iter()
23997 .zip(ranges_to_replace.iter())
23998 .find_map(|(selection, range)| {
23999 if selection.id == newest_selection_id {
24000 Some(
24001 (range.start.0 as isize - selection.head().0 as isize)
24002 ..(range.end.0 as isize - selection.head().0 as isize),
24003 )
24004 } else {
24005 None
24006 }
24007 })
24008 });
24009
24010 cx.emit(EditorEvent::InputHandled {
24011 utf16_range_to_replace: range_to_replace,
24012 text: text.into(),
24013 });
24014
24015 if let Some(ranges) = ranges_to_replace {
24016 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24017 s.select_ranges(ranges)
24018 });
24019 }
24020
24021 let marked_ranges = {
24022 let snapshot = this.buffer.read(cx).read(cx);
24023 this.selections
24024 .disjoint_anchors_arc()
24025 .iter()
24026 .map(|selection| {
24027 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24028 })
24029 .collect::<Vec<_>>()
24030 };
24031
24032 if text.is_empty() {
24033 this.unmark_text(window, cx);
24034 } else {
24035 this.highlight_text::<InputComposition>(
24036 marked_ranges.clone(),
24037 HighlightStyle {
24038 underline: Some(UnderlineStyle {
24039 thickness: px(1.),
24040 color: None,
24041 wavy: false,
24042 }),
24043 ..Default::default()
24044 },
24045 cx,
24046 );
24047 }
24048
24049 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24050 let use_autoclose = this.use_autoclose;
24051 let use_auto_surround = this.use_auto_surround;
24052 this.set_use_autoclose(false);
24053 this.set_use_auto_surround(false);
24054 this.handle_input(text, window, cx);
24055 this.set_use_autoclose(use_autoclose);
24056 this.set_use_auto_surround(use_auto_surround);
24057
24058 if let Some(new_selected_range) = new_selected_range_utf16 {
24059 let snapshot = this.buffer.read(cx).read(cx);
24060 let new_selected_ranges = marked_ranges
24061 .into_iter()
24062 .map(|marked_range| {
24063 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24064 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24065 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24066 snapshot.clip_offset_utf16(new_start, Bias::Left)
24067 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24068 })
24069 .collect::<Vec<_>>();
24070
24071 drop(snapshot);
24072 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24073 selections.select_ranges(new_selected_ranges)
24074 });
24075 }
24076 });
24077
24078 self.ime_transaction = self.ime_transaction.or(transaction);
24079 if let Some(transaction) = self.ime_transaction {
24080 self.buffer.update(cx, |buffer, cx| {
24081 buffer.group_until_transaction(transaction, cx);
24082 });
24083 }
24084
24085 if self.text_highlights::<InputComposition>(cx).is_none() {
24086 self.ime_transaction.take();
24087 }
24088 }
24089
24090 fn bounds_for_range(
24091 &mut self,
24092 range_utf16: Range<usize>,
24093 element_bounds: gpui::Bounds<Pixels>,
24094 window: &mut Window,
24095 cx: &mut Context<Self>,
24096 ) -> Option<gpui::Bounds<Pixels>> {
24097 let text_layout_details = self.text_layout_details(window);
24098 let CharacterDimensions {
24099 em_width,
24100 em_advance,
24101 line_height,
24102 } = self.character_dimensions(window);
24103
24104 let snapshot = self.snapshot(window, cx);
24105 let scroll_position = snapshot.scroll_position();
24106 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24107
24108 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24109 let x = Pixels::from(
24110 ScrollOffset::from(
24111 snapshot.x_for_display_point(start, &text_layout_details)
24112 + self.gutter_dimensions.full_width(),
24113 ) - scroll_left,
24114 );
24115 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24116
24117 Some(Bounds {
24118 origin: element_bounds.origin + point(x, y),
24119 size: size(em_width, line_height),
24120 })
24121 }
24122
24123 fn character_index_for_point(
24124 &mut self,
24125 point: gpui::Point<Pixels>,
24126 _window: &mut Window,
24127 _cx: &mut Context<Self>,
24128 ) -> Option<usize> {
24129 let position_map = self.last_position_map.as_ref()?;
24130 if !position_map.text_hitbox.contains(&point) {
24131 return None;
24132 }
24133 let display_point = position_map.point_for_position(point).previous_valid;
24134 let anchor = position_map
24135 .snapshot
24136 .display_point_to_anchor(display_point, Bias::Left);
24137 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24138 Some(utf16_offset.0)
24139 }
24140}
24141
24142trait SelectionExt {
24143 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24144 fn spanned_rows(
24145 &self,
24146 include_end_if_at_line_start: bool,
24147 map: &DisplaySnapshot,
24148 ) -> Range<MultiBufferRow>;
24149}
24150
24151impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24152 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24153 let start = self
24154 .start
24155 .to_point(map.buffer_snapshot())
24156 .to_display_point(map);
24157 let end = self
24158 .end
24159 .to_point(map.buffer_snapshot())
24160 .to_display_point(map);
24161 if self.reversed {
24162 end..start
24163 } else {
24164 start..end
24165 }
24166 }
24167
24168 fn spanned_rows(
24169 &self,
24170 include_end_if_at_line_start: bool,
24171 map: &DisplaySnapshot,
24172 ) -> Range<MultiBufferRow> {
24173 let start = self.start.to_point(map.buffer_snapshot());
24174 let mut end = self.end.to_point(map.buffer_snapshot());
24175 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24176 end.row -= 1;
24177 }
24178
24179 let buffer_start = map.prev_line_boundary(start).0;
24180 let buffer_end = map.next_line_boundary(end).0;
24181 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24182 }
24183}
24184
24185impl<T: InvalidationRegion> InvalidationStack<T> {
24186 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24187 where
24188 S: Clone + ToOffset,
24189 {
24190 while let Some(region) = self.last() {
24191 let all_selections_inside_invalidation_ranges =
24192 if selections.len() == region.ranges().len() {
24193 selections
24194 .iter()
24195 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24196 .all(|(selection, invalidation_range)| {
24197 let head = selection.head().to_offset(buffer);
24198 invalidation_range.start <= head && invalidation_range.end >= head
24199 })
24200 } else {
24201 false
24202 };
24203
24204 if all_selections_inside_invalidation_ranges {
24205 break;
24206 } else {
24207 self.pop();
24208 }
24209 }
24210 }
24211}
24212
24213impl<T> Default for InvalidationStack<T> {
24214 fn default() -> Self {
24215 Self(Default::default())
24216 }
24217}
24218
24219impl<T> Deref for InvalidationStack<T> {
24220 type Target = Vec<T>;
24221
24222 fn deref(&self) -> &Self::Target {
24223 &self.0
24224 }
24225}
24226
24227impl<T> DerefMut for InvalidationStack<T> {
24228 fn deref_mut(&mut self) -> &mut Self::Target {
24229 &mut self.0
24230 }
24231}
24232
24233impl InvalidationRegion for SnippetState {
24234 fn ranges(&self) -> &[Range<Anchor>] {
24235 &self.ranges[self.active_index]
24236 }
24237}
24238
24239fn edit_prediction_edit_text(
24240 current_snapshot: &BufferSnapshot,
24241 edits: &[(Range<Anchor>, String)],
24242 edit_preview: &EditPreview,
24243 include_deletions: bool,
24244 cx: &App,
24245) -> HighlightedText {
24246 let edits = edits
24247 .iter()
24248 .map(|(anchor, text)| {
24249 (
24250 anchor.start.text_anchor..anchor.end.text_anchor,
24251 text.clone(),
24252 )
24253 })
24254 .collect::<Vec<_>>();
24255
24256 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24257}
24258
24259fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24260 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24261 // Just show the raw edit text with basic styling
24262 let mut text = String::new();
24263 let mut highlights = Vec::new();
24264
24265 let insertion_highlight_style = HighlightStyle {
24266 color: Some(cx.theme().colors().text),
24267 ..Default::default()
24268 };
24269
24270 for (_, edit_text) in edits {
24271 let start_offset = text.len();
24272 text.push_str(edit_text);
24273 let end_offset = text.len();
24274
24275 if start_offset < end_offset {
24276 highlights.push((start_offset..end_offset, insertion_highlight_style));
24277 }
24278 }
24279
24280 HighlightedText {
24281 text: text.into(),
24282 highlights,
24283 }
24284}
24285
24286pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24287 match severity {
24288 lsp::DiagnosticSeverity::ERROR => colors.error,
24289 lsp::DiagnosticSeverity::WARNING => colors.warning,
24290 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24291 lsp::DiagnosticSeverity::HINT => colors.info,
24292 _ => colors.ignored,
24293 }
24294}
24295
24296pub fn styled_runs_for_code_label<'a>(
24297 label: &'a CodeLabel,
24298 syntax_theme: &'a theme::SyntaxTheme,
24299) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24300 let fade_out = HighlightStyle {
24301 fade_out: Some(0.35),
24302 ..Default::default()
24303 };
24304
24305 let mut prev_end = label.filter_range.end;
24306 label
24307 .runs
24308 .iter()
24309 .enumerate()
24310 .flat_map(move |(ix, (range, highlight_id))| {
24311 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24312 style
24313 } else {
24314 return Default::default();
24315 };
24316 let muted_style = style.highlight(fade_out);
24317
24318 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24319 if range.start >= label.filter_range.end {
24320 if range.start > prev_end {
24321 runs.push((prev_end..range.start, fade_out));
24322 }
24323 runs.push((range.clone(), muted_style));
24324 } else if range.end <= label.filter_range.end {
24325 runs.push((range.clone(), style));
24326 } else {
24327 runs.push((range.start..label.filter_range.end, style));
24328 runs.push((label.filter_range.end..range.end, muted_style));
24329 }
24330 prev_end = cmp::max(prev_end, range.end);
24331
24332 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24333 runs.push((prev_end..label.text.len(), fade_out));
24334 }
24335
24336 runs
24337 })
24338}
24339
24340pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24341 let mut prev_index = 0;
24342 let mut prev_codepoint: Option<char> = None;
24343 text.char_indices()
24344 .chain([(text.len(), '\0')])
24345 .filter_map(move |(index, codepoint)| {
24346 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24347 let is_boundary = index == text.len()
24348 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24349 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24350 if is_boundary {
24351 let chunk = &text[prev_index..index];
24352 prev_index = index;
24353 Some(chunk)
24354 } else {
24355 None
24356 }
24357 })
24358}
24359
24360pub trait RangeToAnchorExt: Sized {
24361 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24362
24363 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24364 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24365 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24366 }
24367}
24368
24369impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24370 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24371 let start_offset = self.start.to_offset(snapshot);
24372 let end_offset = self.end.to_offset(snapshot);
24373 if start_offset == end_offset {
24374 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24375 } else {
24376 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24377 }
24378 }
24379}
24380
24381pub trait RowExt {
24382 fn as_f64(&self) -> f64;
24383
24384 fn next_row(&self) -> Self;
24385
24386 fn previous_row(&self) -> Self;
24387
24388 fn minus(&self, other: Self) -> u32;
24389}
24390
24391impl RowExt for DisplayRow {
24392 fn as_f64(&self) -> f64 {
24393 self.0 as _
24394 }
24395
24396 fn next_row(&self) -> Self {
24397 Self(self.0 + 1)
24398 }
24399
24400 fn previous_row(&self) -> Self {
24401 Self(self.0.saturating_sub(1))
24402 }
24403
24404 fn minus(&self, other: Self) -> u32 {
24405 self.0 - other.0
24406 }
24407}
24408
24409impl RowExt for MultiBufferRow {
24410 fn as_f64(&self) -> f64 {
24411 self.0 as _
24412 }
24413
24414 fn next_row(&self) -> Self {
24415 Self(self.0 + 1)
24416 }
24417
24418 fn previous_row(&self) -> Self {
24419 Self(self.0.saturating_sub(1))
24420 }
24421
24422 fn minus(&self, other: Self) -> u32 {
24423 self.0 - other.0
24424 }
24425}
24426
24427trait RowRangeExt {
24428 type Row;
24429
24430 fn len(&self) -> usize;
24431
24432 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24433}
24434
24435impl RowRangeExt for Range<MultiBufferRow> {
24436 type Row = MultiBufferRow;
24437
24438 fn len(&self) -> usize {
24439 (self.end.0 - self.start.0) as usize
24440 }
24441
24442 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24443 (self.start.0..self.end.0).map(MultiBufferRow)
24444 }
24445}
24446
24447impl RowRangeExt for Range<DisplayRow> {
24448 type Row = DisplayRow;
24449
24450 fn len(&self) -> usize {
24451 (self.end.0 - self.start.0) as usize
24452 }
24453
24454 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24455 (self.start.0..self.end.0).map(DisplayRow)
24456 }
24457}
24458
24459/// If select range has more than one line, we
24460/// just point the cursor to range.start.
24461fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24462 if range.start.row == range.end.row {
24463 range
24464 } else {
24465 range.start..range.start
24466 }
24467}
24468pub struct KillRing(ClipboardItem);
24469impl Global for KillRing {}
24470
24471const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24472
24473enum BreakpointPromptEditAction {
24474 Log,
24475 Condition,
24476 HitCondition,
24477}
24478
24479struct BreakpointPromptEditor {
24480 pub(crate) prompt: Entity<Editor>,
24481 editor: WeakEntity<Editor>,
24482 breakpoint_anchor: Anchor,
24483 breakpoint: Breakpoint,
24484 edit_action: BreakpointPromptEditAction,
24485 block_ids: HashSet<CustomBlockId>,
24486 editor_margins: Arc<Mutex<EditorMargins>>,
24487 _subscriptions: Vec<Subscription>,
24488}
24489
24490impl BreakpointPromptEditor {
24491 const MAX_LINES: u8 = 4;
24492
24493 fn new(
24494 editor: WeakEntity<Editor>,
24495 breakpoint_anchor: Anchor,
24496 breakpoint: Breakpoint,
24497 edit_action: BreakpointPromptEditAction,
24498 window: &mut Window,
24499 cx: &mut Context<Self>,
24500 ) -> Self {
24501 let base_text = match edit_action {
24502 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24503 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24504 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24505 }
24506 .map(|msg| msg.to_string())
24507 .unwrap_or_default();
24508
24509 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24510 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24511
24512 let prompt = cx.new(|cx| {
24513 let mut prompt = Editor::new(
24514 EditorMode::AutoHeight {
24515 min_lines: 1,
24516 max_lines: Some(Self::MAX_LINES as usize),
24517 },
24518 buffer,
24519 None,
24520 window,
24521 cx,
24522 );
24523 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24524 prompt.set_show_cursor_when_unfocused(false, cx);
24525 prompt.set_placeholder_text(
24526 match edit_action {
24527 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24528 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24529 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24530 },
24531 window,
24532 cx,
24533 );
24534
24535 prompt
24536 });
24537
24538 Self {
24539 prompt,
24540 editor,
24541 breakpoint_anchor,
24542 breakpoint,
24543 edit_action,
24544 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24545 block_ids: Default::default(),
24546 _subscriptions: vec![],
24547 }
24548 }
24549
24550 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24551 self.block_ids.extend(block_ids)
24552 }
24553
24554 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24555 if let Some(editor) = self.editor.upgrade() {
24556 let message = self
24557 .prompt
24558 .read(cx)
24559 .buffer
24560 .read(cx)
24561 .as_singleton()
24562 .expect("A multi buffer in breakpoint prompt isn't possible")
24563 .read(cx)
24564 .as_rope()
24565 .to_string();
24566
24567 editor.update(cx, |editor, cx| {
24568 editor.edit_breakpoint_at_anchor(
24569 self.breakpoint_anchor,
24570 self.breakpoint.clone(),
24571 match self.edit_action {
24572 BreakpointPromptEditAction::Log => {
24573 BreakpointEditAction::EditLogMessage(message.into())
24574 }
24575 BreakpointPromptEditAction::Condition => {
24576 BreakpointEditAction::EditCondition(message.into())
24577 }
24578 BreakpointPromptEditAction::HitCondition => {
24579 BreakpointEditAction::EditHitCondition(message.into())
24580 }
24581 },
24582 cx,
24583 );
24584
24585 editor.remove_blocks(self.block_ids.clone(), None, cx);
24586 cx.focus_self(window);
24587 });
24588 }
24589 }
24590
24591 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24592 self.editor
24593 .update(cx, |editor, cx| {
24594 editor.remove_blocks(self.block_ids.clone(), None, cx);
24595 window.focus(&editor.focus_handle);
24596 })
24597 .log_err();
24598 }
24599
24600 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24601 let settings = ThemeSettings::get_global(cx);
24602 let text_style = TextStyle {
24603 color: if self.prompt.read(cx).read_only(cx) {
24604 cx.theme().colors().text_disabled
24605 } else {
24606 cx.theme().colors().text
24607 },
24608 font_family: settings.buffer_font.family.clone(),
24609 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24610 font_size: settings.buffer_font_size(cx).into(),
24611 font_weight: settings.buffer_font.weight,
24612 line_height: relative(settings.buffer_line_height.value()),
24613 ..Default::default()
24614 };
24615 EditorElement::new(
24616 &self.prompt,
24617 EditorStyle {
24618 background: cx.theme().colors().editor_background,
24619 local_player: cx.theme().players().local(),
24620 text: text_style,
24621 ..Default::default()
24622 },
24623 )
24624 }
24625}
24626
24627impl Render for BreakpointPromptEditor {
24628 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24629 let editor_margins = *self.editor_margins.lock();
24630 let gutter_dimensions = editor_margins.gutter;
24631 h_flex()
24632 .key_context("Editor")
24633 .bg(cx.theme().colors().editor_background)
24634 .border_y_1()
24635 .border_color(cx.theme().status().info_border)
24636 .size_full()
24637 .py(window.line_height() / 2.5)
24638 .on_action(cx.listener(Self::confirm))
24639 .on_action(cx.listener(Self::cancel))
24640 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24641 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24642 }
24643}
24644
24645impl Focusable for BreakpointPromptEditor {
24646 fn focus_handle(&self, cx: &App) -> FocusHandle {
24647 self.prompt.focus_handle(cx)
24648 }
24649}
24650
24651fn all_edits_insertions_or_deletions(
24652 edits: &Vec<(Range<Anchor>, String)>,
24653 snapshot: &MultiBufferSnapshot,
24654) -> bool {
24655 let mut all_insertions = true;
24656 let mut all_deletions = true;
24657
24658 for (range, new_text) in edits.iter() {
24659 let range_is_empty = range.to_offset(snapshot).is_empty();
24660 let text_is_empty = new_text.is_empty();
24661
24662 if range_is_empty != text_is_empty {
24663 if range_is_empty {
24664 all_deletions = false;
24665 } else {
24666 all_insertions = false;
24667 }
24668 } else {
24669 return false;
24670 }
24671
24672 if !all_insertions && !all_deletions {
24673 return false;
24674 }
24675 }
24676 all_insertions || all_deletions
24677}
24678
24679struct MissingEditPredictionKeybindingTooltip;
24680
24681impl Render for MissingEditPredictionKeybindingTooltip {
24682 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24683 ui::tooltip_container(cx, |container, cx| {
24684 container
24685 .flex_shrink_0()
24686 .max_w_80()
24687 .min_h(rems_from_px(124.))
24688 .justify_between()
24689 .child(
24690 v_flex()
24691 .flex_1()
24692 .text_ui_sm(cx)
24693 .child(Label::new("Conflict with Accept Keybinding"))
24694 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24695 )
24696 .child(
24697 h_flex()
24698 .pb_1()
24699 .gap_1()
24700 .items_end()
24701 .w_full()
24702 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24703 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24704 }))
24705 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24706 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24707 })),
24708 )
24709 })
24710 }
24711}
24712
24713#[derive(Debug, Clone, Copy, PartialEq)]
24714pub struct LineHighlight {
24715 pub background: Background,
24716 pub border: Option<gpui::Hsla>,
24717 pub include_gutter: bool,
24718 pub type_id: Option<TypeId>,
24719}
24720
24721struct LineManipulationResult {
24722 pub new_text: String,
24723 pub line_count_before: usize,
24724 pub line_count_after: usize,
24725}
24726
24727fn render_diff_hunk_controls(
24728 row: u32,
24729 status: &DiffHunkStatus,
24730 hunk_range: Range<Anchor>,
24731 is_created_file: bool,
24732 line_height: Pixels,
24733 editor: &Entity<Editor>,
24734 _window: &mut Window,
24735 cx: &mut App,
24736) -> AnyElement {
24737 h_flex()
24738 .h(line_height)
24739 .mr_1()
24740 .gap_1()
24741 .px_0p5()
24742 .pb_1()
24743 .border_x_1()
24744 .border_b_1()
24745 .border_color(cx.theme().colors().border_variant)
24746 .rounded_b_lg()
24747 .bg(cx.theme().colors().editor_background)
24748 .gap_1()
24749 .block_mouse_except_scroll()
24750 .shadow_md()
24751 .child(if status.has_secondary_hunk() {
24752 Button::new(("stage", row as u64), "Stage")
24753 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24754 .tooltip({
24755 let focus_handle = editor.focus_handle(cx);
24756 move |_window, cx| {
24757 Tooltip::for_action_in(
24758 "Stage Hunk",
24759 &::git::ToggleStaged,
24760 &focus_handle,
24761 cx,
24762 )
24763 }
24764 })
24765 .on_click({
24766 let editor = editor.clone();
24767 move |_event, _window, cx| {
24768 editor.update(cx, |editor, cx| {
24769 editor.stage_or_unstage_diff_hunks(
24770 true,
24771 vec![hunk_range.start..hunk_range.start],
24772 cx,
24773 );
24774 });
24775 }
24776 })
24777 } else {
24778 Button::new(("unstage", row as u64), "Unstage")
24779 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24780 .tooltip({
24781 let focus_handle = editor.focus_handle(cx);
24782 move |_window, cx| {
24783 Tooltip::for_action_in(
24784 "Unstage Hunk",
24785 &::git::ToggleStaged,
24786 &focus_handle,
24787 cx,
24788 )
24789 }
24790 })
24791 .on_click({
24792 let editor = editor.clone();
24793 move |_event, _window, cx| {
24794 editor.update(cx, |editor, cx| {
24795 editor.stage_or_unstage_diff_hunks(
24796 false,
24797 vec![hunk_range.start..hunk_range.start],
24798 cx,
24799 );
24800 });
24801 }
24802 })
24803 })
24804 .child(
24805 Button::new(("restore", row as u64), "Restore")
24806 .tooltip({
24807 let focus_handle = editor.focus_handle(cx);
24808 move |_window, cx| {
24809 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24810 }
24811 })
24812 .on_click({
24813 let editor = editor.clone();
24814 move |_event, window, cx| {
24815 editor.update(cx, |editor, cx| {
24816 let snapshot = editor.snapshot(window, cx);
24817 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24818 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24819 });
24820 }
24821 })
24822 .disabled(is_created_file),
24823 )
24824 .when(
24825 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24826 |el| {
24827 el.child(
24828 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24829 .shape(IconButtonShape::Square)
24830 .icon_size(IconSize::Small)
24831 // .disabled(!has_multiple_hunks)
24832 .tooltip({
24833 let focus_handle = editor.focus_handle(cx);
24834 move |_window, cx| {
24835 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24836 }
24837 })
24838 .on_click({
24839 let editor = editor.clone();
24840 move |_event, window, cx| {
24841 editor.update(cx, |editor, cx| {
24842 let snapshot = editor.snapshot(window, cx);
24843 let position =
24844 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24845 editor.go_to_hunk_before_or_after_position(
24846 &snapshot,
24847 position,
24848 Direction::Next,
24849 window,
24850 cx,
24851 );
24852 editor.expand_selected_diff_hunks(cx);
24853 });
24854 }
24855 }),
24856 )
24857 .child(
24858 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24859 .shape(IconButtonShape::Square)
24860 .icon_size(IconSize::Small)
24861 // .disabled(!has_multiple_hunks)
24862 .tooltip({
24863 let focus_handle = editor.focus_handle(cx);
24864 move |_window, cx| {
24865 Tooltip::for_action_in(
24866 "Previous Hunk",
24867 &GoToPreviousHunk,
24868 &focus_handle,
24869 cx,
24870 )
24871 }
24872 })
24873 .on_click({
24874 let editor = editor.clone();
24875 move |_event, window, cx| {
24876 editor.update(cx, |editor, cx| {
24877 let snapshot = editor.snapshot(window, cx);
24878 let point =
24879 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24880 editor.go_to_hunk_before_or_after_position(
24881 &snapshot,
24882 point,
24883 Direction::Prev,
24884 window,
24885 cx,
24886 );
24887 editor.expand_selected_diff_hunks(cx);
24888 });
24889 }
24890 }),
24891 )
24892 },
24893 )
24894 .into_any_element()
24895}
24896
24897pub fn multibuffer_context_lines(cx: &App) -> u32 {
24898 EditorSettings::try_get(cx)
24899 .map(|settings| settings.excerpt_context_lines)
24900 .unwrap_or(2)
24901 .min(32)
24902}