1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::{SmallVec, smallvec};
168use snippet::Snippet;
169use std::{
170 any::{Any, TypeId},
171 borrow::Cow,
172 cell::{OnceCell, RefCell},
173 cmp::{self, Ordering, Reverse},
174 iter::{self, Peekable},
175 mem,
176 num::NonZeroU32,
177 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 sync::Arc,
181 time::{Duration, Instant},
182};
183use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
184use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
191 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::{
204 code_context_menus::CompletionsMenuSource,
205 editor_settings::MultiCursorModifier,
206 hover_links::{find_url, find_url_from_range},
207 inlays::{
208 InlineValueCache,
209 inlay_hints::{LspInlayHintData, inlay_hint_settings},
210 },
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 selections_collection::resolve_selections_wrapping_blocks,
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265pub enum ActiveDebugLine {}
266pub enum DebugStackFrameLine {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270pub enum PendingInput {}
271enum SelectedTextHighlight {}
272
273pub enum ConflictsOuter {}
274pub enum ConflictsOurs {}
275pub enum ConflictsTheirs {}
276pub enum ConflictsOursMarker {}
277pub enum ConflictsTheirsMarker {}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq)]
280pub enum Navigated {
281 Yes,
282 No,
283}
284
285impl Navigated {
286 pub fn from_bool(yes: bool) -> Navigated {
287 if yes { Navigated::Yes } else { Navigated::No }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292enum DisplayDiffHunk {
293 Folded {
294 display_row: DisplayRow,
295 },
296 Unfolded {
297 is_created_file: bool,
298 diff_base_byte_range: Range<usize>,
299 display_row_range: Range<DisplayRow>,
300 multi_buffer_range: Range<Anchor>,
301 status: DiffHunkStatus,
302 },
303}
304
305pub enum HideMouseCursorOrigin {
306 TypingAction,
307 MovementAction,
308}
309
310pub fn init_settings(cx: &mut App) {
311 EditorSettings::register(cx);
312}
313
314pub fn init(cx: &mut App) {
315 init_settings(cx);
316
317 cx.set_global(GlobalBlameRenderer(Arc::new(())));
318
319 workspace::register_project_item::<Editor>(cx);
320 workspace::FollowableViewRegistry::register::<Editor>(cx);
321 workspace::register_serializable_item::<Editor>(cx);
322
323 cx.observe_new(
324 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
325 workspace.register_action(Editor::new_file);
326 workspace.register_action(Editor::new_file_split);
327 workspace.register_action(Editor::new_file_vertical);
328 workspace.register_action(Editor::new_file_horizontal);
329 workspace.register_action(Editor::cancel_language_server_work);
330 workspace.register_action(Editor::toggle_focus);
331 },
332 )
333 .detach();
334
335 cx.on_action(move |_: &workspace::NewFile, cx| {
336 let app_state = workspace::AppState::global(cx);
337 if let Some(app_state) = app_state.upgrade() {
338 workspace::open_new(
339 Default::default(),
340 app_state,
341 cx,
342 |workspace, window, cx| {
343 Editor::new_file(workspace, &Default::default(), window, cx)
344 },
345 )
346 .detach();
347 }
348 });
349 cx.on_action(move |_: &workspace::NewWindow, cx| {
350 let app_state = workspace::AppState::global(cx);
351 if let Some(app_state) = app_state.upgrade() {
352 workspace::open_new(
353 Default::default(),
354 app_state,
355 cx,
356 |workspace, window, cx| {
357 cx.activate(true);
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 });
364}
365
366pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
367 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
368}
369
370pub trait DiagnosticRenderer {
371 fn render_group(
372 &self,
373 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
374 buffer_id: BufferId,
375 snapshot: EditorSnapshot,
376 editor: WeakEntity<Editor>,
377 cx: &mut App,
378 ) -> Vec<BlockProperties<Anchor>>;
379
380 fn render_hover(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 range: Range<Point>,
384 buffer_id: BufferId,
385 cx: &mut App,
386 ) -> Option<Entity<markdown::Markdown>>;
387
388 fn open_link(
389 &self,
390 editor: &mut Editor,
391 link: SharedString,
392 window: &mut Window,
393 cx: &mut Context<Editor>,
394 );
395}
396
397pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
398
399impl GlobalDiagnosticRenderer {
400 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
401 cx.try_global::<Self>().map(|g| g.0.clone())
402 }
403}
404
405impl gpui::Global for GlobalDiagnosticRenderer {}
406pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
408}
409
410pub struct SearchWithinRange;
411
412trait InvalidationRegion {
413 fn ranges(&self) -> &[Range<Anchor>];
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum SelectPhase {
418 Begin {
419 position: DisplayPoint,
420 add: bool,
421 click_count: usize,
422 },
423 BeginColumnar {
424 position: DisplayPoint,
425 reset: bool,
426 mode: ColumnarMode,
427 goal_column: u32,
428 },
429 Extend {
430 position: DisplayPoint,
431 click_count: usize,
432 },
433 Update {
434 position: DisplayPoint,
435 goal_column: u32,
436 scroll_delta: gpui::Point<f32>,
437 },
438 End,
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum ColumnarMode {
443 FromMouse,
444 FromSelection,
445}
446
447#[derive(Clone, Debug)]
448pub enum SelectMode {
449 Character,
450 Word(Range<Anchor>),
451 Line(Range<Anchor>),
452 All,
453}
454
455#[derive(Clone, PartialEq, Eq, Debug)]
456pub enum EditorMode {
457 SingleLine,
458 AutoHeight {
459 min_lines: usize,
460 max_lines: Option<usize>,
461 },
462 Full {
463 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
464 scale_ui_elements_with_buffer_font_size: bool,
465 /// When set to `true`, the editor will render a background for the active line.
466 show_active_line_background: bool,
467 /// When set to `true`, the editor's height will be determined by its content.
468 sized_by_content: bool,
469 },
470 Minimap {
471 parent: WeakEntity<Editor>,
472 },
473}
474
475impl EditorMode {
476 pub fn full() -> Self {
477 Self::Full {
478 scale_ui_elements_with_buffer_font_size: true,
479 show_active_line_background: true,
480 sized_by_content: false,
481 }
482 }
483
484 #[inline]
485 pub fn is_full(&self) -> bool {
486 matches!(self, Self::Full { .. })
487 }
488
489 #[inline]
490 pub fn is_single_line(&self) -> bool {
491 matches!(self, Self::SingleLine { .. })
492 }
493
494 #[inline]
495 fn is_minimap(&self) -> bool {
496 matches!(self, Self::Minimap { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub border: Hsla,
521 pub local_player: PlayerColor,
522 pub text: TextStyle,
523 pub scrollbar_width: Pixels,
524 pub syntax: Arc<SyntaxTheme>,
525 pub status: StatusColors,
526 pub inlay_hints_style: HighlightStyle,
527 pub edit_prediction_styles: EditPredictionStyles,
528 pub unnecessary_code_fade: f32,
529 pub show_underlines: bool,
530}
531
532impl Default for EditorStyle {
533 fn default() -> Self {
534 Self {
535 background: Hsla::default(),
536 border: Hsla::default(),
537 local_player: PlayerColor::default(),
538 text: TextStyle::default(),
539 scrollbar_width: Pixels::default(),
540 syntax: Default::default(),
541 // HACK: Status colors don't have a real default.
542 // We should look into removing the status colors from the editor
543 // style and retrieve them directly from the theme.
544 status: StatusColors::dark(),
545 inlay_hints_style: HighlightStyle::default(),
546 edit_prediction_styles: EditPredictionStyles {
547 insertion: HighlightStyle::default(),
548 whitespace: HighlightStyle::default(),
549 },
550 unnecessary_code_fade: Default::default(),
551 show_underlines: true,
552 }
553 }
554}
555
556pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
557 let show_background = language_settings::language_settings(None, None, cx)
558 .inlay_hints
559 .show_background;
560
561 let mut style = cx.theme().syntax().get("hint");
562
563 if style.color.is_none() {
564 style.color = Some(cx.theme().status().hint);
565 }
566
567 if !show_background {
568 style.background_color = None;
569 return style;
570 }
571
572 if style.background_color.is_none() {
573 style.background_color = Some(cx.theme().status().hint_background);
574 }
575
576 style
577}
578
579pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
580 EditPredictionStyles {
581 insertion: HighlightStyle {
582 color: Some(cx.theme().status().predictive),
583 ..HighlightStyle::default()
584 },
585 whitespace: HighlightStyle {
586 background_color: Some(cx.theme().status().created_background),
587 ..HighlightStyle::default()
588 },
589 }
590}
591
592type CompletionId = usize;
593
594pub(crate) enum EditDisplayMode {
595 TabAccept,
596 DiffPopover,
597 Inline,
598}
599
600enum EditPrediction {
601 Edit {
602 edits: Vec<(Range<Anchor>, String)>,
603 edit_preview: Option<EditPreview>,
604 display_mode: EditDisplayMode,
605 snapshot: BufferSnapshot,
606 },
607 /// Move to a specific location in the active editor
608 MoveWithin {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612 /// Move to a specific location in a different editor (not the active one)
613 MoveOutside {
614 target: language::Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct EditPredictionState {
620 inlay_ids: Vec<InlayId>,
621 completion: EditPrediction,
622 completion_id: Option<SharedString>,
623 invalidation_range: Option<Range<Anchor>>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum EditPredictionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuEditPredictionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
708type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
709
710#[derive(Default)]
711struct ScrollbarMarkerState {
712 scrollbar_size: Size<Pixels>,
713 dirty: bool,
714 markers: Arc<[PaintQuad]>,
715 pending_refresh: Option<Task<Result<()>>>,
716}
717
718impl ScrollbarMarkerState {
719 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
720 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
721 }
722}
723
724#[derive(Clone, Copy, PartialEq, Eq)]
725pub enum MinimapVisibility {
726 Disabled,
727 Enabled {
728 /// The configuration currently present in the users settings.
729 setting_configuration: bool,
730 /// Whether to override the currently set visibility from the users setting.
731 toggle_override: bool,
732 },
733}
734
735impl MinimapVisibility {
736 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
737 if mode.is_full() {
738 Self::Enabled {
739 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
740 toggle_override: false,
741 }
742 } else {
743 Self::Disabled
744 }
745 }
746
747 fn hidden(&self) -> Self {
748 match *self {
749 Self::Enabled {
750 setting_configuration,
751 ..
752 } => Self::Enabled {
753 setting_configuration,
754 toggle_override: setting_configuration,
755 },
756 Self::Disabled => Self::Disabled,
757 }
758 }
759
760 fn disabled(&self) -> bool {
761 matches!(*self, Self::Disabled)
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832/// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
846 None
847 }
848
849 fn to_any(&self) -> &dyn std::any::Any;
850
851 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
852 None
853 }
854}
855
856struct ChangeLocation {
857 current: Option<Vec<Anchor>>,
858 original: Vec<Anchor>,
859}
860impl ChangeLocation {
861 fn locations(&self) -> &[Anchor] {
862 self.current.as_ref().unwrap_or(&self.original)
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<ChangeLocation>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|change| change.locations())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if let Some(last) = self.changes.last_mut()
902 && group
903 {
904 last.current = Some(new_positions)
905 } else {
906 self.changes.push(ChangeLocation {
907 original: new_positions,
908 current: None,
909 });
910 }
911 }
912
913 pub fn last(&self) -> Option<&[Anchor]> {
914 self.changes.last().map(|change| change.locations())
915 }
916
917 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
918 self.changes.last().map(|change| change.original.as_slice())
919 }
920
921 pub fn invert_last_group(&mut self) {
922 if let Some(last) = self.changes.last_mut()
923 && let Some(current) = last.current.as_mut()
924 {
925 mem::swap(&mut last.original, current);
926 }
927 }
928}
929
930#[derive(Clone)]
931struct InlineBlamePopoverState {
932 scroll_handle: ScrollHandle,
933 commit_message: Option<ParsedCommitMessage>,
934 markdown: Entity<Markdown>,
935}
936
937struct InlineBlamePopover {
938 position: gpui::Point<Pixels>,
939 hide_task: Option<Task<()>>,
940 popover_bounds: Option<Bounds<Pixels>>,
941 popover_state: InlineBlamePopoverState,
942 keyboard_grace: bool,
943}
944
945enum SelectionDragState {
946 /// State when no drag related activity is detected.
947 None,
948 /// State when the mouse is down on a selection that is about to be dragged.
949 ReadyToDrag {
950 selection: Selection<Anchor>,
951 click_position: gpui::Point<Pixels>,
952 mouse_down_time: Instant,
953 },
954 /// State when the mouse is dragging the selection in the editor.
955 Dragging {
956 selection: Selection<Anchor>,
957 drop_cursor: Selection<Anchor>,
958 hide_drop_cursor: bool,
959 },
960}
961
962enum ColumnarSelectionState {
963 FromMouse {
964 selection_tail: Anchor,
965 display_point: Option<DisplayPoint>,
966 },
967 FromSelection {
968 selection_tail: Anchor,
969 },
970}
971
972/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
973/// a breakpoint on them.
974#[derive(Clone, Copy, Debug, PartialEq, Eq)]
975struct PhantomBreakpointIndicator {
976 display_row: DisplayRow,
977 /// There's a small debounce between hovering over the line and showing the indicator.
978 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
979 is_active: bool,
980 collides_with_existing_breakpoint: bool,
981}
982
983/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
984///
985/// See the [module level documentation](self) for more information.
986pub struct Editor {
987 focus_handle: FocusHandle,
988 last_focused_descendant: Option<WeakFocusHandle>,
989 /// The text buffer being edited
990 buffer: Entity<MultiBuffer>,
991 /// Map of how text in the buffer should be displayed.
992 /// Handles soft wraps, folds, fake inlay text insertions, etc.
993 pub display_map: Entity<DisplayMap>,
994 placeholder_display_map: Option<Entity<DisplayMap>>,
995 pub selections: SelectionsCollection,
996 pub scroll_manager: ScrollManager,
997 /// When inline assist editors are linked, they all render cursors because
998 /// typing enters text into each of them, even the ones that aren't focused.
999 pub(crate) show_cursor_when_unfocused: bool,
1000 columnar_selection_state: Option<ColumnarSelectionState>,
1001 add_selections_state: Option<AddSelectionsState>,
1002 select_next_state: Option<SelectNextState>,
1003 select_prev_state: Option<SelectNextState>,
1004 selection_history: SelectionHistory,
1005 defer_selection_effects: bool,
1006 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1007 autoclose_regions: Vec<AutocloseRegion>,
1008 snippet_stack: InvalidationStack<SnippetState>,
1009 select_syntax_node_history: SelectSyntaxNodeHistory,
1010 ime_transaction: Option<TransactionId>,
1011 pub diagnostics_max_severity: DiagnosticSeverity,
1012 active_diagnostics: ActiveDiagnostic,
1013 show_inline_diagnostics: bool,
1014 inline_diagnostics_update: Task<()>,
1015 inline_diagnostics_enabled: bool,
1016 diagnostics_enabled: bool,
1017 word_completions_enabled: bool,
1018 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1019 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1020 hard_wrap: Option<usize>,
1021 project: Option<Entity<Project>>,
1022 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1023 completion_provider: Option<Rc<dyn CompletionProvider>>,
1024 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1025 blink_manager: Entity<BlinkManager>,
1026 show_cursor_names: bool,
1027 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1028 pub show_local_selections: bool,
1029 mode: EditorMode,
1030 show_breadcrumbs: bool,
1031 show_gutter: bool,
1032 show_scrollbars: ScrollbarAxes,
1033 minimap_visibility: MinimapVisibility,
1034 offset_content: bool,
1035 disable_expand_excerpt_buttons: bool,
1036 show_line_numbers: Option<bool>,
1037 use_relative_line_numbers: Option<bool>,
1038 show_git_diff_gutter: Option<bool>,
1039 show_code_actions: Option<bool>,
1040 show_runnables: Option<bool>,
1041 show_breakpoints: Option<bool>,
1042 show_wrap_guides: Option<bool>,
1043 show_indent_guides: Option<bool>,
1044 highlight_order: usize,
1045 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1046 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1047 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1048 scrollbar_marker_state: ScrollbarMarkerState,
1049 active_indent_guides_state: ActiveIndentGuidesState,
1050 nav_history: Option<ItemNavHistory>,
1051 context_menu: RefCell<Option<CodeContextMenu>>,
1052 context_menu_options: Option<ContextMenuOptions>,
1053 mouse_context_menu: Option<MouseContextMenu>,
1054 completion_tasks: Vec<(CompletionId, Task<()>)>,
1055 inline_blame_popover: Option<InlineBlamePopover>,
1056 inline_blame_popover_show_task: Option<Task<()>>,
1057 signature_help_state: SignatureHelpState,
1058 auto_signature_help: Option<bool>,
1059 find_all_references_task_sources: Vec<Anchor>,
1060 next_completion_id: CompletionId,
1061 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1062 code_actions_task: Option<Task<Result<()>>>,
1063 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1064 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1065 document_highlights_task: Option<Task<()>>,
1066 linked_editing_range_task: Option<Task<Option<()>>>,
1067 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1068 pending_rename: Option<RenameState>,
1069 searchable: bool,
1070 cursor_shape: CursorShape,
1071 current_line_highlight: Option<CurrentLineHighlight>,
1072 autoindent_mode: Option<AutoindentMode>,
1073 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1074 input_enabled: bool,
1075 use_modal_editing: bool,
1076 read_only: bool,
1077 leader_id: Option<CollaboratorId>,
1078 remote_id: Option<ViewId>,
1079 pub hover_state: HoverState,
1080 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1081 gutter_hovered: bool,
1082 hovered_link_state: Option<HoveredLinkState>,
1083 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1084 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1085 active_edit_prediction: Option<EditPredictionState>,
1086 /// Used to prevent flickering as the user types while the menu is open
1087 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1088 edit_prediction_settings: EditPredictionSettings,
1089 edit_predictions_hidden_for_vim_mode: bool,
1090 show_edit_predictions_override: Option<bool>,
1091 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1092 edit_prediction_preview: EditPredictionPreview,
1093 edit_prediction_indent_conflict: bool,
1094 edit_prediction_requires_modifier_in_indent_conflict: bool,
1095 next_inlay_id: usize,
1096 next_color_inlay_id: usize,
1097 _subscriptions: Vec<Subscription>,
1098 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1099 gutter_dimensions: GutterDimensions,
1100 style: Option<EditorStyle>,
1101 text_style_refinement: Option<TextStyleRefinement>,
1102 next_editor_action_id: EditorActionId,
1103 editor_actions: Rc<
1104 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1105 >,
1106 use_autoclose: bool,
1107 use_auto_surround: bool,
1108 auto_replace_emoji_shortcode: bool,
1109 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1110 show_git_blame_gutter: bool,
1111 show_git_blame_inline: bool,
1112 show_git_blame_inline_delay_task: Option<Task<()>>,
1113 git_blame_inline_enabled: bool,
1114 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1115 serialize_dirty_buffers: bool,
1116 show_selection_menu: Option<bool>,
1117 blame: Option<Entity<GitBlame>>,
1118 blame_subscription: Option<Subscription>,
1119 custom_context_menu: Option<
1120 Box<
1121 dyn 'static
1122 + Fn(
1123 &mut Self,
1124 DisplayPoint,
1125 &mut Window,
1126 &mut Context<Self>,
1127 ) -> Option<Entity<ui::ContextMenu>>,
1128 >,
1129 >,
1130 last_bounds: Option<Bounds<Pixels>>,
1131 last_position_map: Option<Rc<PositionMap>>,
1132 expect_bounds_change: Option<Bounds<Pixels>>,
1133 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1134 tasks_update_task: Option<Task<()>>,
1135 breakpoint_store: Option<Entity<BreakpointStore>>,
1136 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1137 hovered_diff_hunk_row: Option<DisplayRow>,
1138 pull_diagnostics_task: Task<()>,
1139 in_project_search: bool,
1140 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1141 breadcrumb_header: Option<String>,
1142 focused_block: Option<FocusedBlock>,
1143 next_scroll_position: NextScrollCursorCenterTopBottom,
1144 addons: HashMap<TypeId, Box<dyn Addon>>,
1145 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1146 load_diff_task: Option<Shared<Task<()>>>,
1147 /// Whether we are temporarily displaying a diff other than git's
1148 temporary_diff_override: bool,
1149 selection_mark_mode: bool,
1150 toggle_fold_multiple_buffers: Task<()>,
1151 _scroll_cursor_center_top_bottom_task: Task<()>,
1152 serialize_selections: Task<()>,
1153 serialize_folds: Task<()>,
1154 mouse_cursor_hidden: bool,
1155 minimap: Option<Entity<Self>>,
1156 hide_mouse_mode: HideMouseMode,
1157 pub change_list: ChangeList,
1158 inline_value_cache: InlineValueCache,
1159 selection_drag_state: SelectionDragState,
1160 colors: Option<LspColorData>,
1161 post_scroll_update: Task<()>,
1162 refresh_colors_task: Task<()>,
1163 inlay_hints: Option<LspInlayHintData>,
1164 folding_newlines: Task<()>,
1165 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1166}
1167
1168fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1169 if debounce_ms > 0 {
1170 Some(Duration::from_millis(debounce_ms))
1171 } else {
1172 None
1173 }
1174}
1175
1176#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1177enum NextScrollCursorCenterTopBottom {
1178 #[default]
1179 Center,
1180 Top,
1181 Bottom,
1182}
1183
1184impl NextScrollCursorCenterTopBottom {
1185 fn next(&self) -> Self {
1186 match self {
1187 Self::Center => Self::Top,
1188 Self::Top => Self::Bottom,
1189 Self::Bottom => Self::Center,
1190 }
1191 }
1192}
1193
1194#[derive(Clone)]
1195pub struct EditorSnapshot {
1196 pub mode: EditorMode,
1197 show_gutter: bool,
1198 show_line_numbers: Option<bool>,
1199 show_git_diff_gutter: Option<bool>,
1200 show_code_actions: Option<bool>,
1201 show_runnables: Option<bool>,
1202 show_breakpoints: Option<bool>,
1203 git_blame_gutter_max_author_length: Option<usize>,
1204 pub display_snapshot: DisplaySnapshot,
1205 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1206 is_focused: bool,
1207 scroll_anchor: ScrollAnchor,
1208 ongoing_scroll: OngoingScroll,
1209 current_line_highlight: CurrentLineHighlight,
1210 gutter_hovered: bool,
1211}
1212
1213#[derive(Default, Debug, Clone, Copy)]
1214pub struct GutterDimensions {
1215 pub left_padding: Pixels,
1216 pub right_padding: Pixels,
1217 pub width: Pixels,
1218 pub margin: Pixels,
1219 pub git_blame_entries_width: Option<Pixels>,
1220}
1221
1222impl GutterDimensions {
1223 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1224 Self {
1225 margin: Self::default_gutter_margin(font_id, font_size, cx),
1226 ..Default::default()
1227 }
1228 }
1229
1230 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1231 -cx.text_system().descent(font_id, font_size)
1232 }
1233 /// The full width of the space taken up by the gutter.
1234 pub fn full_width(&self) -> Pixels {
1235 self.margin + self.width
1236 }
1237
1238 /// The width of the space reserved for the fold indicators,
1239 /// use alongside 'justify_end' and `gutter_width` to
1240 /// right align content with the line numbers
1241 pub fn fold_area_width(&self) -> Pixels {
1242 self.margin + self.right_padding
1243 }
1244}
1245
1246struct CharacterDimensions {
1247 em_width: Pixels,
1248 em_advance: Pixels,
1249 line_height: Pixels,
1250}
1251
1252#[derive(Debug)]
1253pub struct RemoteSelection {
1254 pub replica_id: ReplicaId,
1255 pub selection: Selection<Anchor>,
1256 pub cursor_shape: CursorShape,
1257 pub collaborator_id: CollaboratorId,
1258 pub line_mode: bool,
1259 pub user_name: Option<SharedString>,
1260 pub color: PlayerColor,
1261}
1262
1263#[derive(Clone, Debug)]
1264struct SelectionHistoryEntry {
1265 selections: Arc<[Selection<Anchor>]>,
1266 select_next_state: Option<SelectNextState>,
1267 select_prev_state: Option<SelectNextState>,
1268 add_selections_state: Option<AddSelectionsState>,
1269}
1270
1271#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1272enum SelectionHistoryMode {
1273 Normal,
1274 Undoing,
1275 Redoing,
1276 Skipping,
1277}
1278
1279#[derive(Clone, PartialEq, Eq, Hash)]
1280struct HoveredCursor {
1281 replica_id: ReplicaId,
1282 selection_id: usize,
1283}
1284
1285impl Default for SelectionHistoryMode {
1286 fn default() -> Self {
1287 Self::Normal
1288 }
1289}
1290
1291#[derive(Debug)]
1292/// SelectionEffects controls the side-effects of updating the selection.
1293///
1294/// The default behaviour does "what you mostly want":
1295/// - it pushes to the nav history if the cursor moved by >10 lines
1296/// - it re-triggers completion requests
1297/// - it scrolls to fit
1298///
1299/// You might want to modify these behaviours. For example when doing a "jump"
1300/// like go to definition, we always want to add to nav history; but when scrolling
1301/// in vim mode we never do.
1302///
1303/// Similarly, you might want to disable scrolling if you don't want the viewport to
1304/// move.
1305#[derive(Clone)]
1306pub struct SelectionEffects {
1307 nav_history: Option<bool>,
1308 completions: bool,
1309 scroll: Option<Autoscroll>,
1310}
1311
1312impl Default for SelectionEffects {
1313 fn default() -> Self {
1314 Self {
1315 nav_history: None,
1316 completions: true,
1317 scroll: Some(Autoscroll::fit()),
1318 }
1319 }
1320}
1321impl SelectionEffects {
1322 pub fn scroll(scroll: Autoscroll) -> Self {
1323 Self {
1324 scroll: Some(scroll),
1325 ..Default::default()
1326 }
1327 }
1328
1329 pub fn no_scroll() -> Self {
1330 Self {
1331 scroll: None,
1332 ..Default::default()
1333 }
1334 }
1335
1336 pub fn completions(self, completions: bool) -> Self {
1337 Self {
1338 completions,
1339 ..self
1340 }
1341 }
1342
1343 pub fn nav_history(self, nav_history: bool) -> Self {
1344 Self {
1345 nav_history: Some(nav_history),
1346 ..self
1347 }
1348 }
1349}
1350
1351struct DeferredSelectionEffectsState {
1352 changed: bool,
1353 effects: SelectionEffects,
1354 old_cursor_position: Anchor,
1355 history_entry: SelectionHistoryEntry,
1356}
1357
1358#[derive(Default)]
1359struct SelectionHistory {
1360 #[allow(clippy::type_complexity)]
1361 selections_by_transaction:
1362 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1363 mode: SelectionHistoryMode,
1364 undo_stack: VecDeque<SelectionHistoryEntry>,
1365 redo_stack: VecDeque<SelectionHistoryEntry>,
1366}
1367
1368impl SelectionHistory {
1369 #[track_caller]
1370 fn insert_transaction(
1371 &mut self,
1372 transaction_id: TransactionId,
1373 selections: Arc<[Selection<Anchor>]>,
1374 ) {
1375 if selections.is_empty() {
1376 log::error!(
1377 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1378 std::panic::Location::caller()
1379 );
1380 return;
1381 }
1382 self.selections_by_transaction
1383 .insert(transaction_id, (selections, None));
1384 }
1385
1386 #[allow(clippy::type_complexity)]
1387 fn transaction(
1388 &self,
1389 transaction_id: TransactionId,
1390 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1391 self.selections_by_transaction.get(&transaction_id)
1392 }
1393
1394 #[allow(clippy::type_complexity)]
1395 fn transaction_mut(
1396 &mut self,
1397 transaction_id: TransactionId,
1398 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1399 self.selections_by_transaction.get_mut(&transaction_id)
1400 }
1401
1402 fn push(&mut self, entry: SelectionHistoryEntry) {
1403 if !entry.selections.is_empty() {
1404 match self.mode {
1405 SelectionHistoryMode::Normal => {
1406 self.push_undo(entry);
1407 self.redo_stack.clear();
1408 }
1409 SelectionHistoryMode::Undoing => self.push_redo(entry),
1410 SelectionHistoryMode::Redoing => self.push_undo(entry),
1411 SelectionHistoryMode::Skipping => {}
1412 }
1413 }
1414 }
1415
1416 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1417 if self
1418 .undo_stack
1419 .back()
1420 .is_none_or(|e| e.selections != entry.selections)
1421 {
1422 self.undo_stack.push_back(entry);
1423 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1424 self.undo_stack.pop_front();
1425 }
1426 }
1427 }
1428
1429 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1430 if self
1431 .redo_stack
1432 .back()
1433 .is_none_or(|e| e.selections != entry.selections)
1434 {
1435 self.redo_stack.push_back(entry);
1436 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1437 self.redo_stack.pop_front();
1438 }
1439 }
1440 }
1441}
1442
1443#[derive(Clone, Copy)]
1444pub struct RowHighlightOptions {
1445 pub autoscroll: bool,
1446 pub include_gutter: bool,
1447}
1448
1449impl Default for RowHighlightOptions {
1450 fn default() -> Self {
1451 Self {
1452 autoscroll: Default::default(),
1453 include_gutter: true,
1454 }
1455 }
1456}
1457
1458struct RowHighlight {
1459 index: usize,
1460 range: Range<Anchor>,
1461 color: Hsla,
1462 options: RowHighlightOptions,
1463 type_id: TypeId,
1464}
1465
1466#[derive(Clone, Debug)]
1467struct AddSelectionsState {
1468 groups: Vec<AddSelectionsGroup>,
1469}
1470
1471#[derive(Clone, Debug)]
1472struct AddSelectionsGroup {
1473 above: bool,
1474 stack: Vec<usize>,
1475}
1476
1477#[derive(Clone)]
1478struct SelectNextState {
1479 query: AhoCorasick,
1480 wordwise: bool,
1481 done: bool,
1482}
1483
1484impl std::fmt::Debug for SelectNextState {
1485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1486 f.debug_struct(std::any::type_name::<Self>())
1487 .field("wordwise", &self.wordwise)
1488 .field("done", &self.done)
1489 .finish()
1490 }
1491}
1492
1493#[derive(Debug)]
1494struct AutocloseRegion {
1495 selection_id: usize,
1496 range: Range<Anchor>,
1497 pair: BracketPair,
1498}
1499
1500#[derive(Debug)]
1501struct SnippetState {
1502 ranges: Vec<Vec<Range<Anchor>>>,
1503 active_index: usize,
1504 choices: Vec<Option<Vec<String>>>,
1505}
1506
1507#[doc(hidden)]
1508pub struct RenameState {
1509 pub range: Range<Anchor>,
1510 pub old_name: Arc<str>,
1511 pub editor: Entity<Editor>,
1512 block_id: CustomBlockId,
1513}
1514
1515struct InvalidationStack<T>(Vec<T>);
1516
1517struct RegisteredEditPredictionProvider {
1518 provider: Arc<dyn EditPredictionProviderHandle>,
1519 _subscription: Subscription,
1520}
1521
1522#[derive(Debug, PartialEq, Eq)]
1523pub struct ActiveDiagnosticGroup {
1524 pub active_range: Range<Anchor>,
1525 pub active_message: String,
1526 pub group_id: usize,
1527 pub blocks: HashSet<CustomBlockId>,
1528}
1529
1530#[derive(Debug, PartialEq, Eq)]
1531
1532pub(crate) enum ActiveDiagnostic {
1533 None,
1534 All,
1535 Group(ActiveDiagnosticGroup),
1536}
1537
1538#[derive(Serialize, Deserialize, Clone, Debug)]
1539pub struct ClipboardSelection {
1540 /// The number of bytes in this selection.
1541 pub len: usize,
1542 /// Whether this was a full-line selection.
1543 pub is_entire_line: bool,
1544 /// The indentation of the first line when this content was originally copied.
1545 pub first_line_indent: u32,
1546}
1547
1548// selections, scroll behavior, was newest selection reversed
1549type SelectSyntaxNodeHistoryState = (
1550 Box<[Selection<usize>]>,
1551 SelectSyntaxNodeScrollBehavior,
1552 bool,
1553);
1554
1555#[derive(Default)]
1556struct SelectSyntaxNodeHistory {
1557 stack: Vec<SelectSyntaxNodeHistoryState>,
1558 // disable temporarily to allow changing selections without losing the stack
1559 pub disable_clearing: bool,
1560}
1561
1562impl SelectSyntaxNodeHistory {
1563 pub fn try_clear(&mut self) {
1564 if !self.disable_clearing {
1565 self.stack.clear();
1566 }
1567 }
1568
1569 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1570 self.stack.push(selection);
1571 }
1572
1573 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1574 self.stack.pop()
1575 }
1576}
1577
1578enum SelectSyntaxNodeScrollBehavior {
1579 CursorTop,
1580 FitSelection,
1581 CursorBottom,
1582}
1583
1584#[derive(Debug)]
1585pub(crate) struct NavigationData {
1586 cursor_anchor: Anchor,
1587 cursor_position: Point,
1588 scroll_anchor: ScrollAnchor,
1589 scroll_top_row: u32,
1590}
1591
1592#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1593pub enum GotoDefinitionKind {
1594 Symbol,
1595 Declaration,
1596 Type,
1597 Implementation,
1598}
1599
1600pub enum FormatTarget {
1601 Buffers(HashSet<Entity<Buffer>>),
1602 Ranges(Vec<Range<MultiBufferPoint>>),
1603}
1604
1605pub(crate) struct FocusedBlock {
1606 id: BlockId,
1607 focus_handle: WeakFocusHandle,
1608}
1609
1610#[derive(Clone)]
1611enum JumpData {
1612 MultiBufferRow {
1613 row: MultiBufferRow,
1614 line_offset_from_top: u32,
1615 },
1616 MultiBufferPoint {
1617 excerpt_id: ExcerptId,
1618 position: Point,
1619 anchor: text::Anchor,
1620 line_offset_from_top: u32,
1621 },
1622}
1623
1624pub enum MultibufferSelectionMode {
1625 First,
1626 All,
1627}
1628
1629#[derive(Clone, Copy, Debug, Default)]
1630pub struct RewrapOptions {
1631 pub override_language_settings: bool,
1632 pub preserve_existing_whitespace: bool,
1633}
1634
1635impl Editor {
1636 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1637 let buffer = cx.new(|cx| Buffer::local("", cx));
1638 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1639 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1640 }
1641
1642 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1643 let buffer = cx.new(|cx| Buffer::local("", cx));
1644 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1645 Self::new(EditorMode::full(), buffer, None, window, cx)
1646 }
1647
1648 pub fn auto_height(
1649 min_lines: usize,
1650 max_lines: usize,
1651 window: &mut Window,
1652 cx: &mut Context<Self>,
1653 ) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(
1657 EditorMode::AutoHeight {
1658 min_lines,
1659 max_lines: Some(max_lines),
1660 },
1661 buffer,
1662 None,
1663 window,
1664 cx,
1665 )
1666 }
1667
1668 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1669 /// The editor grows as tall as needed to fit its content.
1670 pub fn auto_height_unbounded(
1671 min_lines: usize,
1672 window: &mut Window,
1673 cx: &mut Context<Self>,
1674 ) -> Self {
1675 let buffer = cx.new(|cx| Buffer::local("", cx));
1676 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1677 Self::new(
1678 EditorMode::AutoHeight {
1679 min_lines,
1680 max_lines: None,
1681 },
1682 buffer,
1683 None,
1684 window,
1685 cx,
1686 )
1687 }
1688
1689 pub fn for_buffer(
1690 buffer: Entity<Buffer>,
1691 project: Option<Entity<Project>>,
1692 window: &mut Window,
1693 cx: &mut Context<Self>,
1694 ) -> Self {
1695 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1696 Self::new(EditorMode::full(), buffer, project, window, cx)
1697 }
1698
1699 pub fn for_multibuffer(
1700 buffer: Entity<MultiBuffer>,
1701 project: Option<Entity<Project>>,
1702 window: &mut Window,
1703 cx: &mut Context<Self>,
1704 ) -> Self {
1705 Self::new(EditorMode::full(), buffer, project, window, cx)
1706 }
1707
1708 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1709 let mut clone = Self::new(
1710 self.mode.clone(),
1711 self.buffer.clone(),
1712 self.project.clone(),
1713 window,
1714 cx,
1715 );
1716 self.display_map.update(cx, |display_map, cx| {
1717 let snapshot = display_map.snapshot(cx);
1718 clone.display_map.update(cx, |display_map, cx| {
1719 display_map.set_state(&snapshot, cx);
1720 });
1721 });
1722 clone.folds_did_change(cx);
1723 clone.selections.clone_state(&self.selections);
1724 clone.scroll_manager.clone_state(&self.scroll_manager);
1725 clone.searchable = self.searchable;
1726 clone.read_only = self.read_only;
1727 clone
1728 }
1729
1730 pub fn new(
1731 mode: EditorMode,
1732 buffer: Entity<MultiBuffer>,
1733 project: Option<Entity<Project>>,
1734 window: &mut Window,
1735 cx: &mut Context<Self>,
1736 ) -> Self {
1737 Editor::new_internal(mode, buffer, project, None, window, cx)
1738 }
1739
1740 fn new_internal(
1741 mode: EditorMode,
1742 multi_buffer: Entity<MultiBuffer>,
1743 project: Option<Entity<Project>>,
1744 display_map: Option<Entity<DisplayMap>>,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 debug_assert!(
1749 display_map.is_none() || mode.is_minimap(),
1750 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1751 );
1752
1753 let full_mode = mode.is_full();
1754 let is_minimap = mode.is_minimap();
1755 let diagnostics_max_severity = if full_mode {
1756 EditorSettings::get_global(cx)
1757 .diagnostics_max_severity
1758 .unwrap_or(DiagnosticSeverity::Hint)
1759 } else {
1760 DiagnosticSeverity::Off
1761 };
1762 let style = window.text_style();
1763 let font_size = style.font_size.to_pixels(window.rem_size());
1764 let editor = cx.entity().downgrade();
1765 let fold_placeholder = FoldPlaceholder {
1766 constrain_width: false,
1767 render: Arc::new(move |fold_id, fold_range, cx| {
1768 let editor = editor.clone();
1769 div()
1770 .id(fold_id)
1771 .bg(cx.theme().colors().ghost_element_background)
1772 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1773 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1774 .rounded_xs()
1775 .size_full()
1776 .cursor_pointer()
1777 .child("⋯")
1778 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1779 .on_click(move |_, _window, cx| {
1780 editor
1781 .update(cx, |editor, cx| {
1782 editor.unfold_ranges(
1783 &[fold_range.start..fold_range.end],
1784 true,
1785 false,
1786 cx,
1787 );
1788 cx.stop_propagation();
1789 })
1790 .ok();
1791 })
1792 .into_any()
1793 }),
1794 merge_adjacent: true,
1795 ..FoldPlaceholder::default()
1796 };
1797 let display_map = display_map.unwrap_or_else(|| {
1798 cx.new(|cx| {
1799 DisplayMap::new(
1800 multi_buffer.clone(),
1801 style.font(),
1802 font_size,
1803 None,
1804 FILE_HEADER_HEIGHT,
1805 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1806 fold_placeholder,
1807 diagnostics_max_severity,
1808 cx,
1809 )
1810 })
1811 });
1812
1813 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1814
1815 let blink_manager = cx.new(|cx| {
1816 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1817 if is_minimap {
1818 blink_manager.disable(cx);
1819 }
1820 blink_manager
1821 });
1822
1823 let soft_wrap_mode_override =
1824 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1825
1826 let mut project_subscriptions = Vec::new();
1827 if full_mode && let Some(project) = project.as_ref() {
1828 project_subscriptions.push(cx.subscribe_in(
1829 project,
1830 window,
1831 |editor, _, event, window, cx| match event {
1832 project::Event::RefreshCodeLens => {
1833 // we always query lens with actions, without storing them, always refreshing them
1834 }
1835 project::Event::RefreshInlayHints {
1836 server_id,
1837 request_id,
1838 } => {
1839 editor.refresh_inlay_hints(
1840 InlayHintRefreshReason::RefreshRequested {
1841 server_id: *server_id,
1842 request_id: *request_id,
1843 },
1844 cx,
1845 );
1846 }
1847 project::Event::LanguageServerRemoved(..) => {
1848 if editor.tasks_update_task.is_none() {
1849 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1850 }
1851 editor.registered_buffers.clear();
1852 editor.register_visible_buffers(cx);
1853 }
1854 project::Event::LanguageServerAdded(..) => {
1855 if editor.tasks_update_task.is_none() {
1856 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1857 }
1858 }
1859 project::Event::SnippetEdit(id, snippet_edits) => {
1860 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1861 let focus_handle = editor.focus_handle(cx);
1862 if focus_handle.is_focused(window) {
1863 let snapshot = buffer.read(cx).snapshot();
1864 for (range, snippet) in snippet_edits {
1865 let editor_range =
1866 language::range_from_lsp(*range).to_offset(&snapshot);
1867 editor
1868 .insert_snippet(
1869 &[editor_range],
1870 snippet.clone(),
1871 window,
1872 cx,
1873 )
1874 .ok();
1875 }
1876 }
1877 }
1878 }
1879 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1880 let buffer_id = *buffer_id;
1881 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1882 editor.register_buffer(buffer_id, cx);
1883 editor.update_lsp_data(Some(buffer_id), window, cx);
1884 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1885 refresh_linked_ranges(editor, window, cx);
1886 editor.refresh_code_actions(window, cx);
1887 editor.refresh_document_highlights(cx);
1888 }
1889 }
1890
1891 project::Event::EntryRenamed(transaction) => {
1892 let Some(workspace) = editor.workspace() else {
1893 return;
1894 };
1895 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1896 else {
1897 return;
1898 };
1899 if active_editor.entity_id() == cx.entity_id() {
1900 let edited_buffers_already_open = {
1901 let other_editors: Vec<Entity<Editor>> = workspace
1902 .read(cx)
1903 .panes()
1904 .iter()
1905 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1906 .filter(|editor| editor.entity_id() != cx.entity_id())
1907 .collect();
1908
1909 transaction.0.keys().all(|buffer| {
1910 other_editors.iter().any(|editor| {
1911 let multi_buffer = editor.read(cx).buffer();
1912 multi_buffer.read(cx).is_singleton()
1913 && multi_buffer.read(cx).as_singleton().map_or(
1914 false,
1915 |singleton| {
1916 singleton.entity_id() == buffer.entity_id()
1917 },
1918 )
1919 })
1920 })
1921 };
1922
1923 if !edited_buffers_already_open {
1924 let workspace = workspace.downgrade();
1925 let transaction = transaction.clone();
1926 cx.defer_in(window, move |_, window, cx| {
1927 cx.spawn_in(window, async move |editor, cx| {
1928 Self::open_project_transaction(
1929 &editor,
1930 workspace,
1931 transaction,
1932 "Rename".to_string(),
1933 cx,
1934 )
1935 .await
1936 .ok()
1937 })
1938 .detach();
1939 });
1940 }
1941 }
1942 }
1943
1944 _ => {}
1945 },
1946 ));
1947 if let Some(task_inventory) = project
1948 .read(cx)
1949 .task_store()
1950 .read(cx)
1951 .task_inventory()
1952 .cloned()
1953 {
1954 project_subscriptions.push(cx.observe_in(
1955 &task_inventory,
1956 window,
1957 |editor, _, window, cx| {
1958 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1959 },
1960 ));
1961 };
1962
1963 project_subscriptions.push(cx.subscribe_in(
1964 &project.read(cx).breakpoint_store(),
1965 window,
1966 |editor, _, event, window, cx| match event {
1967 BreakpointStoreEvent::ClearDebugLines => {
1968 editor.clear_row_highlights::<ActiveDebugLine>();
1969 editor.refresh_inline_values(cx);
1970 }
1971 BreakpointStoreEvent::SetDebugLine => {
1972 if editor.go_to_active_debug_line(window, cx) {
1973 cx.stop_propagation();
1974 }
1975
1976 editor.refresh_inline_values(cx);
1977 }
1978 _ => {}
1979 },
1980 ));
1981 let git_store = project.read(cx).git_store().clone();
1982 let project = project.clone();
1983 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1984 if let GitStoreEvent::RepositoryAdded = event {
1985 this.load_diff_task = Some(
1986 update_uncommitted_diff_for_buffer(
1987 cx.entity(),
1988 &project,
1989 this.buffer.read(cx).all_buffers(),
1990 this.buffer.clone(),
1991 cx,
1992 )
1993 .shared(),
1994 );
1995 }
1996 }));
1997 }
1998
1999 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2000
2001 let inlay_hint_settings =
2002 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2003 let focus_handle = cx.focus_handle();
2004 if !is_minimap {
2005 cx.on_focus(&focus_handle, window, Self::handle_focus)
2006 .detach();
2007 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2008 .detach();
2009 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2010 .detach();
2011 cx.on_blur(&focus_handle, window, Self::handle_blur)
2012 .detach();
2013 cx.observe_pending_input(window, Self::observe_pending_input)
2014 .detach();
2015 }
2016
2017 let show_indent_guides =
2018 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2019 Some(false)
2020 } else {
2021 None
2022 };
2023
2024 let breakpoint_store = match (&mode, project.as_ref()) {
2025 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2026 _ => None,
2027 };
2028
2029 let mut code_action_providers = Vec::new();
2030 let mut load_uncommitted_diff = None;
2031 if let Some(project) = project.clone() {
2032 load_uncommitted_diff = Some(
2033 update_uncommitted_diff_for_buffer(
2034 cx.entity(),
2035 &project,
2036 multi_buffer.read(cx).all_buffers(),
2037 multi_buffer.clone(),
2038 cx,
2039 )
2040 .shared(),
2041 );
2042 code_action_providers.push(Rc::new(project) as Rc<_>);
2043 }
2044
2045 let mut editor = Self {
2046 focus_handle,
2047 show_cursor_when_unfocused: false,
2048 last_focused_descendant: None,
2049 buffer: multi_buffer.clone(),
2050 display_map: display_map.clone(),
2051 placeholder_display_map: None,
2052 selections,
2053 scroll_manager: ScrollManager::new(cx),
2054 columnar_selection_state: None,
2055 add_selections_state: None,
2056 select_next_state: None,
2057 select_prev_state: None,
2058 selection_history: SelectionHistory::default(),
2059 defer_selection_effects: false,
2060 deferred_selection_effects_state: None,
2061 autoclose_regions: Vec::new(),
2062 snippet_stack: InvalidationStack::default(),
2063 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2064 ime_transaction: None,
2065 active_diagnostics: ActiveDiagnostic::None,
2066 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2067 inline_diagnostics_update: Task::ready(()),
2068 inline_diagnostics: Vec::new(),
2069 soft_wrap_mode_override,
2070 diagnostics_max_severity,
2071 hard_wrap: None,
2072 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2073 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2074 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2075 project,
2076 blink_manager: blink_manager.clone(),
2077 show_local_selections: true,
2078 show_scrollbars: ScrollbarAxes {
2079 horizontal: full_mode,
2080 vertical: full_mode,
2081 },
2082 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2083 offset_content: !matches!(mode, EditorMode::SingleLine),
2084 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2085 show_gutter: full_mode,
2086 show_line_numbers: (!full_mode).then_some(false),
2087 use_relative_line_numbers: None,
2088 disable_expand_excerpt_buttons: !full_mode,
2089 show_git_diff_gutter: None,
2090 show_code_actions: None,
2091 show_runnables: None,
2092 show_breakpoints: None,
2093 show_wrap_guides: None,
2094 show_indent_guides,
2095 highlight_order: 0,
2096 highlighted_rows: HashMap::default(),
2097 background_highlights: HashMap::default(),
2098 gutter_highlights: HashMap::default(),
2099 scrollbar_marker_state: ScrollbarMarkerState::default(),
2100 active_indent_guides_state: ActiveIndentGuidesState::default(),
2101 nav_history: None,
2102 context_menu: RefCell::new(None),
2103 context_menu_options: None,
2104 mouse_context_menu: None,
2105 completion_tasks: Vec::new(),
2106 inline_blame_popover: None,
2107 inline_blame_popover_show_task: None,
2108 signature_help_state: SignatureHelpState::default(),
2109 auto_signature_help: None,
2110 find_all_references_task_sources: Vec::new(),
2111 next_completion_id: 0,
2112 next_inlay_id: 0,
2113 code_action_providers,
2114 available_code_actions: None,
2115 code_actions_task: None,
2116 quick_selection_highlight_task: None,
2117 debounced_selection_highlight_task: None,
2118 document_highlights_task: None,
2119 linked_editing_range_task: None,
2120 pending_rename: None,
2121 searchable: !is_minimap,
2122 cursor_shape: EditorSettings::get_global(cx)
2123 .cursor_shape
2124 .unwrap_or_default(),
2125 current_line_highlight: None,
2126 autoindent_mode: Some(AutoindentMode::EachLine),
2127
2128 workspace: None,
2129 input_enabled: !is_minimap,
2130 use_modal_editing: full_mode,
2131 read_only: is_minimap,
2132 use_autoclose: true,
2133 use_auto_surround: true,
2134 auto_replace_emoji_shortcode: false,
2135 jsx_tag_auto_close_enabled_in_any_buffer: false,
2136 leader_id: None,
2137 remote_id: None,
2138 hover_state: HoverState::default(),
2139 pending_mouse_down: None,
2140 hovered_link_state: None,
2141 edit_prediction_provider: None,
2142 active_edit_prediction: None,
2143 stale_edit_prediction_in_menu: None,
2144 edit_prediction_preview: EditPredictionPreview::Inactive {
2145 released_too_fast: false,
2146 },
2147 inline_diagnostics_enabled: full_mode,
2148 diagnostics_enabled: full_mode,
2149 word_completions_enabled: full_mode,
2150 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2151 gutter_hovered: false,
2152 pixel_position_of_newest_cursor: None,
2153 last_bounds: None,
2154 last_position_map: None,
2155 expect_bounds_change: None,
2156 gutter_dimensions: GutterDimensions::default(),
2157 style: None,
2158 show_cursor_names: false,
2159 hovered_cursors: HashMap::default(),
2160 next_editor_action_id: EditorActionId::default(),
2161 editor_actions: Rc::default(),
2162 edit_predictions_hidden_for_vim_mode: false,
2163 show_edit_predictions_override: None,
2164 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2165 edit_prediction_settings: EditPredictionSettings::Disabled,
2166 edit_prediction_indent_conflict: false,
2167 edit_prediction_requires_modifier_in_indent_conflict: true,
2168 custom_context_menu: None,
2169 show_git_blame_gutter: false,
2170 show_git_blame_inline: false,
2171 show_selection_menu: None,
2172 show_git_blame_inline_delay_task: None,
2173 git_blame_inline_enabled: full_mode
2174 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2175 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2176 serialize_dirty_buffers: !is_minimap
2177 && ProjectSettings::get_global(cx)
2178 .session
2179 .restore_unsaved_buffers,
2180 blame: None,
2181 blame_subscription: None,
2182 tasks: BTreeMap::default(),
2183
2184 breakpoint_store,
2185 gutter_breakpoint_indicator: (None, None),
2186 hovered_diff_hunk_row: None,
2187 _subscriptions: (!is_minimap)
2188 .then(|| {
2189 vec![
2190 cx.observe(&multi_buffer, Self::on_buffer_changed),
2191 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2192 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2193 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2194 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2195 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2196 cx.observe_window_activation(window, |editor, window, cx| {
2197 let active = window.is_window_active();
2198 editor.blink_manager.update(cx, |blink_manager, cx| {
2199 if active {
2200 blink_manager.enable(cx);
2201 } else {
2202 blink_manager.disable(cx);
2203 }
2204 });
2205 if active {
2206 editor.show_mouse_cursor(cx);
2207 }
2208 }),
2209 ]
2210 })
2211 .unwrap_or_default(),
2212 tasks_update_task: None,
2213 pull_diagnostics_task: Task::ready(()),
2214 colors: None,
2215 refresh_colors_task: Task::ready(()),
2216 inlay_hints: None,
2217 next_color_inlay_id: 0,
2218 post_scroll_update: Task::ready(()),
2219 linked_edit_ranges: Default::default(),
2220 in_project_search: false,
2221 previous_search_ranges: None,
2222 breadcrumb_header: None,
2223 focused_block: None,
2224 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2225 addons: HashMap::default(),
2226 registered_buffers: HashMap::default(),
2227 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2228 selection_mark_mode: false,
2229 toggle_fold_multiple_buffers: Task::ready(()),
2230 serialize_selections: Task::ready(()),
2231 serialize_folds: Task::ready(()),
2232 text_style_refinement: None,
2233 load_diff_task: load_uncommitted_diff,
2234 temporary_diff_override: false,
2235 mouse_cursor_hidden: false,
2236 minimap: None,
2237 hide_mouse_mode: EditorSettings::get_global(cx)
2238 .hide_mouse
2239 .unwrap_or_default(),
2240 change_list: ChangeList::new(),
2241 mode,
2242 selection_drag_state: SelectionDragState::None,
2243 folding_newlines: Task::ready(()),
2244 lookup_key: None,
2245 };
2246
2247 if is_minimap {
2248 return editor;
2249 }
2250
2251 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2252 editor
2253 ._subscriptions
2254 .push(cx.observe(breakpoints, |_, _, cx| {
2255 cx.notify();
2256 }));
2257 }
2258 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2259 editor._subscriptions.extend(project_subscriptions);
2260
2261 editor._subscriptions.push(cx.subscribe_in(
2262 &cx.entity(),
2263 window,
2264 |editor, _, e: &EditorEvent, window, cx| match e {
2265 EditorEvent::ScrollPositionChanged { local, .. } => {
2266 if *local {
2267 let new_anchor = editor.scroll_manager.anchor();
2268 let snapshot = editor.snapshot(window, cx);
2269 editor.update_restoration_data(cx, move |data| {
2270 data.scroll_position = (
2271 new_anchor.top_row(snapshot.buffer_snapshot()),
2272 new_anchor.offset,
2273 );
2274 });
2275 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2276 editor.inline_blame_popover.take();
2277 }
2278 }
2279 EditorEvent::Edited { .. } => {
2280 if vim_flavor(cx).is_none() {
2281 let display_map = editor.display_snapshot(cx);
2282 let selections = editor.selections.all_adjusted_display(&display_map);
2283 let pop_state = editor
2284 .change_list
2285 .last()
2286 .map(|previous| {
2287 previous.len() == selections.len()
2288 && previous.iter().enumerate().all(|(ix, p)| {
2289 p.to_display_point(&display_map).row()
2290 == selections[ix].head().row()
2291 })
2292 })
2293 .unwrap_or(false);
2294 let new_positions = selections
2295 .into_iter()
2296 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2297 .collect();
2298 editor
2299 .change_list
2300 .push_to_change_list(pop_state, new_positions);
2301 }
2302 }
2303 _ => (),
2304 },
2305 ));
2306
2307 if let Some(dap_store) = editor
2308 .project
2309 .as_ref()
2310 .map(|project| project.read(cx).dap_store())
2311 {
2312 let weak_editor = cx.weak_entity();
2313
2314 editor
2315 ._subscriptions
2316 .push(
2317 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2318 let session_entity = cx.entity();
2319 weak_editor
2320 .update(cx, |editor, cx| {
2321 editor._subscriptions.push(
2322 cx.subscribe(&session_entity, Self::on_debug_session_event),
2323 );
2324 })
2325 .ok();
2326 }),
2327 );
2328
2329 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2330 editor
2331 ._subscriptions
2332 .push(cx.subscribe(&session, Self::on_debug_session_event));
2333 }
2334 }
2335
2336 // skip adding the initial selection to selection history
2337 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2338 editor.end_selection(window, cx);
2339 editor.selection_history.mode = SelectionHistoryMode::Normal;
2340
2341 editor.scroll_manager.show_scrollbars(window, cx);
2342 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2343
2344 if full_mode {
2345 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2346 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2347
2348 if editor.git_blame_inline_enabled {
2349 editor.start_git_blame_inline(false, window, cx);
2350 }
2351
2352 editor.go_to_active_debug_line(window, cx);
2353
2354 editor.minimap =
2355 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2356 editor.colors = Some(LspColorData::new(cx));
2357 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2358
2359 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2360 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2361 }
2362 editor.update_lsp_data(None, window, cx);
2363 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2364 }
2365
2366 editor
2367 }
2368
2369 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2370 self.selections.display_map(cx)
2371 }
2372
2373 pub fn deploy_mouse_context_menu(
2374 &mut self,
2375 position: gpui::Point<Pixels>,
2376 context_menu: Entity<ContextMenu>,
2377 window: &mut Window,
2378 cx: &mut Context<Self>,
2379 ) {
2380 self.mouse_context_menu = Some(MouseContextMenu::new(
2381 self,
2382 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2383 context_menu,
2384 window,
2385 cx,
2386 ));
2387 }
2388
2389 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2390 self.mouse_context_menu
2391 .as_ref()
2392 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2393 }
2394
2395 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2396 if self
2397 .selections
2398 .pending_anchor()
2399 .is_some_and(|pending_selection| {
2400 let snapshot = self.buffer().read(cx).snapshot(cx);
2401 pending_selection.range().includes(range, &snapshot)
2402 })
2403 {
2404 return true;
2405 }
2406
2407 self.selections
2408 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2409 .into_iter()
2410 .any(|selection| {
2411 // This is needed to cover a corner case, if we just check for an existing
2412 // selection in the fold range, having a cursor at the start of the fold
2413 // marks it as selected. Non-empty selections don't cause this.
2414 let length = selection.end - selection.start;
2415 length > 0
2416 })
2417 }
2418
2419 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2420 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2421 }
2422
2423 fn key_context_internal(
2424 &self,
2425 has_active_edit_prediction: bool,
2426 window: &mut Window,
2427 cx: &mut App,
2428 ) -> KeyContext {
2429 let mut key_context = KeyContext::new_with_defaults();
2430 key_context.add("Editor");
2431 let mode = match self.mode {
2432 EditorMode::SingleLine => "single_line",
2433 EditorMode::AutoHeight { .. } => "auto_height",
2434 EditorMode::Minimap { .. } => "minimap",
2435 EditorMode::Full { .. } => "full",
2436 };
2437
2438 if EditorSettings::jupyter_enabled(cx) {
2439 key_context.add("jupyter");
2440 }
2441
2442 key_context.set("mode", mode);
2443 if self.pending_rename.is_some() {
2444 key_context.add("renaming");
2445 }
2446
2447 if !self.snippet_stack.is_empty() {
2448 key_context.add("in_snippet");
2449 }
2450
2451 match self.context_menu.borrow().as_ref() {
2452 Some(CodeContextMenu::Completions(menu)) => {
2453 if menu.visible() {
2454 key_context.add("menu");
2455 key_context.add("showing_completions");
2456 }
2457 }
2458 Some(CodeContextMenu::CodeActions(menu)) => {
2459 if menu.visible() {
2460 key_context.add("menu");
2461 key_context.add("showing_code_actions")
2462 }
2463 }
2464 None => {}
2465 }
2466
2467 if self.signature_help_state.has_multiple_signatures() {
2468 key_context.add("showing_signature_help");
2469 }
2470
2471 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2472 if !self.focus_handle(cx).contains_focused(window, cx)
2473 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2474 {
2475 for addon in self.addons.values() {
2476 addon.extend_key_context(&mut key_context, cx)
2477 }
2478 }
2479
2480 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2481 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2482 Some(
2483 file.full_path(cx)
2484 .extension()?
2485 .to_string_lossy()
2486 .into_owned(),
2487 )
2488 }) {
2489 key_context.set("extension", extension);
2490 }
2491 } else {
2492 key_context.add("multibuffer");
2493 }
2494
2495 if has_active_edit_prediction {
2496 if self.edit_prediction_in_conflict() {
2497 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2498 } else {
2499 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2500 key_context.add("copilot_suggestion");
2501 }
2502 }
2503
2504 if self.selection_mark_mode {
2505 key_context.add("selection_mode");
2506 }
2507
2508 let disjoint = self.selections.disjoint_anchors();
2509 let snapshot = self.snapshot(window, cx);
2510 let snapshot = snapshot.buffer_snapshot();
2511 if self.mode == EditorMode::SingleLine
2512 && let [selection] = disjoint
2513 && selection.start == selection.end
2514 && selection.end.to_offset(snapshot) == snapshot.len()
2515 {
2516 key_context.add("end_of_input");
2517 }
2518
2519 key_context
2520 }
2521
2522 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2523 self.last_bounds.as_ref()
2524 }
2525
2526 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2527 if self.mouse_cursor_hidden {
2528 self.mouse_cursor_hidden = false;
2529 cx.notify();
2530 }
2531 }
2532
2533 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2534 let hide_mouse_cursor = match origin {
2535 HideMouseCursorOrigin::TypingAction => {
2536 matches!(
2537 self.hide_mouse_mode,
2538 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2539 )
2540 }
2541 HideMouseCursorOrigin::MovementAction => {
2542 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2543 }
2544 };
2545 if self.mouse_cursor_hidden != hide_mouse_cursor {
2546 self.mouse_cursor_hidden = hide_mouse_cursor;
2547 cx.notify();
2548 }
2549 }
2550
2551 pub fn edit_prediction_in_conflict(&self) -> bool {
2552 if !self.show_edit_predictions_in_menu() {
2553 return false;
2554 }
2555
2556 let showing_completions = self
2557 .context_menu
2558 .borrow()
2559 .as_ref()
2560 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2561
2562 showing_completions
2563 || self.edit_prediction_requires_modifier()
2564 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2565 // bindings to insert tab characters.
2566 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2567 }
2568
2569 pub fn accept_edit_prediction_keybind(
2570 &self,
2571 accept_partial: bool,
2572 window: &mut Window,
2573 cx: &mut App,
2574 ) -> AcceptEditPredictionBinding {
2575 let key_context = self.key_context_internal(true, window, cx);
2576 let in_conflict = self.edit_prediction_in_conflict();
2577
2578 let bindings = if accept_partial {
2579 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2580 } else {
2581 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2582 };
2583
2584 // TODO: if the binding contains multiple keystrokes, display all of them, not
2585 // just the first one.
2586 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2587 !in_conflict
2588 || binding
2589 .keystrokes()
2590 .first()
2591 .is_some_and(|keystroke| keystroke.modifiers().modified())
2592 }))
2593 }
2594
2595 pub fn new_file(
2596 workspace: &mut Workspace,
2597 _: &workspace::NewFile,
2598 window: &mut Window,
2599 cx: &mut Context<Workspace>,
2600 ) {
2601 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2602 "Failed to create buffer",
2603 window,
2604 cx,
2605 |e, _, _| match e.error_code() {
2606 ErrorCode::RemoteUpgradeRequired => Some(format!(
2607 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2608 e.error_tag("required").unwrap_or("the latest version")
2609 )),
2610 _ => None,
2611 },
2612 );
2613 }
2614
2615 pub fn new_in_workspace(
2616 workspace: &mut Workspace,
2617 window: &mut Window,
2618 cx: &mut Context<Workspace>,
2619 ) -> Task<Result<Entity<Editor>>> {
2620 let project = workspace.project().clone();
2621 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2622
2623 cx.spawn_in(window, async move |workspace, cx| {
2624 let buffer = create.await?;
2625 workspace.update_in(cx, |workspace, window, cx| {
2626 let editor =
2627 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2628 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2629 editor
2630 })
2631 })
2632 }
2633
2634 fn new_file_vertical(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitVertical,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2641 }
2642
2643 fn new_file_horizontal(
2644 workspace: &mut Workspace,
2645 _: &workspace::NewFileSplitHorizontal,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2650 }
2651
2652 fn new_file_split(
2653 workspace: &mut Workspace,
2654 action: &workspace::NewFileSplit,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 Self::new_file_in_direction(workspace, action.0, window, cx)
2659 }
2660
2661 fn new_file_in_direction(
2662 workspace: &mut Workspace,
2663 direction: SplitDirection,
2664 window: &mut Window,
2665 cx: &mut Context<Workspace>,
2666 ) {
2667 let project = workspace.project().clone();
2668 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2669
2670 cx.spawn_in(window, async move |workspace, cx| {
2671 let buffer = create.await?;
2672 workspace.update_in(cx, move |workspace, window, cx| {
2673 workspace.split_item(
2674 direction,
2675 Box::new(
2676 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2677 ),
2678 window,
2679 cx,
2680 )
2681 })?;
2682 anyhow::Ok(())
2683 })
2684 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2685 match e.error_code() {
2686 ErrorCode::RemoteUpgradeRequired => Some(format!(
2687 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2688 e.error_tag("required").unwrap_or("the latest version")
2689 )),
2690 _ => None,
2691 }
2692 });
2693 }
2694
2695 pub fn leader_id(&self) -> Option<CollaboratorId> {
2696 self.leader_id
2697 }
2698
2699 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2700 &self.buffer
2701 }
2702
2703 pub fn project(&self) -> Option<&Entity<Project>> {
2704 self.project.as_ref()
2705 }
2706
2707 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2708 self.workspace.as_ref()?.0.upgrade()
2709 }
2710
2711 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2712 self.buffer().read(cx).title(cx)
2713 }
2714
2715 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2716 let git_blame_gutter_max_author_length = self
2717 .render_git_blame_gutter(cx)
2718 .then(|| {
2719 if let Some(blame) = self.blame.as_ref() {
2720 let max_author_length =
2721 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2722 Some(max_author_length)
2723 } else {
2724 None
2725 }
2726 })
2727 .flatten();
2728
2729 EditorSnapshot {
2730 mode: self.mode.clone(),
2731 show_gutter: self.show_gutter,
2732 show_line_numbers: self.show_line_numbers,
2733 show_git_diff_gutter: self.show_git_diff_gutter,
2734 show_code_actions: self.show_code_actions,
2735 show_runnables: self.show_runnables,
2736 show_breakpoints: self.show_breakpoints,
2737 git_blame_gutter_max_author_length,
2738 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2739 placeholder_display_snapshot: self
2740 .placeholder_display_map
2741 .as_ref()
2742 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2743 scroll_anchor: self.scroll_manager.anchor(),
2744 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2745 is_focused: self.focus_handle.is_focused(window),
2746 current_line_highlight: self
2747 .current_line_highlight
2748 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2749 gutter_hovered: self.gutter_hovered,
2750 }
2751 }
2752
2753 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2754 self.buffer.read(cx).language_at(point, cx)
2755 }
2756
2757 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2758 self.buffer.read(cx).read(cx).file_at(point).cloned()
2759 }
2760
2761 pub fn active_excerpt(
2762 &self,
2763 cx: &App,
2764 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2765 self.buffer
2766 .read(cx)
2767 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2768 }
2769
2770 pub fn mode(&self) -> &EditorMode {
2771 &self.mode
2772 }
2773
2774 pub fn set_mode(&mut self, mode: EditorMode) {
2775 self.mode = mode;
2776 }
2777
2778 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2779 self.collaboration_hub.as_deref()
2780 }
2781
2782 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2783 self.collaboration_hub = Some(hub);
2784 }
2785
2786 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2787 self.in_project_search = in_project_search;
2788 }
2789
2790 pub fn set_custom_context_menu(
2791 &mut self,
2792 f: impl 'static
2793 + Fn(
2794 &mut Self,
2795 DisplayPoint,
2796 &mut Window,
2797 &mut Context<Self>,
2798 ) -> Option<Entity<ui::ContextMenu>>,
2799 ) {
2800 self.custom_context_menu = Some(Box::new(f))
2801 }
2802
2803 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2804 self.completion_provider = provider;
2805 }
2806
2807 #[cfg(any(test, feature = "test-support"))]
2808 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2809 self.completion_provider.clone()
2810 }
2811
2812 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2813 self.semantics_provider.clone()
2814 }
2815
2816 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2817 self.semantics_provider = provider;
2818 }
2819
2820 pub fn set_edit_prediction_provider<T>(
2821 &mut self,
2822 provider: Option<Entity<T>>,
2823 window: &mut Window,
2824 cx: &mut Context<Self>,
2825 ) where
2826 T: EditPredictionProvider,
2827 {
2828 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2829 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2830 if this.focus_handle.is_focused(window) {
2831 this.update_visible_edit_prediction(window, cx);
2832 }
2833 }),
2834 provider: Arc::new(provider),
2835 });
2836 self.update_edit_prediction_settings(cx);
2837 self.refresh_edit_prediction(false, false, window, cx);
2838 }
2839
2840 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2841 self.placeholder_display_map
2842 .as_ref()
2843 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2844 }
2845
2846 pub fn set_placeholder_text(
2847 &mut self,
2848 placeholder_text: &str,
2849 window: &mut Window,
2850 cx: &mut Context<Self>,
2851 ) {
2852 let multibuffer = cx
2853 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2854
2855 let style = window.text_style();
2856
2857 self.placeholder_display_map = Some(cx.new(|cx| {
2858 DisplayMap::new(
2859 multibuffer,
2860 style.font(),
2861 style.font_size.to_pixels(window.rem_size()),
2862 None,
2863 FILE_HEADER_HEIGHT,
2864 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2865 Default::default(),
2866 DiagnosticSeverity::Off,
2867 cx,
2868 )
2869 }));
2870 cx.notify();
2871 }
2872
2873 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2874 self.cursor_shape = cursor_shape;
2875
2876 // Disrupt blink for immediate user feedback that the cursor shape has changed
2877 self.blink_manager.update(cx, BlinkManager::show_cursor);
2878
2879 cx.notify();
2880 }
2881
2882 pub fn set_current_line_highlight(
2883 &mut self,
2884 current_line_highlight: Option<CurrentLineHighlight>,
2885 ) {
2886 self.current_line_highlight = current_line_highlight;
2887 }
2888
2889 pub fn range_for_match<T: std::marker::Copy>(
2890 &self,
2891 range: &Range<T>,
2892 collapse: bool,
2893 ) -> Range<T> {
2894 if collapse {
2895 return range.start..range.start;
2896 }
2897 range.clone()
2898 }
2899
2900 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2901 if self.display_map.read(cx).clip_at_line_ends != clip {
2902 self.display_map
2903 .update(cx, |map, _| map.clip_at_line_ends = clip);
2904 }
2905 }
2906
2907 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2908 self.input_enabled = input_enabled;
2909 }
2910
2911 pub fn set_edit_predictions_hidden_for_vim_mode(
2912 &mut self,
2913 hidden: bool,
2914 window: &mut Window,
2915 cx: &mut Context<Self>,
2916 ) {
2917 if hidden != self.edit_predictions_hidden_for_vim_mode {
2918 self.edit_predictions_hidden_for_vim_mode = hidden;
2919 if hidden {
2920 self.update_visible_edit_prediction(window, cx);
2921 } else {
2922 self.refresh_edit_prediction(true, false, window, cx);
2923 }
2924 }
2925 }
2926
2927 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2928 self.menu_edit_predictions_policy = value;
2929 }
2930
2931 pub fn set_autoindent(&mut self, autoindent: bool) {
2932 if autoindent {
2933 self.autoindent_mode = Some(AutoindentMode::EachLine);
2934 } else {
2935 self.autoindent_mode = None;
2936 }
2937 }
2938
2939 pub fn read_only(&self, cx: &App) -> bool {
2940 self.read_only || self.buffer.read(cx).read_only()
2941 }
2942
2943 pub fn set_read_only(&mut self, read_only: bool) {
2944 self.read_only = read_only;
2945 }
2946
2947 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2948 self.use_autoclose = autoclose;
2949 }
2950
2951 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2952 self.use_auto_surround = auto_surround;
2953 }
2954
2955 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2956 self.auto_replace_emoji_shortcode = auto_replace;
2957 }
2958
2959 pub fn toggle_edit_predictions(
2960 &mut self,
2961 _: &ToggleEditPrediction,
2962 window: &mut Window,
2963 cx: &mut Context<Self>,
2964 ) {
2965 if self.show_edit_predictions_override.is_some() {
2966 self.set_show_edit_predictions(None, window, cx);
2967 } else {
2968 let show_edit_predictions = !self.edit_predictions_enabled();
2969 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2970 }
2971 }
2972
2973 pub fn set_show_edit_predictions(
2974 &mut self,
2975 show_edit_predictions: Option<bool>,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) {
2979 self.show_edit_predictions_override = show_edit_predictions;
2980 self.update_edit_prediction_settings(cx);
2981
2982 if let Some(false) = show_edit_predictions {
2983 self.discard_edit_prediction(false, cx);
2984 } else {
2985 self.refresh_edit_prediction(false, true, window, cx);
2986 }
2987 }
2988
2989 fn edit_predictions_disabled_in_scope(
2990 &self,
2991 buffer: &Entity<Buffer>,
2992 buffer_position: language::Anchor,
2993 cx: &App,
2994 ) -> bool {
2995 let snapshot = buffer.read(cx).snapshot();
2996 let settings = snapshot.settings_at(buffer_position, cx);
2997
2998 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2999 return false;
3000 };
3001
3002 scope.override_name().is_some_and(|scope_name| {
3003 settings
3004 .edit_predictions_disabled_in
3005 .iter()
3006 .any(|s| s == scope_name)
3007 })
3008 }
3009
3010 pub fn set_use_modal_editing(&mut self, to: bool) {
3011 self.use_modal_editing = to;
3012 }
3013
3014 pub fn use_modal_editing(&self) -> bool {
3015 self.use_modal_editing
3016 }
3017
3018 fn selections_did_change(
3019 &mut self,
3020 local: bool,
3021 old_cursor_position: &Anchor,
3022 effects: SelectionEffects,
3023 window: &mut Window,
3024 cx: &mut Context<Self>,
3025 ) {
3026 window.invalidate_character_coordinates();
3027
3028 // Copy selections to primary selection buffer
3029 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3030 if local {
3031 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3032 let buffer_handle = self.buffer.read(cx).read(cx);
3033
3034 let mut text = String::new();
3035 for (index, selection) in selections.iter().enumerate() {
3036 let text_for_selection = buffer_handle
3037 .text_for_range(selection.start..selection.end)
3038 .collect::<String>();
3039
3040 text.push_str(&text_for_selection);
3041 if index != selections.len() - 1 {
3042 text.push('\n');
3043 }
3044 }
3045
3046 if !text.is_empty() {
3047 cx.write_to_primary(ClipboardItem::new_string(text));
3048 }
3049 }
3050
3051 let selection_anchors = self.selections.disjoint_anchors_arc();
3052
3053 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3054 self.buffer.update(cx, |buffer, cx| {
3055 buffer.set_active_selections(
3056 &selection_anchors,
3057 self.selections.line_mode(),
3058 self.cursor_shape,
3059 cx,
3060 )
3061 });
3062 }
3063 let display_map = self
3064 .display_map
3065 .update(cx, |display_map, cx| display_map.snapshot(cx));
3066 let buffer = display_map.buffer_snapshot();
3067 if self.selections.count() == 1 {
3068 self.add_selections_state = None;
3069 }
3070 self.select_next_state = None;
3071 self.select_prev_state = None;
3072 self.select_syntax_node_history.try_clear();
3073 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3074 self.snippet_stack.invalidate(&selection_anchors, buffer);
3075 self.take_rename(false, window, cx);
3076
3077 let newest_selection = self.selections.newest_anchor();
3078 let new_cursor_position = newest_selection.head();
3079 let selection_start = newest_selection.start;
3080
3081 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3082 self.push_to_nav_history(
3083 *old_cursor_position,
3084 Some(new_cursor_position.to_point(buffer)),
3085 false,
3086 effects.nav_history == Some(true),
3087 cx,
3088 );
3089 }
3090
3091 if local {
3092 if let Some(buffer_id) = new_cursor_position.buffer_id {
3093 self.register_buffer(buffer_id, cx);
3094 }
3095
3096 let mut context_menu = self.context_menu.borrow_mut();
3097 let completion_menu = match context_menu.as_ref() {
3098 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3099 Some(CodeContextMenu::CodeActions(_)) => {
3100 *context_menu = None;
3101 None
3102 }
3103 None => None,
3104 };
3105 let completion_position = completion_menu.map(|menu| menu.initial_position);
3106 drop(context_menu);
3107
3108 if effects.completions
3109 && let Some(completion_position) = completion_position
3110 {
3111 let start_offset = selection_start.to_offset(buffer);
3112 let position_matches = start_offset == completion_position.to_offset(buffer);
3113 let continue_showing = if position_matches {
3114 if self.snippet_stack.is_empty() {
3115 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3116 == Some(CharKind::Word)
3117 } else {
3118 // Snippet choices can be shown even when the cursor is in whitespace.
3119 // Dismissing the menu with actions like backspace is handled by
3120 // invalidation regions.
3121 true
3122 }
3123 } else {
3124 false
3125 };
3126
3127 if continue_showing {
3128 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3129 } else {
3130 self.hide_context_menu(window, cx);
3131 }
3132 }
3133
3134 hide_hover(self, cx);
3135
3136 if old_cursor_position.to_display_point(&display_map).row()
3137 != new_cursor_position.to_display_point(&display_map).row()
3138 {
3139 self.available_code_actions.take();
3140 }
3141 self.refresh_code_actions(window, cx);
3142 self.refresh_document_highlights(cx);
3143 refresh_linked_ranges(self, window, cx);
3144
3145 self.refresh_selected_text_highlights(false, window, cx);
3146 self.refresh_matching_bracket_highlights(window, cx);
3147 self.update_visible_edit_prediction(window, cx);
3148 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3149 self.inline_blame_popover.take();
3150 if self.git_blame_inline_enabled {
3151 self.start_inline_blame_timer(window, cx);
3152 }
3153 }
3154
3155 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3156 cx.emit(EditorEvent::SelectionsChanged { local });
3157
3158 let selections = &self.selections.disjoint_anchors_arc();
3159 if selections.len() == 1 {
3160 cx.emit(SearchEvent::ActiveMatchChanged)
3161 }
3162 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3163 let inmemory_selections = selections
3164 .iter()
3165 .map(|s| {
3166 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3167 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3168 })
3169 .collect();
3170 self.update_restoration_data(cx, |data| {
3171 data.selections = inmemory_selections;
3172 });
3173
3174 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3175 && let Some(workspace_id) =
3176 self.workspace.as_ref().and_then(|workspace| workspace.1)
3177 {
3178 let snapshot = self.buffer().read(cx).snapshot(cx);
3179 let selections = selections.clone();
3180 let background_executor = cx.background_executor().clone();
3181 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3182 self.serialize_selections = cx.background_spawn(async move {
3183 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3184 let db_selections = selections
3185 .iter()
3186 .map(|selection| {
3187 (
3188 selection.start.to_offset(&snapshot),
3189 selection.end.to_offset(&snapshot),
3190 )
3191 })
3192 .collect();
3193
3194 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3195 .await
3196 .with_context(|| {
3197 format!(
3198 "persisting editor selections for editor {editor_id}, \
3199 workspace {workspace_id:?}"
3200 )
3201 })
3202 .log_err();
3203 });
3204 }
3205 }
3206
3207 cx.notify();
3208 }
3209
3210 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3211 use text::ToOffset as _;
3212 use text::ToPoint as _;
3213
3214 if self.mode.is_minimap()
3215 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3216 {
3217 return;
3218 }
3219
3220 if !self.buffer().read(cx).is_singleton() {
3221 return;
3222 }
3223
3224 let display_snapshot = self
3225 .display_map
3226 .update(cx, |display_map, cx| display_map.snapshot(cx));
3227 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3228 return;
3229 };
3230 let inmemory_folds = display_snapshot
3231 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3232 .map(|fold| {
3233 fold.range.start.text_anchor.to_point(&snapshot)
3234 ..fold.range.end.text_anchor.to_point(&snapshot)
3235 })
3236 .collect();
3237 self.update_restoration_data(cx, |data| {
3238 data.folds = inmemory_folds;
3239 });
3240
3241 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3242 return;
3243 };
3244 let background_executor = cx.background_executor().clone();
3245 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3246 let db_folds = display_snapshot
3247 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3248 .map(|fold| {
3249 (
3250 fold.range.start.text_anchor.to_offset(&snapshot),
3251 fold.range.end.text_anchor.to_offset(&snapshot),
3252 )
3253 })
3254 .collect();
3255 self.serialize_folds = cx.background_spawn(async move {
3256 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3257 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3258 .await
3259 .with_context(|| {
3260 format!(
3261 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3262 )
3263 })
3264 .log_err();
3265 });
3266 }
3267
3268 pub fn sync_selections(
3269 &mut self,
3270 other: Entity<Editor>,
3271 cx: &mut Context<Self>,
3272 ) -> gpui::Subscription {
3273 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3274 if !other_selections.is_empty() {
3275 self.selections.change_with(cx, |selections| {
3276 selections.select_anchors(other_selections);
3277 });
3278 }
3279
3280 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3281 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3282 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3283 if other_selections.is_empty() {
3284 return;
3285 }
3286 this.selections.change_with(cx, |selections| {
3287 selections.select_anchors(other_selections);
3288 });
3289 }
3290 });
3291
3292 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3293 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3294 let these_selections = this.selections.disjoint_anchors().to_vec();
3295 if these_selections.is_empty() {
3296 return;
3297 }
3298 other.update(cx, |other_editor, cx| {
3299 other_editor.selections.change_with(cx, |selections| {
3300 selections.select_anchors(these_selections);
3301 })
3302 });
3303 }
3304 });
3305
3306 Subscription::join(other_subscription, this_subscription)
3307 }
3308
3309 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3310 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3311 /// effects of selection change occur at the end of the transaction.
3312 pub fn change_selections<R>(
3313 &mut self,
3314 effects: SelectionEffects,
3315 window: &mut Window,
3316 cx: &mut Context<Self>,
3317 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3318 ) -> R {
3319 if let Some(state) = &mut self.deferred_selection_effects_state {
3320 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3321 state.effects.completions = effects.completions;
3322 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3323 let (changed, result) = self.selections.change_with(cx, change);
3324 state.changed |= changed;
3325 return result;
3326 }
3327 let mut state = DeferredSelectionEffectsState {
3328 changed: false,
3329 effects,
3330 old_cursor_position: self.selections.newest_anchor().head(),
3331 history_entry: SelectionHistoryEntry {
3332 selections: self.selections.disjoint_anchors_arc(),
3333 select_next_state: self.select_next_state.clone(),
3334 select_prev_state: self.select_prev_state.clone(),
3335 add_selections_state: self.add_selections_state.clone(),
3336 },
3337 };
3338 let (changed, result) = self.selections.change_with(cx, change);
3339 state.changed = state.changed || changed;
3340 if self.defer_selection_effects {
3341 self.deferred_selection_effects_state = Some(state);
3342 } else {
3343 self.apply_selection_effects(state, window, cx);
3344 }
3345 result
3346 }
3347
3348 /// Defers the effects of selection change, so that the effects of multiple calls to
3349 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3350 /// to selection history and the state of popovers based on selection position aren't
3351 /// erroneously updated.
3352 pub fn with_selection_effects_deferred<R>(
3353 &mut self,
3354 window: &mut Window,
3355 cx: &mut Context<Self>,
3356 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3357 ) -> R {
3358 let already_deferred = self.defer_selection_effects;
3359 self.defer_selection_effects = true;
3360 let result = update(self, window, cx);
3361 if !already_deferred {
3362 self.defer_selection_effects = false;
3363 if let Some(state) = self.deferred_selection_effects_state.take() {
3364 self.apply_selection_effects(state, window, cx);
3365 }
3366 }
3367 result
3368 }
3369
3370 fn apply_selection_effects(
3371 &mut self,
3372 state: DeferredSelectionEffectsState,
3373 window: &mut Window,
3374 cx: &mut Context<Self>,
3375 ) {
3376 if state.changed {
3377 self.selection_history.push(state.history_entry);
3378
3379 if let Some(autoscroll) = state.effects.scroll {
3380 self.request_autoscroll(autoscroll, cx);
3381 }
3382
3383 let old_cursor_position = &state.old_cursor_position;
3384
3385 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3386
3387 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3388 self.show_signature_help(&ShowSignatureHelp, window, cx);
3389 }
3390 }
3391 }
3392
3393 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3394 where
3395 I: IntoIterator<Item = (Range<S>, T)>,
3396 S: ToOffset,
3397 T: Into<Arc<str>>,
3398 {
3399 if self.read_only(cx) {
3400 return;
3401 }
3402
3403 self.buffer
3404 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3405 }
3406
3407 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3408 where
3409 I: IntoIterator<Item = (Range<S>, T)>,
3410 S: ToOffset,
3411 T: Into<Arc<str>>,
3412 {
3413 if self.read_only(cx) {
3414 return;
3415 }
3416
3417 self.buffer.update(cx, |buffer, cx| {
3418 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3419 });
3420 }
3421
3422 pub fn edit_with_block_indent<I, S, T>(
3423 &mut self,
3424 edits: I,
3425 original_indent_columns: Vec<Option<u32>>,
3426 cx: &mut Context<Self>,
3427 ) where
3428 I: IntoIterator<Item = (Range<S>, T)>,
3429 S: ToOffset,
3430 T: Into<Arc<str>>,
3431 {
3432 if self.read_only(cx) {
3433 return;
3434 }
3435
3436 self.buffer.update(cx, |buffer, cx| {
3437 buffer.edit(
3438 edits,
3439 Some(AutoindentMode::Block {
3440 original_indent_columns,
3441 }),
3442 cx,
3443 )
3444 });
3445 }
3446
3447 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3448 self.hide_context_menu(window, cx);
3449
3450 match phase {
3451 SelectPhase::Begin {
3452 position,
3453 add,
3454 click_count,
3455 } => self.begin_selection(position, add, click_count, window, cx),
3456 SelectPhase::BeginColumnar {
3457 position,
3458 goal_column,
3459 reset,
3460 mode,
3461 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3462 SelectPhase::Extend {
3463 position,
3464 click_count,
3465 } => self.extend_selection(position, click_count, window, cx),
3466 SelectPhase::Update {
3467 position,
3468 goal_column,
3469 scroll_delta,
3470 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3471 SelectPhase::End => self.end_selection(window, cx),
3472 }
3473 }
3474
3475 fn extend_selection(
3476 &mut self,
3477 position: DisplayPoint,
3478 click_count: usize,
3479 window: &mut Window,
3480 cx: &mut Context<Self>,
3481 ) {
3482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3483 let tail = self.selections.newest::<usize>(&display_map).tail();
3484 let click_count = click_count.max(match self.selections.select_mode() {
3485 SelectMode::Character => 1,
3486 SelectMode::Word(_) => 2,
3487 SelectMode::Line(_) => 3,
3488 SelectMode::All => 4,
3489 });
3490 self.begin_selection(position, false, click_count, window, cx);
3491
3492 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3493
3494 let current_selection = match self.selections.select_mode() {
3495 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3496 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3497 };
3498
3499 let mut pending_selection = self
3500 .selections
3501 .pending_anchor()
3502 .cloned()
3503 .expect("extend_selection not called with pending selection");
3504
3505 if pending_selection
3506 .start
3507 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3508 == Ordering::Greater
3509 {
3510 pending_selection.start = current_selection.start;
3511 }
3512 if pending_selection
3513 .end
3514 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3515 == Ordering::Less
3516 {
3517 pending_selection.end = current_selection.end;
3518 pending_selection.reversed = true;
3519 }
3520
3521 let mut pending_mode = self.selections.pending_mode().unwrap();
3522 match &mut pending_mode {
3523 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3524 _ => {}
3525 }
3526
3527 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3528 SelectionEffects::scroll(Autoscroll::fit())
3529 } else {
3530 SelectionEffects::no_scroll()
3531 };
3532
3533 self.change_selections(effects, window, cx, |s| {
3534 s.set_pending(pending_selection.clone(), pending_mode);
3535 s.set_is_extending(true);
3536 });
3537 }
3538
3539 fn begin_selection(
3540 &mut self,
3541 position: DisplayPoint,
3542 add: bool,
3543 click_count: usize,
3544 window: &mut Window,
3545 cx: &mut Context<Self>,
3546 ) {
3547 if !self.focus_handle.is_focused(window) {
3548 self.last_focused_descendant = None;
3549 window.focus(&self.focus_handle);
3550 }
3551
3552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3553 let buffer = display_map.buffer_snapshot();
3554 let position = display_map.clip_point(position, Bias::Left);
3555
3556 let start;
3557 let end;
3558 let mode;
3559 let mut auto_scroll;
3560 match click_count {
3561 1 => {
3562 start = buffer.anchor_before(position.to_point(&display_map));
3563 end = start;
3564 mode = SelectMode::Character;
3565 auto_scroll = true;
3566 }
3567 2 => {
3568 let position = display_map
3569 .clip_point(position, Bias::Left)
3570 .to_offset(&display_map, Bias::Left);
3571 let (range, _) = buffer.surrounding_word(position, None);
3572 start = buffer.anchor_before(range.start);
3573 end = buffer.anchor_before(range.end);
3574 mode = SelectMode::Word(start..end);
3575 auto_scroll = true;
3576 }
3577 3 => {
3578 let position = display_map
3579 .clip_point(position, Bias::Left)
3580 .to_point(&display_map);
3581 let line_start = display_map.prev_line_boundary(position).0;
3582 let next_line_start = buffer.clip_point(
3583 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3584 Bias::Left,
3585 );
3586 start = buffer.anchor_before(line_start);
3587 end = buffer.anchor_before(next_line_start);
3588 mode = SelectMode::Line(start..end);
3589 auto_scroll = true;
3590 }
3591 _ => {
3592 start = buffer.anchor_before(0);
3593 end = buffer.anchor_before(buffer.len());
3594 mode = SelectMode::All;
3595 auto_scroll = false;
3596 }
3597 }
3598 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3599
3600 let point_to_delete: Option<usize> = {
3601 let selected_points: Vec<Selection<Point>> =
3602 self.selections.disjoint_in_range(start..end, &display_map);
3603
3604 if !add || click_count > 1 {
3605 None
3606 } else if !selected_points.is_empty() {
3607 Some(selected_points[0].id)
3608 } else {
3609 let clicked_point_already_selected =
3610 self.selections.disjoint_anchors().iter().find(|selection| {
3611 selection.start.to_point(buffer) == start.to_point(buffer)
3612 || selection.end.to_point(buffer) == end.to_point(buffer)
3613 });
3614
3615 clicked_point_already_selected.map(|selection| selection.id)
3616 }
3617 };
3618
3619 let selections_count = self.selections.count();
3620 let effects = if auto_scroll {
3621 SelectionEffects::default()
3622 } else {
3623 SelectionEffects::no_scroll()
3624 };
3625
3626 self.change_selections(effects, window, cx, |s| {
3627 if let Some(point_to_delete) = point_to_delete {
3628 s.delete(point_to_delete);
3629
3630 if selections_count == 1 {
3631 s.set_pending_anchor_range(start..end, mode);
3632 }
3633 } else {
3634 if !add {
3635 s.clear_disjoint();
3636 }
3637
3638 s.set_pending_anchor_range(start..end, mode);
3639 }
3640 });
3641 }
3642
3643 fn begin_columnar_selection(
3644 &mut self,
3645 position: DisplayPoint,
3646 goal_column: u32,
3647 reset: bool,
3648 mode: ColumnarMode,
3649 window: &mut Window,
3650 cx: &mut Context<Self>,
3651 ) {
3652 if !self.focus_handle.is_focused(window) {
3653 self.last_focused_descendant = None;
3654 window.focus(&self.focus_handle);
3655 }
3656
3657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3658
3659 if reset {
3660 let pointer_position = display_map
3661 .buffer_snapshot()
3662 .anchor_before(position.to_point(&display_map));
3663
3664 self.change_selections(
3665 SelectionEffects::scroll(Autoscroll::newest()),
3666 window,
3667 cx,
3668 |s| {
3669 s.clear_disjoint();
3670 s.set_pending_anchor_range(
3671 pointer_position..pointer_position,
3672 SelectMode::Character,
3673 );
3674 },
3675 );
3676 };
3677
3678 let tail = self.selections.newest::<Point>(&display_map).tail();
3679 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3680 self.columnar_selection_state = match mode {
3681 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3682 selection_tail: selection_anchor,
3683 display_point: if reset {
3684 if position.column() != goal_column {
3685 Some(DisplayPoint::new(position.row(), goal_column))
3686 } else {
3687 None
3688 }
3689 } else {
3690 None
3691 },
3692 }),
3693 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3694 selection_tail: selection_anchor,
3695 }),
3696 };
3697
3698 if !reset {
3699 self.select_columns(position, goal_column, &display_map, window, cx);
3700 }
3701 }
3702
3703 fn update_selection(
3704 &mut self,
3705 position: DisplayPoint,
3706 goal_column: u32,
3707 scroll_delta: gpui::Point<f32>,
3708 window: &mut Window,
3709 cx: &mut Context<Self>,
3710 ) {
3711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3712
3713 if self.columnar_selection_state.is_some() {
3714 self.select_columns(position, goal_column, &display_map, window, cx);
3715 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3716 let buffer = display_map.buffer_snapshot();
3717 let head;
3718 let tail;
3719 let mode = self.selections.pending_mode().unwrap();
3720 match &mode {
3721 SelectMode::Character => {
3722 head = position.to_point(&display_map);
3723 tail = pending.tail().to_point(buffer);
3724 }
3725 SelectMode::Word(original_range) => {
3726 let offset = display_map
3727 .clip_point(position, Bias::Left)
3728 .to_offset(&display_map, Bias::Left);
3729 let original_range = original_range.to_offset(buffer);
3730
3731 let head_offset = if buffer.is_inside_word(offset, None)
3732 || original_range.contains(&offset)
3733 {
3734 let (word_range, _) = buffer.surrounding_word(offset, None);
3735 if word_range.start < original_range.start {
3736 word_range.start
3737 } else {
3738 word_range.end
3739 }
3740 } else {
3741 offset
3742 };
3743
3744 head = head_offset.to_point(buffer);
3745 if head_offset <= original_range.start {
3746 tail = original_range.end.to_point(buffer);
3747 } else {
3748 tail = original_range.start.to_point(buffer);
3749 }
3750 }
3751 SelectMode::Line(original_range) => {
3752 let original_range = original_range.to_point(display_map.buffer_snapshot());
3753
3754 let position = display_map
3755 .clip_point(position, Bias::Left)
3756 .to_point(&display_map);
3757 let line_start = display_map.prev_line_boundary(position).0;
3758 let next_line_start = buffer.clip_point(
3759 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3760 Bias::Left,
3761 );
3762
3763 if line_start < original_range.start {
3764 head = line_start
3765 } else {
3766 head = next_line_start
3767 }
3768
3769 if head <= original_range.start {
3770 tail = original_range.end;
3771 } else {
3772 tail = original_range.start;
3773 }
3774 }
3775 SelectMode::All => {
3776 return;
3777 }
3778 };
3779
3780 if head < tail {
3781 pending.start = buffer.anchor_before(head);
3782 pending.end = buffer.anchor_before(tail);
3783 pending.reversed = true;
3784 } else {
3785 pending.start = buffer.anchor_before(tail);
3786 pending.end = buffer.anchor_before(head);
3787 pending.reversed = false;
3788 }
3789
3790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3791 s.set_pending(pending.clone(), mode);
3792 });
3793 } else {
3794 log::error!("update_selection dispatched with no pending selection");
3795 return;
3796 }
3797
3798 self.apply_scroll_delta(scroll_delta, window, cx);
3799 cx.notify();
3800 }
3801
3802 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3803 self.columnar_selection_state.take();
3804 if let Some(pending_mode) = self.selections.pending_mode() {
3805 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3806 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3807 s.select(selections);
3808 s.clear_pending();
3809 if s.is_extending() {
3810 s.set_is_extending(false);
3811 } else {
3812 s.set_select_mode(pending_mode);
3813 }
3814 });
3815 }
3816 }
3817
3818 fn select_columns(
3819 &mut self,
3820 head: DisplayPoint,
3821 goal_column: u32,
3822 display_map: &DisplaySnapshot,
3823 window: &mut Window,
3824 cx: &mut Context<Self>,
3825 ) {
3826 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3827 return;
3828 };
3829
3830 let tail = match columnar_state {
3831 ColumnarSelectionState::FromMouse {
3832 selection_tail,
3833 display_point,
3834 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3835 ColumnarSelectionState::FromSelection { selection_tail } => {
3836 selection_tail.to_display_point(display_map)
3837 }
3838 };
3839
3840 let start_row = cmp::min(tail.row(), head.row());
3841 let end_row = cmp::max(tail.row(), head.row());
3842 let start_column = cmp::min(tail.column(), goal_column);
3843 let end_column = cmp::max(tail.column(), goal_column);
3844 let reversed = start_column < tail.column();
3845
3846 let selection_ranges = (start_row.0..=end_row.0)
3847 .map(DisplayRow)
3848 .filter_map(|row| {
3849 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3850 || start_column <= display_map.line_len(row))
3851 && !display_map.is_block_line(row)
3852 {
3853 let start = display_map
3854 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3855 .to_point(display_map);
3856 let end = display_map
3857 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3858 .to_point(display_map);
3859 if reversed {
3860 Some(end..start)
3861 } else {
3862 Some(start..end)
3863 }
3864 } else {
3865 None
3866 }
3867 })
3868 .collect::<Vec<_>>();
3869 if selection_ranges.is_empty() {
3870 return;
3871 }
3872
3873 let ranges = match columnar_state {
3874 ColumnarSelectionState::FromMouse { .. } => {
3875 let mut non_empty_ranges = selection_ranges
3876 .iter()
3877 .filter(|selection_range| selection_range.start != selection_range.end)
3878 .peekable();
3879 if non_empty_ranges.peek().is_some() {
3880 non_empty_ranges.cloned().collect()
3881 } else {
3882 selection_ranges
3883 }
3884 }
3885 _ => selection_ranges,
3886 };
3887
3888 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3889 s.select_ranges(ranges);
3890 });
3891 cx.notify();
3892 }
3893
3894 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3895 self.selections
3896 .all_adjusted(snapshot)
3897 .iter()
3898 .any(|selection| !selection.is_empty())
3899 }
3900
3901 pub fn has_pending_nonempty_selection(&self) -> bool {
3902 let pending_nonempty_selection = match self.selections.pending_anchor() {
3903 Some(Selection { start, end, .. }) => start != end,
3904 None => false,
3905 };
3906
3907 pending_nonempty_selection
3908 || (self.columnar_selection_state.is_some()
3909 && self.selections.disjoint_anchors().len() > 1)
3910 }
3911
3912 pub fn has_pending_selection(&self) -> bool {
3913 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3914 }
3915
3916 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3917 self.selection_mark_mode = false;
3918 self.selection_drag_state = SelectionDragState::None;
3919
3920 if self.clear_expanded_diff_hunks(cx) {
3921 cx.notify();
3922 return;
3923 }
3924 if self.dismiss_menus_and_popups(true, window, cx) {
3925 return;
3926 }
3927
3928 if self.mode.is_full()
3929 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3930 {
3931 return;
3932 }
3933
3934 cx.propagate();
3935 }
3936
3937 pub fn dismiss_menus_and_popups(
3938 &mut self,
3939 is_user_requested: bool,
3940 window: &mut Window,
3941 cx: &mut Context<Self>,
3942 ) -> bool {
3943 if self.take_rename(false, window, cx).is_some() {
3944 return true;
3945 }
3946
3947 if self.hide_blame_popover(true, cx) {
3948 return true;
3949 }
3950
3951 if hide_hover(self, cx) {
3952 return true;
3953 }
3954
3955 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3956 return true;
3957 }
3958
3959 if self.hide_context_menu(window, cx).is_some() {
3960 return true;
3961 }
3962
3963 if self.mouse_context_menu.take().is_some() {
3964 return true;
3965 }
3966
3967 if is_user_requested && self.discard_edit_prediction(true, cx) {
3968 return true;
3969 }
3970
3971 if self.snippet_stack.pop().is_some() {
3972 return true;
3973 }
3974
3975 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3976 self.dismiss_diagnostics(cx);
3977 return true;
3978 }
3979
3980 false
3981 }
3982
3983 fn linked_editing_ranges_for(
3984 &self,
3985 selection: Range<text::Anchor>,
3986 cx: &App,
3987 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3988 if self.linked_edit_ranges.is_empty() {
3989 return None;
3990 }
3991 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3992 selection.end.buffer_id.and_then(|end_buffer_id| {
3993 if selection.start.buffer_id != Some(end_buffer_id) {
3994 return None;
3995 }
3996 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3997 let snapshot = buffer.read(cx).snapshot();
3998 self.linked_edit_ranges
3999 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4000 .map(|ranges| (ranges, snapshot, buffer))
4001 })?;
4002 use text::ToOffset as TO;
4003 // find offset from the start of current range to current cursor position
4004 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4005
4006 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4007 let start_difference = start_offset - start_byte_offset;
4008 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4009 let end_difference = end_offset - start_byte_offset;
4010 // Current range has associated linked ranges.
4011 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4012 for range in linked_ranges.iter() {
4013 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4014 let end_offset = start_offset + end_difference;
4015 let start_offset = start_offset + start_difference;
4016 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4017 continue;
4018 }
4019 if self.selections.disjoint_anchor_ranges().any(|s| {
4020 if s.start.buffer_id != selection.start.buffer_id
4021 || s.end.buffer_id != selection.end.buffer_id
4022 {
4023 return false;
4024 }
4025 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4026 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4027 }) {
4028 continue;
4029 }
4030 let start = buffer_snapshot.anchor_after(start_offset);
4031 let end = buffer_snapshot.anchor_after(end_offset);
4032 linked_edits
4033 .entry(buffer.clone())
4034 .or_default()
4035 .push(start..end);
4036 }
4037 Some(linked_edits)
4038 }
4039
4040 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4041 let text: Arc<str> = text.into();
4042
4043 if self.read_only(cx) {
4044 return;
4045 }
4046
4047 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4048
4049 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4050 let mut bracket_inserted = false;
4051 let mut edits = Vec::new();
4052 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4053 let mut new_selections = Vec::with_capacity(selections.len());
4054 let mut new_autoclose_regions = Vec::new();
4055 let snapshot = self.buffer.read(cx).read(cx);
4056 let mut clear_linked_edit_ranges = false;
4057
4058 for (selection, autoclose_region) in
4059 self.selections_with_autoclose_regions(selections, &snapshot)
4060 {
4061 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4062 // Determine if the inserted text matches the opening or closing
4063 // bracket of any of this language's bracket pairs.
4064 let mut bracket_pair = None;
4065 let mut is_bracket_pair_start = false;
4066 let mut is_bracket_pair_end = false;
4067 if !text.is_empty() {
4068 let mut bracket_pair_matching_end = None;
4069 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4070 // and they are removing the character that triggered IME popup.
4071 for (pair, enabled) in scope.brackets() {
4072 if !pair.close && !pair.surround {
4073 continue;
4074 }
4075
4076 if enabled && pair.start.ends_with(text.as_ref()) {
4077 let prefix_len = pair.start.len() - text.len();
4078 let preceding_text_matches_prefix = prefix_len == 0
4079 || (selection.start.column >= (prefix_len as u32)
4080 && snapshot.contains_str_at(
4081 Point::new(
4082 selection.start.row,
4083 selection.start.column - (prefix_len as u32),
4084 ),
4085 &pair.start[..prefix_len],
4086 ));
4087 if preceding_text_matches_prefix {
4088 bracket_pair = Some(pair.clone());
4089 is_bracket_pair_start = true;
4090 break;
4091 }
4092 }
4093 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4094 {
4095 // take first bracket pair matching end, but don't break in case a later bracket
4096 // pair matches start
4097 bracket_pair_matching_end = Some(pair.clone());
4098 }
4099 }
4100 if let Some(end) = bracket_pair_matching_end
4101 && bracket_pair.is_none()
4102 {
4103 bracket_pair = Some(end);
4104 is_bracket_pair_end = true;
4105 }
4106 }
4107
4108 if let Some(bracket_pair) = bracket_pair {
4109 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4110 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4111 let auto_surround =
4112 self.use_auto_surround && snapshot_settings.use_auto_surround;
4113 if selection.is_empty() {
4114 if is_bracket_pair_start {
4115 // If the inserted text is a suffix of an opening bracket and the
4116 // selection is preceded by the rest of the opening bracket, then
4117 // insert the closing bracket.
4118 let following_text_allows_autoclose = snapshot
4119 .chars_at(selection.start)
4120 .next()
4121 .is_none_or(|c| scope.should_autoclose_before(c));
4122
4123 let preceding_text_allows_autoclose = selection.start.column == 0
4124 || snapshot
4125 .reversed_chars_at(selection.start)
4126 .next()
4127 .is_none_or(|c| {
4128 bracket_pair.start != bracket_pair.end
4129 || !snapshot
4130 .char_classifier_at(selection.start)
4131 .is_word(c)
4132 });
4133
4134 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4135 && bracket_pair.start.len() == 1
4136 {
4137 let target = bracket_pair.start.chars().next().unwrap();
4138 let current_line_count = snapshot
4139 .reversed_chars_at(selection.start)
4140 .take_while(|&c| c != '\n')
4141 .filter(|&c| c == target)
4142 .count();
4143 current_line_count % 2 == 1
4144 } else {
4145 false
4146 };
4147
4148 if autoclose
4149 && bracket_pair.close
4150 && following_text_allows_autoclose
4151 && preceding_text_allows_autoclose
4152 && !is_closing_quote
4153 {
4154 let anchor = snapshot.anchor_before(selection.end);
4155 new_selections.push((selection.map(|_| anchor), text.len()));
4156 new_autoclose_regions.push((
4157 anchor,
4158 text.len(),
4159 selection.id,
4160 bracket_pair.clone(),
4161 ));
4162 edits.push((
4163 selection.range(),
4164 format!("{}{}", text, bracket_pair.end).into(),
4165 ));
4166 bracket_inserted = true;
4167 continue;
4168 }
4169 }
4170
4171 if let Some(region) = autoclose_region {
4172 // If the selection is followed by an auto-inserted closing bracket,
4173 // then don't insert that closing bracket again; just move the selection
4174 // past the closing bracket.
4175 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4176 && text.as_ref() == region.pair.end.as_str()
4177 && snapshot.contains_str_at(region.range.end, text.as_ref());
4178 if should_skip {
4179 let anchor = snapshot.anchor_after(selection.end);
4180 new_selections
4181 .push((selection.map(|_| anchor), region.pair.end.len()));
4182 continue;
4183 }
4184 }
4185
4186 let always_treat_brackets_as_autoclosed = snapshot
4187 .language_settings_at(selection.start, cx)
4188 .always_treat_brackets_as_autoclosed;
4189 if always_treat_brackets_as_autoclosed
4190 && is_bracket_pair_end
4191 && snapshot.contains_str_at(selection.end, text.as_ref())
4192 {
4193 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4194 // and the inserted text is a closing bracket and the selection is followed
4195 // by the closing bracket then move the selection past the closing bracket.
4196 let anchor = snapshot.anchor_after(selection.end);
4197 new_selections.push((selection.map(|_| anchor), text.len()));
4198 continue;
4199 }
4200 }
4201 // If an opening bracket is 1 character long and is typed while
4202 // text is selected, then surround that text with the bracket pair.
4203 else if auto_surround
4204 && bracket_pair.surround
4205 && is_bracket_pair_start
4206 && bracket_pair.start.chars().count() == 1
4207 {
4208 edits.push((selection.start..selection.start, text.clone()));
4209 edits.push((
4210 selection.end..selection.end,
4211 bracket_pair.end.as_str().into(),
4212 ));
4213 bracket_inserted = true;
4214 new_selections.push((
4215 Selection {
4216 id: selection.id,
4217 start: snapshot.anchor_after(selection.start),
4218 end: snapshot.anchor_before(selection.end),
4219 reversed: selection.reversed,
4220 goal: selection.goal,
4221 },
4222 0,
4223 ));
4224 continue;
4225 }
4226 }
4227 }
4228
4229 if self.auto_replace_emoji_shortcode
4230 && selection.is_empty()
4231 && text.as_ref().ends_with(':')
4232 && let Some(possible_emoji_short_code) =
4233 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4234 && !possible_emoji_short_code.is_empty()
4235 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4236 {
4237 let emoji_shortcode_start = Point::new(
4238 selection.start.row,
4239 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4240 );
4241
4242 // Remove shortcode from buffer
4243 edits.push((
4244 emoji_shortcode_start..selection.start,
4245 "".to_string().into(),
4246 ));
4247 new_selections.push((
4248 Selection {
4249 id: selection.id,
4250 start: snapshot.anchor_after(emoji_shortcode_start),
4251 end: snapshot.anchor_before(selection.start),
4252 reversed: selection.reversed,
4253 goal: selection.goal,
4254 },
4255 0,
4256 ));
4257
4258 // Insert emoji
4259 let selection_start_anchor = snapshot.anchor_after(selection.start);
4260 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4261 edits.push((selection.start..selection.end, emoji.to_string().into()));
4262
4263 continue;
4264 }
4265
4266 // If not handling any auto-close operation, then just replace the selected
4267 // text with the given input and move the selection to the end of the
4268 // newly inserted text.
4269 let anchor = snapshot.anchor_after(selection.end);
4270 if !self.linked_edit_ranges.is_empty() {
4271 let start_anchor = snapshot.anchor_before(selection.start);
4272
4273 let is_word_char = text.chars().next().is_none_or(|char| {
4274 let classifier = snapshot
4275 .char_classifier_at(start_anchor.to_offset(&snapshot))
4276 .scope_context(Some(CharScopeContext::LinkedEdit));
4277 classifier.is_word(char)
4278 });
4279
4280 if is_word_char {
4281 if let Some(ranges) = self
4282 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4283 {
4284 for (buffer, edits) in ranges {
4285 linked_edits
4286 .entry(buffer.clone())
4287 .or_default()
4288 .extend(edits.into_iter().map(|range| (range, text.clone())));
4289 }
4290 }
4291 } else {
4292 clear_linked_edit_ranges = true;
4293 }
4294 }
4295
4296 new_selections.push((selection.map(|_| anchor), 0));
4297 edits.push((selection.start..selection.end, text.clone()));
4298 }
4299
4300 drop(snapshot);
4301
4302 self.transact(window, cx, |this, window, cx| {
4303 if clear_linked_edit_ranges {
4304 this.linked_edit_ranges.clear();
4305 }
4306 let initial_buffer_versions =
4307 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4308
4309 this.buffer.update(cx, |buffer, cx| {
4310 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4311 });
4312 for (buffer, edits) in linked_edits {
4313 buffer.update(cx, |buffer, cx| {
4314 let snapshot = buffer.snapshot();
4315 let edits = edits
4316 .into_iter()
4317 .map(|(range, text)| {
4318 use text::ToPoint as TP;
4319 let end_point = TP::to_point(&range.end, &snapshot);
4320 let start_point = TP::to_point(&range.start, &snapshot);
4321 (start_point..end_point, text)
4322 })
4323 .sorted_by_key(|(range, _)| range.start);
4324 buffer.edit(edits, None, cx);
4325 })
4326 }
4327 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4328 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4329 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4330 let new_selections =
4331 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4332 .zip(new_selection_deltas)
4333 .map(|(selection, delta)| Selection {
4334 id: selection.id,
4335 start: selection.start + delta,
4336 end: selection.end + delta,
4337 reversed: selection.reversed,
4338 goal: SelectionGoal::None,
4339 })
4340 .collect::<Vec<_>>();
4341
4342 let mut i = 0;
4343 for (position, delta, selection_id, pair) in new_autoclose_regions {
4344 let position = position.to_offset(map.buffer_snapshot()) + delta;
4345 let start = map.buffer_snapshot().anchor_before(position);
4346 let end = map.buffer_snapshot().anchor_after(position);
4347 while let Some(existing_state) = this.autoclose_regions.get(i) {
4348 match existing_state
4349 .range
4350 .start
4351 .cmp(&start, map.buffer_snapshot())
4352 {
4353 Ordering::Less => i += 1,
4354 Ordering::Greater => break,
4355 Ordering::Equal => {
4356 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4357 Ordering::Less => i += 1,
4358 Ordering::Equal => break,
4359 Ordering::Greater => break,
4360 }
4361 }
4362 }
4363 }
4364 this.autoclose_regions.insert(
4365 i,
4366 AutocloseRegion {
4367 selection_id,
4368 range: start..end,
4369 pair,
4370 },
4371 );
4372 }
4373
4374 let had_active_edit_prediction = this.has_active_edit_prediction();
4375 this.change_selections(
4376 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4377 window,
4378 cx,
4379 |s| s.select(new_selections),
4380 );
4381
4382 if !bracket_inserted
4383 && let Some(on_type_format_task) =
4384 this.trigger_on_type_formatting(text.to_string(), window, cx)
4385 {
4386 on_type_format_task.detach_and_log_err(cx);
4387 }
4388
4389 let editor_settings = EditorSettings::get_global(cx);
4390 if bracket_inserted
4391 && (editor_settings.auto_signature_help
4392 || editor_settings.show_signature_help_after_edits)
4393 {
4394 this.show_signature_help(&ShowSignatureHelp, window, cx);
4395 }
4396
4397 let trigger_in_words =
4398 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4399 if this.hard_wrap.is_some() {
4400 let latest: Range<Point> = this.selections.newest(&map).range();
4401 if latest.is_empty()
4402 && this
4403 .buffer()
4404 .read(cx)
4405 .snapshot(cx)
4406 .line_len(MultiBufferRow(latest.start.row))
4407 == latest.start.column
4408 {
4409 this.rewrap_impl(
4410 RewrapOptions {
4411 override_language_settings: true,
4412 preserve_existing_whitespace: true,
4413 },
4414 cx,
4415 )
4416 }
4417 }
4418 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4419 refresh_linked_ranges(this, window, cx);
4420 this.refresh_edit_prediction(true, false, window, cx);
4421 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4422 });
4423 }
4424
4425 fn find_possible_emoji_shortcode_at_position(
4426 snapshot: &MultiBufferSnapshot,
4427 position: Point,
4428 ) -> Option<String> {
4429 let mut chars = Vec::new();
4430 let mut found_colon = false;
4431 for char in snapshot.reversed_chars_at(position).take(100) {
4432 // Found a possible emoji shortcode in the middle of the buffer
4433 if found_colon {
4434 if char.is_whitespace() {
4435 chars.reverse();
4436 return Some(chars.iter().collect());
4437 }
4438 // If the previous character is not a whitespace, we are in the middle of a word
4439 // and we only want to complete the shortcode if the word is made up of other emojis
4440 let mut containing_word = String::new();
4441 for ch in snapshot
4442 .reversed_chars_at(position)
4443 .skip(chars.len() + 1)
4444 .take(100)
4445 {
4446 if ch.is_whitespace() {
4447 break;
4448 }
4449 containing_word.push(ch);
4450 }
4451 let containing_word = containing_word.chars().rev().collect::<String>();
4452 if util::word_consists_of_emojis(containing_word.as_str()) {
4453 chars.reverse();
4454 return Some(chars.iter().collect());
4455 }
4456 }
4457
4458 if char.is_whitespace() || !char.is_ascii() {
4459 return None;
4460 }
4461 if char == ':' {
4462 found_colon = true;
4463 } else {
4464 chars.push(char);
4465 }
4466 }
4467 // Found a possible emoji shortcode at the beginning of the buffer
4468 chars.reverse();
4469 Some(chars.iter().collect())
4470 }
4471
4472 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4474 self.transact(window, cx, |this, window, cx| {
4475 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4476 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4477 let multi_buffer = this.buffer.read(cx);
4478 let buffer = multi_buffer.snapshot(cx);
4479 selections
4480 .iter()
4481 .map(|selection| {
4482 let start_point = selection.start.to_point(&buffer);
4483 let mut existing_indent =
4484 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4485 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4486 let start = selection.start;
4487 let end = selection.end;
4488 let selection_is_empty = start == end;
4489 let language_scope = buffer.language_scope_at(start);
4490 let (
4491 comment_delimiter,
4492 doc_delimiter,
4493 insert_extra_newline,
4494 indent_on_newline,
4495 indent_on_extra_newline,
4496 ) = if let Some(language) = &language_scope {
4497 let mut insert_extra_newline =
4498 insert_extra_newline_brackets(&buffer, start..end, language)
4499 || insert_extra_newline_tree_sitter(&buffer, start..end);
4500
4501 // Comment extension on newline is allowed only for cursor selections
4502 let comment_delimiter = maybe!({
4503 if !selection_is_empty {
4504 return None;
4505 }
4506
4507 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4508 return None;
4509 }
4510
4511 let delimiters = language.line_comment_prefixes();
4512 let max_len_of_delimiter =
4513 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4514 let (snapshot, range) =
4515 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4516
4517 let num_of_whitespaces = snapshot
4518 .chars_for_range(range.clone())
4519 .take_while(|c| c.is_whitespace())
4520 .count();
4521 let comment_candidate = snapshot
4522 .chars_for_range(range.clone())
4523 .skip(num_of_whitespaces)
4524 .take(max_len_of_delimiter)
4525 .collect::<String>();
4526 let (delimiter, trimmed_len) = delimiters
4527 .iter()
4528 .filter_map(|delimiter| {
4529 let prefix = delimiter.trim_end();
4530 if comment_candidate.starts_with(prefix) {
4531 Some((delimiter, prefix.len()))
4532 } else {
4533 None
4534 }
4535 })
4536 .max_by_key(|(_, len)| *len)?;
4537
4538 if let Some(BlockCommentConfig {
4539 start: block_start, ..
4540 }) = language.block_comment()
4541 {
4542 let block_start_trimmed = block_start.trim_end();
4543 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4544 let line_content = snapshot
4545 .chars_for_range(range)
4546 .skip(num_of_whitespaces)
4547 .take(block_start_trimmed.len())
4548 .collect::<String>();
4549
4550 if line_content.starts_with(block_start_trimmed) {
4551 return None;
4552 }
4553 }
4554 }
4555
4556 let cursor_is_placed_after_comment_marker =
4557 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4558 if cursor_is_placed_after_comment_marker {
4559 Some(delimiter.clone())
4560 } else {
4561 None
4562 }
4563 });
4564
4565 let mut indent_on_newline = IndentSize::spaces(0);
4566 let mut indent_on_extra_newline = IndentSize::spaces(0);
4567
4568 let doc_delimiter = maybe!({
4569 if !selection_is_empty {
4570 return None;
4571 }
4572
4573 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4574 return None;
4575 }
4576
4577 let BlockCommentConfig {
4578 start: start_tag,
4579 end: end_tag,
4580 prefix: delimiter,
4581 tab_size: len,
4582 } = language.documentation_comment()?;
4583 let is_within_block_comment = buffer
4584 .language_scope_at(start_point)
4585 .is_some_and(|scope| scope.override_name() == Some("comment"));
4586 if !is_within_block_comment {
4587 return None;
4588 }
4589
4590 let (snapshot, range) =
4591 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4592
4593 let num_of_whitespaces = snapshot
4594 .chars_for_range(range.clone())
4595 .take_while(|c| c.is_whitespace())
4596 .count();
4597
4598 // 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.
4599 let column = start_point.column;
4600 let cursor_is_after_start_tag = {
4601 let start_tag_len = start_tag.len();
4602 let start_tag_line = snapshot
4603 .chars_for_range(range.clone())
4604 .skip(num_of_whitespaces)
4605 .take(start_tag_len)
4606 .collect::<String>();
4607 if start_tag_line.starts_with(start_tag.as_ref()) {
4608 num_of_whitespaces + start_tag_len <= column as usize
4609 } else {
4610 false
4611 }
4612 };
4613
4614 let cursor_is_after_delimiter = {
4615 let delimiter_trim = delimiter.trim_end();
4616 let delimiter_line = snapshot
4617 .chars_for_range(range.clone())
4618 .skip(num_of_whitespaces)
4619 .take(delimiter_trim.len())
4620 .collect::<String>();
4621 if delimiter_line.starts_with(delimiter_trim) {
4622 num_of_whitespaces + delimiter_trim.len() <= column as usize
4623 } else {
4624 false
4625 }
4626 };
4627
4628 let cursor_is_before_end_tag_if_exists = {
4629 let mut char_position = 0u32;
4630 let mut end_tag_offset = None;
4631
4632 'outer: for chunk in snapshot.text_for_range(range) {
4633 if let Some(byte_pos) = chunk.find(&**end_tag) {
4634 let chars_before_match =
4635 chunk[..byte_pos].chars().count() as u32;
4636 end_tag_offset =
4637 Some(char_position + chars_before_match);
4638 break 'outer;
4639 }
4640 char_position += chunk.chars().count() as u32;
4641 }
4642
4643 if let Some(end_tag_offset) = end_tag_offset {
4644 let cursor_is_before_end_tag = column <= end_tag_offset;
4645 if cursor_is_after_start_tag {
4646 if cursor_is_before_end_tag {
4647 insert_extra_newline = true;
4648 }
4649 let cursor_is_at_start_of_end_tag =
4650 column == end_tag_offset;
4651 if cursor_is_at_start_of_end_tag {
4652 indent_on_extra_newline.len = *len;
4653 }
4654 }
4655 cursor_is_before_end_tag
4656 } else {
4657 true
4658 }
4659 };
4660
4661 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4662 && cursor_is_before_end_tag_if_exists
4663 {
4664 if cursor_is_after_start_tag {
4665 indent_on_newline.len = *len;
4666 }
4667 Some(delimiter.clone())
4668 } else {
4669 None
4670 }
4671 });
4672
4673 (
4674 comment_delimiter,
4675 doc_delimiter,
4676 insert_extra_newline,
4677 indent_on_newline,
4678 indent_on_extra_newline,
4679 )
4680 } else {
4681 (
4682 None,
4683 None,
4684 false,
4685 IndentSize::default(),
4686 IndentSize::default(),
4687 )
4688 };
4689
4690 let prevent_auto_indent = doc_delimiter.is_some();
4691 let delimiter = comment_delimiter.or(doc_delimiter);
4692
4693 let capacity_for_delimiter =
4694 delimiter.as_deref().map(str::len).unwrap_or_default();
4695 let mut new_text = String::with_capacity(
4696 1 + capacity_for_delimiter
4697 + existing_indent.len as usize
4698 + indent_on_newline.len as usize
4699 + indent_on_extra_newline.len as usize,
4700 );
4701 new_text.push('\n');
4702 new_text.extend(existing_indent.chars());
4703 new_text.extend(indent_on_newline.chars());
4704
4705 if let Some(delimiter) = &delimiter {
4706 new_text.push_str(delimiter);
4707 }
4708
4709 if insert_extra_newline {
4710 new_text.push('\n');
4711 new_text.extend(existing_indent.chars());
4712 new_text.extend(indent_on_extra_newline.chars());
4713 }
4714
4715 let anchor = buffer.anchor_after(end);
4716 let new_selection = selection.map(|_| anchor);
4717 (
4718 ((start..end, new_text), prevent_auto_indent),
4719 (insert_extra_newline, new_selection),
4720 )
4721 })
4722 .unzip()
4723 };
4724
4725 let mut auto_indent_edits = Vec::new();
4726 let mut edits = Vec::new();
4727 for (edit, prevent_auto_indent) in edits_with_flags {
4728 if prevent_auto_indent {
4729 edits.push(edit);
4730 } else {
4731 auto_indent_edits.push(edit);
4732 }
4733 }
4734 if !edits.is_empty() {
4735 this.edit(edits, cx);
4736 }
4737 if !auto_indent_edits.is_empty() {
4738 this.edit_with_autoindent(auto_indent_edits, cx);
4739 }
4740
4741 let buffer = this.buffer.read(cx).snapshot(cx);
4742 let new_selections = selection_info
4743 .into_iter()
4744 .map(|(extra_newline_inserted, new_selection)| {
4745 let mut cursor = new_selection.end.to_point(&buffer);
4746 if extra_newline_inserted {
4747 cursor.row -= 1;
4748 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4749 }
4750 new_selection.map(|_| cursor)
4751 })
4752 .collect();
4753
4754 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4755 this.refresh_edit_prediction(true, false, window, cx);
4756 });
4757 }
4758
4759 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4761
4762 let buffer = self.buffer.read(cx);
4763 let snapshot = buffer.snapshot(cx);
4764
4765 let mut edits = Vec::new();
4766 let mut rows = Vec::new();
4767
4768 for (rows_inserted, selection) in self
4769 .selections
4770 .all_adjusted(&self.display_snapshot(cx))
4771 .into_iter()
4772 .enumerate()
4773 {
4774 let cursor = selection.head();
4775 let row = cursor.row;
4776
4777 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4778
4779 let newline = "\n".to_string();
4780 edits.push((start_of_line..start_of_line, newline));
4781
4782 rows.push(row + rows_inserted as u32);
4783 }
4784
4785 self.transact(window, cx, |editor, window, cx| {
4786 editor.edit(edits, cx);
4787
4788 editor.change_selections(Default::default(), window, cx, |s| {
4789 let mut index = 0;
4790 s.move_cursors_with(|map, _, _| {
4791 let row = rows[index];
4792 index += 1;
4793
4794 let point = Point::new(row, 0);
4795 let boundary = map.next_line_boundary(point).1;
4796 let clipped = map.clip_point(boundary, Bias::Left);
4797
4798 (clipped, SelectionGoal::None)
4799 });
4800 });
4801
4802 let mut indent_edits = Vec::new();
4803 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4804 for row in rows {
4805 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4806 for (row, indent) in indents {
4807 if indent.len == 0 {
4808 continue;
4809 }
4810
4811 let text = match indent.kind {
4812 IndentKind::Space => " ".repeat(indent.len as usize),
4813 IndentKind::Tab => "\t".repeat(indent.len as usize),
4814 };
4815 let point = Point::new(row.0, 0);
4816 indent_edits.push((point..point, text));
4817 }
4818 }
4819 editor.edit(indent_edits, cx);
4820 });
4821 }
4822
4823 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4824 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4825
4826 let buffer = self.buffer.read(cx);
4827 let snapshot = buffer.snapshot(cx);
4828
4829 let mut edits = Vec::new();
4830 let mut rows = Vec::new();
4831 let mut rows_inserted = 0;
4832
4833 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4834 let cursor = selection.head();
4835 let row = cursor.row;
4836
4837 let point = Point::new(row + 1, 0);
4838 let start_of_line = snapshot.clip_point(point, Bias::Left);
4839
4840 let newline = "\n".to_string();
4841 edits.push((start_of_line..start_of_line, newline));
4842
4843 rows_inserted += 1;
4844 rows.push(row + rows_inserted);
4845 }
4846
4847 self.transact(window, cx, |editor, window, cx| {
4848 editor.edit(edits, cx);
4849
4850 editor.change_selections(Default::default(), window, cx, |s| {
4851 let mut index = 0;
4852 s.move_cursors_with(|map, _, _| {
4853 let row = rows[index];
4854 index += 1;
4855
4856 let point = Point::new(row, 0);
4857 let boundary = map.next_line_boundary(point).1;
4858 let clipped = map.clip_point(boundary, Bias::Left);
4859
4860 (clipped, SelectionGoal::None)
4861 });
4862 });
4863
4864 let mut indent_edits = Vec::new();
4865 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4866 for row in rows {
4867 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4868 for (row, indent) in indents {
4869 if indent.len == 0 {
4870 continue;
4871 }
4872
4873 let text = match indent.kind {
4874 IndentKind::Space => " ".repeat(indent.len as usize),
4875 IndentKind::Tab => "\t".repeat(indent.len as usize),
4876 };
4877 let point = Point::new(row.0, 0);
4878 indent_edits.push((point..point, text));
4879 }
4880 }
4881 editor.edit(indent_edits, cx);
4882 });
4883 }
4884
4885 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4886 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4887 original_indent_columns: Vec::new(),
4888 });
4889 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4890 }
4891
4892 fn insert_with_autoindent_mode(
4893 &mut self,
4894 text: &str,
4895 autoindent_mode: Option<AutoindentMode>,
4896 window: &mut Window,
4897 cx: &mut Context<Self>,
4898 ) {
4899 if self.read_only(cx) {
4900 return;
4901 }
4902
4903 let text: Arc<str> = text.into();
4904 self.transact(window, cx, |this, window, cx| {
4905 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4906 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4907 let anchors = {
4908 let snapshot = buffer.read(cx);
4909 old_selections
4910 .iter()
4911 .map(|s| {
4912 let anchor = snapshot.anchor_after(s.head());
4913 s.map(|_| anchor)
4914 })
4915 .collect::<Vec<_>>()
4916 };
4917 buffer.edit(
4918 old_selections
4919 .iter()
4920 .map(|s| (s.start..s.end, text.clone())),
4921 autoindent_mode,
4922 cx,
4923 );
4924 anchors
4925 });
4926
4927 this.change_selections(Default::default(), window, cx, |s| {
4928 s.select_anchors(selection_anchors);
4929 });
4930
4931 cx.notify();
4932 });
4933 }
4934
4935 fn trigger_completion_on_input(
4936 &mut self,
4937 text: &str,
4938 trigger_in_words: bool,
4939 window: &mut Window,
4940 cx: &mut Context<Self>,
4941 ) {
4942 let completions_source = self
4943 .context_menu
4944 .borrow()
4945 .as_ref()
4946 .and_then(|menu| match menu {
4947 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4948 CodeContextMenu::CodeActions(_) => None,
4949 });
4950
4951 match completions_source {
4952 Some(CompletionsMenuSource::Words { .. }) => {
4953 self.open_or_update_completions_menu(
4954 Some(CompletionsMenuSource::Words {
4955 ignore_threshold: false,
4956 }),
4957 None,
4958 window,
4959 cx,
4960 );
4961 }
4962 Some(CompletionsMenuSource::Normal)
4963 | Some(CompletionsMenuSource::SnippetChoices)
4964 | None
4965 if self.is_completion_trigger(
4966 text,
4967 trigger_in_words,
4968 completions_source.is_some(),
4969 cx,
4970 ) =>
4971 {
4972 self.show_completions(
4973 &ShowCompletions {
4974 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4975 },
4976 window,
4977 cx,
4978 )
4979 }
4980 _ => {
4981 self.hide_context_menu(window, cx);
4982 }
4983 }
4984 }
4985
4986 fn is_completion_trigger(
4987 &self,
4988 text: &str,
4989 trigger_in_words: bool,
4990 menu_is_open: bool,
4991 cx: &mut Context<Self>,
4992 ) -> bool {
4993 let position = self.selections.newest_anchor().head();
4994 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4995 return false;
4996 };
4997
4998 if let Some(completion_provider) = &self.completion_provider {
4999 completion_provider.is_completion_trigger(
5000 &buffer,
5001 position.text_anchor,
5002 text,
5003 trigger_in_words,
5004 menu_is_open,
5005 cx,
5006 )
5007 } else {
5008 false
5009 }
5010 }
5011
5012 /// If any empty selections is touching the start of its innermost containing autoclose
5013 /// region, expand it to select the brackets.
5014 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5015 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5016 let buffer = self.buffer.read(cx).read(cx);
5017 let new_selections = self
5018 .selections_with_autoclose_regions(selections, &buffer)
5019 .map(|(mut selection, region)| {
5020 if !selection.is_empty() {
5021 return selection;
5022 }
5023
5024 if let Some(region) = region {
5025 let mut range = region.range.to_offset(&buffer);
5026 if selection.start == range.start && range.start >= region.pair.start.len() {
5027 range.start -= region.pair.start.len();
5028 if buffer.contains_str_at(range.start, ®ion.pair.start)
5029 && buffer.contains_str_at(range.end, ®ion.pair.end)
5030 {
5031 range.end += region.pair.end.len();
5032 selection.start = range.start;
5033 selection.end = range.end;
5034
5035 return selection;
5036 }
5037 }
5038 }
5039
5040 let always_treat_brackets_as_autoclosed = buffer
5041 .language_settings_at(selection.start, cx)
5042 .always_treat_brackets_as_autoclosed;
5043
5044 if !always_treat_brackets_as_autoclosed {
5045 return selection;
5046 }
5047
5048 if let Some(scope) = buffer.language_scope_at(selection.start) {
5049 for (pair, enabled) in scope.brackets() {
5050 if !enabled || !pair.close {
5051 continue;
5052 }
5053
5054 if buffer.contains_str_at(selection.start, &pair.end) {
5055 let pair_start_len = pair.start.len();
5056 if buffer.contains_str_at(
5057 selection.start.saturating_sub(pair_start_len),
5058 &pair.start,
5059 ) {
5060 selection.start -= pair_start_len;
5061 selection.end += pair.end.len();
5062
5063 return selection;
5064 }
5065 }
5066 }
5067 }
5068
5069 selection
5070 })
5071 .collect();
5072
5073 drop(buffer);
5074 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5075 selections.select(new_selections)
5076 });
5077 }
5078
5079 /// Iterate the given selections, and for each one, find the smallest surrounding
5080 /// autoclose region. This uses the ordering of the selections and the autoclose
5081 /// regions to avoid repeated comparisons.
5082 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5083 &'a self,
5084 selections: impl IntoIterator<Item = Selection<D>>,
5085 buffer: &'a MultiBufferSnapshot,
5086 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5087 let mut i = 0;
5088 let mut regions = self.autoclose_regions.as_slice();
5089 selections.into_iter().map(move |selection| {
5090 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5091
5092 let mut enclosing = None;
5093 while let Some(pair_state) = regions.get(i) {
5094 if pair_state.range.end.to_offset(buffer) < range.start {
5095 regions = ®ions[i + 1..];
5096 i = 0;
5097 } else if pair_state.range.start.to_offset(buffer) > range.end {
5098 break;
5099 } else {
5100 if pair_state.selection_id == selection.id {
5101 enclosing = Some(pair_state);
5102 }
5103 i += 1;
5104 }
5105 }
5106
5107 (selection, enclosing)
5108 })
5109 }
5110
5111 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5112 fn invalidate_autoclose_regions(
5113 &mut self,
5114 mut selections: &[Selection<Anchor>],
5115 buffer: &MultiBufferSnapshot,
5116 ) {
5117 self.autoclose_regions.retain(|state| {
5118 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5119 return false;
5120 }
5121
5122 let mut i = 0;
5123 while let Some(selection) = selections.get(i) {
5124 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5125 selections = &selections[1..];
5126 continue;
5127 }
5128 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5129 break;
5130 }
5131 if selection.id == state.selection_id {
5132 return true;
5133 } else {
5134 i += 1;
5135 }
5136 }
5137 false
5138 });
5139 }
5140
5141 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5142 let offset = position.to_offset(buffer);
5143 let (word_range, kind) =
5144 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5145 if offset > word_range.start && kind == Some(CharKind::Word) {
5146 Some(
5147 buffer
5148 .text_for_range(word_range.start..offset)
5149 .collect::<String>(),
5150 )
5151 } else {
5152 None
5153 }
5154 }
5155
5156 pub fn visible_excerpts(
5157 &self,
5158 cx: &mut Context<Editor>,
5159 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5160 let Some(project) = self.project() else {
5161 return HashMap::default();
5162 };
5163 let project = project.read(cx);
5164 let multi_buffer = self.buffer().read(cx);
5165 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5166 let multi_buffer_visible_start = self
5167 .scroll_manager
5168 .anchor()
5169 .anchor
5170 .to_point(&multi_buffer_snapshot);
5171 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5172 multi_buffer_visible_start
5173 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5174 Bias::Left,
5175 );
5176 multi_buffer_snapshot
5177 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5178 .into_iter()
5179 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5180 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5181 let buffer_file = project::File::from_dyn(buffer.file())?;
5182 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5183 let worktree_entry = buffer_worktree
5184 .read(cx)
5185 .entry_for_id(buffer_file.project_entry_id()?)?;
5186 if worktree_entry.is_ignored {
5187 None
5188 } else {
5189 Some((
5190 excerpt_id,
5191 (
5192 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5193 buffer.version().clone(),
5194 excerpt_visible_range,
5195 ),
5196 ))
5197 }
5198 })
5199 .collect()
5200 }
5201
5202 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5203 TextLayoutDetails {
5204 text_system: window.text_system().clone(),
5205 editor_style: self.style.clone().unwrap(),
5206 rem_size: window.rem_size(),
5207 scroll_anchor: self.scroll_manager.anchor(),
5208 visible_rows: self.visible_line_count(),
5209 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5210 }
5211 }
5212
5213 fn trigger_on_type_formatting(
5214 &self,
5215 input: String,
5216 window: &mut Window,
5217 cx: &mut Context<Self>,
5218 ) -> Option<Task<Result<()>>> {
5219 if input.len() != 1 {
5220 return None;
5221 }
5222
5223 let project = self.project()?;
5224 let position = self.selections.newest_anchor().head();
5225 let (buffer, buffer_position) = self
5226 .buffer
5227 .read(cx)
5228 .text_anchor_for_position(position, cx)?;
5229
5230 let settings = language_settings::language_settings(
5231 buffer
5232 .read(cx)
5233 .language_at(buffer_position)
5234 .map(|l| l.name()),
5235 buffer.read(cx).file(),
5236 cx,
5237 );
5238 if !settings.use_on_type_format {
5239 return None;
5240 }
5241
5242 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5243 // hence we do LSP request & edit on host side only — add formats to host's history.
5244 let push_to_lsp_host_history = true;
5245 // If this is not the host, append its history with new edits.
5246 let push_to_client_history = project.read(cx).is_via_collab();
5247
5248 let on_type_formatting = project.update(cx, |project, cx| {
5249 project.on_type_format(
5250 buffer.clone(),
5251 buffer_position,
5252 input,
5253 push_to_lsp_host_history,
5254 cx,
5255 )
5256 });
5257 Some(cx.spawn_in(window, async move |editor, cx| {
5258 if let Some(transaction) = on_type_formatting.await? {
5259 if push_to_client_history {
5260 buffer
5261 .update(cx, |buffer, _| {
5262 buffer.push_transaction(transaction, Instant::now());
5263 buffer.finalize_last_transaction();
5264 })
5265 .ok();
5266 }
5267 editor.update(cx, |editor, cx| {
5268 editor.refresh_document_highlights(cx);
5269 })?;
5270 }
5271 Ok(())
5272 }))
5273 }
5274
5275 pub fn show_word_completions(
5276 &mut self,
5277 _: &ShowWordCompletions,
5278 window: &mut Window,
5279 cx: &mut Context<Self>,
5280 ) {
5281 self.open_or_update_completions_menu(
5282 Some(CompletionsMenuSource::Words {
5283 ignore_threshold: true,
5284 }),
5285 None,
5286 window,
5287 cx,
5288 );
5289 }
5290
5291 pub fn show_completions(
5292 &mut self,
5293 options: &ShowCompletions,
5294 window: &mut Window,
5295 cx: &mut Context<Self>,
5296 ) {
5297 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5298 }
5299
5300 fn open_or_update_completions_menu(
5301 &mut self,
5302 requested_source: Option<CompletionsMenuSource>,
5303 trigger: Option<&str>,
5304 window: &mut Window,
5305 cx: &mut Context<Self>,
5306 ) {
5307 if self.pending_rename.is_some() {
5308 return;
5309 }
5310
5311 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5312
5313 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5314 // inserted and selected. To handle that case, the start of the selection is used so that
5315 // the menu starts with all choices.
5316 let position = self
5317 .selections
5318 .newest_anchor()
5319 .start
5320 .bias_right(&multibuffer_snapshot);
5321 if position.diff_base_anchor.is_some() {
5322 return;
5323 }
5324 let buffer_position = multibuffer_snapshot.anchor_before(position);
5325 let Some(buffer) = buffer_position
5326 .buffer_id
5327 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5328 else {
5329 return;
5330 };
5331 let buffer_snapshot = buffer.read(cx).snapshot();
5332
5333 let query: Option<Arc<String>> =
5334 Self::completion_query(&multibuffer_snapshot, buffer_position)
5335 .map(|query| query.into());
5336
5337 drop(multibuffer_snapshot);
5338
5339 // Hide the current completions menu when query is empty. Without this, cached
5340 // completions from before the trigger char may be reused (#32774).
5341 if query.is_none() {
5342 let menu_is_open = matches!(
5343 self.context_menu.borrow().as_ref(),
5344 Some(CodeContextMenu::Completions(_))
5345 );
5346 if menu_is_open {
5347 self.hide_context_menu(window, cx);
5348 }
5349 }
5350
5351 let mut ignore_word_threshold = false;
5352 let provider = match requested_source {
5353 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5354 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5355 ignore_word_threshold = ignore_threshold;
5356 None
5357 }
5358 Some(CompletionsMenuSource::SnippetChoices) => {
5359 log::error!("bug: SnippetChoices requested_source is not handled");
5360 None
5361 }
5362 };
5363
5364 let sort_completions = provider
5365 .as_ref()
5366 .is_some_and(|provider| provider.sort_completions());
5367
5368 let filter_completions = provider
5369 .as_ref()
5370 .is_none_or(|provider| provider.filter_completions());
5371
5372 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5373 if filter_completions {
5374 menu.filter(query.clone(), provider.clone(), window, cx);
5375 }
5376 // When `is_incomplete` is false, no need to re-query completions when the current query
5377 // is a suffix of the initial query.
5378 if !menu.is_incomplete {
5379 // If the new query is a suffix of the old query (typing more characters) and
5380 // the previous result was complete, the existing completions can be filtered.
5381 //
5382 // Note that this is always true for snippet completions.
5383 let query_matches = match (&menu.initial_query, &query) {
5384 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5385 (None, _) => true,
5386 _ => false,
5387 };
5388 if query_matches {
5389 let position_matches = if menu.initial_position == position {
5390 true
5391 } else {
5392 let snapshot = self.buffer.read(cx).read(cx);
5393 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5394 };
5395 if position_matches {
5396 return;
5397 }
5398 }
5399 }
5400 };
5401
5402 let trigger_kind = match trigger {
5403 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5404 CompletionTriggerKind::TRIGGER_CHARACTER
5405 }
5406 _ => CompletionTriggerKind::INVOKED,
5407 };
5408 let completion_context = CompletionContext {
5409 trigger_character: trigger.and_then(|trigger| {
5410 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5411 Some(String::from(trigger))
5412 } else {
5413 None
5414 }
5415 }),
5416 trigger_kind,
5417 };
5418
5419 let Anchor {
5420 excerpt_id: buffer_excerpt_id,
5421 text_anchor: buffer_position,
5422 ..
5423 } = buffer_position;
5424
5425 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5426 buffer_snapshot.surrounding_word(buffer_position, None)
5427 {
5428 let word_to_exclude = buffer_snapshot
5429 .text_for_range(word_range.clone())
5430 .collect::<String>();
5431 (
5432 buffer_snapshot.anchor_before(word_range.start)
5433 ..buffer_snapshot.anchor_after(buffer_position),
5434 Some(word_to_exclude),
5435 )
5436 } else {
5437 (buffer_position..buffer_position, None)
5438 };
5439
5440 let language = buffer_snapshot
5441 .language_at(buffer_position)
5442 .map(|language| language.name());
5443
5444 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5445 .completions
5446 .clone();
5447
5448 let show_completion_documentation = buffer_snapshot
5449 .settings_at(buffer_position, cx)
5450 .show_completion_documentation;
5451
5452 // The document can be large, so stay in reasonable bounds when searching for words,
5453 // otherwise completion pop-up might be slow to appear.
5454 const WORD_LOOKUP_ROWS: u32 = 5_000;
5455 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5456 let min_word_search = buffer_snapshot.clip_point(
5457 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5458 Bias::Left,
5459 );
5460 let max_word_search = buffer_snapshot.clip_point(
5461 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5462 Bias::Right,
5463 );
5464 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5465 ..buffer_snapshot.point_to_offset(max_word_search);
5466
5467 let skip_digits = query
5468 .as_ref()
5469 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5470
5471 let omit_word_completions = !self.word_completions_enabled
5472 || (!ignore_word_threshold
5473 && match &query {
5474 Some(query) => query.chars().count() < completion_settings.words_min_length,
5475 None => completion_settings.words_min_length != 0,
5476 });
5477
5478 let (mut words, provider_responses) = match &provider {
5479 Some(provider) => {
5480 let provider_responses = provider.completions(
5481 buffer_excerpt_id,
5482 &buffer,
5483 buffer_position,
5484 completion_context,
5485 window,
5486 cx,
5487 );
5488
5489 let words = match (omit_word_completions, completion_settings.words) {
5490 (true, _) | (_, WordsCompletionMode::Disabled) => {
5491 Task::ready(BTreeMap::default())
5492 }
5493 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5494 .background_spawn(async move {
5495 buffer_snapshot.words_in_range(WordsQuery {
5496 fuzzy_contents: None,
5497 range: word_search_range,
5498 skip_digits,
5499 })
5500 }),
5501 };
5502
5503 (words, provider_responses)
5504 }
5505 None => {
5506 let words = if omit_word_completions {
5507 Task::ready(BTreeMap::default())
5508 } else {
5509 cx.background_spawn(async move {
5510 buffer_snapshot.words_in_range(WordsQuery {
5511 fuzzy_contents: None,
5512 range: word_search_range,
5513 skip_digits,
5514 })
5515 })
5516 };
5517 (words, Task::ready(Ok(Vec::new())))
5518 }
5519 };
5520
5521 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5522
5523 let id = post_inc(&mut self.next_completion_id);
5524 let task = cx.spawn_in(window, async move |editor, cx| {
5525 let Ok(()) = editor.update(cx, |this, _| {
5526 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5527 }) else {
5528 return;
5529 };
5530
5531 // TODO: Ideally completions from different sources would be selectively re-queried, so
5532 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5533 let mut completions = Vec::new();
5534 let mut is_incomplete = false;
5535 let mut display_options: Option<CompletionDisplayOptions> = None;
5536 if let Some(provider_responses) = provider_responses.await.log_err()
5537 && !provider_responses.is_empty()
5538 {
5539 for response in provider_responses {
5540 completions.extend(response.completions);
5541 is_incomplete = is_incomplete || response.is_incomplete;
5542 match display_options.as_mut() {
5543 None => {
5544 display_options = Some(response.display_options);
5545 }
5546 Some(options) => options.merge(&response.display_options),
5547 }
5548 }
5549 if completion_settings.words == WordsCompletionMode::Fallback {
5550 words = Task::ready(BTreeMap::default());
5551 }
5552 }
5553 let display_options = display_options.unwrap_or_default();
5554
5555 let mut words = words.await;
5556 if let Some(word_to_exclude) = &word_to_exclude {
5557 words.remove(word_to_exclude);
5558 }
5559 for lsp_completion in &completions {
5560 words.remove(&lsp_completion.new_text);
5561 }
5562 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5563 replace_range: word_replace_range.clone(),
5564 new_text: word.clone(),
5565 label: CodeLabel::plain(word, None),
5566 icon_path: None,
5567 documentation: None,
5568 source: CompletionSource::BufferWord {
5569 word_range,
5570 resolved: false,
5571 },
5572 insert_text_mode: Some(InsertTextMode::AS_IS),
5573 confirm: None,
5574 }));
5575
5576 let menu = if completions.is_empty() {
5577 None
5578 } else {
5579 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5580 let languages = editor
5581 .workspace
5582 .as_ref()
5583 .and_then(|(workspace, _)| workspace.upgrade())
5584 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5585 let menu = CompletionsMenu::new(
5586 id,
5587 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5588 sort_completions,
5589 show_completion_documentation,
5590 position,
5591 query.clone(),
5592 is_incomplete,
5593 buffer.clone(),
5594 completions.into(),
5595 display_options,
5596 snippet_sort_order,
5597 languages,
5598 language,
5599 cx,
5600 );
5601
5602 let query = if filter_completions { query } else { None };
5603 let matches_task = if let Some(query) = query {
5604 menu.do_async_filtering(query, cx)
5605 } else {
5606 Task::ready(menu.unfiltered_matches())
5607 };
5608 (menu, matches_task)
5609 }) else {
5610 return;
5611 };
5612
5613 let matches = matches_task.await;
5614
5615 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5616 // Newer menu already set, so exit.
5617 if let Some(CodeContextMenu::Completions(prev_menu)) =
5618 editor.context_menu.borrow().as_ref()
5619 && prev_menu.id > id
5620 {
5621 return;
5622 };
5623
5624 // Only valid to take prev_menu because it the new menu is immediately set
5625 // below, or the menu is hidden.
5626 if let Some(CodeContextMenu::Completions(prev_menu)) =
5627 editor.context_menu.borrow_mut().take()
5628 {
5629 let position_matches =
5630 if prev_menu.initial_position == menu.initial_position {
5631 true
5632 } else {
5633 let snapshot = editor.buffer.read(cx).read(cx);
5634 prev_menu.initial_position.to_offset(&snapshot)
5635 == menu.initial_position.to_offset(&snapshot)
5636 };
5637 if position_matches {
5638 // Preserve markdown cache before `set_filter_results` because it will
5639 // try to populate the documentation cache.
5640 menu.preserve_markdown_cache(prev_menu);
5641 }
5642 };
5643
5644 menu.set_filter_results(matches, provider, window, cx);
5645 }) else {
5646 return;
5647 };
5648
5649 menu.visible().then_some(menu)
5650 };
5651
5652 editor
5653 .update_in(cx, |editor, window, cx| {
5654 if editor.focus_handle.is_focused(window)
5655 && let Some(menu) = menu
5656 {
5657 *editor.context_menu.borrow_mut() =
5658 Some(CodeContextMenu::Completions(menu));
5659
5660 crate::hover_popover::hide_hover(editor, cx);
5661 if editor.show_edit_predictions_in_menu() {
5662 editor.update_visible_edit_prediction(window, cx);
5663 } else {
5664 editor.discard_edit_prediction(false, cx);
5665 }
5666
5667 cx.notify();
5668 return;
5669 }
5670
5671 if editor.completion_tasks.len() <= 1 {
5672 // If there are no more completion tasks and the last menu was empty, we should hide it.
5673 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5674 // If it was already hidden and we don't show edit predictions in the menu,
5675 // we should also show the edit prediction when available.
5676 if was_hidden && editor.show_edit_predictions_in_menu() {
5677 editor.update_visible_edit_prediction(window, cx);
5678 }
5679 }
5680 })
5681 .ok();
5682 });
5683
5684 self.completion_tasks.push((id, task));
5685 }
5686
5687 #[cfg(feature = "test-support")]
5688 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5689 let menu = self.context_menu.borrow();
5690 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5691 let completions = menu.completions.borrow();
5692 Some(completions.to_vec())
5693 } else {
5694 None
5695 }
5696 }
5697
5698 pub fn with_completions_menu_matching_id<R>(
5699 &self,
5700 id: CompletionId,
5701 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5702 ) -> R {
5703 let mut context_menu = self.context_menu.borrow_mut();
5704 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5705 return f(None);
5706 };
5707 if completions_menu.id != id {
5708 return f(None);
5709 }
5710 f(Some(completions_menu))
5711 }
5712
5713 pub fn confirm_completion(
5714 &mut self,
5715 action: &ConfirmCompletion,
5716 window: &mut Window,
5717 cx: &mut Context<Self>,
5718 ) -> Option<Task<Result<()>>> {
5719 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5720 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5721 }
5722
5723 pub fn confirm_completion_insert(
5724 &mut self,
5725 _: &ConfirmCompletionInsert,
5726 window: &mut Window,
5727 cx: &mut Context<Self>,
5728 ) -> Option<Task<Result<()>>> {
5729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5730 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5731 }
5732
5733 pub fn confirm_completion_replace(
5734 &mut self,
5735 _: &ConfirmCompletionReplace,
5736 window: &mut Window,
5737 cx: &mut Context<Self>,
5738 ) -> Option<Task<Result<()>>> {
5739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5740 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5741 }
5742
5743 pub fn compose_completion(
5744 &mut self,
5745 action: &ComposeCompletion,
5746 window: &mut Window,
5747 cx: &mut Context<Self>,
5748 ) -> Option<Task<Result<()>>> {
5749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5750 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5751 }
5752
5753 fn do_completion(
5754 &mut self,
5755 item_ix: Option<usize>,
5756 intent: CompletionIntent,
5757 window: &mut Window,
5758 cx: &mut Context<Editor>,
5759 ) -> Option<Task<Result<()>>> {
5760 use language::ToOffset as _;
5761
5762 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5763 else {
5764 return None;
5765 };
5766
5767 let candidate_id = {
5768 let entries = completions_menu.entries.borrow();
5769 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5770 if self.show_edit_predictions_in_menu() {
5771 self.discard_edit_prediction(true, cx);
5772 }
5773 mat.candidate_id
5774 };
5775
5776 let completion = completions_menu
5777 .completions
5778 .borrow()
5779 .get(candidate_id)?
5780 .clone();
5781 cx.stop_propagation();
5782
5783 let buffer_handle = completions_menu.buffer.clone();
5784
5785 let CompletionEdit {
5786 new_text,
5787 snippet,
5788 replace_range,
5789 } = process_completion_for_edit(
5790 &completion,
5791 intent,
5792 &buffer_handle,
5793 &completions_menu.initial_position.text_anchor,
5794 cx,
5795 );
5796
5797 let buffer = buffer_handle.read(cx);
5798 let snapshot = self.buffer.read(cx).snapshot(cx);
5799 let newest_anchor = self.selections.newest_anchor();
5800 let replace_range_multibuffer = {
5801 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5802 excerpt.map_range_from_buffer(replace_range.clone())
5803 };
5804 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5805 return None;
5806 }
5807
5808 let old_text = buffer
5809 .text_for_range(replace_range.clone())
5810 .collect::<String>();
5811 let lookbehind = newest_anchor
5812 .start
5813 .text_anchor
5814 .to_offset(buffer)
5815 .saturating_sub(replace_range.start);
5816 let lookahead = replace_range
5817 .end
5818 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5819 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5820 let suffix = &old_text[lookbehind.min(old_text.len())..];
5821
5822 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5823 let mut ranges = Vec::new();
5824 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5825
5826 for selection in &selections {
5827 let range = if selection.id == newest_anchor.id {
5828 replace_range_multibuffer.clone()
5829 } else {
5830 let mut range = selection.range();
5831
5832 // if prefix is present, don't duplicate it
5833 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5834 range.start = range.start.saturating_sub(lookbehind);
5835
5836 // if suffix is also present, mimic the newest cursor and replace it
5837 if selection.id != newest_anchor.id
5838 && snapshot.contains_str_at(range.end, suffix)
5839 {
5840 range.end += lookahead;
5841 }
5842 }
5843 range
5844 };
5845
5846 ranges.push(range.clone());
5847
5848 if !self.linked_edit_ranges.is_empty() {
5849 let start_anchor = snapshot.anchor_before(range.start);
5850 let end_anchor = snapshot.anchor_after(range.end);
5851 if let Some(ranges) = self
5852 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5853 {
5854 for (buffer, edits) in ranges {
5855 linked_edits
5856 .entry(buffer.clone())
5857 .or_default()
5858 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5859 }
5860 }
5861 }
5862 }
5863
5864 let common_prefix_len = old_text
5865 .chars()
5866 .zip(new_text.chars())
5867 .take_while(|(a, b)| a == b)
5868 .map(|(a, _)| a.len_utf8())
5869 .sum::<usize>();
5870
5871 cx.emit(EditorEvent::InputHandled {
5872 utf16_range_to_replace: None,
5873 text: new_text[common_prefix_len..].into(),
5874 });
5875
5876 self.transact(window, cx, |editor, window, cx| {
5877 if let Some(mut snippet) = snippet {
5878 snippet.text = new_text.to_string();
5879 editor
5880 .insert_snippet(&ranges, snippet, window, cx)
5881 .log_err();
5882 } else {
5883 editor.buffer.update(cx, |multi_buffer, cx| {
5884 let auto_indent = match completion.insert_text_mode {
5885 Some(InsertTextMode::AS_IS) => None,
5886 _ => editor.autoindent_mode.clone(),
5887 };
5888 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5889 multi_buffer.edit(edits, auto_indent, cx);
5890 });
5891 }
5892 for (buffer, edits) in linked_edits {
5893 buffer.update(cx, |buffer, cx| {
5894 let snapshot = buffer.snapshot();
5895 let edits = edits
5896 .into_iter()
5897 .map(|(range, text)| {
5898 use text::ToPoint as TP;
5899 let end_point = TP::to_point(&range.end, &snapshot);
5900 let start_point = TP::to_point(&range.start, &snapshot);
5901 (start_point..end_point, text)
5902 })
5903 .sorted_by_key(|(range, _)| range.start);
5904 buffer.edit(edits, None, cx);
5905 })
5906 }
5907
5908 editor.refresh_edit_prediction(true, false, window, cx);
5909 });
5910 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5911
5912 let show_new_completions_on_confirm = completion
5913 .confirm
5914 .as_ref()
5915 .is_some_and(|confirm| confirm(intent, window, cx));
5916 if show_new_completions_on_confirm {
5917 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5918 }
5919
5920 let provider = self.completion_provider.as_ref()?;
5921 drop(completion);
5922 let apply_edits = provider.apply_additional_edits_for_completion(
5923 buffer_handle,
5924 completions_menu.completions.clone(),
5925 candidate_id,
5926 true,
5927 cx,
5928 );
5929
5930 let editor_settings = EditorSettings::get_global(cx);
5931 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5932 // After the code completion is finished, users often want to know what signatures are needed.
5933 // so we should automatically call signature_help
5934 self.show_signature_help(&ShowSignatureHelp, window, cx);
5935 }
5936
5937 Some(cx.foreground_executor().spawn(async move {
5938 apply_edits.await?;
5939 Ok(())
5940 }))
5941 }
5942
5943 pub fn toggle_code_actions(
5944 &mut self,
5945 action: &ToggleCodeActions,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) {
5949 let quick_launch = action.quick_launch;
5950 let mut context_menu = self.context_menu.borrow_mut();
5951 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5952 if code_actions.deployed_from == action.deployed_from {
5953 // Toggle if we're selecting the same one
5954 *context_menu = None;
5955 cx.notify();
5956 return;
5957 } else {
5958 // Otherwise, clear it and start a new one
5959 *context_menu = None;
5960 cx.notify();
5961 }
5962 }
5963 drop(context_menu);
5964 let snapshot = self.snapshot(window, cx);
5965 let deployed_from = action.deployed_from.clone();
5966 let action = action.clone();
5967 self.completion_tasks.clear();
5968 self.discard_edit_prediction(false, cx);
5969
5970 let multibuffer_point = match &action.deployed_from {
5971 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5972 DisplayPoint::new(*row, 0).to_point(&snapshot)
5973 }
5974 _ => self
5975 .selections
5976 .newest::<Point>(&snapshot.display_snapshot)
5977 .head(),
5978 };
5979 let Some((buffer, buffer_row)) = snapshot
5980 .buffer_snapshot()
5981 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5982 .and_then(|(buffer_snapshot, range)| {
5983 self.buffer()
5984 .read(cx)
5985 .buffer(buffer_snapshot.remote_id())
5986 .map(|buffer| (buffer, range.start.row))
5987 })
5988 else {
5989 return;
5990 };
5991 let buffer_id = buffer.read(cx).remote_id();
5992 let tasks = self
5993 .tasks
5994 .get(&(buffer_id, buffer_row))
5995 .map(|t| Arc::new(t.to_owned()));
5996
5997 if !self.focus_handle.is_focused(window) {
5998 return;
5999 }
6000 let project = self.project.clone();
6001
6002 let code_actions_task = match deployed_from {
6003 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6004 _ => self.code_actions(buffer_row, window, cx),
6005 };
6006
6007 let runnable_task = match deployed_from {
6008 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6009 _ => {
6010 let mut task_context_task = Task::ready(None);
6011 if let Some(tasks) = &tasks
6012 && let Some(project) = project
6013 {
6014 task_context_task =
6015 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6016 }
6017
6018 cx.spawn_in(window, {
6019 let buffer = buffer.clone();
6020 async move |editor, cx| {
6021 let task_context = task_context_task.await;
6022
6023 let resolved_tasks =
6024 tasks
6025 .zip(task_context.clone())
6026 .map(|(tasks, task_context)| ResolvedTasks {
6027 templates: tasks.resolve(&task_context).collect(),
6028 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6029 multibuffer_point.row,
6030 tasks.column,
6031 )),
6032 });
6033 let debug_scenarios = editor
6034 .update(cx, |editor, cx| {
6035 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6036 })?
6037 .await;
6038 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6039 }
6040 })
6041 }
6042 };
6043
6044 cx.spawn_in(window, async move |editor, cx| {
6045 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6046 let code_actions = code_actions_task.await;
6047 let spawn_straight_away = quick_launch
6048 && resolved_tasks
6049 .as_ref()
6050 .is_some_and(|tasks| tasks.templates.len() == 1)
6051 && code_actions
6052 .as_ref()
6053 .is_none_or(|actions| actions.is_empty())
6054 && debug_scenarios.is_empty();
6055
6056 editor.update_in(cx, |editor, window, cx| {
6057 crate::hover_popover::hide_hover(editor, cx);
6058 let actions = CodeActionContents::new(
6059 resolved_tasks,
6060 code_actions,
6061 debug_scenarios,
6062 task_context.unwrap_or_default(),
6063 );
6064
6065 // Don't show the menu if there are no actions available
6066 if actions.is_empty() {
6067 cx.notify();
6068 return Task::ready(Ok(()));
6069 }
6070
6071 *editor.context_menu.borrow_mut() =
6072 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6073 buffer,
6074 actions,
6075 selected_item: Default::default(),
6076 scroll_handle: UniformListScrollHandle::default(),
6077 deployed_from,
6078 }));
6079 cx.notify();
6080 if spawn_straight_away
6081 && let Some(task) = editor.confirm_code_action(
6082 &ConfirmCodeAction { item_ix: Some(0) },
6083 window,
6084 cx,
6085 )
6086 {
6087 return task;
6088 }
6089
6090 Task::ready(Ok(()))
6091 })
6092 })
6093 .detach_and_log_err(cx);
6094 }
6095
6096 fn debug_scenarios(
6097 &mut self,
6098 resolved_tasks: &Option<ResolvedTasks>,
6099 buffer: &Entity<Buffer>,
6100 cx: &mut App,
6101 ) -> Task<Vec<task::DebugScenario>> {
6102 maybe!({
6103 let project = self.project()?;
6104 let dap_store = project.read(cx).dap_store();
6105 let mut scenarios = vec![];
6106 let resolved_tasks = resolved_tasks.as_ref()?;
6107 let buffer = buffer.read(cx);
6108 let language = buffer.language()?;
6109 let file = buffer.file();
6110 let debug_adapter = language_settings(language.name().into(), file, cx)
6111 .debuggers
6112 .first()
6113 .map(SharedString::from)
6114 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6115
6116 dap_store.update(cx, |dap_store, cx| {
6117 for (_, task) in &resolved_tasks.templates {
6118 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6119 task.original_task().clone(),
6120 debug_adapter.clone().into(),
6121 task.display_label().to_owned().into(),
6122 cx,
6123 );
6124 scenarios.push(maybe_scenario);
6125 }
6126 });
6127 Some(cx.background_spawn(async move {
6128 futures::future::join_all(scenarios)
6129 .await
6130 .into_iter()
6131 .flatten()
6132 .collect::<Vec<_>>()
6133 }))
6134 })
6135 .unwrap_or_else(|| Task::ready(vec![]))
6136 }
6137
6138 fn code_actions(
6139 &mut self,
6140 buffer_row: u32,
6141 window: &mut Window,
6142 cx: &mut Context<Self>,
6143 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6144 let mut task = self.code_actions_task.take();
6145 cx.spawn_in(window, async move |editor, cx| {
6146 while let Some(prev_task) = task {
6147 prev_task.await.log_err();
6148 task = editor
6149 .update(cx, |this, _| this.code_actions_task.take())
6150 .ok()?;
6151 }
6152
6153 editor
6154 .update(cx, |editor, cx| {
6155 editor
6156 .available_code_actions
6157 .clone()
6158 .and_then(|(location, code_actions)| {
6159 let snapshot = location.buffer.read(cx).snapshot();
6160 let point_range = location.range.to_point(&snapshot);
6161 let point_range = point_range.start.row..=point_range.end.row;
6162 if point_range.contains(&buffer_row) {
6163 Some(code_actions)
6164 } else {
6165 None
6166 }
6167 })
6168 })
6169 .ok()
6170 .flatten()
6171 })
6172 }
6173
6174 pub fn confirm_code_action(
6175 &mut self,
6176 action: &ConfirmCodeAction,
6177 window: &mut Window,
6178 cx: &mut Context<Self>,
6179 ) -> Option<Task<Result<()>>> {
6180 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6181
6182 let actions_menu =
6183 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6184 menu
6185 } else {
6186 return None;
6187 };
6188
6189 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6190 let action = actions_menu.actions.get(action_ix)?;
6191 let title = action.label();
6192 let buffer = actions_menu.buffer;
6193 let workspace = self.workspace()?;
6194
6195 match action {
6196 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6197 workspace.update(cx, |workspace, cx| {
6198 workspace.schedule_resolved_task(
6199 task_source_kind,
6200 resolved_task,
6201 false,
6202 window,
6203 cx,
6204 );
6205
6206 Some(Task::ready(Ok(())))
6207 })
6208 }
6209 CodeActionsItem::CodeAction {
6210 excerpt_id,
6211 action,
6212 provider,
6213 } => {
6214 let apply_code_action =
6215 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6216 let workspace = workspace.downgrade();
6217 Some(cx.spawn_in(window, async move |editor, cx| {
6218 let project_transaction = apply_code_action.await?;
6219 Self::open_project_transaction(
6220 &editor,
6221 workspace,
6222 project_transaction,
6223 title,
6224 cx,
6225 )
6226 .await
6227 }))
6228 }
6229 CodeActionsItem::DebugScenario(scenario) => {
6230 let context = actions_menu.actions.context;
6231
6232 workspace.update(cx, |workspace, cx| {
6233 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6234 workspace.start_debug_session(
6235 scenario,
6236 context,
6237 Some(buffer),
6238 None,
6239 window,
6240 cx,
6241 );
6242 });
6243 Some(Task::ready(Ok(())))
6244 }
6245 }
6246 }
6247
6248 pub async fn open_project_transaction(
6249 editor: &WeakEntity<Editor>,
6250 workspace: WeakEntity<Workspace>,
6251 transaction: ProjectTransaction,
6252 title: String,
6253 cx: &mut AsyncWindowContext,
6254 ) -> Result<()> {
6255 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6256 cx.update(|_, cx| {
6257 entries.sort_unstable_by_key(|(buffer, _)| {
6258 buffer.read(cx).file().map(|f| f.path().clone())
6259 });
6260 })?;
6261 if entries.is_empty() {
6262 return Ok(());
6263 }
6264
6265 // If the project transaction's edits are all contained within this editor, then
6266 // avoid opening a new editor to display them.
6267
6268 if let [(buffer, transaction)] = &*entries {
6269 let excerpt = editor.update(cx, |editor, cx| {
6270 editor
6271 .buffer()
6272 .read(cx)
6273 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6274 })?;
6275 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6276 && excerpted_buffer == *buffer
6277 {
6278 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6279 let excerpt_range = excerpt_range.to_offset(buffer);
6280 buffer
6281 .edited_ranges_for_transaction::<usize>(transaction)
6282 .all(|range| {
6283 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6284 })
6285 })?;
6286
6287 if all_edits_within_excerpt {
6288 return Ok(());
6289 }
6290 }
6291 }
6292
6293 let mut ranges_to_highlight = Vec::new();
6294 let excerpt_buffer = cx.new(|cx| {
6295 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6296 for (buffer_handle, transaction) in &entries {
6297 let edited_ranges = buffer_handle
6298 .read(cx)
6299 .edited_ranges_for_transaction::<Point>(transaction)
6300 .collect::<Vec<_>>();
6301 let (ranges, _) = multibuffer.set_excerpts_for_path(
6302 PathKey::for_buffer(buffer_handle, cx),
6303 buffer_handle.clone(),
6304 edited_ranges,
6305 multibuffer_context_lines(cx),
6306 cx,
6307 );
6308
6309 ranges_to_highlight.extend(ranges);
6310 }
6311 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6312 multibuffer
6313 })?;
6314
6315 workspace.update_in(cx, |workspace, window, cx| {
6316 let project = workspace.project().clone();
6317 let editor =
6318 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6319 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6320 editor.update(cx, |editor, cx| {
6321 editor.highlight_background::<Self>(
6322 &ranges_to_highlight,
6323 |theme| theme.colors().editor_highlighted_line_background,
6324 cx,
6325 );
6326 });
6327 })?;
6328
6329 Ok(())
6330 }
6331
6332 pub fn clear_code_action_providers(&mut self) {
6333 self.code_action_providers.clear();
6334 self.available_code_actions.take();
6335 }
6336
6337 pub fn add_code_action_provider(
6338 &mut self,
6339 provider: Rc<dyn CodeActionProvider>,
6340 window: &mut Window,
6341 cx: &mut Context<Self>,
6342 ) {
6343 if self
6344 .code_action_providers
6345 .iter()
6346 .any(|existing_provider| existing_provider.id() == provider.id())
6347 {
6348 return;
6349 }
6350
6351 self.code_action_providers.push(provider);
6352 self.refresh_code_actions(window, cx);
6353 }
6354
6355 pub fn remove_code_action_provider(
6356 &mut self,
6357 id: Arc<str>,
6358 window: &mut Window,
6359 cx: &mut Context<Self>,
6360 ) {
6361 self.code_action_providers
6362 .retain(|provider| provider.id() != id);
6363 self.refresh_code_actions(window, cx);
6364 }
6365
6366 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6367 !self.code_action_providers.is_empty()
6368 && EditorSettings::get_global(cx).toolbar.code_actions
6369 }
6370
6371 pub fn has_available_code_actions(&self) -> bool {
6372 self.available_code_actions
6373 .as_ref()
6374 .is_some_and(|(_, actions)| !actions.is_empty())
6375 }
6376
6377 fn render_inline_code_actions(
6378 &self,
6379 icon_size: ui::IconSize,
6380 display_row: DisplayRow,
6381 is_active: bool,
6382 cx: &mut Context<Self>,
6383 ) -> AnyElement {
6384 let show_tooltip = !self.context_menu_visible();
6385 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6386 .icon_size(icon_size)
6387 .shape(ui::IconButtonShape::Square)
6388 .icon_color(ui::Color::Hidden)
6389 .toggle_state(is_active)
6390 .when(show_tooltip, |this| {
6391 this.tooltip({
6392 let focus_handle = self.focus_handle.clone();
6393 move |_window, cx| {
6394 Tooltip::for_action_in(
6395 "Toggle Code Actions",
6396 &ToggleCodeActions {
6397 deployed_from: None,
6398 quick_launch: false,
6399 },
6400 &focus_handle,
6401 cx,
6402 )
6403 }
6404 })
6405 })
6406 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6407 window.focus(&editor.focus_handle(cx));
6408 editor.toggle_code_actions(
6409 &crate::actions::ToggleCodeActions {
6410 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6411 display_row,
6412 )),
6413 quick_launch: false,
6414 },
6415 window,
6416 cx,
6417 );
6418 }))
6419 .into_any_element()
6420 }
6421
6422 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6423 &self.context_menu
6424 }
6425
6426 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6427 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6428 cx.background_executor()
6429 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6430 .await;
6431
6432 let (start_buffer, start, _, end, newest_selection) = this
6433 .update(cx, |this, cx| {
6434 let newest_selection = this.selections.newest_anchor().clone();
6435 if newest_selection.head().diff_base_anchor.is_some() {
6436 return None;
6437 }
6438 let display_snapshot = this.display_snapshot(cx);
6439 let newest_selection_adjusted =
6440 this.selections.newest_adjusted(&display_snapshot);
6441 let buffer = this.buffer.read(cx);
6442
6443 let (start_buffer, start) =
6444 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6445 let (end_buffer, end) =
6446 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6447
6448 Some((start_buffer, start, end_buffer, end, newest_selection))
6449 })?
6450 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6451 .context(
6452 "Expected selection to lie in a single buffer when refreshing code actions",
6453 )?;
6454 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6455 let providers = this.code_action_providers.clone();
6456 let tasks = this
6457 .code_action_providers
6458 .iter()
6459 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6460 .collect::<Vec<_>>();
6461 (providers, tasks)
6462 })?;
6463
6464 let mut actions = Vec::new();
6465 for (provider, provider_actions) in
6466 providers.into_iter().zip(future::join_all(tasks).await)
6467 {
6468 if let Some(provider_actions) = provider_actions.log_err() {
6469 actions.extend(provider_actions.into_iter().map(|action| {
6470 AvailableCodeAction {
6471 excerpt_id: newest_selection.start.excerpt_id,
6472 action,
6473 provider: provider.clone(),
6474 }
6475 }));
6476 }
6477 }
6478
6479 this.update(cx, |this, cx| {
6480 this.available_code_actions = if actions.is_empty() {
6481 None
6482 } else {
6483 Some((
6484 Location {
6485 buffer: start_buffer,
6486 range: start..end,
6487 },
6488 actions.into(),
6489 ))
6490 };
6491 cx.notify();
6492 })
6493 }));
6494 }
6495
6496 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6497 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6498 self.show_git_blame_inline = false;
6499
6500 self.show_git_blame_inline_delay_task =
6501 Some(cx.spawn_in(window, async move |this, cx| {
6502 cx.background_executor().timer(delay).await;
6503
6504 this.update(cx, |this, cx| {
6505 this.show_git_blame_inline = true;
6506 cx.notify();
6507 })
6508 .log_err();
6509 }));
6510 }
6511 }
6512
6513 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6514 let snapshot = self.snapshot(window, cx);
6515 let cursor = self
6516 .selections
6517 .newest::<Point>(&snapshot.display_snapshot)
6518 .head();
6519 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6520 else {
6521 return;
6522 };
6523
6524 let Some(blame) = self.blame.as_ref() else {
6525 return;
6526 };
6527
6528 let row_info = RowInfo {
6529 buffer_id: Some(buffer.remote_id()),
6530 buffer_row: Some(point.row),
6531 ..Default::default()
6532 };
6533 let Some((buffer, blame_entry)) = blame
6534 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6535 .flatten()
6536 else {
6537 return;
6538 };
6539
6540 let anchor = self.selections.newest_anchor().head();
6541 let position = self.to_pixel_point(anchor, &snapshot, window);
6542 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6543 self.show_blame_popover(
6544 buffer,
6545 &blame_entry,
6546 position + last_bounds.origin,
6547 true,
6548 cx,
6549 );
6550 };
6551 }
6552
6553 fn show_blame_popover(
6554 &mut self,
6555 buffer: BufferId,
6556 blame_entry: &BlameEntry,
6557 position: gpui::Point<Pixels>,
6558 ignore_timeout: bool,
6559 cx: &mut Context<Self>,
6560 ) {
6561 if let Some(state) = &mut self.inline_blame_popover {
6562 state.hide_task.take();
6563 } else {
6564 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6565 let blame_entry = blame_entry.clone();
6566 let show_task = cx.spawn(async move |editor, cx| {
6567 if !ignore_timeout {
6568 cx.background_executor()
6569 .timer(std::time::Duration::from_millis(blame_popover_delay))
6570 .await;
6571 }
6572 editor
6573 .update(cx, |editor, cx| {
6574 editor.inline_blame_popover_show_task.take();
6575 let Some(blame) = editor.blame.as_ref() else {
6576 return;
6577 };
6578 let blame = blame.read(cx);
6579 let details = blame.details_for_entry(buffer, &blame_entry);
6580 let markdown = cx.new(|cx| {
6581 Markdown::new(
6582 details
6583 .as_ref()
6584 .map(|message| message.message.clone())
6585 .unwrap_or_default(),
6586 None,
6587 None,
6588 cx,
6589 )
6590 });
6591 editor.inline_blame_popover = Some(InlineBlamePopover {
6592 position,
6593 hide_task: None,
6594 popover_bounds: None,
6595 popover_state: InlineBlamePopoverState {
6596 scroll_handle: ScrollHandle::new(),
6597 commit_message: details,
6598 markdown,
6599 },
6600 keyboard_grace: ignore_timeout,
6601 });
6602 cx.notify();
6603 })
6604 .ok();
6605 });
6606 self.inline_blame_popover_show_task = Some(show_task);
6607 }
6608 }
6609
6610 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6611 self.inline_blame_popover_show_task.take();
6612 if let Some(state) = &mut self.inline_blame_popover {
6613 let hide_task = cx.spawn(async move |editor, cx| {
6614 if !ignore_timeout {
6615 cx.background_executor()
6616 .timer(std::time::Duration::from_millis(100))
6617 .await;
6618 }
6619 editor
6620 .update(cx, |editor, cx| {
6621 editor.inline_blame_popover.take();
6622 cx.notify();
6623 })
6624 .ok();
6625 });
6626 state.hide_task = Some(hide_task);
6627 true
6628 } else {
6629 false
6630 }
6631 }
6632
6633 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6634 if self.pending_rename.is_some() {
6635 return None;
6636 }
6637
6638 let provider = self.semantics_provider.clone()?;
6639 let buffer = self.buffer.read(cx);
6640 let newest_selection = self.selections.newest_anchor().clone();
6641 let cursor_position = newest_selection.head();
6642 let (cursor_buffer, cursor_buffer_position) =
6643 buffer.text_anchor_for_position(cursor_position, cx)?;
6644 let (tail_buffer, tail_buffer_position) =
6645 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6646 if cursor_buffer != tail_buffer {
6647 return None;
6648 }
6649
6650 let snapshot = cursor_buffer.read(cx).snapshot();
6651 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6652 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6653 if start_word_range != end_word_range {
6654 self.document_highlights_task.take();
6655 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6656 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6657 return None;
6658 }
6659
6660 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6661 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6662 cx.background_executor()
6663 .timer(Duration::from_millis(debounce))
6664 .await;
6665
6666 let highlights = if let Some(highlights) = cx
6667 .update(|cx| {
6668 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6669 })
6670 .ok()
6671 .flatten()
6672 {
6673 highlights.await.log_err()
6674 } else {
6675 None
6676 };
6677
6678 if let Some(highlights) = highlights {
6679 this.update(cx, |this, cx| {
6680 if this.pending_rename.is_some() {
6681 return;
6682 }
6683
6684 let buffer = this.buffer.read(cx);
6685 if buffer
6686 .text_anchor_for_position(cursor_position, cx)
6687 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6688 {
6689 return;
6690 }
6691
6692 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6693 let mut write_ranges = Vec::new();
6694 let mut read_ranges = Vec::new();
6695 for highlight in highlights {
6696 let buffer_id = cursor_buffer.read(cx).remote_id();
6697 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6698 {
6699 let start = highlight
6700 .range
6701 .start
6702 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6703 let end = highlight
6704 .range
6705 .end
6706 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6707 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6708 continue;
6709 }
6710
6711 let range =
6712 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6713 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6714 write_ranges.push(range);
6715 } else {
6716 read_ranges.push(range);
6717 }
6718 }
6719 }
6720
6721 this.highlight_background::<DocumentHighlightRead>(
6722 &read_ranges,
6723 |theme| theme.colors().editor_document_highlight_read_background,
6724 cx,
6725 );
6726 this.highlight_background::<DocumentHighlightWrite>(
6727 &write_ranges,
6728 |theme| theme.colors().editor_document_highlight_write_background,
6729 cx,
6730 );
6731 cx.notify();
6732 })
6733 .log_err();
6734 }
6735 }));
6736 None
6737 }
6738
6739 fn prepare_highlight_query_from_selection(
6740 &mut self,
6741 window: &Window,
6742 cx: &mut Context<Editor>,
6743 ) -> Option<(String, Range<Anchor>)> {
6744 if matches!(self.mode, EditorMode::SingleLine) {
6745 return None;
6746 }
6747 if !EditorSettings::get_global(cx).selection_highlight {
6748 return None;
6749 }
6750 if self.selections.count() != 1 || self.selections.line_mode() {
6751 return None;
6752 }
6753 let snapshot = self.snapshot(window, cx);
6754 let selection = self.selections.newest::<Point>(&snapshot);
6755 // If the selection spans multiple rows OR it is empty
6756 if selection.start.row != selection.end.row
6757 || selection.start.column == selection.end.column
6758 {
6759 return None;
6760 }
6761 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6762 let query = snapshot
6763 .buffer_snapshot()
6764 .text_for_range(selection_anchor_range.clone())
6765 .collect::<String>();
6766 if query.trim().is_empty() {
6767 return None;
6768 }
6769 Some((query, selection_anchor_range))
6770 }
6771
6772 fn update_selection_occurrence_highlights(
6773 &mut self,
6774 query_text: String,
6775 query_range: Range<Anchor>,
6776 multi_buffer_range_to_query: Range<Point>,
6777 use_debounce: bool,
6778 window: &mut Window,
6779 cx: &mut Context<Editor>,
6780 ) -> Task<()> {
6781 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6782 cx.spawn_in(window, async move |editor, cx| {
6783 if use_debounce {
6784 cx.background_executor()
6785 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6786 .await;
6787 }
6788 let match_task = cx.background_spawn(async move {
6789 let buffer_ranges = multi_buffer_snapshot
6790 .range_to_buffer_ranges(multi_buffer_range_to_query)
6791 .into_iter()
6792 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6793 let mut match_ranges = Vec::new();
6794 let Ok(regex) = project::search::SearchQuery::text(
6795 query_text.clone(),
6796 false,
6797 false,
6798 false,
6799 Default::default(),
6800 Default::default(),
6801 false,
6802 None,
6803 ) else {
6804 return Vec::default();
6805 };
6806 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6807 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6808 match_ranges.extend(
6809 regex
6810 .search(buffer_snapshot, Some(search_range.clone()))
6811 .await
6812 .into_iter()
6813 .filter_map(|match_range| {
6814 let match_start = buffer_snapshot
6815 .anchor_after(search_range.start + match_range.start);
6816 let match_end = buffer_snapshot
6817 .anchor_before(search_range.start + match_range.end);
6818 let match_anchor_range = Anchor::range_in_buffer(
6819 excerpt_id,
6820 buffer_snapshot.remote_id(),
6821 match_start..match_end,
6822 );
6823 (match_anchor_range != query_range).then_some(match_anchor_range)
6824 }),
6825 );
6826 }
6827 match_ranges
6828 });
6829 let match_ranges = match_task.await;
6830 editor
6831 .update_in(cx, |editor, _, cx| {
6832 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6833 if !match_ranges.is_empty() {
6834 editor.highlight_background::<SelectedTextHighlight>(
6835 &match_ranges,
6836 |theme| theme.colors().editor_document_highlight_bracket_background,
6837 cx,
6838 )
6839 }
6840 })
6841 .log_err();
6842 })
6843 }
6844
6845 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6846 struct NewlineFold;
6847 let type_id = std::any::TypeId::of::<NewlineFold>();
6848 if !self.mode.is_single_line() {
6849 return;
6850 }
6851 let snapshot = self.snapshot(window, cx);
6852 if snapshot.buffer_snapshot().max_point().row == 0 {
6853 return;
6854 }
6855 let task = cx.background_spawn(async move {
6856 let new_newlines = snapshot
6857 .buffer_chars_at(0)
6858 .filter_map(|(c, i)| {
6859 if c == '\n' {
6860 Some(
6861 snapshot.buffer_snapshot().anchor_after(i)
6862 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6863 )
6864 } else {
6865 None
6866 }
6867 })
6868 .collect::<Vec<_>>();
6869 let existing_newlines = snapshot
6870 .folds_in_range(0..snapshot.buffer_snapshot().len())
6871 .filter_map(|fold| {
6872 if fold.placeholder.type_tag == Some(type_id) {
6873 Some(fold.range.start..fold.range.end)
6874 } else {
6875 None
6876 }
6877 })
6878 .collect::<Vec<_>>();
6879
6880 (new_newlines, existing_newlines)
6881 });
6882 self.folding_newlines = cx.spawn(async move |this, cx| {
6883 let (new_newlines, existing_newlines) = task.await;
6884 if new_newlines == existing_newlines {
6885 return;
6886 }
6887 let placeholder = FoldPlaceholder {
6888 render: Arc::new(move |_, _, cx| {
6889 div()
6890 .bg(cx.theme().status().hint_background)
6891 .border_b_1()
6892 .size_full()
6893 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6894 .border_color(cx.theme().status().hint)
6895 .child("\\n")
6896 .into_any()
6897 }),
6898 constrain_width: false,
6899 merge_adjacent: false,
6900 type_tag: Some(type_id),
6901 };
6902 let creases = new_newlines
6903 .into_iter()
6904 .map(|range| Crease::simple(range, placeholder.clone()))
6905 .collect();
6906 this.update(cx, |this, cx| {
6907 this.display_map.update(cx, |display_map, cx| {
6908 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6909 display_map.fold(creases, cx);
6910 });
6911 })
6912 .ok();
6913 });
6914 }
6915
6916 fn refresh_selected_text_highlights(
6917 &mut self,
6918 on_buffer_edit: bool,
6919 window: &mut Window,
6920 cx: &mut Context<Editor>,
6921 ) {
6922 let Some((query_text, query_range)) =
6923 self.prepare_highlight_query_from_selection(window, cx)
6924 else {
6925 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6926 self.quick_selection_highlight_task.take();
6927 self.debounced_selection_highlight_task.take();
6928 return;
6929 };
6930 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6931 if on_buffer_edit
6932 || self
6933 .quick_selection_highlight_task
6934 .as_ref()
6935 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6936 {
6937 let multi_buffer_visible_start = self
6938 .scroll_manager
6939 .anchor()
6940 .anchor
6941 .to_point(&multi_buffer_snapshot);
6942 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6943 multi_buffer_visible_start
6944 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6945 Bias::Left,
6946 );
6947 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6948 self.quick_selection_highlight_task = Some((
6949 query_range.clone(),
6950 self.update_selection_occurrence_highlights(
6951 query_text.clone(),
6952 query_range.clone(),
6953 multi_buffer_visible_range,
6954 false,
6955 window,
6956 cx,
6957 ),
6958 ));
6959 }
6960 if on_buffer_edit
6961 || self
6962 .debounced_selection_highlight_task
6963 .as_ref()
6964 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6965 {
6966 let multi_buffer_start = multi_buffer_snapshot
6967 .anchor_before(0)
6968 .to_point(&multi_buffer_snapshot);
6969 let multi_buffer_end = multi_buffer_snapshot
6970 .anchor_after(multi_buffer_snapshot.len())
6971 .to_point(&multi_buffer_snapshot);
6972 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6973 self.debounced_selection_highlight_task = Some((
6974 query_range.clone(),
6975 self.update_selection_occurrence_highlights(
6976 query_text,
6977 query_range,
6978 multi_buffer_full_range,
6979 true,
6980 window,
6981 cx,
6982 ),
6983 ));
6984 }
6985 }
6986
6987 pub fn refresh_edit_prediction(
6988 &mut self,
6989 debounce: bool,
6990 user_requested: bool,
6991 window: &mut Window,
6992 cx: &mut Context<Self>,
6993 ) -> Option<()> {
6994 if DisableAiSettings::get_global(cx).disable_ai {
6995 return None;
6996 }
6997
6998 let provider = self.edit_prediction_provider()?;
6999 let cursor = self.selections.newest_anchor().head();
7000 let (buffer, cursor_buffer_position) =
7001 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7002
7003 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7004 self.discard_edit_prediction(false, cx);
7005 return None;
7006 }
7007
7008 self.update_visible_edit_prediction(window, cx);
7009
7010 if !user_requested
7011 && (!self.should_show_edit_predictions()
7012 || !self.is_focused(window)
7013 || buffer.read(cx).is_empty())
7014 {
7015 self.discard_edit_prediction(false, cx);
7016 return None;
7017 }
7018
7019 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7020 Some(())
7021 }
7022
7023 fn show_edit_predictions_in_menu(&self) -> bool {
7024 match self.edit_prediction_settings {
7025 EditPredictionSettings::Disabled => false,
7026 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7027 }
7028 }
7029
7030 pub fn edit_predictions_enabled(&self) -> bool {
7031 match self.edit_prediction_settings {
7032 EditPredictionSettings::Disabled => false,
7033 EditPredictionSettings::Enabled { .. } => true,
7034 }
7035 }
7036
7037 fn edit_prediction_requires_modifier(&self) -> bool {
7038 match self.edit_prediction_settings {
7039 EditPredictionSettings::Disabled => false,
7040 EditPredictionSettings::Enabled {
7041 preview_requires_modifier,
7042 ..
7043 } => preview_requires_modifier,
7044 }
7045 }
7046
7047 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7048 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7049 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7050 self.discard_edit_prediction(false, cx);
7051 } else {
7052 let selection = self.selections.newest_anchor();
7053 let cursor = selection.head();
7054
7055 if let Some((buffer, cursor_buffer_position)) =
7056 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7057 {
7058 self.edit_prediction_settings =
7059 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7060 }
7061 }
7062 }
7063
7064 fn edit_prediction_settings_at_position(
7065 &self,
7066 buffer: &Entity<Buffer>,
7067 buffer_position: language::Anchor,
7068 cx: &App,
7069 ) -> EditPredictionSettings {
7070 if !self.mode.is_full()
7071 || !self.show_edit_predictions_override.unwrap_or(true)
7072 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7073 {
7074 return EditPredictionSettings::Disabled;
7075 }
7076
7077 let buffer = buffer.read(cx);
7078
7079 let file = buffer.file();
7080
7081 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7082 return EditPredictionSettings::Disabled;
7083 };
7084
7085 let by_provider = matches!(
7086 self.menu_edit_predictions_policy,
7087 MenuEditPredictionsPolicy::ByProvider
7088 );
7089
7090 let show_in_menu = by_provider
7091 && self
7092 .edit_prediction_provider
7093 .as_ref()
7094 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7095
7096 let preview_requires_modifier =
7097 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7098
7099 EditPredictionSettings::Enabled {
7100 show_in_menu,
7101 preview_requires_modifier,
7102 }
7103 }
7104
7105 fn should_show_edit_predictions(&self) -> bool {
7106 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7107 }
7108
7109 pub fn edit_prediction_preview_is_active(&self) -> bool {
7110 matches!(
7111 self.edit_prediction_preview,
7112 EditPredictionPreview::Active { .. }
7113 )
7114 }
7115
7116 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7117 let cursor = self.selections.newest_anchor().head();
7118 if let Some((buffer, cursor_position)) =
7119 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7120 {
7121 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7122 } else {
7123 false
7124 }
7125 }
7126
7127 pub fn supports_minimap(&self, cx: &App) -> bool {
7128 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7129 }
7130
7131 fn edit_predictions_enabled_in_buffer(
7132 &self,
7133 buffer: &Entity<Buffer>,
7134 buffer_position: language::Anchor,
7135 cx: &App,
7136 ) -> bool {
7137 maybe!({
7138 if self.read_only(cx) {
7139 return Some(false);
7140 }
7141 let provider = self.edit_prediction_provider()?;
7142 if !provider.is_enabled(buffer, buffer_position, cx) {
7143 return Some(false);
7144 }
7145 let buffer = buffer.read(cx);
7146 let Some(file) = buffer.file() else {
7147 return Some(true);
7148 };
7149 let settings = all_language_settings(Some(file), cx);
7150 Some(settings.edit_predictions_enabled_for_file(file, cx))
7151 })
7152 .unwrap_or(false)
7153 }
7154
7155 fn cycle_edit_prediction(
7156 &mut self,
7157 direction: Direction,
7158 window: &mut Window,
7159 cx: &mut Context<Self>,
7160 ) -> Option<()> {
7161 let provider = self.edit_prediction_provider()?;
7162 let cursor = self.selections.newest_anchor().head();
7163 let (buffer, cursor_buffer_position) =
7164 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7165 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7166 return None;
7167 }
7168
7169 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7170 self.update_visible_edit_prediction(window, cx);
7171
7172 Some(())
7173 }
7174
7175 pub fn show_edit_prediction(
7176 &mut self,
7177 _: &ShowEditPrediction,
7178 window: &mut Window,
7179 cx: &mut Context<Self>,
7180 ) {
7181 if !self.has_active_edit_prediction() {
7182 self.refresh_edit_prediction(false, true, window, cx);
7183 return;
7184 }
7185
7186 self.update_visible_edit_prediction(window, cx);
7187 }
7188
7189 pub fn display_cursor_names(
7190 &mut self,
7191 _: &DisplayCursorNames,
7192 window: &mut Window,
7193 cx: &mut Context<Self>,
7194 ) {
7195 self.show_cursor_names(window, cx);
7196 }
7197
7198 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7199 self.show_cursor_names = true;
7200 cx.notify();
7201 cx.spawn_in(window, async move |this, cx| {
7202 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7203 this.update(cx, |this, cx| {
7204 this.show_cursor_names = false;
7205 cx.notify()
7206 })
7207 .ok()
7208 })
7209 .detach();
7210 }
7211
7212 pub fn next_edit_prediction(
7213 &mut self,
7214 _: &NextEditPrediction,
7215 window: &mut Window,
7216 cx: &mut Context<Self>,
7217 ) {
7218 if self.has_active_edit_prediction() {
7219 self.cycle_edit_prediction(Direction::Next, window, cx);
7220 } else {
7221 let is_copilot_disabled = self
7222 .refresh_edit_prediction(false, true, window, cx)
7223 .is_none();
7224 if is_copilot_disabled {
7225 cx.propagate();
7226 }
7227 }
7228 }
7229
7230 pub fn previous_edit_prediction(
7231 &mut self,
7232 _: &PreviousEditPrediction,
7233 window: &mut Window,
7234 cx: &mut Context<Self>,
7235 ) {
7236 if self.has_active_edit_prediction() {
7237 self.cycle_edit_prediction(Direction::Prev, window, cx);
7238 } else {
7239 let is_copilot_disabled = self
7240 .refresh_edit_prediction(false, true, window, cx)
7241 .is_none();
7242 if is_copilot_disabled {
7243 cx.propagate();
7244 }
7245 }
7246 }
7247
7248 pub fn accept_edit_prediction(
7249 &mut self,
7250 _: &AcceptEditPrediction,
7251 window: &mut Window,
7252 cx: &mut Context<Self>,
7253 ) {
7254 if self.show_edit_predictions_in_menu() {
7255 self.hide_context_menu(window, cx);
7256 }
7257
7258 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7259 return;
7260 };
7261
7262 match &active_edit_prediction.completion {
7263 EditPrediction::MoveWithin { target, .. } => {
7264 let target = *target;
7265
7266 if let Some(position_map) = &self.last_position_map {
7267 if position_map
7268 .visible_row_range
7269 .contains(&target.to_display_point(&position_map.snapshot).row())
7270 || !self.edit_prediction_requires_modifier()
7271 {
7272 self.unfold_ranges(&[target..target], true, false, cx);
7273 // Note that this is also done in vim's handler of the Tab action.
7274 self.change_selections(
7275 SelectionEffects::scroll(Autoscroll::newest()),
7276 window,
7277 cx,
7278 |selections| {
7279 selections.select_anchor_ranges([target..target]);
7280 },
7281 );
7282 self.clear_row_highlights::<EditPredictionPreview>();
7283
7284 self.edit_prediction_preview
7285 .set_previous_scroll_position(None);
7286 } else {
7287 self.edit_prediction_preview
7288 .set_previous_scroll_position(Some(
7289 position_map.snapshot.scroll_anchor,
7290 ));
7291
7292 self.highlight_rows::<EditPredictionPreview>(
7293 target..target,
7294 cx.theme().colors().editor_highlighted_line_background,
7295 RowHighlightOptions {
7296 autoscroll: true,
7297 ..Default::default()
7298 },
7299 cx,
7300 );
7301 self.request_autoscroll(Autoscroll::fit(), cx);
7302 }
7303 }
7304 }
7305 EditPrediction::MoveOutside { snapshot, target } => {
7306 if let Some(workspace) = self.workspace() {
7307 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7308 .detach_and_log_err(cx);
7309 }
7310 }
7311 EditPrediction::Edit { edits, .. } => {
7312 self.report_edit_prediction_event(
7313 active_edit_prediction.completion_id.clone(),
7314 true,
7315 cx,
7316 );
7317
7318 if let Some(provider) = self.edit_prediction_provider() {
7319 provider.accept(cx);
7320 }
7321
7322 // Store the transaction ID and selections before applying the edit
7323 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7324
7325 let snapshot = self.buffer.read(cx).snapshot(cx);
7326 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7327
7328 self.buffer.update(cx, |buffer, cx| {
7329 buffer.edit(edits.iter().cloned(), None, cx)
7330 });
7331
7332 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7333 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7334 });
7335
7336 let selections = self.selections.disjoint_anchors_arc();
7337 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7338 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7339 if has_new_transaction {
7340 self.selection_history
7341 .insert_transaction(transaction_id_now, selections);
7342 }
7343 }
7344
7345 self.update_visible_edit_prediction(window, cx);
7346 if self.active_edit_prediction.is_none() {
7347 self.refresh_edit_prediction(true, true, window, cx);
7348 }
7349
7350 cx.notify();
7351 }
7352 }
7353
7354 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7355 }
7356
7357 pub fn accept_partial_edit_prediction(
7358 &mut self,
7359 _: &AcceptPartialEditPrediction,
7360 window: &mut Window,
7361 cx: &mut Context<Self>,
7362 ) {
7363 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7364 return;
7365 };
7366 if self.selections.count() != 1 {
7367 return;
7368 }
7369
7370 match &active_edit_prediction.completion {
7371 EditPrediction::MoveWithin { target, .. } => {
7372 let target = *target;
7373 self.change_selections(
7374 SelectionEffects::scroll(Autoscroll::newest()),
7375 window,
7376 cx,
7377 |selections| {
7378 selections.select_anchor_ranges([target..target]);
7379 },
7380 );
7381 }
7382 EditPrediction::MoveOutside { snapshot, target } => {
7383 if let Some(workspace) = self.workspace() {
7384 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7385 .detach_and_log_err(cx);
7386 }
7387 }
7388 EditPrediction::Edit { edits, .. } => {
7389 self.report_edit_prediction_event(
7390 active_edit_prediction.completion_id.clone(),
7391 true,
7392 cx,
7393 );
7394
7395 // Find an insertion that starts at the cursor position.
7396 let snapshot = self.buffer.read(cx).snapshot(cx);
7397 let cursor_offset = self
7398 .selections
7399 .newest::<usize>(&self.display_snapshot(cx))
7400 .head();
7401 let insertion = edits.iter().find_map(|(range, text)| {
7402 let range = range.to_offset(&snapshot);
7403 if range.is_empty() && range.start == cursor_offset {
7404 Some(text)
7405 } else {
7406 None
7407 }
7408 });
7409
7410 if let Some(text) = insertion {
7411 let mut partial_completion = text
7412 .chars()
7413 .by_ref()
7414 .take_while(|c| c.is_alphabetic())
7415 .collect::<String>();
7416 if partial_completion.is_empty() {
7417 partial_completion = text
7418 .chars()
7419 .by_ref()
7420 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7421 .collect::<String>();
7422 }
7423
7424 cx.emit(EditorEvent::InputHandled {
7425 utf16_range_to_replace: None,
7426 text: partial_completion.clone().into(),
7427 });
7428
7429 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7430
7431 self.refresh_edit_prediction(true, true, window, cx);
7432 cx.notify();
7433 } else {
7434 self.accept_edit_prediction(&Default::default(), window, cx);
7435 }
7436 }
7437 }
7438 }
7439
7440 fn discard_edit_prediction(
7441 &mut self,
7442 should_report_edit_prediction_event: bool,
7443 cx: &mut Context<Self>,
7444 ) -> bool {
7445 if should_report_edit_prediction_event {
7446 let completion_id = self
7447 .active_edit_prediction
7448 .as_ref()
7449 .and_then(|active_completion| active_completion.completion_id.clone());
7450
7451 self.report_edit_prediction_event(completion_id, false, cx);
7452 }
7453
7454 if let Some(provider) = self.edit_prediction_provider() {
7455 provider.discard(cx);
7456 }
7457
7458 self.take_active_edit_prediction(cx)
7459 }
7460
7461 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7462 let Some(provider) = self.edit_prediction_provider() else {
7463 return;
7464 };
7465
7466 let Some((_, buffer, _)) = self
7467 .buffer
7468 .read(cx)
7469 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7470 else {
7471 return;
7472 };
7473
7474 let extension = buffer
7475 .read(cx)
7476 .file()
7477 .and_then(|file| Some(file.path().extension()?.to_string()));
7478
7479 let event_type = match accepted {
7480 true => "Edit Prediction Accepted",
7481 false => "Edit Prediction Discarded",
7482 };
7483 telemetry::event!(
7484 event_type,
7485 provider = provider.name(),
7486 prediction_id = id,
7487 suggestion_accepted = accepted,
7488 file_extension = extension,
7489 );
7490 }
7491
7492 fn open_editor_at_anchor(
7493 snapshot: &language::BufferSnapshot,
7494 target: language::Anchor,
7495 workspace: &Entity<Workspace>,
7496 window: &mut Window,
7497 cx: &mut App,
7498 ) -> Task<Result<()>> {
7499 workspace.update(cx, |workspace, cx| {
7500 let path = snapshot.file().map(|file| file.full_path(cx));
7501 let Some(path) =
7502 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7503 else {
7504 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7505 };
7506 let target = text::ToPoint::to_point(&target, snapshot);
7507 let item = workspace.open_path(path, None, true, window, cx);
7508 window.spawn(cx, async move |cx| {
7509 let Some(editor) = item.await?.downcast::<Editor>() else {
7510 return Ok(());
7511 };
7512 editor
7513 .update_in(cx, |editor, window, cx| {
7514 editor.go_to_singleton_buffer_point(target, window, cx);
7515 })
7516 .ok();
7517 anyhow::Ok(())
7518 })
7519 })
7520 }
7521
7522 pub fn has_active_edit_prediction(&self) -> bool {
7523 self.active_edit_prediction.is_some()
7524 }
7525
7526 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7527 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7528 return false;
7529 };
7530
7531 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7532 self.clear_highlights::<EditPredictionHighlight>(cx);
7533 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7534 true
7535 }
7536
7537 /// Returns true when we're displaying the edit prediction popover below the cursor
7538 /// like we are not previewing and the LSP autocomplete menu is visible
7539 /// or we are in `when_holding_modifier` mode.
7540 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7541 if self.edit_prediction_preview_is_active()
7542 || !self.show_edit_predictions_in_menu()
7543 || !self.edit_predictions_enabled()
7544 {
7545 return false;
7546 }
7547
7548 if self.has_visible_completions_menu() {
7549 return true;
7550 }
7551
7552 has_completion && self.edit_prediction_requires_modifier()
7553 }
7554
7555 fn handle_modifiers_changed(
7556 &mut self,
7557 modifiers: Modifiers,
7558 position_map: &PositionMap,
7559 window: &mut Window,
7560 cx: &mut Context<Self>,
7561 ) {
7562 if self.show_edit_predictions_in_menu() {
7563 self.update_edit_prediction_preview(&modifiers, window, cx);
7564 }
7565
7566 self.update_selection_mode(&modifiers, position_map, window, cx);
7567
7568 let mouse_position = window.mouse_position();
7569 if !position_map.text_hitbox.is_hovered(window) {
7570 return;
7571 }
7572
7573 self.update_hovered_link(
7574 position_map.point_for_position(mouse_position),
7575 &position_map.snapshot,
7576 modifiers,
7577 window,
7578 cx,
7579 )
7580 }
7581
7582 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7583 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7584 if invert {
7585 match multi_cursor_setting {
7586 MultiCursorModifier::Alt => modifiers.alt,
7587 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7588 }
7589 } else {
7590 match multi_cursor_setting {
7591 MultiCursorModifier::Alt => modifiers.secondary(),
7592 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7593 }
7594 }
7595 }
7596
7597 fn columnar_selection_mode(
7598 modifiers: &Modifiers,
7599 cx: &mut Context<Self>,
7600 ) -> Option<ColumnarMode> {
7601 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7602 if Self::multi_cursor_modifier(false, modifiers, cx) {
7603 Some(ColumnarMode::FromMouse)
7604 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7605 Some(ColumnarMode::FromSelection)
7606 } else {
7607 None
7608 }
7609 } else {
7610 None
7611 }
7612 }
7613
7614 fn update_selection_mode(
7615 &mut self,
7616 modifiers: &Modifiers,
7617 position_map: &PositionMap,
7618 window: &mut Window,
7619 cx: &mut Context<Self>,
7620 ) {
7621 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7622 return;
7623 };
7624 if self.selections.pending_anchor().is_none() {
7625 return;
7626 }
7627
7628 let mouse_position = window.mouse_position();
7629 let point_for_position = position_map.point_for_position(mouse_position);
7630 let position = point_for_position.previous_valid;
7631
7632 self.select(
7633 SelectPhase::BeginColumnar {
7634 position,
7635 reset: false,
7636 mode,
7637 goal_column: point_for_position.exact_unclipped.column(),
7638 },
7639 window,
7640 cx,
7641 );
7642 }
7643
7644 fn update_edit_prediction_preview(
7645 &mut self,
7646 modifiers: &Modifiers,
7647 window: &mut Window,
7648 cx: &mut Context<Self>,
7649 ) {
7650 let mut modifiers_held = false;
7651 if let Some(accept_keystroke) = self
7652 .accept_edit_prediction_keybind(false, window, cx)
7653 .keystroke()
7654 {
7655 modifiers_held = modifiers_held
7656 || (accept_keystroke.modifiers() == modifiers
7657 && accept_keystroke.modifiers().modified());
7658 };
7659 if let Some(accept_partial_keystroke) = self
7660 .accept_edit_prediction_keybind(true, window, cx)
7661 .keystroke()
7662 {
7663 modifiers_held = modifiers_held
7664 || (accept_partial_keystroke.modifiers() == modifiers
7665 && accept_partial_keystroke.modifiers().modified());
7666 }
7667
7668 if modifiers_held {
7669 if matches!(
7670 self.edit_prediction_preview,
7671 EditPredictionPreview::Inactive { .. }
7672 ) {
7673 self.edit_prediction_preview = EditPredictionPreview::Active {
7674 previous_scroll_position: None,
7675 since: Instant::now(),
7676 };
7677
7678 self.update_visible_edit_prediction(window, cx);
7679 cx.notify();
7680 }
7681 } else if let EditPredictionPreview::Active {
7682 previous_scroll_position,
7683 since,
7684 } = self.edit_prediction_preview
7685 {
7686 if let (Some(previous_scroll_position), Some(position_map)) =
7687 (previous_scroll_position, self.last_position_map.as_ref())
7688 {
7689 self.set_scroll_position(
7690 previous_scroll_position
7691 .scroll_position(&position_map.snapshot.display_snapshot),
7692 window,
7693 cx,
7694 );
7695 }
7696
7697 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7698 released_too_fast: since.elapsed() < Duration::from_millis(200),
7699 };
7700 self.clear_row_highlights::<EditPredictionPreview>();
7701 self.update_visible_edit_prediction(window, cx);
7702 cx.notify();
7703 }
7704 }
7705
7706 fn update_visible_edit_prediction(
7707 &mut self,
7708 _window: &mut Window,
7709 cx: &mut Context<Self>,
7710 ) -> Option<()> {
7711 if DisableAiSettings::get_global(cx).disable_ai {
7712 return None;
7713 }
7714
7715 if self.ime_transaction.is_some() {
7716 self.discard_edit_prediction(false, cx);
7717 return None;
7718 }
7719
7720 let selection = self.selections.newest_anchor();
7721 let cursor = selection.head();
7722 let multibuffer = self.buffer.read(cx).snapshot(cx);
7723 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7724 let excerpt_id = cursor.excerpt_id;
7725
7726 let show_in_menu = self.show_edit_predictions_in_menu();
7727 let completions_menu_has_precedence = !show_in_menu
7728 && (self.context_menu.borrow().is_some()
7729 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7730
7731 if completions_menu_has_precedence
7732 || !offset_selection.is_empty()
7733 || self
7734 .active_edit_prediction
7735 .as_ref()
7736 .is_some_and(|completion| {
7737 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7738 return false;
7739 };
7740 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7741 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7742 !invalidation_range.contains(&offset_selection.head())
7743 })
7744 {
7745 self.discard_edit_prediction(false, cx);
7746 return None;
7747 }
7748
7749 self.take_active_edit_prediction(cx);
7750 let Some(provider) = self.edit_prediction_provider() else {
7751 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7752 return None;
7753 };
7754
7755 let (buffer, cursor_buffer_position) =
7756 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7757
7758 self.edit_prediction_settings =
7759 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7760
7761 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7762
7763 if self.edit_prediction_indent_conflict {
7764 let cursor_point = cursor.to_point(&multibuffer);
7765
7766 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7767
7768 if let Some((_, indent)) = indents.iter().next()
7769 && indent.len == cursor_point.column
7770 {
7771 self.edit_prediction_indent_conflict = false;
7772 }
7773 }
7774
7775 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7776
7777 let (completion_id, edits, edit_preview) = match edit_prediction {
7778 edit_prediction::EditPrediction::Local {
7779 id,
7780 edits,
7781 edit_preview,
7782 } => (id, edits, edit_preview),
7783 edit_prediction::EditPrediction::Jump {
7784 id,
7785 snapshot,
7786 target,
7787 } => {
7788 self.stale_edit_prediction_in_menu = None;
7789 self.active_edit_prediction = Some(EditPredictionState {
7790 inlay_ids: vec![],
7791 completion: EditPrediction::MoveOutside { snapshot, target },
7792 completion_id: id,
7793 invalidation_range: None,
7794 });
7795 cx.notify();
7796 return Some(());
7797 }
7798 };
7799
7800 let edits = edits
7801 .into_iter()
7802 .flat_map(|(range, new_text)| {
7803 Some((
7804 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7805 new_text,
7806 ))
7807 })
7808 .collect::<Vec<_>>();
7809 if edits.is_empty() {
7810 return None;
7811 }
7812
7813 let first_edit_start = edits.first().unwrap().0.start;
7814 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7815 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7816
7817 let last_edit_end = edits.last().unwrap().0.end;
7818 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7819 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7820
7821 let cursor_row = cursor.to_point(&multibuffer).row;
7822
7823 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7824
7825 let mut inlay_ids = Vec::new();
7826 let invalidation_row_range;
7827 let move_invalidation_row_range = if cursor_row < edit_start_row {
7828 Some(cursor_row..edit_end_row)
7829 } else if cursor_row > edit_end_row {
7830 Some(edit_start_row..cursor_row)
7831 } else {
7832 None
7833 };
7834 let supports_jump = self
7835 .edit_prediction_provider
7836 .as_ref()
7837 .map(|provider| provider.provider.supports_jump_to_edit())
7838 .unwrap_or(true);
7839
7840 let is_move = supports_jump
7841 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7842 let completion = if is_move {
7843 invalidation_row_range =
7844 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7845 let target = first_edit_start;
7846 EditPrediction::MoveWithin { target, snapshot }
7847 } else {
7848 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7849 && !self.edit_predictions_hidden_for_vim_mode;
7850
7851 if show_completions_in_buffer {
7852 if edits
7853 .iter()
7854 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7855 {
7856 let mut inlays = Vec::new();
7857 for (range, new_text) in &edits {
7858 let inlay = Inlay::edit_prediction(
7859 post_inc(&mut self.next_inlay_id),
7860 range.start,
7861 new_text.as_str(),
7862 );
7863 inlay_ids.push(inlay.id);
7864 inlays.push(inlay);
7865 }
7866
7867 self.splice_inlays(&[], inlays, cx);
7868 } else {
7869 let background_color = cx.theme().status().deleted_background;
7870 self.highlight_text::<EditPredictionHighlight>(
7871 edits.iter().map(|(range, _)| range.clone()).collect(),
7872 HighlightStyle {
7873 background_color: Some(background_color),
7874 ..Default::default()
7875 },
7876 cx,
7877 );
7878 }
7879 }
7880
7881 invalidation_row_range = edit_start_row..edit_end_row;
7882
7883 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7884 if provider.show_tab_accept_marker() {
7885 EditDisplayMode::TabAccept
7886 } else {
7887 EditDisplayMode::Inline
7888 }
7889 } else {
7890 EditDisplayMode::DiffPopover
7891 };
7892
7893 EditPrediction::Edit {
7894 edits,
7895 edit_preview,
7896 display_mode,
7897 snapshot,
7898 }
7899 };
7900
7901 let invalidation_range = multibuffer
7902 .anchor_before(Point::new(invalidation_row_range.start, 0))
7903 ..multibuffer.anchor_after(Point::new(
7904 invalidation_row_range.end,
7905 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7906 ));
7907
7908 self.stale_edit_prediction_in_menu = None;
7909 self.active_edit_prediction = Some(EditPredictionState {
7910 inlay_ids,
7911 completion,
7912 completion_id,
7913 invalidation_range: Some(invalidation_range),
7914 });
7915
7916 cx.notify();
7917
7918 Some(())
7919 }
7920
7921 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7922 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7923 }
7924
7925 fn clear_tasks(&mut self) {
7926 self.tasks.clear()
7927 }
7928
7929 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7930 if self.tasks.insert(key, value).is_some() {
7931 // This case should hopefully be rare, but just in case...
7932 log::error!(
7933 "multiple different run targets found on a single line, only the last target will be rendered"
7934 )
7935 }
7936 }
7937
7938 /// Get all display points of breakpoints that will be rendered within editor
7939 ///
7940 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7941 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7942 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7943 fn active_breakpoints(
7944 &self,
7945 range: Range<DisplayRow>,
7946 window: &mut Window,
7947 cx: &mut Context<Self>,
7948 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7949 let mut breakpoint_display_points = HashMap::default();
7950
7951 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7952 return breakpoint_display_points;
7953 };
7954
7955 let snapshot = self.snapshot(window, cx);
7956
7957 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7958 let Some(project) = self.project() else {
7959 return breakpoint_display_points;
7960 };
7961
7962 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7963 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7964
7965 for (buffer_snapshot, range, excerpt_id) in
7966 multi_buffer_snapshot.range_to_buffer_ranges(range)
7967 {
7968 let Some(buffer) = project
7969 .read(cx)
7970 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7971 else {
7972 continue;
7973 };
7974 let breakpoints = breakpoint_store.read(cx).breakpoints(
7975 &buffer,
7976 Some(
7977 buffer_snapshot.anchor_before(range.start)
7978 ..buffer_snapshot.anchor_after(range.end),
7979 ),
7980 buffer_snapshot,
7981 cx,
7982 );
7983 for (breakpoint, state) in breakpoints {
7984 let multi_buffer_anchor =
7985 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7986 let position = multi_buffer_anchor
7987 .to_point(&multi_buffer_snapshot)
7988 .to_display_point(&snapshot);
7989
7990 breakpoint_display_points.insert(
7991 position.row(),
7992 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7993 );
7994 }
7995 }
7996
7997 breakpoint_display_points
7998 }
7999
8000 fn breakpoint_context_menu(
8001 &self,
8002 anchor: Anchor,
8003 window: &mut Window,
8004 cx: &mut Context<Self>,
8005 ) -> Entity<ui::ContextMenu> {
8006 let weak_editor = cx.weak_entity();
8007 let focus_handle = self.focus_handle(cx);
8008
8009 let row = self
8010 .buffer
8011 .read(cx)
8012 .snapshot(cx)
8013 .summary_for_anchor::<Point>(&anchor)
8014 .row;
8015
8016 let breakpoint = self
8017 .breakpoint_at_row(row, window, cx)
8018 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8019
8020 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8021 "Edit Log Breakpoint"
8022 } else {
8023 "Set Log Breakpoint"
8024 };
8025
8026 let condition_breakpoint_msg = if breakpoint
8027 .as_ref()
8028 .is_some_and(|bp| bp.1.condition.is_some())
8029 {
8030 "Edit Condition Breakpoint"
8031 } else {
8032 "Set Condition Breakpoint"
8033 };
8034
8035 let hit_condition_breakpoint_msg = if breakpoint
8036 .as_ref()
8037 .is_some_and(|bp| bp.1.hit_condition.is_some())
8038 {
8039 "Edit Hit Condition Breakpoint"
8040 } else {
8041 "Set Hit Condition Breakpoint"
8042 };
8043
8044 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8045 "Unset Breakpoint"
8046 } else {
8047 "Set Breakpoint"
8048 };
8049
8050 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8051
8052 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8053 BreakpointState::Enabled => Some("Disable"),
8054 BreakpointState::Disabled => Some("Enable"),
8055 });
8056
8057 let (anchor, breakpoint) =
8058 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8059
8060 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8061 menu.on_blur_subscription(Subscription::new(|| {}))
8062 .context(focus_handle)
8063 .when(run_to_cursor, |this| {
8064 let weak_editor = weak_editor.clone();
8065 this.entry("Run to cursor", None, move |window, cx| {
8066 weak_editor
8067 .update(cx, |editor, cx| {
8068 editor.change_selections(
8069 SelectionEffects::no_scroll(),
8070 window,
8071 cx,
8072 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8073 );
8074 })
8075 .ok();
8076
8077 window.dispatch_action(Box::new(RunToCursor), cx);
8078 })
8079 .separator()
8080 })
8081 .when_some(toggle_state_msg, |this, msg| {
8082 this.entry(msg, None, {
8083 let weak_editor = weak_editor.clone();
8084 let breakpoint = breakpoint.clone();
8085 move |_window, cx| {
8086 weak_editor
8087 .update(cx, |this, cx| {
8088 this.edit_breakpoint_at_anchor(
8089 anchor,
8090 breakpoint.as_ref().clone(),
8091 BreakpointEditAction::InvertState,
8092 cx,
8093 );
8094 })
8095 .log_err();
8096 }
8097 })
8098 })
8099 .entry(set_breakpoint_msg, None, {
8100 let weak_editor = weak_editor.clone();
8101 let breakpoint = breakpoint.clone();
8102 move |_window, cx| {
8103 weak_editor
8104 .update(cx, |this, cx| {
8105 this.edit_breakpoint_at_anchor(
8106 anchor,
8107 breakpoint.as_ref().clone(),
8108 BreakpointEditAction::Toggle,
8109 cx,
8110 );
8111 })
8112 .log_err();
8113 }
8114 })
8115 .entry(log_breakpoint_msg, None, {
8116 let breakpoint = breakpoint.clone();
8117 let weak_editor = weak_editor.clone();
8118 move |window, cx| {
8119 weak_editor
8120 .update(cx, |this, cx| {
8121 this.add_edit_breakpoint_block(
8122 anchor,
8123 breakpoint.as_ref(),
8124 BreakpointPromptEditAction::Log,
8125 window,
8126 cx,
8127 );
8128 })
8129 .log_err();
8130 }
8131 })
8132 .entry(condition_breakpoint_msg, None, {
8133 let breakpoint = breakpoint.clone();
8134 let weak_editor = weak_editor.clone();
8135 move |window, cx| {
8136 weak_editor
8137 .update(cx, |this, cx| {
8138 this.add_edit_breakpoint_block(
8139 anchor,
8140 breakpoint.as_ref(),
8141 BreakpointPromptEditAction::Condition,
8142 window,
8143 cx,
8144 );
8145 })
8146 .log_err();
8147 }
8148 })
8149 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8150 weak_editor
8151 .update(cx, |this, cx| {
8152 this.add_edit_breakpoint_block(
8153 anchor,
8154 breakpoint.as_ref(),
8155 BreakpointPromptEditAction::HitCondition,
8156 window,
8157 cx,
8158 );
8159 })
8160 .log_err();
8161 })
8162 })
8163 }
8164
8165 fn render_breakpoint(
8166 &self,
8167 position: Anchor,
8168 row: DisplayRow,
8169 breakpoint: &Breakpoint,
8170 state: Option<BreakpointSessionState>,
8171 cx: &mut Context<Self>,
8172 ) -> IconButton {
8173 let is_rejected = state.is_some_and(|s| !s.verified);
8174 // Is it a breakpoint that shows up when hovering over gutter?
8175 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8176 (false, false),
8177 |PhantomBreakpointIndicator {
8178 is_active,
8179 display_row,
8180 collides_with_existing_breakpoint,
8181 }| {
8182 (
8183 is_active && display_row == row,
8184 collides_with_existing_breakpoint,
8185 )
8186 },
8187 );
8188
8189 let (color, icon) = {
8190 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8191 (false, false) => ui::IconName::DebugBreakpoint,
8192 (true, false) => ui::IconName::DebugLogBreakpoint,
8193 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8194 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8195 };
8196
8197 let color = if is_phantom {
8198 Color::Hint
8199 } else if is_rejected {
8200 Color::Disabled
8201 } else {
8202 Color::Debugger
8203 };
8204
8205 (color, icon)
8206 };
8207
8208 let breakpoint = Arc::from(breakpoint.clone());
8209
8210 let alt_as_text = gpui::Keystroke {
8211 modifiers: Modifiers::secondary_key(),
8212 ..Default::default()
8213 };
8214 let primary_action_text = if breakpoint.is_disabled() {
8215 "Enable breakpoint"
8216 } else if is_phantom && !collides_with_existing {
8217 "Set breakpoint"
8218 } else {
8219 "Unset breakpoint"
8220 };
8221 let focus_handle = self.focus_handle.clone();
8222
8223 let meta = if is_rejected {
8224 SharedString::from("No executable code is associated with this line.")
8225 } else if collides_with_existing && !breakpoint.is_disabled() {
8226 SharedString::from(format!(
8227 "{alt_as_text}-click to disable,\nright-click for more options."
8228 ))
8229 } else {
8230 SharedString::from("Right-click for more options.")
8231 };
8232 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8233 .icon_size(IconSize::XSmall)
8234 .size(ui::ButtonSize::None)
8235 .when(is_rejected, |this| {
8236 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8237 })
8238 .icon_color(color)
8239 .style(ButtonStyle::Transparent)
8240 .on_click(cx.listener({
8241 move |editor, event: &ClickEvent, window, cx| {
8242 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8243 BreakpointEditAction::InvertState
8244 } else {
8245 BreakpointEditAction::Toggle
8246 };
8247
8248 window.focus(&editor.focus_handle(cx));
8249 editor.edit_breakpoint_at_anchor(
8250 position,
8251 breakpoint.as_ref().clone(),
8252 edit_action,
8253 cx,
8254 );
8255 }
8256 }))
8257 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8258 editor.set_breakpoint_context_menu(
8259 row,
8260 Some(position),
8261 event.position(),
8262 window,
8263 cx,
8264 );
8265 }))
8266 .tooltip(move |_window, cx| {
8267 Tooltip::with_meta_in(
8268 primary_action_text,
8269 Some(&ToggleBreakpoint),
8270 meta.clone(),
8271 &focus_handle,
8272 cx,
8273 )
8274 })
8275 }
8276
8277 fn build_tasks_context(
8278 project: &Entity<Project>,
8279 buffer: &Entity<Buffer>,
8280 buffer_row: u32,
8281 tasks: &Arc<RunnableTasks>,
8282 cx: &mut Context<Self>,
8283 ) -> Task<Option<task::TaskContext>> {
8284 let position = Point::new(buffer_row, tasks.column);
8285 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8286 let location = Location {
8287 buffer: buffer.clone(),
8288 range: range_start..range_start,
8289 };
8290 // Fill in the environmental variables from the tree-sitter captures
8291 let mut captured_task_variables = TaskVariables::default();
8292 for (capture_name, value) in tasks.extra_variables.clone() {
8293 captured_task_variables.insert(
8294 task::VariableName::Custom(capture_name.into()),
8295 value.clone(),
8296 );
8297 }
8298 project.update(cx, |project, cx| {
8299 project.task_store().update(cx, |task_store, cx| {
8300 task_store.task_context_for_location(captured_task_variables, location, cx)
8301 })
8302 })
8303 }
8304
8305 pub fn spawn_nearest_task(
8306 &mut self,
8307 action: &SpawnNearestTask,
8308 window: &mut Window,
8309 cx: &mut Context<Self>,
8310 ) {
8311 let Some((workspace, _)) = self.workspace.clone() else {
8312 return;
8313 };
8314 let Some(project) = self.project.clone() else {
8315 return;
8316 };
8317
8318 // Try to find a closest, enclosing node using tree-sitter that has a task
8319 let Some((buffer, buffer_row, tasks)) = self
8320 .find_enclosing_node_task(cx)
8321 // Or find the task that's closest in row-distance.
8322 .or_else(|| self.find_closest_task(cx))
8323 else {
8324 return;
8325 };
8326
8327 let reveal_strategy = action.reveal;
8328 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8329 cx.spawn_in(window, async move |_, cx| {
8330 let context = task_context.await?;
8331 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8332
8333 let resolved = &mut resolved_task.resolved;
8334 resolved.reveal = reveal_strategy;
8335
8336 workspace
8337 .update_in(cx, |workspace, window, cx| {
8338 workspace.schedule_resolved_task(
8339 task_source_kind,
8340 resolved_task,
8341 false,
8342 window,
8343 cx,
8344 );
8345 })
8346 .ok()
8347 })
8348 .detach();
8349 }
8350
8351 fn find_closest_task(
8352 &mut self,
8353 cx: &mut Context<Self>,
8354 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8355 let cursor_row = self
8356 .selections
8357 .newest_adjusted(&self.display_snapshot(cx))
8358 .head()
8359 .row;
8360
8361 let ((buffer_id, row), tasks) = self
8362 .tasks
8363 .iter()
8364 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8365
8366 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8367 let tasks = Arc::new(tasks.to_owned());
8368 Some((buffer, *row, tasks))
8369 }
8370
8371 fn find_enclosing_node_task(
8372 &mut self,
8373 cx: &mut Context<Self>,
8374 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8375 let snapshot = self.buffer.read(cx).snapshot(cx);
8376 let offset = self
8377 .selections
8378 .newest::<usize>(&self.display_snapshot(cx))
8379 .head();
8380 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8381 let buffer_id = excerpt.buffer().remote_id();
8382
8383 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8384 let mut cursor = layer.node().walk();
8385
8386 while cursor.goto_first_child_for_byte(offset).is_some() {
8387 if cursor.node().end_byte() == offset {
8388 cursor.goto_next_sibling();
8389 }
8390 }
8391
8392 // Ascend to the smallest ancestor that contains the range and has a task.
8393 loop {
8394 let node = cursor.node();
8395 let node_range = node.byte_range();
8396 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8397
8398 // Check if this node contains our offset
8399 if node_range.start <= offset && node_range.end >= offset {
8400 // If it contains offset, check for task
8401 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8402 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8403 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8404 }
8405 }
8406
8407 if !cursor.goto_parent() {
8408 break;
8409 }
8410 }
8411 None
8412 }
8413
8414 fn render_run_indicator(
8415 &self,
8416 _style: &EditorStyle,
8417 is_active: bool,
8418 row: DisplayRow,
8419 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8420 cx: &mut Context<Self>,
8421 ) -> IconButton {
8422 let color = Color::Muted;
8423 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8424
8425 IconButton::new(
8426 ("run_indicator", row.0 as usize),
8427 ui::IconName::PlayOutlined,
8428 )
8429 .shape(ui::IconButtonShape::Square)
8430 .icon_size(IconSize::XSmall)
8431 .icon_color(color)
8432 .toggle_state(is_active)
8433 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8434 let quick_launch = match e {
8435 ClickEvent::Keyboard(_) => true,
8436 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8437 };
8438
8439 window.focus(&editor.focus_handle(cx));
8440 editor.toggle_code_actions(
8441 &ToggleCodeActions {
8442 deployed_from: Some(CodeActionSource::RunMenu(row)),
8443 quick_launch,
8444 },
8445 window,
8446 cx,
8447 );
8448 }))
8449 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8450 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8451 }))
8452 }
8453
8454 pub fn context_menu_visible(&self) -> bool {
8455 !self.edit_prediction_preview_is_active()
8456 && self
8457 .context_menu
8458 .borrow()
8459 .as_ref()
8460 .is_some_and(|menu| menu.visible())
8461 }
8462
8463 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8464 self.context_menu
8465 .borrow()
8466 .as_ref()
8467 .map(|menu| menu.origin())
8468 }
8469
8470 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8471 self.context_menu_options = Some(options);
8472 }
8473
8474 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8475 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8476
8477 fn render_edit_prediction_popover(
8478 &mut self,
8479 text_bounds: &Bounds<Pixels>,
8480 content_origin: gpui::Point<Pixels>,
8481 right_margin: Pixels,
8482 editor_snapshot: &EditorSnapshot,
8483 visible_row_range: Range<DisplayRow>,
8484 scroll_top: ScrollOffset,
8485 scroll_bottom: ScrollOffset,
8486 line_layouts: &[LineWithInvisibles],
8487 line_height: Pixels,
8488 scroll_position: gpui::Point<ScrollOffset>,
8489 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8490 newest_selection_head: Option<DisplayPoint>,
8491 editor_width: Pixels,
8492 style: &EditorStyle,
8493 window: &mut Window,
8494 cx: &mut App,
8495 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8496 if self.mode().is_minimap() {
8497 return None;
8498 }
8499 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8500
8501 if self.edit_prediction_visible_in_cursor_popover(true) {
8502 return None;
8503 }
8504
8505 match &active_edit_prediction.completion {
8506 EditPrediction::MoveWithin { target, .. } => {
8507 let target_display_point = target.to_display_point(editor_snapshot);
8508
8509 if self.edit_prediction_requires_modifier() {
8510 if !self.edit_prediction_preview_is_active() {
8511 return None;
8512 }
8513
8514 self.render_edit_prediction_modifier_jump_popover(
8515 text_bounds,
8516 content_origin,
8517 visible_row_range,
8518 line_layouts,
8519 line_height,
8520 scroll_pixel_position,
8521 newest_selection_head,
8522 target_display_point,
8523 window,
8524 cx,
8525 )
8526 } else {
8527 self.render_edit_prediction_eager_jump_popover(
8528 text_bounds,
8529 content_origin,
8530 editor_snapshot,
8531 visible_row_range,
8532 scroll_top,
8533 scroll_bottom,
8534 line_height,
8535 scroll_pixel_position,
8536 target_display_point,
8537 editor_width,
8538 window,
8539 cx,
8540 )
8541 }
8542 }
8543 EditPrediction::Edit {
8544 display_mode: EditDisplayMode::Inline,
8545 ..
8546 } => None,
8547 EditPrediction::Edit {
8548 display_mode: EditDisplayMode::TabAccept,
8549 edits,
8550 ..
8551 } => {
8552 let range = &edits.first()?.0;
8553 let target_display_point = range.end.to_display_point(editor_snapshot);
8554
8555 self.render_edit_prediction_end_of_line_popover(
8556 "Accept",
8557 editor_snapshot,
8558 visible_row_range,
8559 target_display_point,
8560 line_height,
8561 scroll_pixel_position,
8562 content_origin,
8563 editor_width,
8564 window,
8565 cx,
8566 )
8567 }
8568 EditPrediction::Edit {
8569 edits,
8570 edit_preview,
8571 display_mode: EditDisplayMode::DiffPopover,
8572 snapshot,
8573 } => self.render_edit_prediction_diff_popover(
8574 text_bounds,
8575 content_origin,
8576 right_margin,
8577 editor_snapshot,
8578 visible_row_range,
8579 line_layouts,
8580 line_height,
8581 scroll_position,
8582 scroll_pixel_position,
8583 newest_selection_head,
8584 editor_width,
8585 style,
8586 edits,
8587 edit_preview,
8588 snapshot,
8589 window,
8590 cx,
8591 ),
8592 EditPrediction::MoveOutside { snapshot, .. } => {
8593 let file_name = snapshot
8594 .file()
8595 .map(|file| file.file_name(cx))
8596 .unwrap_or("untitled");
8597 let mut element = self
8598 .render_edit_prediction_line_popover(
8599 format!("Jump to {file_name}"),
8600 Some(IconName::ZedPredict),
8601 window,
8602 cx,
8603 )
8604 .into_any();
8605
8606 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8607 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8608 let origin_y = text_bounds.size.height - size.height - px(30.);
8609 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8610 element.prepaint_at(origin, window, cx);
8611
8612 Some((element, origin))
8613 }
8614 }
8615 }
8616
8617 fn render_edit_prediction_modifier_jump_popover(
8618 &mut self,
8619 text_bounds: &Bounds<Pixels>,
8620 content_origin: gpui::Point<Pixels>,
8621 visible_row_range: Range<DisplayRow>,
8622 line_layouts: &[LineWithInvisibles],
8623 line_height: Pixels,
8624 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8625 newest_selection_head: Option<DisplayPoint>,
8626 target_display_point: DisplayPoint,
8627 window: &mut Window,
8628 cx: &mut App,
8629 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8630 let scrolled_content_origin =
8631 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8632
8633 const SCROLL_PADDING_Y: Pixels = px(12.);
8634
8635 if target_display_point.row() < visible_row_range.start {
8636 return self.render_edit_prediction_scroll_popover(
8637 |_| SCROLL_PADDING_Y,
8638 IconName::ArrowUp,
8639 visible_row_range,
8640 line_layouts,
8641 newest_selection_head,
8642 scrolled_content_origin,
8643 window,
8644 cx,
8645 );
8646 } else if target_display_point.row() >= visible_row_range.end {
8647 return self.render_edit_prediction_scroll_popover(
8648 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8649 IconName::ArrowDown,
8650 visible_row_range,
8651 line_layouts,
8652 newest_selection_head,
8653 scrolled_content_origin,
8654 window,
8655 cx,
8656 );
8657 }
8658
8659 const POLE_WIDTH: Pixels = px(2.);
8660
8661 let line_layout =
8662 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8663 let target_column = target_display_point.column() as usize;
8664
8665 let target_x = line_layout.x_for_index(target_column);
8666 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8667 - scroll_pixel_position.y;
8668
8669 let flag_on_right = target_x < text_bounds.size.width / 2.;
8670
8671 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8672 border_color.l += 0.001;
8673
8674 let mut element = v_flex()
8675 .items_end()
8676 .when(flag_on_right, |el| el.items_start())
8677 .child(if flag_on_right {
8678 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8679 .rounded_bl(px(0.))
8680 .rounded_tl(px(0.))
8681 .border_l_2()
8682 .border_color(border_color)
8683 } else {
8684 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8685 .rounded_br(px(0.))
8686 .rounded_tr(px(0.))
8687 .border_r_2()
8688 .border_color(border_color)
8689 })
8690 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8691 .into_any();
8692
8693 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8694
8695 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8696 - point(
8697 if flag_on_right {
8698 POLE_WIDTH
8699 } else {
8700 size.width - POLE_WIDTH
8701 },
8702 size.height - line_height,
8703 );
8704
8705 origin.x = origin.x.max(content_origin.x);
8706
8707 element.prepaint_at(origin, window, cx);
8708
8709 Some((element, origin))
8710 }
8711
8712 fn render_edit_prediction_scroll_popover(
8713 &mut self,
8714 to_y: impl Fn(Size<Pixels>) -> Pixels,
8715 scroll_icon: IconName,
8716 visible_row_range: Range<DisplayRow>,
8717 line_layouts: &[LineWithInvisibles],
8718 newest_selection_head: Option<DisplayPoint>,
8719 scrolled_content_origin: gpui::Point<Pixels>,
8720 window: &mut Window,
8721 cx: &mut App,
8722 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8723 let mut element = self
8724 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8725 .into_any();
8726
8727 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8728
8729 let cursor = newest_selection_head?;
8730 let cursor_row_layout =
8731 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8732 let cursor_column = cursor.column() as usize;
8733
8734 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8735
8736 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8737
8738 element.prepaint_at(origin, window, cx);
8739 Some((element, origin))
8740 }
8741
8742 fn render_edit_prediction_eager_jump_popover(
8743 &mut self,
8744 text_bounds: &Bounds<Pixels>,
8745 content_origin: gpui::Point<Pixels>,
8746 editor_snapshot: &EditorSnapshot,
8747 visible_row_range: Range<DisplayRow>,
8748 scroll_top: ScrollOffset,
8749 scroll_bottom: ScrollOffset,
8750 line_height: Pixels,
8751 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8752 target_display_point: DisplayPoint,
8753 editor_width: Pixels,
8754 window: &mut Window,
8755 cx: &mut App,
8756 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8757 if target_display_point.row().as_f64() < scroll_top {
8758 let mut element = self
8759 .render_edit_prediction_line_popover(
8760 "Jump to Edit",
8761 Some(IconName::ArrowUp),
8762 window,
8763 cx,
8764 )
8765 .into_any();
8766
8767 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8768 let offset = point(
8769 (text_bounds.size.width - size.width) / 2.,
8770 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8771 );
8772
8773 let origin = text_bounds.origin + offset;
8774 element.prepaint_at(origin, window, cx);
8775 Some((element, origin))
8776 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8777 let mut element = self
8778 .render_edit_prediction_line_popover(
8779 "Jump to Edit",
8780 Some(IconName::ArrowDown),
8781 window,
8782 cx,
8783 )
8784 .into_any();
8785
8786 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8787 let offset = point(
8788 (text_bounds.size.width - size.width) / 2.,
8789 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8790 );
8791
8792 let origin = text_bounds.origin + offset;
8793 element.prepaint_at(origin, window, cx);
8794 Some((element, origin))
8795 } else {
8796 self.render_edit_prediction_end_of_line_popover(
8797 "Jump to Edit",
8798 editor_snapshot,
8799 visible_row_range,
8800 target_display_point,
8801 line_height,
8802 scroll_pixel_position,
8803 content_origin,
8804 editor_width,
8805 window,
8806 cx,
8807 )
8808 }
8809 }
8810
8811 fn render_edit_prediction_end_of_line_popover(
8812 self: &mut Editor,
8813 label: &'static str,
8814 editor_snapshot: &EditorSnapshot,
8815 visible_row_range: Range<DisplayRow>,
8816 target_display_point: DisplayPoint,
8817 line_height: Pixels,
8818 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8819 content_origin: gpui::Point<Pixels>,
8820 editor_width: Pixels,
8821 window: &mut Window,
8822 cx: &mut App,
8823 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8824 let target_line_end = DisplayPoint::new(
8825 target_display_point.row(),
8826 editor_snapshot.line_len(target_display_point.row()),
8827 );
8828
8829 let mut element = self
8830 .render_edit_prediction_line_popover(label, None, window, cx)
8831 .into_any();
8832
8833 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8834
8835 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8836
8837 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8838 let mut origin = start_point
8839 + line_origin
8840 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8841 origin.x = origin.x.max(content_origin.x);
8842
8843 let max_x = content_origin.x + editor_width - size.width;
8844
8845 if origin.x > max_x {
8846 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8847
8848 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8849 origin.y += offset;
8850 IconName::ArrowUp
8851 } else {
8852 origin.y -= offset;
8853 IconName::ArrowDown
8854 };
8855
8856 element = self
8857 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8858 .into_any();
8859
8860 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8861
8862 origin.x = content_origin.x + editor_width - size.width - px(2.);
8863 }
8864
8865 element.prepaint_at(origin, window, cx);
8866 Some((element, origin))
8867 }
8868
8869 fn render_edit_prediction_diff_popover(
8870 self: &Editor,
8871 text_bounds: &Bounds<Pixels>,
8872 content_origin: gpui::Point<Pixels>,
8873 right_margin: Pixels,
8874 editor_snapshot: &EditorSnapshot,
8875 visible_row_range: Range<DisplayRow>,
8876 line_layouts: &[LineWithInvisibles],
8877 line_height: Pixels,
8878 scroll_position: gpui::Point<ScrollOffset>,
8879 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8880 newest_selection_head: Option<DisplayPoint>,
8881 editor_width: Pixels,
8882 style: &EditorStyle,
8883 edits: &Vec<(Range<Anchor>, String)>,
8884 edit_preview: &Option<language::EditPreview>,
8885 snapshot: &language::BufferSnapshot,
8886 window: &mut Window,
8887 cx: &mut App,
8888 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8889 let edit_start = edits
8890 .first()
8891 .unwrap()
8892 .0
8893 .start
8894 .to_display_point(editor_snapshot);
8895 let edit_end = edits
8896 .last()
8897 .unwrap()
8898 .0
8899 .end
8900 .to_display_point(editor_snapshot);
8901
8902 let is_visible = visible_row_range.contains(&edit_start.row())
8903 || visible_row_range.contains(&edit_end.row());
8904 if !is_visible {
8905 return None;
8906 }
8907
8908 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8909 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8910 } else {
8911 // Fallback for providers without edit_preview
8912 crate::edit_prediction_fallback_text(edits, cx)
8913 };
8914
8915 let styled_text = highlighted_edits.to_styled_text(&style.text);
8916 let line_count = highlighted_edits.text.lines().count();
8917
8918 const BORDER_WIDTH: Pixels = px(1.);
8919
8920 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8921 let has_keybind = keybind.is_some();
8922
8923 let mut element = h_flex()
8924 .items_start()
8925 .child(
8926 h_flex()
8927 .bg(cx.theme().colors().editor_background)
8928 .border(BORDER_WIDTH)
8929 .shadow_xs()
8930 .border_color(cx.theme().colors().border)
8931 .rounded_l_lg()
8932 .when(line_count > 1, |el| el.rounded_br_lg())
8933 .pr_1()
8934 .child(styled_text),
8935 )
8936 .child(
8937 h_flex()
8938 .h(line_height + BORDER_WIDTH * 2.)
8939 .px_1p5()
8940 .gap_1()
8941 // Workaround: For some reason, there's a gap if we don't do this
8942 .ml(-BORDER_WIDTH)
8943 .shadow(vec![gpui::BoxShadow {
8944 color: gpui::black().opacity(0.05),
8945 offset: point(px(1.), px(1.)),
8946 blur_radius: px(2.),
8947 spread_radius: px(0.),
8948 }])
8949 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8950 .border(BORDER_WIDTH)
8951 .border_color(cx.theme().colors().border)
8952 .rounded_r_lg()
8953 .id("edit_prediction_diff_popover_keybind")
8954 .when(!has_keybind, |el| {
8955 let status_colors = cx.theme().status();
8956
8957 el.bg(status_colors.error_background)
8958 .border_color(status_colors.error.opacity(0.6))
8959 .child(Icon::new(IconName::Info).color(Color::Error))
8960 .cursor_default()
8961 .hoverable_tooltip(move |_window, cx| {
8962 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8963 })
8964 })
8965 .children(keybind),
8966 )
8967 .into_any();
8968
8969 let longest_row =
8970 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8971 let longest_line_width = if visible_row_range.contains(&longest_row) {
8972 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8973 } else {
8974 layout_line(
8975 longest_row,
8976 editor_snapshot,
8977 style,
8978 editor_width,
8979 |_| false,
8980 window,
8981 cx,
8982 )
8983 .width
8984 };
8985
8986 let viewport_bounds =
8987 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8988 right: -right_margin,
8989 ..Default::default()
8990 });
8991
8992 let x_after_longest = Pixels::from(
8993 ScrollPixelOffset::from(
8994 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8995 ) - scroll_pixel_position.x,
8996 );
8997
8998 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8999
9000 // Fully visible if it can be displayed within the window (allow overlapping other
9001 // panes). However, this is only allowed if the popover starts within text_bounds.
9002 let can_position_to_the_right = x_after_longest < text_bounds.right()
9003 && x_after_longest + element_bounds.width < viewport_bounds.right();
9004
9005 let mut origin = if can_position_to_the_right {
9006 point(
9007 x_after_longest,
9008 text_bounds.origin.y
9009 + Pixels::from(
9010 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9011 - scroll_pixel_position.y,
9012 ),
9013 )
9014 } else {
9015 let cursor_row = newest_selection_head.map(|head| head.row());
9016 let above_edit = edit_start
9017 .row()
9018 .0
9019 .checked_sub(line_count as u32)
9020 .map(DisplayRow);
9021 let below_edit = Some(edit_end.row() + 1);
9022 let above_cursor =
9023 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9024 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9025
9026 // Place the edit popover adjacent to the edit if there is a location
9027 // available that is onscreen and does not obscure the cursor. Otherwise,
9028 // place it adjacent to the cursor.
9029 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9030 .into_iter()
9031 .flatten()
9032 .find(|&start_row| {
9033 let end_row = start_row + line_count as u32;
9034 visible_row_range.contains(&start_row)
9035 && visible_row_range.contains(&end_row)
9036 && cursor_row
9037 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9038 })?;
9039
9040 content_origin
9041 + point(
9042 Pixels::from(-scroll_pixel_position.x),
9043 Pixels::from(
9044 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9045 ),
9046 )
9047 };
9048
9049 origin.x -= BORDER_WIDTH;
9050
9051 window.defer_draw(element, origin, 1);
9052
9053 // Do not return an element, since it will already be drawn due to defer_draw.
9054 None
9055 }
9056
9057 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9058 px(30.)
9059 }
9060
9061 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9062 if self.read_only(cx) {
9063 cx.theme().players().read_only()
9064 } else {
9065 self.style.as_ref().unwrap().local_player
9066 }
9067 }
9068
9069 fn render_edit_prediction_accept_keybind(
9070 &self,
9071 window: &mut Window,
9072 cx: &mut App,
9073 ) -> Option<AnyElement> {
9074 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9075 let accept_keystroke = accept_binding.keystroke()?;
9076
9077 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9078
9079 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9080 Color::Accent
9081 } else {
9082 Color::Muted
9083 };
9084
9085 h_flex()
9086 .px_0p5()
9087 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9088 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9089 .text_size(TextSize::XSmall.rems(cx))
9090 .child(h_flex().children(ui::render_modifiers(
9091 accept_keystroke.modifiers(),
9092 PlatformStyle::platform(),
9093 Some(modifiers_color),
9094 Some(IconSize::XSmall.rems().into()),
9095 true,
9096 )))
9097 .when(is_platform_style_mac, |parent| {
9098 parent.child(accept_keystroke.key().to_string())
9099 })
9100 .when(!is_platform_style_mac, |parent| {
9101 parent.child(
9102 Key::new(
9103 util::capitalize(accept_keystroke.key()),
9104 Some(Color::Default),
9105 )
9106 .size(Some(IconSize::XSmall.rems().into())),
9107 )
9108 })
9109 .into_any()
9110 .into()
9111 }
9112
9113 fn render_edit_prediction_line_popover(
9114 &self,
9115 label: impl Into<SharedString>,
9116 icon: Option<IconName>,
9117 window: &mut Window,
9118 cx: &mut App,
9119 ) -> Stateful<Div> {
9120 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9121
9122 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9123 let has_keybind = keybind.is_some();
9124
9125 h_flex()
9126 .id("ep-line-popover")
9127 .py_0p5()
9128 .pl_1()
9129 .pr(padding_right)
9130 .gap_1()
9131 .rounded_md()
9132 .border_1()
9133 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9134 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9135 .shadow_xs()
9136 .when(!has_keybind, |el| {
9137 let status_colors = cx.theme().status();
9138
9139 el.bg(status_colors.error_background)
9140 .border_color(status_colors.error.opacity(0.6))
9141 .pl_2()
9142 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9143 .cursor_default()
9144 .hoverable_tooltip(move |_window, cx| {
9145 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9146 })
9147 })
9148 .children(keybind)
9149 .child(
9150 Label::new(label)
9151 .size(LabelSize::Small)
9152 .when(!has_keybind, |el| {
9153 el.color(cx.theme().status().error.into()).strikethrough()
9154 }),
9155 )
9156 .when(!has_keybind, |el| {
9157 el.child(
9158 h_flex().ml_1().child(
9159 Icon::new(IconName::Info)
9160 .size(IconSize::Small)
9161 .color(cx.theme().status().error.into()),
9162 ),
9163 )
9164 })
9165 .when_some(icon, |element, icon| {
9166 element.child(
9167 div()
9168 .mt(px(1.5))
9169 .child(Icon::new(icon).size(IconSize::Small)),
9170 )
9171 })
9172 }
9173
9174 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9175 let accent_color = cx.theme().colors().text_accent;
9176 let editor_bg_color = cx.theme().colors().editor_background;
9177 editor_bg_color.blend(accent_color.opacity(0.1))
9178 }
9179
9180 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9181 let accent_color = cx.theme().colors().text_accent;
9182 let editor_bg_color = cx.theme().colors().editor_background;
9183 editor_bg_color.blend(accent_color.opacity(0.6))
9184 }
9185 fn get_prediction_provider_icon_name(
9186 provider: &Option<RegisteredEditPredictionProvider>,
9187 ) -> IconName {
9188 match provider {
9189 Some(provider) => match provider.provider.name() {
9190 "copilot" => IconName::Copilot,
9191 "supermaven" => IconName::Supermaven,
9192 _ => IconName::ZedPredict,
9193 },
9194 None => IconName::ZedPredict,
9195 }
9196 }
9197
9198 fn render_edit_prediction_cursor_popover(
9199 &self,
9200 min_width: Pixels,
9201 max_width: Pixels,
9202 cursor_point: Point,
9203 style: &EditorStyle,
9204 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9205 _window: &Window,
9206 cx: &mut Context<Editor>,
9207 ) -> Option<AnyElement> {
9208 let provider = self.edit_prediction_provider.as_ref()?;
9209 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9210
9211 let is_refreshing = provider.provider.is_refreshing(cx);
9212
9213 fn pending_completion_container(icon: IconName) -> Div {
9214 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9215 }
9216
9217 let completion = match &self.active_edit_prediction {
9218 Some(prediction) => {
9219 if !self.has_visible_completions_menu() {
9220 const RADIUS: Pixels = px(6.);
9221 const BORDER_WIDTH: Pixels = px(1.);
9222
9223 return Some(
9224 h_flex()
9225 .elevation_2(cx)
9226 .border(BORDER_WIDTH)
9227 .border_color(cx.theme().colors().border)
9228 .when(accept_keystroke.is_none(), |el| {
9229 el.border_color(cx.theme().status().error)
9230 })
9231 .rounded(RADIUS)
9232 .rounded_tl(px(0.))
9233 .overflow_hidden()
9234 .child(div().px_1p5().child(match &prediction.completion {
9235 EditPrediction::MoveWithin { target, snapshot } => {
9236 use text::ToPoint as _;
9237 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9238 {
9239 Icon::new(IconName::ZedPredictDown)
9240 } else {
9241 Icon::new(IconName::ZedPredictUp)
9242 }
9243 }
9244 EditPrediction::MoveOutside { .. } => {
9245 // TODO [zeta2] custom icon for external jump?
9246 Icon::new(provider_icon)
9247 }
9248 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9249 }))
9250 .child(
9251 h_flex()
9252 .gap_1()
9253 .py_1()
9254 .px_2()
9255 .rounded_r(RADIUS - BORDER_WIDTH)
9256 .border_l_1()
9257 .border_color(cx.theme().colors().border)
9258 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9259 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9260 el.child(
9261 Label::new("Hold")
9262 .size(LabelSize::Small)
9263 .when(accept_keystroke.is_none(), |el| {
9264 el.strikethrough()
9265 })
9266 .line_height_style(LineHeightStyle::UiLabel),
9267 )
9268 })
9269 .id("edit_prediction_cursor_popover_keybind")
9270 .when(accept_keystroke.is_none(), |el| {
9271 let status_colors = cx.theme().status();
9272
9273 el.bg(status_colors.error_background)
9274 .border_color(status_colors.error.opacity(0.6))
9275 .child(Icon::new(IconName::Info).color(Color::Error))
9276 .cursor_default()
9277 .hoverable_tooltip(move |_window, cx| {
9278 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9279 .into()
9280 })
9281 })
9282 .when_some(
9283 accept_keystroke.as_ref(),
9284 |el, accept_keystroke| {
9285 el.child(h_flex().children(ui::render_modifiers(
9286 accept_keystroke.modifiers(),
9287 PlatformStyle::platform(),
9288 Some(Color::Default),
9289 Some(IconSize::XSmall.rems().into()),
9290 false,
9291 )))
9292 },
9293 ),
9294 )
9295 .into_any(),
9296 );
9297 }
9298
9299 self.render_edit_prediction_cursor_popover_preview(
9300 prediction,
9301 cursor_point,
9302 style,
9303 cx,
9304 )?
9305 }
9306
9307 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9308 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9309 stale_completion,
9310 cursor_point,
9311 style,
9312 cx,
9313 )?,
9314
9315 None => pending_completion_container(provider_icon)
9316 .child(Label::new("...").size(LabelSize::Small)),
9317 },
9318
9319 None => pending_completion_container(provider_icon)
9320 .child(Label::new("...").size(LabelSize::Small)),
9321 };
9322
9323 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9324 completion
9325 .with_animation(
9326 "loading-completion",
9327 Animation::new(Duration::from_secs(2))
9328 .repeat()
9329 .with_easing(pulsating_between(0.4, 0.8)),
9330 |label, delta| label.opacity(delta),
9331 )
9332 .into_any_element()
9333 } else {
9334 completion.into_any_element()
9335 };
9336
9337 let has_completion = self.active_edit_prediction.is_some();
9338
9339 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9340 Some(
9341 h_flex()
9342 .min_w(min_width)
9343 .max_w(max_width)
9344 .flex_1()
9345 .elevation_2(cx)
9346 .border_color(cx.theme().colors().border)
9347 .child(
9348 div()
9349 .flex_1()
9350 .py_1()
9351 .px_2()
9352 .overflow_hidden()
9353 .child(completion),
9354 )
9355 .when_some(accept_keystroke, |el, accept_keystroke| {
9356 if !accept_keystroke.modifiers().modified() {
9357 return el;
9358 }
9359
9360 el.child(
9361 h_flex()
9362 .h_full()
9363 .border_l_1()
9364 .rounded_r_lg()
9365 .border_color(cx.theme().colors().border)
9366 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9367 .gap_1()
9368 .py_1()
9369 .px_2()
9370 .child(
9371 h_flex()
9372 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9373 .when(is_platform_style_mac, |parent| parent.gap_1())
9374 .child(h_flex().children(ui::render_modifiers(
9375 accept_keystroke.modifiers(),
9376 PlatformStyle::platform(),
9377 Some(if !has_completion {
9378 Color::Muted
9379 } else {
9380 Color::Default
9381 }),
9382 None,
9383 false,
9384 ))),
9385 )
9386 .child(Label::new("Preview").into_any_element())
9387 .opacity(if has_completion { 1.0 } else { 0.4 }),
9388 )
9389 })
9390 .into_any(),
9391 )
9392 }
9393
9394 fn render_edit_prediction_cursor_popover_preview(
9395 &self,
9396 completion: &EditPredictionState,
9397 cursor_point: Point,
9398 style: &EditorStyle,
9399 cx: &mut Context<Editor>,
9400 ) -> Option<Div> {
9401 use text::ToPoint as _;
9402
9403 fn render_relative_row_jump(
9404 prefix: impl Into<String>,
9405 current_row: u32,
9406 target_row: u32,
9407 ) -> Div {
9408 let (row_diff, arrow) = if target_row < current_row {
9409 (current_row - target_row, IconName::ArrowUp)
9410 } else {
9411 (target_row - current_row, IconName::ArrowDown)
9412 };
9413
9414 h_flex()
9415 .child(
9416 Label::new(format!("{}{}", prefix.into(), row_diff))
9417 .color(Color::Muted)
9418 .size(LabelSize::Small),
9419 )
9420 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9421 }
9422
9423 let supports_jump = self
9424 .edit_prediction_provider
9425 .as_ref()
9426 .map(|provider| provider.provider.supports_jump_to_edit())
9427 .unwrap_or(true);
9428
9429 match &completion.completion {
9430 EditPrediction::MoveWithin {
9431 target, snapshot, ..
9432 } => {
9433 if !supports_jump {
9434 return None;
9435 }
9436
9437 Some(
9438 h_flex()
9439 .px_2()
9440 .gap_2()
9441 .flex_1()
9442 .child(
9443 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9444 Icon::new(IconName::ZedPredictDown)
9445 } else {
9446 Icon::new(IconName::ZedPredictUp)
9447 },
9448 )
9449 .child(Label::new("Jump to Edit")),
9450 )
9451 }
9452 EditPrediction::MoveOutside { snapshot, .. } => {
9453 let file_name = snapshot
9454 .file()
9455 .map(|file| file.file_name(cx))
9456 .unwrap_or("untitled");
9457 Some(
9458 h_flex()
9459 .px_2()
9460 .gap_2()
9461 .flex_1()
9462 .child(Icon::new(IconName::ZedPredict))
9463 .child(Label::new(format!("Jump to {file_name}"))),
9464 )
9465 }
9466 EditPrediction::Edit {
9467 edits,
9468 edit_preview,
9469 snapshot,
9470 display_mode: _,
9471 } => {
9472 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9473
9474 let (highlighted_edits, has_more_lines) =
9475 if let Some(edit_preview) = edit_preview.as_ref() {
9476 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9477 .first_line_preview()
9478 } else {
9479 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9480 };
9481
9482 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9483 .with_default_highlights(&style.text, highlighted_edits.highlights);
9484
9485 let preview = h_flex()
9486 .gap_1()
9487 .min_w_16()
9488 .child(styled_text)
9489 .when(has_more_lines, |parent| parent.child("…"));
9490
9491 let left = if supports_jump && first_edit_row != cursor_point.row {
9492 render_relative_row_jump("", cursor_point.row, first_edit_row)
9493 .into_any_element()
9494 } else {
9495 let icon_name =
9496 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9497 Icon::new(icon_name).into_any_element()
9498 };
9499
9500 Some(
9501 h_flex()
9502 .h_full()
9503 .flex_1()
9504 .gap_2()
9505 .pr_1()
9506 .overflow_x_hidden()
9507 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9508 .child(left)
9509 .child(preview),
9510 )
9511 }
9512 }
9513 }
9514
9515 pub fn render_context_menu(
9516 &self,
9517 style: &EditorStyle,
9518 max_height_in_lines: u32,
9519 window: &mut Window,
9520 cx: &mut Context<Editor>,
9521 ) -> Option<AnyElement> {
9522 let menu = self.context_menu.borrow();
9523 let menu = menu.as_ref()?;
9524 if !menu.visible() {
9525 return None;
9526 };
9527 Some(menu.render(style, max_height_in_lines, window, cx))
9528 }
9529
9530 fn render_context_menu_aside(
9531 &mut self,
9532 max_size: Size<Pixels>,
9533 window: &mut Window,
9534 cx: &mut Context<Editor>,
9535 ) -> Option<AnyElement> {
9536 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9537 if menu.visible() {
9538 menu.render_aside(max_size, window, cx)
9539 } else {
9540 None
9541 }
9542 })
9543 }
9544
9545 fn hide_context_menu(
9546 &mut self,
9547 window: &mut Window,
9548 cx: &mut Context<Self>,
9549 ) -> Option<CodeContextMenu> {
9550 cx.notify();
9551 self.completion_tasks.clear();
9552 let context_menu = self.context_menu.borrow_mut().take();
9553 self.stale_edit_prediction_in_menu.take();
9554 self.update_visible_edit_prediction(window, cx);
9555 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9556 && let Some(completion_provider) = &self.completion_provider
9557 {
9558 completion_provider.selection_changed(None, window, cx);
9559 }
9560 context_menu
9561 }
9562
9563 fn show_snippet_choices(
9564 &mut self,
9565 choices: &Vec<String>,
9566 selection: Range<Anchor>,
9567 cx: &mut Context<Self>,
9568 ) {
9569 let Some((_, buffer, _)) = self
9570 .buffer()
9571 .read(cx)
9572 .excerpt_containing(selection.start, cx)
9573 else {
9574 return;
9575 };
9576 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9577 else {
9578 return;
9579 };
9580 if buffer != end_buffer {
9581 log::error!("expected anchor range to have matching buffer IDs");
9582 return;
9583 }
9584
9585 let id = post_inc(&mut self.next_completion_id);
9586 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9587 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9588 CompletionsMenu::new_snippet_choices(
9589 id,
9590 true,
9591 choices,
9592 selection,
9593 buffer,
9594 snippet_sort_order,
9595 ),
9596 ));
9597 }
9598
9599 pub fn insert_snippet(
9600 &mut self,
9601 insertion_ranges: &[Range<usize>],
9602 snippet: Snippet,
9603 window: &mut Window,
9604 cx: &mut Context<Self>,
9605 ) -> Result<()> {
9606 struct Tabstop<T> {
9607 is_end_tabstop: bool,
9608 ranges: Vec<Range<T>>,
9609 choices: Option<Vec<String>>,
9610 }
9611
9612 let tabstops = self.buffer.update(cx, |buffer, cx| {
9613 let snippet_text: Arc<str> = snippet.text.clone().into();
9614 let edits = insertion_ranges
9615 .iter()
9616 .cloned()
9617 .map(|range| (range, snippet_text.clone()));
9618 let autoindent_mode = AutoindentMode::Block {
9619 original_indent_columns: Vec::new(),
9620 };
9621 buffer.edit(edits, Some(autoindent_mode), cx);
9622
9623 let snapshot = &*buffer.read(cx);
9624 let snippet = &snippet;
9625 snippet
9626 .tabstops
9627 .iter()
9628 .map(|tabstop| {
9629 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9630 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9631 });
9632 let mut tabstop_ranges = tabstop
9633 .ranges
9634 .iter()
9635 .flat_map(|tabstop_range| {
9636 let mut delta = 0_isize;
9637 insertion_ranges.iter().map(move |insertion_range| {
9638 let insertion_start = insertion_range.start as isize + delta;
9639 delta +=
9640 snippet.text.len() as isize - insertion_range.len() as isize;
9641
9642 let start = ((insertion_start + tabstop_range.start) as usize)
9643 .min(snapshot.len());
9644 let end = ((insertion_start + tabstop_range.end) as usize)
9645 .min(snapshot.len());
9646 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9647 })
9648 })
9649 .collect::<Vec<_>>();
9650 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9651
9652 Tabstop {
9653 is_end_tabstop,
9654 ranges: tabstop_ranges,
9655 choices: tabstop.choices.clone(),
9656 }
9657 })
9658 .collect::<Vec<_>>()
9659 });
9660 if let Some(tabstop) = tabstops.first() {
9661 self.change_selections(Default::default(), window, cx, |s| {
9662 // Reverse order so that the first range is the newest created selection.
9663 // Completions will use it and autoscroll will prioritize it.
9664 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9665 });
9666
9667 if let Some(choices) = &tabstop.choices
9668 && let Some(selection) = tabstop.ranges.first()
9669 {
9670 self.show_snippet_choices(choices, selection.clone(), cx)
9671 }
9672
9673 // If we're already at the last tabstop and it's at the end of the snippet,
9674 // we're done, we don't need to keep the state around.
9675 if !tabstop.is_end_tabstop {
9676 let choices = tabstops
9677 .iter()
9678 .map(|tabstop| tabstop.choices.clone())
9679 .collect();
9680
9681 let ranges = tabstops
9682 .into_iter()
9683 .map(|tabstop| tabstop.ranges)
9684 .collect::<Vec<_>>();
9685
9686 self.snippet_stack.push(SnippetState {
9687 active_index: 0,
9688 ranges,
9689 choices,
9690 });
9691 }
9692
9693 // Check whether the just-entered snippet ends with an auto-closable bracket.
9694 if self.autoclose_regions.is_empty() {
9695 let snapshot = self.buffer.read(cx).snapshot(cx);
9696 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9697 let selection_head = selection.head();
9698 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9699 continue;
9700 };
9701
9702 let mut bracket_pair = None;
9703 let max_lookup_length = scope
9704 .brackets()
9705 .map(|(pair, _)| {
9706 pair.start
9707 .as_str()
9708 .chars()
9709 .count()
9710 .max(pair.end.as_str().chars().count())
9711 })
9712 .max();
9713 if let Some(max_lookup_length) = max_lookup_length {
9714 let next_text = snapshot
9715 .chars_at(selection_head)
9716 .take(max_lookup_length)
9717 .collect::<String>();
9718 let prev_text = snapshot
9719 .reversed_chars_at(selection_head)
9720 .take(max_lookup_length)
9721 .collect::<String>();
9722
9723 for (pair, enabled) in scope.brackets() {
9724 if enabled
9725 && pair.close
9726 && prev_text.starts_with(pair.start.as_str())
9727 && next_text.starts_with(pair.end.as_str())
9728 {
9729 bracket_pair = Some(pair.clone());
9730 break;
9731 }
9732 }
9733 }
9734
9735 if let Some(pair) = bracket_pair {
9736 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9737 let autoclose_enabled =
9738 self.use_autoclose && snapshot_settings.use_autoclose;
9739 if autoclose_enabled {
9740 let start = snapshot.anchor_after(selection_head);
9741 let end = snapshot.anchor_after(selection_head);
9742 self.autoclose_regions.push(AutocloseRegion {
9743 selection_id: selection.id,
9744 range: start..end,
9745 pair,
9746 });
9747 }
9748 }
9749 }
9750 }
9751 }
9752 Ok(())
9753 }
9754
9755 pub fn move_to_next_snippet_tabstop(
9756 &mut self,
9757 window: &mut Window,
9758 cx: &mut Context<Self>,
9759 ) -> bool {
9760 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9761 }
9762
9763 pub fn move_to_prev_snippet_tabstop(
9764 &mut self,
9765 window: &mut Window,
9766 cx: &mut Context<Self>,
9767 ) -> bool {
9768 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9769 }
9770
9771 pub fn move_to_snippet_tabstop(
9772 &mut self,
9773 bias: Bias,
9774 window: &mut Window,
9775 cx: &mut Context<Self>,
9776 ) -> bool {
9777 if let Some(mut snippet) = self.snippet_stack.pop() {
9778 match bias {
9779 Bias::Left => {
9780 if snippet.active_index > 0 {
9781 snippet.active_index -= 1;
9782 } else {
9783 self.snippet_stack.push(snippet);
9784 return false;
9785 }
9786 }
9787 Bias::Right => {
9788 if snippet.active_index + 1 < snippet.ranges.len() {
9789 snippet.active_index += 1;
9790 } else {
9791 self.snippet_stack.push(snippet);
9792 return false;
9793 }
9794 }
9795 }
9796 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9797 self.change_selections(Default::default(), window, cx, |s| {
9798 // Reverse order so that the first range is the newest created selection.
9799 // Completions will use it and autoscroll will prioritize it.
9800 s.select_ranges(current_ranges.iter().rev().cloned())
9801 });
9802
9803 if let Some(choices) = &snippet.choices[snippet.active_index]
9804 && let Some(selection) = current_ranges.first()
9805 {
9806 self.show_snippet_choices(choices, selection.clone(), cx);
9807 }
9808
9809 // If snippet state is not at the last tabstop, push it back on the stack
9810 if snippet.active_index + 1 < snippet.ranges.len() {
9811 self.snippet_stack.push(snippet);
9812 }
9813 return true;
9814 }
9815 }
9816
9817 false
9818 }
9819
9820 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9821 self.transact(window, cx, |this, window, cx| {
9822 this.select_all(&SelectAll, window, cx);
9823 this.insert("", window, cx);
9824 });
9825 }
9826
9827 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9828 if self.read_only(cx) {
9829 return;
9830 }
9831 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9832 self.transact(window, cx, |this, window, cx| {
9833 this.select_autoclose_pair(window, cx);
9834
9835 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9836
9837 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9838 if !this.linked_edit_ranges.is_empty() {
9839 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9840 let snapshot = this.buffer.read(cx).snapshot(cx);
9841
9842 for selection in selections.iter() {
9843 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9844 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9845 if selection_start.buffer_id != selection_end.buffer_id {
9846 continue;
9847 }
9848 if let Some(ranges) =
9849 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9850 {
9851 for (buffer, entries) in ranges {
9852 linked_ranges.entry(buffer).or_default().extend(entries);
9853 }
9854 }
9855 }
9856 }
9857
9858 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9859 for selection in &mut selections {
9860 if selection.is_empty() {
9861 let old_head = selection.head();
9862 let mut new_head =
9863 movement::left(&display_map, old_head.to_display_point(&display_map))
9864 .to_point(&display_map);
9865 if let Some((buffer, line_buffer_range)) = display_map
9866 .buffer_snapshot()
9867 .buffer_line_for_row(MultiBufferRow(old_head.row))
9868 {
9869 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9870 let indent_len = match indent_size.kind {
9871 IndentKind::Space => {
9872 buffer.settings_at(line_buffer_range.start, cx).tab_size
9873 }
9874 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9875 };
9876 if old_head.column <= indent_size.len && old_head.column > 0 {
9877 let indent_len = indent_len.get();
9878 new_head = cmp::min(
9879 new_head,
9880 MultiBufferPoint::new(
9881 old_head.row,
9882 ((old_head.column - 1) / indent_len) * indent_len,
9883 ),
9884 );
9885 }
9886 }
9887
9888 selection.set_head(new_head, SelectionGoal::None);
9889 }
9890 }
9891
9892 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9893 this.insert("", window, cx);
9894 let empty_str: Arc<str> = Arc::from("");
9895 for (buffer, edits) in linked_ranges {
9896 let snapshot = buffer.read(cx).snapshot();
9897 use text::ToPoint as TP;
9898
9899 let edits = edits
9900 .into_iter()
9901 .map(|range| {
9902 let end_point = TP::to_point(&range.end, &snapshot);
9903 let mut start_point = TP::to_point(&range.start, &snapshot);
9904
9905 if end_point == start_point {
9906 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9907 .saturating_sub(1);
9908 start_point =
9909 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9910 };
9911
9912 (start_point..end_point, empty_str.clone())
9913 })
9914 .sorted_by_key(|(range, _)| range.start)
9915 .collect::<Vec<_>>();
9916 buffer.update(cx, |this, cx| {
9917 this.edit(edits, None, cx);
9918 })
9919 }
9920 this.refresh_edit_prediction(true, false, window, cx);
9921 refresh_linked_ranges(this, window, cx);
9922 });
9923 }
9924
9925 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9926 if self.read_only(cx) {
9927 return;
9928 }
9929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9930 self.transact(window, cx, |this, window, cx| {
9931 this.change_selections(Default::default(), window, cx, |s| {
9932 s.move_with(|map, selection| {
9933 if selection.is_empty() {
9934 let cursor = movement::right(map, selection.head());
9935 selection.end = cursor;
9936 selection.reversed = true;
9937 selection.goal = SelectionGoal::None;
9938 }
9939 })
9940 });
9941 this.insert("", window, cx);
9942 this.refresh_edit_prediction(true, false, window, cx);
9943 });
9944 }
9945
9946 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9947 if self.mode.is_single_line() {
9948 cx.propagate();
9949 return;
9950 }
9951
9952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9953 if self.move_to_prev_snippet_tabstop(window, cx) {
9954 return;
9955 }
9956 self.outdent(&Outdent, window, cx);
9957 }
9958
9959 pub fn next_snippet_tabstop(
9960 &mut self,
9961 _: &NextSnippetTabstop,
9962 window: &mut Window,
9963 cx: &mut Context<Self>,
9964 ) {
9965 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9966 return;
9967 }
9968
9969 if self.move_to_next_snippet_tabstop(window, cx) {
9970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9971 return;
9972 }
9973 }
9974
9975 pub fn previous_snippet_tabstop(
9976 &mut self,
9977 _: &PreviousSnippetTabstop,
9978 window: &mut Window,
9979 cx: &mut Context<Self>,
9980 ) {
9981 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9982 return;
9983 }
9984
9985 if self.move_to_prev_snippet_tabstop(window, cx) {
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 return;
9988 }
9989 }
9990
9991 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9992 if self.mode.is_single_line() {
9993 cx.propagate();
9994 return;
9995 }
9996
9997 if self.move_to_next_snippet_tabstop(window, cx) {
9998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9999 return;
10000 }
10001 if self.read_only(cx) {
10002 return;
10003 }
10004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10005 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10006 let buffer = self.buffer.read(cx);
10007 let snapshot = buffer.snapshot(cx);
10008 let rows_iter = selections.iter().map(|s| s.head().row);
10009 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10010
10011 let has_some_cursor_in_whitespace = selections
10012 .iter()
10013 .filter(|selection| selection.is_empty())
10014 .any(|selection| {
10015 let cursor = selection.head();
10016 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10017 cursor.column < current_indent.len
10018 });
10019
10020 let mut edits = Vec::new();
10021 let mut prev_edited_row = 0;
10022 let mut row_delta = 0;
10023 for selection in &mut selections {
10024 if selection.start.row != prev_edited_row {
10025 row_delta = 0;
10026 }
10027 prev_edited_row = selection.end.row;
10028
10029 // If the selection is non-empty, then increase the indentation of the selected lines.
10030 if !selection.is_empty() {
10031 row_delta =
10032 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10033 continue;
10034 }
10035
10036 let cursor = selection.head();
10037 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10038 if let Some(suggested_indent) =
10039 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10040 {
10041 // Don't do anything if already at suggested indent
10042 // and there is any other cursor which is not
10043 if has_some_cursor_in_whitespace
10044 && cursor.column == current_indent.len
10045 && current_indent.len == suggested_indent.len
10046 {
10047 continue;
10048 }
10049
10050 // Adjust line and move cursor to suggested indent
10051 // if cursor is not at suggested indent
10052 if cursor.column < suggested_indent.len
10053 && cursor.column <= current_indent.len
10054 && current_indent.len <= suggested_indent.len
10055 {
10056 selection.start = Point::new(cursor.row, suggested_indent.len);
10057 selection.end = selection.start;
10058 if row_delta == 0 {
10059 edits.extend(Buffer::edit_for_indent_size_adjustment(
10060 cursor.row,
10061 current_indent,
10062 suggested_indent,
10063 ));
10064 row_delta = suggested_indent.len - current_indent.len;
10065 }
10066 continue;
10067 }
10068
10069 // If current indent is more than suggested indent
10070 // only move cursor to current indent and skip indent
10071 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10072 selection.start = Point::new(cursor.row, current_indent.len);
10073 selection.end = selection.start;
10074 continue;
10075 }
10076 }
10077
10078 // Otherwise, insert a hard or soft tab.
10079 let settings = buffer.language_settings_at(cursor, cx);
10080 let tab_size = if settings.hard_tabs {
10081 IndentSize::tab()
10082 } else {
10083 let tab_size = settings.tab_size.get();
10084 let indent_remainder = snapshot
10085 .text_for_range(Point::new(cursor.row, 0)..cursor)
10086 .flat_map(str::chars)
10087 .fold(row_delta % tab_size, |counter: u32, c| {
10088 if c == '\t' {
10089 0
10090 } else {
10091 (counter + 1) % tab_size
10092 }
10093 });
10094
10095 let chars_to_next_tab_stop = tab_size - indent_remainder;
10096 IndentSize::spaces(chars_to_next_tab_stop)
10097 };
10098 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10099 selection.end = selection.start;
10100 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10101 row_delta += tab_size.len;
10102 }
10103
10104 self.transact(window, cx, |this, window, cx| {
10105 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10106 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10107 this.refresh_edit_prediction(true, false, window, cx);
10108 });
10109 }
10110
10111 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10112 if self.read_only(cx) {
10113 return;
10114 }
10115 if self.mode.is_single_line() {
10116 cx.propagate();
10117 return;
10118 }
10119
10120 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10121 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10122 let mut prev_edited_row = 0;
10123 let mut row_delta = 0;
10124 let mut edits = Vec::new();
10125 let buffer = self.buffer.read(cx);
10126 let snapshot = buffer.snapshot(cx);
10127 for selection in &mut selections {
10128 if selection.start.row != prev_edited_row {
10129 row_delta = 0;
10130 }
10131 prev_edited_row = selection.end.row;
10132
10133 row_delta =
10134 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10135 }
10136
10137 self.transact(window, cx, |this, window, cx| {
10138 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10139 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10140 });
10141 }
10142
10143 fn indent_selection(
10144 buffer: &MultiBuffer,
10145 snapshot: &MultiBufferSnapshot,
10146 selection: &mut Selection<Point>,
10147 edits: &mut Vec<(Range<Point>, String)>,
10148 delta_for_start_row: u32,
10149 cx: &App,
10150 ) -> u32 {
10151 let settings = buffer.language_settings_at(selection.start, cx);
10152 let tab_size = settings.tab_size.get();
10153 let indent_kind = if settings.hard_tabs {
10154 IndentKind::Tab
10155 } else {
10156 IndentKind::Space
10157 };
10158 let mut start_row = selection.start.row;
10159 let mut end_row = selection.end.row + 1;
10160
10161 // If a selection ends at the beginning of a line, don't indent
10162 // that last line.
10163 if selection.end.column == 0 && selection.end.row > selection.start.row {
10164 end_row -= 1;
10165 }
10166
10167 // Avoid re-indenting a row that has already been indented by a
10168 // previous selection, but still update this selection's column
10169 // to reflect that indentation.
10170 if delta_for_start_row > 0 {
10171 start_row += 1;
10172 selection.start.column += delta_for_start_row;
10173 if selection.end.row == selection.start.row {
10174 selection.end.column += delta_for_start_row;
10175 }
10176 }
10177
10178 let mut delta_for_end_row = 0;
10179 let has_multiple_rows = start_row + 1 != end_row;
10180 for row in start_row..end_row {
10181 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10182 let indent_delta = match (current_indent.kind, indent_kind) {
10183 (IndentKind::Space, IndentKind::Space) => {
10184 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10185 IndentSize::spaces(columns_to_next_tab_stop)
10186 }
10187 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10188 (_, IndentKind::Tab) => IndentSize::tab(),
10189 };
10190
10191 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10192 0
10193 } else {
10194 selection.start.column
10195 };
10196 let row_start = Point::new(row, start);
10197 edits.push((
10198 row_start..row_start,
10199 indent_delta.chars().collect::<String>(),
10200 ));
10201
10202 // Update this selection's endpoints to reflect the indentation.
10203 if row == selection.start.row {
10204 selection.start.column += indent_delta.len;
10205 }
10206 if row == selection.end.row {
10207 selection.end.column += indent_delta.len;
10208 delta_for_end_row = indent_delta.len;
10209 }
10210 }
10211
10212 if selection.start.row == selection.end.row {
10213 delta_for_start_row + delta_for_end_row
10214 } else {
10215 delta_for_end_row
10216 }
10217 }
10218
10219 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10220 if self.read_only(cx) {
10221 return;
10222 }
10223 if self.mode.is_single_line() {
10224 cx.propagate();
10225 return;
10226 }
10227
10228 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10229 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10230 let selections = self.selections.all::<Point>(&display_map);
10231 let mut deletion_ranges = Vec::new();
10232 let mut last_outdent = None;
10233 {
10234 let buffer = self.buffer.read(cx);
10235 let snapshot = buffer.snapshot(cx);
10236 for selection in &selections {
10237 let settings = buffer.language_settings_at(selection.start, cx);
10238 let tab_size = settings.tab_size.get();
10239 let mut rows = selection.spanned_rows(false, &display_map);
10240
10241 // Avoid re-outdenting a row that has already been outdented by a
10242 // previous selection.
10243 if let Some(last_row) = last_outdent
10244 && last_row == rows.start
10245 {
10246 rows.start = rows.start.next_row();
10247 }
10248 let has_multiple_rows = rows.len() > 1;
10249 for row in rows.iter_rows() {
10250 let indent_size = snapshot.indent_size_for_line(row);
10251 if indent_size.len > 0 {
10252 let deletion_len = match indent_size.kind {
10253 IndentKind::Space => {
10254 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10255 if columns_to_prev_tab_stop == 0 {
10256 tab_size
10257 } else {
10258 columns_to_prev_tab_stop
10259 }
10260 }
10261 IndentKind::Tab => 1,
10262 };
10263 let start = if has_multiple_rows
10264 || deletion_len > selection.start.column
10265 || indent_size.len < selection.start.column
10266 {
10267 0
10268 } else {
10269 selection.start.column - deletion_len
10270 };
10271 deletion_ranges.push(
10272 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10273 );
10274 last_outdent = Some(row);
10275 }
10276 }
10277 }
10278 }
10279
10280 self.transact(window, cx, |this, window, cx| {
10281 this.buffer.update(cx, |buffer, cx| {
10282 let empty_str: Arc<str> = Arc::default();
10283 buffer.edit(
10284 deletion_ranges
10285 .into_iter()
10286 .map(|range| (range, empty_str.clone())),
10287 None,
10288 cx,
10289 );
10290 });
10291 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10292 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10293 });
10294 }
10295
10296 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10297 if self.read_only(cx) {
10298 return;
10299 }
10300 if self.mode.is_single_line() {
10301 cx.propagate();
10302 return;
10303 }
10304
10305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10306 let selections = self
10307 .selections
10308 .all::<usize>(&self.display_snapshot(cx))
10309 .into_iter()
10310 .map(|s| s.range());
10311
10312 self.transact(window, cx, |this, window, cx| {
10313 this.buffer.update(cx, |buffer, cx| {
10314 buffer.autoindent_ranges(selections, cx);
10315 });
10316 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10317 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10318 });
10319 }
10320
10321 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10322 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10323 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10324 let selections = self.selections.all::<Point>(&display_map);
10325
10326 let mut new_cursors = Vec::new();
10327 let mut edit_ranges = Vec::new();
10328 let mut selections = selections.iter().peekable();
10329 while let Some(selection) = selections.next() {
10330 let mut rows = selection.spanned_rows(false, &display_map);
10331
10332 // Accumulate contiguous regions of rows that we want to delete.
10333 while let Some(next_selection) = selections.peek() {
10334 let next_rows = next_selection.spanned_rows(false, &display_map);
10335 if next_rows.start <= rows.end {
10336 rows.end = next_rows.end;
10337 selections.next().unwrap();
10338 } else {
10339 break;
10340 }
10341 }
10342
10343 let buffer = display_map.buffer_snapshot();
10344 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10345 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10346 // If there's a line after the range, delete the \n from the end of the row range
10347 (
10348 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10349 rows.end,
10350 )
10351 } else {
10352 // If there isn't a line after the range, delete the \n from the line before the
10353 // start of the row range
10354 edit_start = edit_start.saturating_sub(1);
10355 (buffer.len(), rows.start.previous_row())
10356 };
10357
10358 let text_layout_details = self.text_layout_details(window);
10359 let x = display_map.x_for_display_point(
10360 selection.head().to_display_point(&display_map),
10361 &text_layout_details,
10362 );
10363 let row = Point::new(target_row.0, 0)
10364 .to_display_point(&display_map)
10365 .row();
10366 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10367
10368 new_cursors.push((
10369 selection.id,
10370 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10371 SelectionGoal::None,
10372 ));
10373 edit_ranges.push(edit_start..edit_end);
10374 }
10375
10376 self.transact(window, cx, |this, window, cx| {
10377 let buffer = this.buffer.update(cx, |buffer, cx| {
10378 let empty_str: Arc<str> = Arc::default();
10379 buffer.edit(
10380 edit_ranges
10381 .into_iter()
10382 .map(|range| (range, empty_str.clone())),
10383 None,
10384 cx,
10385 );
10386 buffer.snapshot(cx)
10387 });
10388 let new_selections = new_cursors
10389 .into_iter()
10390 .map(|(id, cursor, goal)| {
10391 let cursor = cursor.to_point(&buffer);
10392 Selection {
10393 id,
10394 start: cursor,
10395 end: cursor,
10396 reversed: false,
10397 goal,
10398 }
10399 })
10400 .collect();
10401
10402 this.change_selections(Default::default(), window, cx, |s| {
10403 s.select(new_selections);
10404 });
10405 });
10406 }
10407
10408 pub fn join_lines_impl(
10409 &mut self,
10410 insert_whitespace: bool,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 if self.read_only(cx) {
10415 return;
10416 }
10417 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10418 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10419 let start = MultiBufferRow(selection.start.row);
10420 // Treat single line selections as if they include the next line. Otherwise this action
10421 // would do nothing for single line selections individual cursors.
10422 let end = if selection.start.row == selection.end.row {
10423 MultiBufferRow(selection.start.row + 1)
10424 } else {
10425 MultiBufferRow(selection.end.row)
10426 };
10427
10428 if let Some(last_row_range) = row_ranges.last_mut()
10429 && start <= last_row_range.end
10430 {
10431 last_row_range.end = end;
10432 continue;
10433 }
10434 row_ranges.push(start..end);
10435 }
10436
10437 let snapshot = self.buffer.read(cx).snapshot(cx);
10438 let mut cursor_positions = Vec::new();
10439 for row_range in &row_ranges {
10440 let anchor = snapshot.anchor_before(Point::new(
10441 row_range.end.previous_row().0,
10442 snapshot.line_len(row_range.end.previous_row()),
10443 ));
10444 cursor_positions.push(anchor..anchor);
10445 }
10446
10447 self.transact(window, cx, |this, window, cx| {
10448 for row_range in row_ranges.into_iter().rev() {
10449 for row in row_range.iter_rows().rev() {
10450 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10451 let next_line_row = row.next_row();
10452 let indent = snapshot.indent_size_for_line(next_line_row);
10453 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10454
10455 let replace =
10456 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10457 " "
10458 } else {
10459 ""
10460 };
10461
10462 this.buffer.update(cx, |buffer, cx| {
10463 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10464 });
10465 }
10466 }
10467
10468 this.change_selections(Default::default(), window, cx, |s| {
10469 s.select_anchor_ranges(cursor_positions)
10470 });
10471 });
10472 }
10473
10474 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10476 self.join_lines_impl(true, window, cx);
10477 }
10478
10479 pub fn sort_lines_case_sensitive(
10480 &mut self,
10481 _: &SortLinesCaseSensitive,
10482 window: &mut Window,
10483 cx: &mut Context<Self>,
10484 ) {
10485 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10486 }
10487
10488 pub fn sort_lines_by_length(
10489 &mut self,
10490 _: &SortLinesByLength,
10491 window: &mut Window,
10492 cx: &mut Context<Self>,
10493 ) {
10494 self.manipulate_immutable_lines(window, cx, |lines| {
10495 lines.sort_by_key(|&line| line.chars().count())
10496 })
10497 }
10498
10499 pub fn sort_lines_case_insensitive(
10500 &mut self,
10501 _: &SortLinesCaseInsensitive,
10502 window: &mut Window,
10503 cx: &mut Context<Self>,
10504 ) {
10505 self.manipulate_immutable_lines(window, cx, |lines| {
10506 lines.sort_by_key(|line| line.to_lowercase())
10507 })
10508 }
10509
10510 pub fn unique_lines_case_insensitive(
10511 &mut self,
10512 _: &UniqueLinesCaseInsensitive,
10513 window: &mut Window,
10514 cx: &mut Context<Self>,
10515 ) {
10516 self.manipulate_immutable_lines(window, cx, |lines| {
10517 let mut seen = HashSet::default();
10518 lines.retain(|line| seen.insert(line.to_lowercase()));
10519 })
10520 }
10521
10522 pub fn unique_lines_case_sensitive(
10523 &mut self,
10524 _: &UniqueLinesCaseSensitive,
10525 window: &mut Window,
10526 cx: &mut Context<Self>,
10527 ) {
10528 self.manipulate_immutable_lines(window, cx, |lines| {
10529 let mut seen = HashSet::default();
10530 lines.retain(|line| seen.insert(*line));
10531 })
10532 }
10533
10534 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10535 let snapshot = self.buffer.read(cx).snapshot(cx);
10536 for selection in self.selections.disjoint_anchors_arc().iter() {
10537 if snapshot
10538 .language_at(selection.start)
10539 .and_then(|lang| lang.config().wrap_characters.as_ref())
10540 .is_some()
10541 {
10542 return true;
10543 }
10544 }
10545 false
10546 }
10547
10548 fn wrap_selections_in_tag(
10549 &mut self,
10550 _: &WrapSelectionsInTag,
10551 window: &mut Window,
10552 cx: &mut Context<Self>,
10553 ) {
10554 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10555
10556 let snapshot = self.buffer.read(cx).snapshot(cx);
10557
10558 let mut edits = Vec::new();
10559 let mut boundaries = Vec::new();
10560
10561 for selection in self
10562 .selections
10563 .all_adjusted(&self.display_snapshot(cx))
10564 .iter()
10565 {
10566 let Some(wrap_config) = snapshot
10567 .language_at(selection.start)
10568 .and_then(|lang| lang.config().wrap_characters.clone())
10569 else {
10570 continue;
10571 };
10572
10573 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10574 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10575
10576 let start_before = snapshot.anchor_before(selection.start);
10577 let end_after = snapshot.anchor_after(selection.end);
10578
10579 edits.push((start_before..start_before, open_tag));
10580 edits.push((end_after..end_after, close_tag));
10581
10582 boundaries.push((
10583 start_before,
10584 end_after,
10585 wrap_config.start_prefix.len(),
10586 wrap_config.end_suffix.len(),
10587 ));
10588 }
10589
10590 if edits.is_empty() {
10591 return;
10592 }
10593
10594 self.transact(window, cx, |this, window, cx| {
10595 let buffer = this.buffer.update(cx, |buffer, cx| {
10596 buffer.edit(edits, None, cx);
10597 buffer.snapshot(cx)
10598 });
10599
10600 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10601 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10602 boundaries.into_iter()
10603 {
10604 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10605 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10606 new_selections.push(open_offset..open_offset);
10607 new_selections.push(close_offset..close_offset);
10608 }
10609
10610 this.change_selections(Default::default(), window, cx, |s| {
10611 s.select_ranges(new_selections);
10612 });
10613
10614 this.request_autoscroll(Autoscroll::fit(), cx);
10615 });
10616 }
10617
10618 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10619 let Some(project) = self.project.clone() else {
10620 return;
10621 };
10622 self.reload(project, window, cx)
10623 .detach_and_notify_err(window, cx);
10624 }
10625
10626 pub fn restore_file(
10627 &mut self,
10628 _: &::git::RestoreFile,
10629 window: &mut Window,
10630 cx: &mut Context<Self>,
10631 ) {
10632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10633 let mut buffer_ids = HashSet::default();
10634 let snapshot = self.buffer().read(cx).snapshot(cx);
10635 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10636 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10637 }
10638
10639 let buffer = self.buffer().read(cx);
10640 let ranges = buffer_ids
10641 .into_iter()
10642 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10643 .collect::<Vec<_>>();
10644
10645 self.restore_hunks_in_ranges(ranges, window, cx);
10646 }
10647
10648 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10650 let selections = self
10651 .selections
10652 .all(&self.display_snapshot(cx))
10653 .into_iter()
10654 .map(|s| s.range())
10655 .collect();
10656 self.restore_hunks_in_ranges(selections, window, cx);
10657 }
10658
10659 pub fn restore_hunks_in_ranges(
10660 &mut self,
10661 ranges: Vec<Range<Point>>,
10662 window: &mut Window,
10663 cx: &mut Context<Editor>,
10664 ) {
10665 let mut revert_changes = HashMap::default();
10666 let chunk_by = self
10667 .snapshot(window, cx)
10668 .hunks_for_ranges(ranges)
10669 .into_iter()
10670 .chunk_by(|hunk| hunk.buffer_id);
10671 for (buffer_id, hunks) in &chunk_by {
10672 let hunks = hunks.collect::<Vec<_>>();
10673 for hunk in &hunks {
10674 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10675 }
10676 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10677 }
10678 drop(chunk_by);
10679 if !revert_changes.is_empty() {
10680 self.transact(window, cx, |editor, window, cx| {
10681 editor.restore(revert_changes, window, cx);
10682 });
10683 }
10684 }
10685
10686 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10687 if let Some(status) = self
10688 .addons
10689 .iter()
10690 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10691 {
10692 return Some(status);
10693 }
10694 self.project
10695 .as_ref()?
10696 .read(cx)
10697 .status_for_buffer_id(buffer_id, cx)
10698 }
10699
10700 pub fn open_active_item_in_terminal(
10701 &mut self,
10702 _: &OpenInTerminal,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10707 let project_path = buffer.read(cx).project_path(cx)?;
10708 let project = self.project()?.read(cx);
10709 let entry = project.entry_for_path(&project_path, cx)?;
10710 let parent = match &entry.canonical_path {
10711 Some(canonical_path) => canonical_path.to_path_buf(),
10712 None => project.absolute_path(&project_path, cx)?,
10713 }
10714 .parent()?
10715 .to_path_buf();
10716 Some(parent)
10717 }) {
10718 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10719 }
10720 }
10721
10722 fn set_breakpoint_context_menu(
10723 &mut self,
10724 display_row: DisplayRow,
10725 position: Option<Anchor>,
10726 clicked_point: gpui::Point<Pixels>,
10727 window: &mut Window,
10728 cx: &mut Context<Self>,
10729 ) {
10730 let source = self
10731 .buffer
10732 .read(cx)
10733 .snapshot(cx)
10734 .anchor_before(Point::new(display_row.0, 0u32));
10735
10736 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10737
10738 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10739 self,
10740 source,
10741 clicked_point,
10742 context_menu,
10743 window,
10744 cx,
10745 );
10746 }
10747
10748 fn add_edit_breakpoint_block(
10749 &mut self,
10750 anchor: Anchor,
10751 breakpoint: &Breakpoint,
10752 edit_action: BreakpointPromptEditAction,
10753 window: &mut Window,
10754 cx: &mut Context<Self>,
10755 ) {
10756 let weak_editor = cx.weak_entity();
10757 let bp_prompt = cx.new(|cx| {
10758 BreakpointPromptEditor::new(
10759 weak_editor,
10760 anchor,
10761 breakpoint.clone(),
10762 edit_action,
10763 window,
10764 cx,
10765 )
10766 });
10767
10768 let height = bp_prompt.update(cx, |this, cx| {
10769 this.prompt
10770 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10771 });
10772 let cloned_prompt = bp_prompt.clone();
10773 let blocks = vec![BlockProperties {
10774 style: BlockStyle::Sticky,
10775 placement: BlockPlacement::Above(anchor),
10776 height: Some(height),
10777 render: Arc::new(move |cx| {
10778 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10779 cloned_prompt.clone().into_any_element()
10780 }),
10781 priority: 0,
10782 }];
10783
10784 let focus_handle = bp_prompt.focus_handle(cx);
10785 window.focus(&focus_handle);
10786
10787 let block_ids = self.insert_blocks(blocks, None, cx);
10788 bp_prompt.update(cx, |prompt, _| {
10789 prompt.add_block_ids(block_ids);
10790 });
10791 }
10792
10793 pub(crate) fn breakpoint_at_row(
10794 &self,
10795 row: u32,
10796 window: &mut Window,
10797 cx: &mut Context<Self>,
10798 ) -> Option<(Anchor, Breakpoint)> {
10799 let snapshot = self.snapshot(window, cx);
10800 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10801
10802 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10803 }
10804
10805 pub(crate) fn breakpoint_at_anchor(
10806 &self,
10807 breakpoint_position: Anchor,
10808 snapshot: &EditorSnapshot,
10809 cx: &mut Context<Self>,
10810 ) -> Option<(Anchor, Breakpoint)> {
10811 let buffer = self
10812 .buffer
10813 .read(cx)
10814 .buffer_for_anchor(breakpoint_position, cx)?;
10815
10816 let enclosing_excerpt = breakpoint_position.excerpt_id;
10817 let buffer_snapshot = buffer.read(cx).snapshot();
10818
10819 let row = buffer_snapshot
10820 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10821 .row;
10822
10823 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10824 let anchor_end = snapshot
10825 .buffer_snapshot()
10826 .anchor_after(Point::new(row, line_len));
10827
10828 self.breakpoint_store
10829 .as_ref()?
10830 .read_with(cx, |breakpoint_store, cx| {
10831 breakpoint_store
10832 .breakpoints(
10833 &buffer,
10834 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10835 &buffer_snapshot,
10836 cx,
10837 )
10838 .next()
10839 .and_then(|(bp, _)| {
10840 let breakpoint_row = buffer_snapshot
10841 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10842 .row;
10843
10844 if breakpoint_row == row {
10845 snapshot
10846 .buffer_snapshot()
10847 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10848 .map(|position| (position, bp.bp.clone()))
10849 } else {
10850 None
10851 }
10852 })
10853 })
10854 }
10855
10856 pub fn edit_log_breakpoint(
10857 &mut self,
10858 _: &EditLogBreakpoint,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10863 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10864 message: None,
10865 state: BreakpointState::Enabled,
10866 condition: None,
10867 hit_condition: None,
10868 });
10869
10870 self.add_edit_breakpoint_block(
10871 anchor,
10872 &breakpoint,
10873 BreakpointPromptEditAction::Log,
10874 window,
10875 cx,
10876 );
10877 }
10878 }
10879
10880 fn breakpoints_at_cursors(
10881 &self,
10882 window: &mut Window,
10883 cx: &mut Context<Self>,
10884 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10885 let snapshot = self.snapshot(window, cx);
10886 let cursors = self
10887 .selections
10888 .disjoint_anchors_arc()
10889 .iter()
10890 .map(|selection| {
10891 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10892
10893 let breakpoint_position = self
10894 .breakpoint_at_row(cursor_position.row, window, cx)
10895 .map(|bp| bp.0)
10896 .unwrap_or_else(|| {
10897 snapshot
10898 .display_snapshot
10899 .buffer_snapshot()
10900 .anchor_after(Point::new(cursor_position.row, 0))
10901 });
10902
10903 let breakpoint = self
10904 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10905 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10906
10907 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10908 })
10909 // 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.
10910 .collect::<HashMap<Anchor, _>>();
10911
10912 cursors.into_iter().collect()
10913 }
10914
10915 pub fn enable_breakpoint(
10916 &mut self,
10917 _: &crate::actions::EnableBreakpoint,
10918 window: &mut Window,
10919 cx: &mut Context<Self>,
10920 ) {
10921 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10922 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10923 continue;
10924 };
10925 self.edit_breakpoint_at_anchor(
10926 anchor,
10927 breakpoint,
10928 BreakpointEditAction::InvertState,
10929 cx,
10930 );
10931 }
10932 }
10933
10934 pub fn disable_breakpoint(
10935 &mut self,
10936 _: &crate::actions::DisableBreakpoint,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10941 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10942 continue;
10943 };
10944 self.edit_breakpoint_at_anchor(
10945 anchor,
10946 breakpoint,
10947 BreakpointEditAction::InvertState,
10948 cx,
10949 );
10950 }
10951 }
10952
10953 pub fn toggle_breakpoint(
10954 &mut self,
10955 _: &crate::actions::ToggleBreakpoint,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) {
10959 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10960 if let Some(breakpoint) = breakpoint {
10961 self.edit_breakpoint_at_anchor(
10962 anchor,
10963 breakpoint,
10964 BreakpointEditAction::Toggle,
10965 cx,
10966 );
10967 } else {
10968 self.edit_breakpoint_at_anchor(
10969 anchor,
10970 Breakpoint::new_standard(),
10971 BreakpointEditAction::Toggle,
10972 cx,
10973 );
10974 }
10975 }
10976 }
10977
10978 pub fn edit_breakpoint_at_anchor(
10979 &mut self,
10980 breakpoint_position: Anchor,
10981 breakpoint: Breakpoint,
10982 edit_action: BreakpointEditAction,
10983 cx: &mut Context<Self>,
10984 ) {
10985 let Some(breakpoint_store) = &self.breakpoint_store else {
10986 return;
10987 };
10988
10989 let Some(buffer) = self
10990 .buffer
10991 .read(cx)
10992 .buffer_for_anchor(breakpoint_position, cx)
10993 else {
10994 return;
10995 };
10996
10997 breakpoint_store.update(cx, |breakpoint_store, cx| {
10998 breakpoint_store.toggle_breakpoint(
10999 buffer,
11000 BreakpointWithPosition {
11001 position: breakpoint_position.text_anchor,
11002 bp: breakpoint,
11003 },
11004 edit_action,
11005 cx,
11006 );
11007 });
11008
11009 cx.notify();
11010 }
11011
11012 #[cfg(any(test, feature = "test-support"))]
11013 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11014 self.breakpoint_store.clone()
11015 }
11016
11017 pub fn prepare_restore_change(
11018 &self,
11019 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11020 hunk: &MultiBufferDiffHunk,
11021 cx: &mut App,
11022 ) -> Option<()> {
11023 if hunk.is_created_file() {
11024 return None;
11025 }
11026 let buffer = self.buffer.read(cx);
11027 let diff = buffer.diff_for(hunk.buffer_id)?;
11028 let buffer = buffer.buffer(hunk.buffer_id)?;
11029 let buffer = buffer.read(cx);
11030 let original_text = diff
11031 .read(cx)
11032 .base_text()
11033 .as_rope()
11034 .slice(hunk.diff_base_byte_range.clone());
11035 let buffer_snapshot = buffer.snapshot();
11036 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11037 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11038 probe
11039 .0
11040 .start
11041 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11042 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11043 }) {
11044 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11045 Some(())
11046 } else {
11047 None
11048 }
11049 }
11050
11051 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11052 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11053 }
11054
11055 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11056 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11057 }
11058
11059 fn manipulate_lines<M>(
11060 &mut self,
11061 window: &mut Window,
11062 cx: &mut Context<Self>,
11063 mut manipulate: M,
11064 ) where
11065 M: FnMut(&str) -> LineManipulationResult,
11066 {
11067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11068
11069 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11070 let buffer = self.buffer.read(cx).snapshot(cx);
11071
11072 let mut edits = Vec::new();
11073
11074 let selections = self.selections.all::<Point>(&display_map);
11075 let mut selections = selections.iter().peekable();
11076 let mut contiguous_row_selections = Vec::new();
11077 let mut new_selections = Vec::new();
11078 let mut added_lines = 0;
11079 let mut removed_lines = 0;
11080
11081 while let Some(selection) = selections.next() {
11082 let (start_row, end_row) = consume_contiguous_rows(
11083 &mut contiguous_row_selections,
11084 selection,
11085 &display_map,
11086 &mut selections,
11087 );
11088
11089 let start_point = Point::new(start_row.0, 0);
11090 let end_point = Point::new(
11091 end_row.previous_row().0,
11092 buffer.line_len(end_row.previous_row()),
11093 );
11094 let text = buffer
11095 .text_for_range(start_point..end_point)
11096 .collect::<String>();
11097
11098 let LineManipulationResult {
11099 new_text,
11100 line_count_before,
11101 line_count_after,
11102 } = manipulate(&text);
11103
11104 edits.push((start_point..end_point, new_text));
11105
11106 // Selections must change based on added and removed line count
11107 let start_row =
11108 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11109 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11110 new_selections.push(Selection {
11111 id: selection.id,
11112 start: start_row,
11113 end: end_row,
11114 goal: SelectionGoal::None,
11115 reversed: selection.reversed,
11116 });
11117
11118 if line_count_after > line_count_before {
11119 added_lines += line_count_after - line_count_before;
11120 } else if line_count_before > line_count_after {
11121 removed_lines += line_count_before - line_count_after;
11122 }
11123 }
11124
11125 self.transact(window, cx, |this, window, cx| {
11126 let buffer = this.buffer.update(cx, |buffer, cx| {
11127 buffer.edit(edits, None, cx);
11128 buffer.snapshot(cx)
11129 });
11130
11131 // Recalculate offsets on newly edited buffer
11132 let new_selections = new_selections
11133 .iter()
11134 .map(|s| {
11135 let start_point = Point::new(s.start.0, 0);
11136 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11137 Selection {
11138 id: s.id,
11139 start: buffer.point_to_offset(start_point),
11140 end: buffer.point_to_offset(end_point),
11141 goal: s.goal,
11142 reversed: s.reversed,
11143 }
11144 })
11145 .collect();
11146
11147 this.change_selections(Default::default(), window, cx, |s| {
11148 s.select(new_selections);
11149 });
11150
11151 this.request_autoscroll(Autoscroll::fit(), cx);
11152 });
11153 }
11154
11155 fn manipulate_immutable_lines<Fn>(
11156 &mut self,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 mut callback: Fn,
11160 ) where
11161 Fn: FnMut(&mut Vec<&str>),
11162 {
11163 self.manipulate_lines(window, cx, |text| {
11164 let mut lines: Vec<&str> = text.split('\n').collect();
11165 let line_count_before = lines.len();
11166
11167 callback(&mut lines);
11168
11169 LineManipulationResult {
11170 new_text: lines.join("\n"),
11171 line_count_before,
11172 line_count_after: lines.len(),
11173 }
11174 });
11175 }
11176
11177 fn manipulate_mutable_lines<Fn>(
11178 &mut self,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 mut callback: Fn,
11182 ) where
11183 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11184 {
11185 self.manipulate_lines(window, cx, |text| {
11186 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11187 let line_count_before = lines.len();
11188
11189 callback(&mut lines);
11190
11191 LineManipulationResult {
11192 new_text: lines.join("\n"),
11193 line_count_before,
11194 line_count_after: lines.len(),
11195 }
11196 });
11197 }
11198
11199 pub fn convert_indentation_to_spaces(
11200 &mut self,
11201 _: &ConvertIndentationToSpaces,
11202 window: &mut Window,
11203 cx: &mut Context<Self>,
11204 ) {
11205 let settings = self.buffer.read(cx).language_settings(cx);
11206 let tab_size = settings.tab_size.get() as usize;
11207
11208 self.manipulate_mutable_lines(window, cx, |lines| {
11209 // Allocates a reasonably sized scratch buffer once for the whole loop
11210 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11211 // Avoids recomputing spaces that could be inserted many times
11212 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11213 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11214 .collect();
11215
11216 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11217 let mut chars = line.as_ref().chars();
11218 let mut col = 0;
11219 let mut changed = false;
11220
11221 for ch in chars.by_ref() {
11222 match ch {
11223 ' ' => {
11224 reindented_line.push(' ');
11225 col += 1;
11226 }
11227 '\t' => {
11228 // \t are converted to spaces depending on the current column
11229 let spaces_len = tab_size - (col % tab_size);
11230 reindented_line.extend(&space_cache[spaces_len - 1]);
11231 col += spaces_len;
11232 changed = true;
11233 }
11234 _ => {
11235 // If we dont append before break, the character is consumed
11236 reindented_line.push(ch);
11237 break;
11238 }
11239 }
11240 }
11241
11242 if !changed {
11243 reindented_line.clear();
11244 continue;
11245 }
11246 // Append the rest of the line and replace old reference with new one
11247 reindented_line.extend(chars);
11248 *line = Cow::Owned(reindented_line.clone());
11249 reindented_line.clear();
11250 }
11251 });
11252 }
11253
11254 pub fn convert_indentation_to_tabs(
11255 &mut self,
11256 _: &ConvertIndentationToTabs,
11257 window: &mut Window,
11258 cx: &mut Context<Self>,
11259 ) {
11260 let settings = self.buffer.read(cx).language_settings(cx);
11261 let tab_size = settings.tab_size.get() as usize;
11262
11263 self.manipulate_mutable_lines(window, cx, |lines| {
11264 // Allocates a reasonably sized buffer once for the whole loop
11265 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11266 // Avoids recomputing spaces that could be inserted many times
11267 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11268 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11269 .collect();
11270
11271 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11272 let mut chars = line.chars();
11273 let mut spaces_count = 0;
11274 let mut first_non_indent_char = None;
11275 let mut changed = false;
11276
11277 for ch in chars.by_ref() {
11278 match ch {
11279 ' ' => {
11280 // Keep track of spaces. Append \t when we reach tab_size
11281 spaces_count += 1;
11282 changed = true;
11283 if spaces_count == tab_size {
11284 reindented_line.push('\t');
11285 spaces_count = 0;
11286 }
11287 }
11288 '\t' => {
11289 reindented_line.push('\t');
11290 spaces_count = 0;
11291 }
11292 _ => {
11293 // Dont append it yet, we might have remaining spaces
11294 first_non_indent_char = Some(ch);
11295 break;
11296 }
11297 }
11298 }
11299
11300 if !changed {
11301 reindented_line.clear();
11302 continue;
11303 }
11304 // Remaining spaces that didn't make a full tab stop
11305 if spaces_count > 0 {
11306 reindented_line.extend(&space_cache[spaces_count - 1]);
11307 }
11308 // If we consume an extra character that was not indentation, add it back
11309 if let Some(extra_char) = first_non_indent_char {
11310 reindented_line.push(extra_char);
11311 }
11312 // Append the rest of the line and replace old reference with new one
11313 reindented_line.extend(chars);
11314 *line = Cow::Owned(reindented_line.clone());
11315 reindented_line.clear();
11316 }
11317 });
11318 }
11319
11320 pub fn convert_to_upper_case(
11321 &mut self,
11322 _: &ConvertToUpperCase,
11323 window: &mut Window,
11324 cx: &mut Context<Self>,
11325 ) {
11326 self.manipulate_text(window, cx, |text| text.to_uppercase())
11327 }
11328
11329 pub fn convert_to_lower_case(
11330 &mut self,
11331 _: &ConvertToLowerCase,
11332 window: &mut Window,
11333 cx: &mut Context<Self>,
11334 ) {
11335 self.manipulate_text(window, cx, |text| text.to_lowercase())
11336 }
11337
11338 pub fn convert_to_title_case(
11339 &mut self,
11340 _: &ConvertToTitleCase,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 self.manipulate_text(window, cx, |text| {
11345 text.split('\n')
11346 .map(|line| line.to_case(Case::Title))
11347 .join("\n")
11348 })
11349 }
11350
11351 pub fn convert_to_snake_case(
11352 &mut self,
11353 _: &ConvertToSnakeCase,
11354 window: &mut Window,
11355 cx: &mut Context<Self>,
11356 ) {
11357 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11358 }
11359
11360 pub fn convert_to_kebab_case(
11361 &mut self,
11362 _: &ConvertToKebabCase,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11367 }
11368
11369 pub fn convert_to_upper_camel_case(
11370 &mut self,
11371 _: &ConvertToUpperCamelCase,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.manipulate_text(window, cx, |text| {
11376 text.split('\n')
11377 .map(|line| line.to_case(Case::UpperCamel))
11378 .join("\n")
11379 })
11380 }
11381
11382 pub fn convert_to_lower_camel_case(
11383 &mut self,
11384 _: &ConvertToLowerCamelCase,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11389 }
11390
11391 pub fn convert_to_opposite_case(
11392 &mut self,
11393 _: &ConvertToOppositeCase,
11394 window: &mut Window,
11395 cx: &mut Context<Self>,
11396 ) {
11397 self.manipulate_text(window, cx, |text| {
11398 text.chars()
11399 .fold(String::with_capacity(text.len()), |mut t, c| {
11400 if c.is_uppercase() {
11401 t.extend(c.to_lowercase());
11402 } else {
11403 t.extend(c.to_uppercase());
11404 }
11405 t
11406 })
11407 })
11408 }
11409
11410 pub fn convert_to_sentence_case(
11411 &mut self,
11412 _: &ConvertToSentenceCase,
11413 window: &mut Window,
11414 cx: &mut Context<Self>,
11415 ) {
11416 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11417 }
11418
11419 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11420 self.manipulate_text(window, cx, |text| {
11421 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11422 if has_upper_case_characters {
11423 text.to_lowercase()
11424 } else {
11425 text.to_uppercase()
11426 }
11427 })
11428 }
11429
11430 pub fn convert_to_rot13(
11431 &mut self,
11432 _: &ConvertToRot13,
11433 window: &mut Window,
11434 cx: &mut Context<Self>,
11435 ) {
11436 self.manipulate_text(window, cx, |text| {
11437 text.chars()
11438 .map(|c| match c {
11439 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11440 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11441 _ => c,
11442 })
11443 .collect()
11444 })
11445 }
11446
11447 pub fn convert_to_rot47(
11448 &mut self,
11449 _: &ConvertToRot47,
11450 window: &mut Window,
11451 cx: &mut Context<Self>,
11452 ) {
11453 self.manipulate_text(window, cx, |text| {
11454 text.chars()
11455 .map(|c| {
11456 let code_point = c as u32;
11457 if code_point >= 33 && code_point <= 126 {
11458 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11459 }
11460 c
11461 })
11462 .collect()
11463 })
11464 }
11465
11466 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11467 where
11468 Fn: FnMut(&str) -> String,
11469 {
11470 let buffer = self.buffer.read(cx).snapshot(cx);
11471
11472 let mut new_selections = Vec::new();
11473 let mut edits = Vec::new();
11474 let mut selection_adjustment = 0i32;
11475
11476 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11477 let selection_is_empty = selection.is_empty();
11478
11479 let (start, end) = if selection_is_empty {
11480 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11481 (word_range.start, word_range.end)
11482 } else {
11483 (
11484 buffer.point_to_offset(selection.start),
11485 buffer.point_to_offset(selection.end),
11486 )
11487 };
11488
11489 let text = buffer.text_for_range(start..end).collect::<String>();
11490 let old_length = text.len() as i32;
11491 let text = callback(&text);
11492
11493 new_selections.push(Selection {
11494 start: (start as i32 - selection_adjustment) as usize,
11495 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11496 goal: SelectionGoal::None,
11497 id: selection.id,
11498 reversed: selection.reversed,
11499 });
11500
11501 selection_adjustment += old_length - text.len() as i32;
11502
11503 edits.push((start..end, text));
11504 }
11505
11506 self.transact(window, cx, |this, window, cx| {
11507 this.buffer.update(cx, |buffer, cx| {
11508 buffer.edit(edits, None, cx);
11509 });
11510
11511 this.change_selections(Default::default(), window, cx, |s| {
11512 s.select(new_selections);
11513 });
11514
11515 this.request_autoscroll(Autoscroll::fit(), cx);
11516 });
11517 }
11518
11519 pub fn move_selection_on_drop(
11520 &mut self,
11521 selection: &Selection<Anchor>,
11522 target: DisplayPoint,
11523 is_cut: bool,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11528 let buffer = display_map.buffer_snapshot();
11529 let mut edits = Vec::new();
11530 let insert_point = display_map
11531 .clip_point(target, Bias::Left)
11532 .to_point(&display_map);
11533 let text = buffer
11534 .text_for_range(selection.start..selection.end)
11535 .collect::<String>();
11536 if is_cut {
11537 edits.push(((selection.start..selection.end), String::new()));
11538 }
11539 let insert_anchor = buffer.anchor_before(insert_point);
11540 edits.push(((insert_anchor..insert_anchor), text));
11541 let last_edit_start = insert_anchor.bias_left(buffer);
11542 let last_edit_end = insert_anchor.bias_right(buffer);
11543 self.transact(window, cx, |this, window, cx| {
11544 this.buffer.update(cx, |buffer, cx| {
11545 buffer.edit(edits, None, cx);
11546 });
11547 this.change_selections(Default::default(), window, cx, |s| {
11548 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11549 });
11550 });
11551 }
11552
11553 pub fn clear_selection_drag_state(&mut self) {
11554 self.selection_drag_state = SelectionDragState::None;
11555 }
11556
11557 pub fn duplicate(
11558 &mut self,
11559 upwards: bool,
11560 whole_lines: bool,
11561 window: &mut Window,
11562 cx: &mut Context<Self>,
11563 ) {
11564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11565
11566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11567 let buffer = display_map.buffer_snapshot();
11568 let selections = self.selections.all::<Point>(&display_map);
11569
11570 let mut edits = Vec::new();
11571 let mut selections_iter = selections.iter().peekable();
11572 while let Some(selection) = selections_iter.next() {
11573 let mut rows = selection.spanned_rows(false, &display_map);
11574 // duplicate line-wise
11575 if whole_lines || selection.start == selection.end {
11576 // Avoid duplicating the same lines twice.
11577 while let Some(next_selection) = selections_iter.peek() {
11578 let next_rows = next_selection.spanned_rows(false, &display_map);
11579 if next_rows.start < rows.end {
11580 rows.end = next_rows.end;
11581 selections_iter.next().unwrap();
11582 } else {
11583 break;
11584 }
11585 }
11586
11587 // Copy the text from the selected row region and splice it either at the start
11588 // or end of the region.
11589 let start = Point::new(rows.start.0, 0);
11590 let end = Point::new(
11591 rows.end.previous_row().0,
11592 buffer.line_len(rows.end.previous_row()),
11593 );
11594
11595 let mut text = buffer.text_for_range(start..end).collect::<String>();
11596
11597 let insert_location = if upwards {
11598 // When duplicating upward, we need to insert before the current line.
11599 // If we're on the last line and it doesn't end with a newline,
11600 // we need to add a newline before the duplicated content.
11601 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11602 && buffer.max_point().column > 0
11603 && !text.ends_with('\n');
11604
11605 if needs_leading_newline {
11606 text.insert(0, '\n');
11607 end
11608 } else {
11609 text.push('\n');
11610 Point::new(rows.start.0, 0)
11611 }
11612 } else {
11613 text.push('\n');
11614 start
11615 };
11616 edits.push((insert_location..insert_location, text));
11617 } else {
11618 // duplicate character-wise
11619 let start = selection.start;
11620 let end = selection.end;
11621 let text = buffer.text_for_range(start..end).collect::<String>();
11622 edits.push((selection.end..selection.end, text));
11623 }
11624 }
11625
11626 self.transact(window, cx, |this, window, cx| {
11627 this.buffer.update(cx, |buffer, cx| {
11628 buffer.edit(edits, None, cx);
11629 });
11630
11631 // When duplicating upward with whole lines, move the cursor to the duplicated line
11632 if upwards && whole_lines {
11633 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11634
11635 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11636 let mut new_ranges = Vec::new();
11637 let selections = s.all::<Point>(&display_map);
11638 let mut selections_iter = selections.iter().peekable();
11639
11640 while let Some(first_selection) = selections_iter.next() {
11641 // Group contiguous selections together to find the total row span
11642 let mut group_selections = vec![first_selection];
11643 let mut rows = first_selection.spanned_rows(false, &display_map);
11644
11645 while let Some(next_selection) = selections_iter.peek() {
11646 let next_rows = next_selection.spanned_rows(false, &display_map);
11647 if next_rows.start < rows.end {
11648 rows.end = next_rows.end;
11649 group_selections.push(selections_iter.next().unwrap());
11650 } else {
11651 break;
11652 }
11653 }
11654
11655 let row_count = rows.end.0 - rows.start.0;
11656
11657 // Move all selections in this group up by the total number of duplicated rows
11658 for selection in group_selections {
11659 let new_start = Point::new(
11660 selection.start.row.saturating_sub(row_count),
11661 selection.start.column,
11662 );
11663
11664 let new_end = Point::new(
11665 selection.end.row.saturating_sub(row_count),
11666 selection.end.column,
11667 );
11668
11669 new_ranges.push(new_start..new_end);
11670 }
11671 }
11672
11673 s.select_ranges(new_ranges);
11674 });
11675 }
11676
11677 this.request_autoscroll(Autoscroll::fit(), cx);
11678 });
11679 }
11680
11681 pub fn duplicate_line_up(
11682 &mut self,
11683 _: &DuplicateLineUp,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.duplicate(true, true, window, cx);
11688 }
11689
11690 pub fn duplicate_line_down(
11691 &mut self,
11692 _: &DuplicateLineDown,
11693 window: &mut Window,
11694 cx: &mut Context<Self>,
11695 ) {
11696 self.duplicate(false, true, window, cx);
11697 }
11698
11699 pub fn duplicate_selection(
11700 &mut self,
11701 _: &DuplicateSelection,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.duplicate(false, false, window, cx);
11706 }
11707
11708 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11710 if self.mode.is_single_line() {
11711 cx.propagate();
11712 return;
11713 }
11714
11715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11716 let buffer = self.buffer.read(cx).snapshot(cx);
11717
11718 let mut edits = Vec::new();
11719 let mut unfold_ranges = Vec::new();
11720 let mut refold_creases = Vec::new();
11721
11722 let selections = self.selections.all::<Point>(&display_map);
11723 let mut selections = selections.iter().peekable();
11724 let mut contiguous_row_selections = Vec::new();
11725 let mut new_selections = Vec::new();
11726
11727 while let Some(selection) = selections.next() {
11728 // Find all the selections that span a contiguous row range
11729 let (start_row, end_row) = consume_contiguous_rows(
11730 &mut contiguous_row_selections,
11731 selection,
11732 &display_map,
11733 &mut selections,
11734 );
11735
11736 // Move the text spanned by the row range to be before the line preceding the row range
11737 if start_row.0 > 0 {
11738 let range_to_move = Point::new(
11739 start_row.previous_row().0,
11740 buffer.line_len(start_row.previous_row()),
11741 )
11742 ..Point::new(
11743 end_row.previous_row().0,
11744 buffer.line_len(end_row.previous_row()),
11745 );
11746 let insertion_point = display_map
11747 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11748 .0;
11749
11750 // Don't move lines across excerpts
11751 if buffer
11752 .excerpt_containing(insertion_point..range_to_move.end)
11753 .is_some()
11754 {
11755 let text = buffer
11756 .text_for_range(range_to_move.clone())
11757 .flat_map(|s| s.chars())
11758 .skip(1)
11759 .chain(['\n'])
11760 .collect::<String>();
11761
11762 edits.push((
11763 buffer.anchor_after(range_to_move.start)
11764 ..buffer.anchor_before(range_to_move.end),
11765 String::new(),
11766 ));
11767 let insertion_anchor = buffer.anchor_after(insertion_point);
11768 edits.push((insertion_anchor..insertion_anchor, text));
11769
11770 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11771
11772 // Move selections up
11773 new_selections.extend(contiguous_row_selections.drain(..).map(
11774 |mut selection| {
11775 selection.start.row -= row_delta;
11776 selection.end.row -= row_delta;
11777 selection
11778 },
11779 ));
11780
11781 // Move folds up
11782 unfold_ranges.push(range_to_move.clone());
11783 for fold in display_map.folds_in_range(
11784 buffer.anchor_before(range_to_move.start)
11785 ..buffer.anchor_after(range_to_move.end),
11786 ) {
11787 let mut start = fold.range.start.to_point(&buffer);
11788 let mut end = fold.range.end.to_point(&buffer);
11789 start.row -= row_delta;
11790 end.row -= row_delta;
11791 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11792 }
11793 }
11794 }
11795
11796 // If we didn't move line(s), preserve the existing selections
11797 new_selections.append(&mut contiguous_row_selections);
11798 }
11799
11800 self.transact(window, cx, |this, window, cx| {
11801 this.unfold_ranges(&unfold_ranges, true, true, cx);
11802 this.buffer.update(cx, |buffer, cx| {
11803 for (range, text) in edits {
11804 buffer.edit([(range, text)], None, cx);
11805 }
11806 });
11807 this.fold_creases(refold_creases, true, window, cx);
11808 this.change_selections(Default::default(), window, cx, |s| {
11809 s.select(new_selections);
11810 })
11811 });
11812 }
11813
11814 pub fn move_line_down(
11815 &mut self,
11816 _: &MoveLineDown,
11817 window: &mut Window,
11818 cx: &mut Context<Self>,
11819 ) {
11820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11821 if self.mode.is_single_line() {
11822 cx.propagate();
11823 return;
11824 }
11825
11826 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11827 let buffer = self.buffer.read(cx).snapshot(cx);
11828
11829 let mut edits = Vec::new();
11830 let mut unfold_ranges = Vec::new();
11831 let mut refold_creases = Vec::new();
11832
11833 let selections = self.selections.all::<Point>(&display_map);
11834 let mut selections = selections.iter().peekable();
11835 let mut contiguous_row_selections = Vec::new();
11836 let mut new_selections = Vec::new();
11837
11838 while let Some(selection) = selections.next() {
11839 // Find all the selections that span a contiguous row range
11840 let (start_row, end_row) = consume_contiguous_rows(
11841 &mut contiguous_row_selections,
11842 selection,
11843 &display_map,
11844 &mut selections,
11845 );
11846
11847 // Move the text spanned by the row range to be after the last line of the row range
11848 if end_row.0 <= buffer.max_point().row {
11849 let range_to_move =
11850 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11851 let insertion_point = display_map
11852 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11853 .0;
11854
11855 // Don't move lines across excerpt boundaries
11856 if buffer
11857 .excerpt_containing(range_to_move.start..insertion_point)
11858 .is_some()
11859 {
11860 let mut text = String::from("\n");
11861 text.extend(buffer.text_for_range(range_to_move.clone()));
11862 text.pop(); // Drop trailing newline
11863 edits.push((
11864 buffer.anchor_after(range_to_move.start)
11865 ..buffer.anchor_before(range_to_move.end),
11866 String::new(),
11867 ));
11868 let insertion_anchor = buffer.anchor_after(insertion_point);
11869 edits.push((insertion_anchor..insertion_anchor, text));
11870
11871 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11872
11873 // Move selections down
11874 new_selections.extend(contiguous_row_selections.drain(..).map(
11875 |mut selection| {
11876 selection.start.row += row_delta;
11877 selection.end.row += row_delta;
11878 selection
11879 },
11880 ));
11881
11882 // Move folds down
11883 unfold_ranges.push(range_to_move.clone());
11884 for fold in display_map.folds_in_range(
11885 buffer.anchor_before(range_to_move.start)
11886 ..buffer.anchor_after(range_to_move.end),
11887 ) {
11888 let mut start = fold.range.start.to_point(&buffer);
11889 let mut end = fold.range.end.to_point(&buffer);
11890 start.row += row_delta;
11891 end.row += row_delta;
11892 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11893 }
11894 }
11895 }
11896
11897 // If we didn't move line(s), preserve the existing selections
11898 new_selections.append(&mut contiguous_row_selections);
11899 }
11900
11901 self.transact(window, cx, |this, window, cx| {
11902 this.unfold_ranges(&unfold_ranges, true, true, cx);
11903 this.buffer.update(cx, |buffer, cx| {
11904 for (range, text) in edits {
11905 buffer.edit([(range, text)], None, cx);
11906 }
11907 });
11908 this.fold_creases(refold_creases, true, window, cx);
11909 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11910 });
11911 }
11912
11913 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11915 let text_layout_details = &self.text_layout_details(window);
11916 self.transact(window, cx, |this, window, cx| {
11917 let edits = this.change_selections(Default::default(), window, cx, |s| {
11918 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11919 s.move_with(|display_map, selection| {
11920 if !selection.is_empty() {
11921 return;
11922 }
11923
11924 let mut head = selection.head();
11925 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11926 if head.column() == display_map.line_len(head.row()) {
11927 transpose_offset = display_map
11928 .buffer_snapshot()
11929 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11930 }
11931
11932 if transpose_offset == 0 {
11933 return;
11934 }
11935
11936 *head.column_mut() += 1;
11937 head = display_map.clip_point(head, Bias::Right);
11938 let goal = SelectionGoal::HorizontalPosition(
11939 display_map
11940 .x_for_display_point(head, text_layout_details)
11941 .into(),
11942 );
11943 selection.collapse_to(head, goal);
11944
11945 let transpose_start = display_map
11946 .buffer_snapshot()
11947 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11948 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11949 let transpose_end = display_map
11950 .buffer_snapshot()
11951 .clip_offset(transpose_offset + 1, Bias::Right);
11952 if let Some(ch) = display_map
11953 .buffer_snapshot()
11954 .chars_at(transpose_start)
11955 .next()
11956 {
11957 edits.push((transpose_start..transpose_offset, String::new()));
11958 edits.push((transpose_end..transpose_end, ch.to_string()));
11959 }
11960 }
11961 });
11962 edits
11963 });
11964 this.buffer
11965 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11966 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11967 this.change_selections(Default::default(), window, cx, |s| {
11968 s.select(selections);
11969 });
11970 });
11971 }
11972
11973 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11975 if self.mode.is_single_line() {
11976 cx.propagate();
11977 return;
11978 }
11979
11980 self.rewrap_impl(RewrapOptions::default(), cx)
11981 }
11982
11983 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11984 let buffer = self.buffer.read(cx).snapshot(cx);
11985 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11986
11987 #[derive(Clone, Debug, PartialEq)]
11988 enum CommentFormat {
11989 /// single line comment, with prefix for line
11990 Line(String),
11991 /// single line within a block comment, with prefix for line
11992 BlockLine(String),
11993 /// a single line of a block comment that includes the initial delimiter
11994 BlockCommentWithStart(BlockCommentConfig),
11995 /// a single line of a block comment that includes the ending delimiter
11996 BlockCommentWithEnd(BlockCommentConfig),
11997 }
11998
11999 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12000 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12001 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12002 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12003 .peekable();
12004
12005 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12006 row
12007 } else {
12008 return Vec::new();
12009 };
12010
12011 let language_settings = buffer.language_settings_at(selection.head(), cx);
12012 let language_scope = buffer.language_scope_at(selection.head());
12013
12014 let indent_and_prefix_for_row =
12015 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12016 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12017 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12018 &language_scope
12019 {
12020 let indent_end = Point::new(row, indent.len);
12021 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12022 let line_text_after_indent = buffer
12023 .text_for_range(indent_end..line_end)
12024 .collect::<String>();
12025
12026 let is_within_comment_override = buffer
12027 .language_scope_at(indent_end)
12028 .is_some_and(|scope| scope.override_name() == Some("comment"));
12029 let comment_delimiters = if is_within_comment_override {
12030 // we are within a comment syntax node, but we don't
12031 // yet know what kind of comment: block, doc or line
12032 match (
12033 language_scope.documentation_comment(),
12034 language_scope.block_comment(),
12035 ) {
12036 (Some(config), _) | (_, Some(config))
12037 if buffer.contains_str_at(indent_end, &config.start) =>
12038 {
12039 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12040 }
12041 (Some(config), _) | (_, Some(config))
12042 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12043 {
12044 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12045 }
12046 (Some(config), _) | (_, Some(config))
12047 if buffer.contains_str_at(indent_end, &config.prefix) =>
12048 {
12049 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12050 }
12051 (_, _) => language_scope
12052 .line_comment_prefixes()
12053 .iter()
12054 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12055 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12056 }
12057 } else {
12058 // we not in an overridden comment node, but we may
12059 // be within a non-overridden line comment node
12060 language_scope
12061 .line_comment_prefixes()
12062 .iter()
12063 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12064 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12065 };
12066
12067 let rewrap_prefix = language_scope
12068 .rewrap_prefixes()
12069 .iter()
12070 .find_map(|prefix_regex| {
12071 prefix_regex.find(&line_text_after_indent).map(|mat| {
12072 if mat.start() == 0 {
12073 Some(mat.as_str().to_string())
12074 } else {
12075 None
12076 }
12077 })
12078 })
12079 .flatten();
12080 (comment_delimiters, rewrap_prefix)
12081 } else {
12082 (None, None)
12083 };
12084 (indent, comment_prefix, rewrap_prefix)
12085 };
12086
12087 let mut ranges = Vec::new();
12088 let from_empty_selection = selection.is_empty();
12089
12090 let mut current_range_start = first_row;
12091 let mut prev_row = first_row;
12092 let (
12093 mut current_range_indent,
12094 mut current_range_comment_delimiters,
12095 mut current_range_rewrap_prefix,
12096 ) = indent_and_prefix_for_row(first_row);
12097
12098 for row in non_blank_rows_iter.skip(1) {
12099 let has_paragraph_break = row > prev_row + 1;
12100
12101 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12102 indent_and_prefix_for_row(row);
12103
12104 let has_indent_change = row_indent != current_range_indent;
12105 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12106
12107 let has_boundary_change = has_comment_change
12108 || row_rewrap_prefix.is_some()
12109 || (has_indent_change && current_range_comment_delimiters.is_some());
12110
12111 if has_paragraph_break || has_boundary_change {
12112 ranges.push((
12113 language_settings.clone(),
12114 Point::new(current_range_start, 0)
12115 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12116 current_range_indent,
12117 current_range_comment_delimiters.clone(),
12118 current_range_rewrap_prefix.clone(),
12119 from_empty_selection,
12120 ));
12121 current_range_start = row;
12122 current_range_indent = row_indent;
12123 current_range_comment_delimiters = row_comment_delimiters;
12124 current_range_rewrap_prefix = row_rewrap_prefix;
12125 }
12126 prev_row = row;
12127 }
12128
12129 ranges.push((
12130 language_settings.clone(),
12131 Point::new(current_range_start, 0)
12132 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12133 current_range_indent,
12134 current_range_comment_delimiters,
12135 current_range_rewrap_prefix,
12136 from_empty_selection,
12137 ));
12138
12139 ranges
12140 });
12141
12142 let mut edits = Vec::new();
12143 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12144
12145 for (
12146 language_settings,
12147 wrap_range,
12148 mut indent_size,
12149 comment_prefix,
12150 rewrap_prefix,
12151 from_empty_selection,
12152 ) in wrap_ranges
12153 {
12154 let mut start_row = wrap_range.start.row;
12155 let mut end_row = wrap_range.end.row;
12156
12157 // Skip selections that overlap with a range that has already been rewrapped.
12158 let selection_range = start_row..end_row;
12159 if rewrapped_row_ranges
12160 .iter()
12161 .any(|range| range.overlaps(&selection_range))
12162 {
12163 continue;
12164 }
12165
12166 let tab_size = language_settings.tab_size;
12167
12168 let (line_prefix, inside_comment) = match &comment_prefix {
12169 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12170 (Some(prefix.as_str()), true)
12171 }
12172 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12173 (Some(prefix.as_ref()), true)
12174 }
12175 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12176 start: _,
12177 end: _,
12178 prefix,
12179 tab_size,
12180 })) => {
12181 indent_size.len += tab_size;
12182 (Some(prefix.as_ref()), true)
12183 }
12184 None => (None, false),
12185 };
12186 let indent_prefix = indent_size.chars().collect::<String>();
12187 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12188
12189 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12190 RewrapBehavior::InComments => inside_comment,
12191 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12192 RewrapBehavior::Anywhere => true,
12193 };
12194
12195 let should_rewrap = options.override_language_settings
12196 || allow_rewrap_based_on_language
12197 || self.hard_wrap.is_some();
12198 if !should_rewrap {
12199 continue;
12200 }
12201
12202 if from_empty_selection {
12203 'expand_upwards: while start_row > 0 {
12204 let prev_row = start_row - 1;
12205 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12206 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12207 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12208 {
12209 start_row = prev_row;
12210 } else {
12211 break 'expand_upwards;
12212 }
12213 }
12214
12215 'expand_downwards: while end_row < buffer.max_point().row {
12216 let next_row = end_row + 1;
12217 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12218 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12219 && !buffer.is_line_blank(MultiBufferRow(next_row))
12220 {
12221 end_row = next_row;
12222 } else {
12223 break 'expand_downwards;
12224 }
12225 }
12226 }
12227
12228 let start = Point::new(start_row, 0);
12229 let start_offset = ToOffset::to_offset(&start, &buffer);
12230 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12231 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12232 let mut first_line_delimiter = None;
12233 let mut last_line_delimiter = None;
12234 let Some(lines_without_prefixes) = selection_text
12235 .lines()
12236 .enumerate()
12237 .map(|(ix, line)| {
12238 let line_trimmed = line.trim_start();
12239 if rewrap_prefix.is_some() && ix > 0 {
12240 Ok(line_trimmed)
12241 } else if let Some(
12242 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12243 start,
12244 prefix,
12245 end,
12246 tab_size,
12247 })
12248 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12249 start,
12250 prefix,
12251 end,
12252 tab_size,
12253 }),
12254 ) = &comment_prefix
12255 {
12256 let line_trimmed = line_trimmed
12257 .strip_prefix(start.as_ref())
12258 .map(|s| {
12259 let mut indent_size = indent_size;
12260 indent_size.len -= tab_size;
12261 let indent_prefix: String = indent_size.chars().collect();
12262 first_line_delimiter = Some((indent_prefix, start));
12263 s.trim_start()
12264 })
12265 .unwrap_or(line_trimmed);
12266 let line_trimmed = line_trimmed
12267 .strip_suffix(end.as_ref())
12268 .map(|s| {
12269 last_line_delimiter = Some(end);
12270 s.trim_end()
12271 })
12272 .unwrap_or(line_trimmed);
12273 let line_trimmed = line_trimmed
12274 .strip_prefix(prefix.as_ref())
12275 .unwrap_or(line_trimmed);
12276 Ok(line_trimmed)
12277 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12278 line_trimmed.strip_prefix(prefix).with_context(|| {
12279 format!("line did not start with prefix {prefix:?}: {line:?}")
12280 })
12281 } else {
12282 line_trimmed
12283 .strip_prefix(&line_prefix.trim_start())
12284 .with_context(|| {
12285 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12286 })
12287 }
12288 })
12289 .collect::<Result<Vec<_>, _>>()
12290 .log_err()
12291 else {
12292 continue;
12293 };
12294
12295 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12296 buffer
12297 .language_settings_at(Point::new(start_row, 0), cx)
12298 .preferred_line_length as usize
12299 });
12300
12301 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12302 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12303 } else {
12304 line_prefix.clone()
12305 };
12306
12307 let wrapped_text = {
12308 let mut wrapped_text = wrap_with_prefix(
12309 line_prefix,
12310 subsequent_lines_prefix,
12311 lines_without_prefixes.join("\n"),
12312 wrap_column,
12313 tab_size,
12314 options.preserve_existing_whitespace,
12315 );
12316
12317 if let Some((indent, delimiter)) = first_line_delimiter {
12318 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12319 }
12320 if let Some(last_line) = last_line_delimiter {
12321 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12322 }
12323
12324 wrapped_text
12325 };
12326
12327 // TODO: should always use char-based diff while still supporting cursor behavior that
12328 // matches vim.
12329 let mut diff_options = DiffOptions::default();
12330 if options.override_language_settings {
12331 diff_options.max_word_diff_len = 0;
12332 diff_options.max_word_diff_line_count = 0;
12333 } else {
12334 diff_options.max_word_diff_len = usize::MAX;
12335 diff_options.max_word_diff_line_count = usize::MAX;
12336 }
12337
12338 for (old_range, new_text) in
12339 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12340 {
12341 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12342 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12343 edits.push((edit_start..edit_end, new_text));
12344 }
12345
12346 rewrapped_row_ranges.push(start_row..=end_row);
12347 }
12348
12349 self.buffer
12350 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12351 }
12352
12353 pub fn cut_common(
12354 &mut self,
12355 cut_no_selection_line: bool,
12356 window: &mut Window,
12357 cx: &mut Context<Self>,
12358 ) -> ClipboardItem {
12359 let mut text = String::new();
12360 let buffer = self.buffer.read(cx).snapshot(cx);
12361 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12362 let mut clipboard_selections = Vec::with_capacity(selections.len());
12363 {
12364 let max_point = buffer.max_point();
12365 let mut is_first = true;
12366 for selection in &mut selections {
12367 let is_entire_line =
12368 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12369 if is_entire_line {
12370 selection.start = Point::new(selection.start.row, 0);
12371 if !selection.is_empty() && selection.end.column == 0 {
12372 selection.end = cmp::min(max_point, selection.end);
12373 } else {
12374 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12375 }
12376 selection.goal = SelectionGoal::None;
12377 }
12378 if is_first {
12379 is_first = false;
12380 } else {
12381 text += "\n";
12382 }
12383 let mut len = 0;
12384 for chunk in buffer.text_for_range(selection.start..selection.end) {
12385 text.push_str(chunk);
12386 len += chunk.len();
12387 }
12388 clipboard_selections.push(ClipboardSelection {
12389 len,
12390 is_entire_line,
12391 first_line_indent: buffer
12392 .indent_size_for_line(MultiBufferRow(selection.start.row))
12393 .len,
12394 });
12395 }
12396 }
12397
12398 self.transact(window, cx, |this, window, cx| {
12399 this.change_selections(Default::default(), window, cx, |s| {
12400 s.select(selections);
12401 });
12402 this.insert("", window, cx);
12403 });
12404 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12405 }
12406
12407 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12408 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12409 let item = self.cut_common(true, window, cx);
12410 cx.write_to_clipboard(item);
12411 }
12412
12413 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12415 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12416 s.move_with(|snapshot, sel| {
12417 if sel.is_empty() {
12418 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12419 }
12420 if sel.is_empty() {
12421 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12422 }
12423 });
12424 });
12425 let item = self.cut_common(false, window, cx);
12426 cx.set_global(KillRing(item))
12427 }
12428
12429 pub fn kill_ring_yank(
12430 &mut self,
12431 _: &KillRingYank,
12432 window: &mut Window,
12433 cx: &mut Context<Self>,
12434 ) {
12435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12436 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12437 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12438 (kill_ring.text().to_string(), kill_ring.metadata_json())
12439 } else {
12440 return;
12441 }
12442 } else {
12443 return;
12444 };
12445 self.do_paste(&text, metadata, false, window, cx);
12446 }
12447
12448 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12449 self.do_copy(true, cx);
12450 }
12451
12452 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12453 self.do_copy(false, cx);
12454 }
12455
12456 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12457 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12458 let buffer = self.buffer.read(cx).read(cx);
12459 let mut text = String::new();
12460
12461 let mut clipboard_selections = Vec::with_capacity(selections.len());
12462 {
12463 let max_point = buffer.max_point();
12464 let mut is_first = true;
12465 for selection in &selections {
12466 let mut start = selection.start;
12467 let mut end = selection.end;
12468 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12469 let mut add_trailing_newline = false;
12470 if is_entire_line {
12471 start = Point::new(start.row, 0);
12472 let next_line_start = Point::new(end.row + 1, 0);
12473 if next_line_start <= max_point {
12474 end = next_line_start;
12475 } else {
12476 // We're on the last line without a trailing newline.
12477 // Copy to the end of the line and add a newline afterwards.
12478 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12479 add_trailing_newline = true;
12480 }
12481 }
12482
12483 let mut trimmed_selections = Vec::new();
12484 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12485 let row = MultiBufferRow(start.row);
12486 let first_indent = buffer.indent_size_for_line(row);
12487 if first_indent.len == 0 || start.column > first_indent.len {
12488 trimmed_selections.push(start..end);
12489 } else {
12490 trimmed_selections.push(
12491 Point::new(row.0, first_indent.len)
12492 ..Point::new(row.0, buffer.line_len(row)),
12493 );
12494 for row in start.row + 1..=end.row {
12495 let mut line_len = buffer.line_len(MultiBufferRow(row));
12496 if row == end.row {
12497 line_len = end.column;
12498 }
12499 if line_len == 0 {
12500 trimmed_selections
12501 .push(Point::new(row, 0)..Point::new(row, line_len));
12502 continue;
12503 }
12504 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12505 if row_indent_size.len >= first_indent.len {
12506 trimmed_selections.push(
12507 Point::new(row, first_indent.len)..Point::new(row, line_len),
12508 );
12509 } else {
12510 trimmed_selections.clear();
12511 trimmed_selections.push(start..end);
12512 break;
12513 }
12514 }
12515 }
12516 } else {
12517 trimmed_selections.push(start..end);
12518 }
12519
12520 for trimmed_range in trimmed_selections {
12521 if is_first {
12522 is_first = false;
12523 } else {
12524 text += "\n";
12525 }
12526 let mut len = 0;
12527 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12528 text.push_str(chunk);
12529 len += chunk.len();
12530 }
12531 if add_trailing_newline {
12532 text.push('\n');
12533 len += 1;
12534 }
12535 clipboard_selections.push(ClipboardSelection {
12536 len,
12537 is_entire_line,
12538 first_line_indent: buffer
12539 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12540 .len,
12541 });
12542 }
12543 }
12544 }
12545
12546 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12547 text,
12548 clipboard_selections,
12549 ));
12550 }
12551
12552 pub fn do_paste(
12553 &mut self,
12554 text: &String,
12555 clipboard_selections: Option<Vec<ClipboardSelection>>,
12556 handle_entire_lines: bool,
12557 window: &mut Window,
12558 cx: &mut Context<Self>,
12559 ) {
12560 if self.read_only(cx) {
12561 return;
12562 }
12563
12564 let clipboard_text = Cow::Borrowed(text.as_str());
12565
12566 self.transact(window, cx, |this, window, cx| {
12567 let had_active_edit_prediction = this.has_active_edit_prediction();
12568 let display_map = this.display_snapshot(cx);
12569 let old_selections = this.selections.all::<usize>(&display_map);
12570 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12571
12572 if let Some(mut clipboard_selections) = clipboard_selections {
12573 let all_selections_were_entire_line =
12574 clipboard_selections.iter().all(|s| s.is_entire_line);
12575 let first_selection_indent_column =
12576 clipboard_selections.first().map(|s| s.first_line_indent);
12577 if clipboard_selections.len() != old_selections.len() {
12578 clipboard_selections.drain(..);
12579 }
12580 let mut auto_indent_on_paste = true;
12581
12582 this.buffer.update(cx, |buffer, cx| {
12583 let snapshot = buffer.read(cx);
12584 auto_indent_on_paste = snapshot
12585 .language_settings_at(cursor_offset, cx)
12586 .auto_indent_on_paste;
12587
12588 let mut start_offset = 0;
12589 let mut edits = Vec::new();
12590 let mut original_indent_columns = Vec::new();
12591 for (ix, selection) in old_selections.iter().enumerate() {
12592 let to_insert;
12593 let entire_line;
12594 let original_indent_column;
12595 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12596 let end_offset = start_offset + clipboard_selection.len;
12597 to_insert = &clipboard_text[start_offset..end_offset];
12598 entire_line = clipboard_selection.is_entire_line;
12599 start_offset = end_offset + 1;
12600 original_indent_column = Some(clipboard_selection.first_line_indent);
12601 } else {
12602 to_insert = &*clipboard_text;
12603 entire_line = all_selections_were_entire_line;
12604 original_indent_column = first_selection_indent_column
12605 }
12606
12607 let (range, to_insert) =
12608 if selection.is_empty() && handle_entire_lines && entire_line {
12609 // If the corresponding selection was empty when this slice of the
12610 // clipboard text was written, then the entire line containing the
12611 // selection was copied. If this selection is also currently empty,
12612 // then paste the line before the current line of the buffer.
12613 let column = selection.start.to_point(&snapshot).column as usize;
12614 let line_start = selection.start - column;
12615 (line_start..line_start, Cow::Borrowed(to_insert))
12616 } else {
12617 let language = snapshot.language_at(selection.head());
12618 let range = selection.range();
12619 if let Some(language) = language
12620 && language.name() == "Markdown".into()
12621 {
12622 edit_for_markdown_paste(
12623 &snapshot,
12624 range,
12625 to_insert,
12626 url::Url::parse(to_insert).ok(),
12627 )
12628 } else {
12629 (range, Cow::Borrowed(to_insert))
12630 }
12631 };
12632
12633 edits.push((range, to_insert));
12634 original_indent_columns.push(original_indent_column);
12635 }
12636 drop(snapshot);
12637
12638 buffer.edit(
12639 edits,
12640 if auto_indent_on_paste {
12641 Some(AutoindentMode::Block {
12642 original_indent_columns,
12643 })
12644 } else {
12645 None
12646 },
12647 cx,
12648 );
12649 });
12650
12651 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12652 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12653 } else {
12654 let url = url::Url::parse(&clipboard_text).ok();
12655
12656 let auto_indent_mode = if !clipboard_text.is_empty() {
12657 Some(AutoindentMode::Block {
12658 original_indent_columns: Vec::new(),
12659 })
12660 } else {
12661 None
12662 };
12663
12664 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12665 let snapshot = buffer.snapshot(cx);
12666
12667 let anchors = old_selections
12668 .iter()
12669 .map(|s| {
12670 let anchor = snapshot.anchor_after(s.head());
12671 s.map(|_| anchor)
12672 })
12673 .collect::<Vec<_>>();
12674
12675 let mut edits = Vec::new();
12676
12677 for selection in old_selections.iter() {
12678 let language = snapshot.language_at(selection.head());
12679 let range = selection.range();
12680
12681 let (edit_range, edit_text) = if let Some(language) = language
12682 && language.name() == "Markdown".into()
12683 {
12684 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12685 } else {
12686 (range, clipboard_text.clone())
12687 };
12688
12689 edits.push((edit_range, edit_text));
12690 }
12691
12692 drop(snapshot);
12693 buffer.edit(edits, auto_indent_mode, cx);
12694
12695 anchors
12696 });
12697
12698 this.change_selections(Default::default(), window, cx, |s| {
12699 s.select_anchors(selection_anchors);
12700 });
12701 }
12702
12703 let trigger_in_words =
12704 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12705
12706 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12707 });
12708 }
12709
12710 pub fn diff_clipboard_with_selection(
12711 &mut self,
12712 _: &DiffClipboardWithSelection,
12713 window: &mut Window,
12714 cx: &mut Context<Self>,
12715 ) {
12716 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12717
12718 if selections.is_empty() {
12719 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12720 return;
12721 };
12722
12723 let clipboard_text = match cx.read_from_clipboard() {
12724 Some(item) => match item.entries().first() {
12725 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12726 _ => None,
12727 },
12728 None => None,
12729 };
12730
12731 let Some(clipboard_text) = clipboard_text else {
12732 log::warn!("Clipboard doesn't contain text.");
12733 return;
12734 };
12735
12736 window.dispatch_action(
12737 Box::new(DiffClipboardWithSelectionData {
12738 clipboard_text,
12739 editor: cx.entity(),
12740 }),
12741 cx,
12742 );
12743 }
12744
12745 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12747 if let Some(item) = cx.read_from_clipboard() {
12748 let entries = item.entries();
12749
12750 match entries.first() {
12751 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12752 // of all the pasted entries.
12753 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12754 .do_paste(
12755 clipboard_string.text(),
12756 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12757 true,
12758 window,
12759 cx,
12760 ),
12761 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12762 }
12763 }
12764 }
12765
12766 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12767 if self.read_only(cx) {
12768 return;
12769 }
12770
12771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12772
12773 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12774 if let Some((selections, _)) =
12775 self.selection_history.transaction(transaction_id).cloned()
12776 {
12777 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12778 s.select_anchors(selections.to_vec());
12779 });
12780 } else {
12781 log::error!(
12782 "No entry in selection_history found for undo. \
12783 This may correspond to a bug where undo does not update the selection. \
12784 If this is occurring, please add details to \
12785 https://github.com/zed-industries/zed/issues/22692"
12786 );
12787 }
12788 self.request_autoscroll(Autoscroll::fit(), cx);
12789 self.unmark_text(window, cx);
12790 self.refresh_edit_prediction(true, false, window, cx);
12791 cx.emit(EditorEvent::Edited { transaction_id });
12792 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12793 }
12794 }
12795
12796 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12797 if self.read_only(cx) {
12798 return;
12799 }
12800
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12802
12803 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12804 if let Some((_, Some(selections))) =
12805 self.selection_history.transaction(transaction_id).cloned()
12806 {
12807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12808 s.select_anchors(selections.to_vec());
12809 });
12810 } else {
12811 log::error!(
12812 "No entry in selection_history found for redo. \
12813 This may correspond to a bug where undo does not update the selection. \
12814 If this is occurring, please add details to \
12815 https://github.com/zed-industries/zed/issues/22692"
12816 );
12817 }
12818 self.request_autoscroll(Autoscroll::fit(), cx);
12819 self.unmark_text(window, cx);
12820 self.refresh_edit_prediction(true, false, window, cx);
12821 cx.emit(EditorEvent::Edited { transaction_id });
12822 }
12823 }
12824
12825 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12826 self.buffer
12827 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12828 }
12829
12830 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12831 self.buffer
12832 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12833 }
12834
12835 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12837 self.change_selections(Default::default(), window, cx, |s| {
12838 s.move_with(|map, selection| {
12839 let cursor = if selection.is_empty() {
12840 movement::left(map, selection.start)
12841 } else {
12842 selection.start
12843 };
12844 selection.collapse_to(cursor, SelectionGoal::None);
12845 });
12846 })
12847 }
12848
12849 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12851 self.change_selections(Default::default(), window, cx, |s| {
12852 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12853 })
12854 }
12855
12856 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12858 self.change_selections(Default::default(), window, cx, |s| {
12859 s.move_with(|map, selection| {
12860 let cursor = if selection.is_empty() {
12861 movement::right(map, selection.end)
12862 } else {
12863 selection.end
12864 };
12865 selection.collapse_to(cursor, SelectionGoal::None)
12866 });
12867 })
12868 }
12869
12870 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12872 self.change_selections(Default::default(), window, cx, |s| {
12873 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12874 });
12875 }
12876
12877 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12878 if self.take_rename(true, window, cx).is_some() {
12879 return;
12880 }
12881
12882 if self.mode.is_single_line() {
12883 cx.propagate();
12884 return;
12885 }
12886
12887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12888
12889 let text_layout_details = &self.text_layout_details(window);
12890 let selection_count = self.selections.count();
12891 let first_selection = self.selections.first_anchor();
12892
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_with(|map, selection| {
12895 if !selection.is_empty() {
12896 selection.goal = SelectionGoal::None;
12897 }
12898 let (cursor, goal) = movement::up(
12899 map,
12900 selection.start,
12901 selection.goal,
12902 false,
12903 text_layout_details,
12904 );
12905 selection.collapse_to(cursor, goal);
12906 });
12907 });
12908
12909 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12910 {
12911 cx.propagate();
12912 }
12913 }
12914
12915 pub fn move_up_by_lines(
12916 &mut self,
12917 action: &MoveUpByLines,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 if self.take_rename(true, window, cx).is_some() {
12922 return;
12923 }
12924
12925 if self.mode.is_single_line() {
12926 cx.propagate();
12927 return;
12928 }
12929
12930 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12931
12932 let text_layout_details = &self.text_layout_details(window);
12933
12934 self.change_selections(Default::default(), window, cx, |s| {
12935 s.move_with(|map, selection| {
12936 if !selection.is_empty() {
12937 selection.goal = SelectionGoal::None;
12938 }
12939 let (cursor, goal) = movement::up_by_rows(
12940 map,
12941 selection.start,
12942 action.lines,
12943 selection.goal,
12944 false,
12945 text_layout_details,
12946 );
12947 selection.collapse_to(cursor, goal);
12948 });
12949 })
12950 }
12951
12952 pub fn move_down_by_lines(
12953 &mut self,
12954 action: &MoveDownByLines,
12955 window: &mut Window,
12956 cx: &mut Context<Self>,
12957 ) {
12958 if self.take_rename(true, window, cx).is_some() {
12959 return;
12960 }
12961
12962 if self.mode.is_single_line() {
12963 cx.propagate();
12964 return;
12965 }
12966
12967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12968
12969 let text_layout_details = &self.text_layout_details(window);
12970
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_with(|map, selection| {
12973 if !selection.is_empty() {
12974 selection.goal = SelectionGoal::None;
12975 }
12976 let (cursor, goal) = movement::down_by_rows(
12977 map,
12978 selection.start,
12979 action.lines,
12980 selection.goal,
12981 false,
12982 text_layout_details,
12983 );
12984 selection.collapse_to(cursor, goal);
12985 });
12986 })
12987 }
12988
12989 pub fn select_down_by_lines(
12990 &mut self,
12991 action: &SelectDownByLines,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12996 let text_layout_details = &self.text_layout_details(window);
12997 self.change_selections(Default::default(), window, cx, |s| {
12998 s.move_heads_with(|map, head, goal| {
12999 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13000 })
13001 })
13002 }
13003
13004 pub fn select_up_by_lines(
13005 &mut self,
13006 action: &SelectUpByLines,
13007 window: &mut Window,
13008 cx: &mut Context<Self>,
13009 ) {
13010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13011 let text_layout_details = &self.text_layout_details(window);
13012 self.change_selections(Default::default(), window, cx, |s| {
13013 s.move_heads_with(|map, head, goal| {
13014 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13015 })
13016 })
13017 }
13018
13019 pub fn select_page_up(
13020 &mut self,
13021 _: &SelectPageUp,
13022 window: &mut Window,
13023 cx: &mut Context<Self>,
13024 ) {
13025 let Some(row_count) = self.visible_row_count() else {
13026 return;
13027 };
13028
13029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13030
13031 let text_layout_details = &self.text_layout_details(window);
13032
13033 self.change_selections(Default::default(), window, cx, |s| {
13034 s.move_heads_with(|map, head, goal| {
13035 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13036 })
13037 })
13038 }
13039
13040 pub fn move_page_up(
13041 &mut self,
13042 action: &MovePageUp,
13043 window: &mut Window,
13044 cx: &mut Context<Self>,
13045 ) {
13046 if self.take_rename(true, window, cx).is_some() {
13047 return;
13048 }
13049
13050 if self
13051 .context_menu
13052 .borrow_mut()
13053 .as_mut()
13054 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13055 .unwrap_or(false)
13056 {
13057 return;
13058 }
13059
13060 if matches!(self.mode, EditorMode::SingleLine) {
13061 cx.propagate();
13062 return;
13063 }
13064
13065 let Some(row_count) = self.visible_row_count() else {
13066 return;
13067 };
13068
13069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13070
13071 let effects = if action.center_cursor {
13072 SelectionEffects::scroll(Autoscroll::center())
13073 } else {
13074 SelectionEffects::default()
13075 };
13076
13077 let text_layout_details = &self.text_layout_details(window);
13078
13079 self.change_selections(effects, window, cx, |s| {
13080 s.move_with(|map, selection| {
13081 if !selection.is_empty() {
13082 selection.goal = SelectionGoal::None;
13083 }
13084 let (cursor, goal) = movement::up_by_rows(
13085 map,
13086 selection.end,
13087 row_count,
13088 selection.goal,
13089 false,
13090 text_layout_details,
13091 );
13092 selection.collapse_to(cursor, goal);
13093 });
13094 });
13095 }
13096
13097 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13099 let text_layout_details = &self.text_layout_details(window);
13100 self.change_selections(Default::default(), window, cx, |s| {
13101 s.move_heads_with(|map, head, goal| {
13102 movement::up(map, head, goal, false, text_layout_details)
13103 })
13104 })
13105 }
13106
13107 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13108 self.take_rename(true, window, cx);
13109
13110 if self.mode.is_single_line() {
13111 cx.propagate();
13112 return;
13113 }
13114
13115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13116
13117 let text_layout_details = &self.text_layout_details(window);
13118 let selection_count = self.selections.count();
13119 let first_selection = self.selections.first_anchor();
13120
13121 self.change_selections(Default::default(), window, cx, |s| {
13122 s.move_with(|map, selection| {
13123 if !selection.is_empty() {
13124 selection.goal = SelectionGoal::None;
13125 }
13126 let (cursor, goal) = movement::down(
13127 map,
13128 selection.end,
13129 selection.goal,
13130 false,
13131 text_layout_details,
13132 );
13133 selection.collapse_to(cursor, goal);
13134 });
13135 });
13136
13137 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13138 {
13139 cx.propagate();
13140 }
13141 }
13142
13143 pub fn select_page_down(
13144 &mut self,
13145 _: &SelectPageDown,
13146 window: &mut Window,
13147 cx: &mut Context<Self>,
13148 ) {
13149 let Some(row_count) = self.visible_row_count() else {
13150 return;
13151 };
13152
13153 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13154
13155 let text_layout_details = &self.text_layout_details(window);
13156
13157 self.change_selections(Default::default(), window, cx, |s| {
13158 s.move_heads_with(|map, head, goal| {
13159 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13160 })
13161 })
13162 }
13163
13164 pub fn move_page_down(
13165 &mut self,
13166 action: &MovePageDown,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 if self.take_rename(true, window, cx).is_some() {
13171 return;
13172 }
13173
13174 if self
13175 .context_menu
13176 .borrow_mut()
13177 .as_mut()
13178 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13179 .unwrap_or(false)
13180 {
13181 return;
13182 }
13183
13184 if matches!(self.mode, EditorMode::SingleLine) {
13185 cx.propagate();
13186 return;
13187 }
13188
13189 let Some(row_count) = self.visible_row_count() else {
13190 return;
13191 };
13192
13193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13194
13195 let effects = if action.center_cursor {
13196 SelectionEffects::scroll(Autoscroll::center())
13197 } else {
13198 SelectionEffects::default()
13199 };
13200
13201 let text_layout_details = &self.text_layout_details(window);
13202 self.change_selections(effects, window, cx, |s| {
13203 s.move_with(|map, selection| {
13204 if !selection.is_empty() {
13205 selection.goal = SelectionGoal::None;
13206 }
13207 let (cursor, goal) = movement::down_by_rows(
13208 map,
13209 selection.end,
13210 row_count,
13211 selection.goal,
13212 false,
13213 text_layout_details,
13214 );
13215 selection.collapse_to(cursor, goal);
13216 });
13217 });
13218 }
13219
13220 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222 let text_layout_details = &self.text_layout_details(window);
13223 self.change_selections(Default::default(), window, cx, |s| {
13224 s.move_heads_with(|map, head, goal| {
13225 movement::down(map, head, goal, false, text_layout_details)
13226 })
13227 });
13228 }
13229
13230 pub fn context_menu_first(
13231 &mut self,
13232 _: &ContextMenuFirst,
13233 window: &mut Window,
13234 cx: &mut Context<Self>,
13235 ) {
13236 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13237 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13238 }
13239 }
13240
13241 pub fn context_menu_prev(
13242 &mut self,
13243 _: &ContextMenuPrevious,
13244 window: &mut Window,
13245 cx: &mut Context<Self>,
13246 ) {
13247 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13248 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13249 }
13250 }
13251
13252 pub fn context_menu_next(
13253 &mut self,
13254 _: &ContextMenuNext,
13255 window: &mut Window,
13256 cx: &mut Context<Self>,
13257 ) {
13258 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13259 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13260 }
13261 }
13262
13263 pub fn context_menu_last(
13264 &mut self,
13265 _: &ContextMenuLast,
13266 window: &mut Window,
13267 cx: &mut Context<Self>,
13268 ) {
13269 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13270 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13271 }
13272 }
13273
13274 pub fn signature_help_prev(
13275 &mut self,
13276 _: &SignatureHelpPrevious,
13277 _: &mut Window,
13278 cx: &mut Context<Self>,
13279 ) {
13280 if let Some(popover) = self.signature_help_state.popover_mut() {
13281 if popover.current_signature == 0 {
13282 popover.current_signature = popover.signatures.len() - 1;
13283 } else {
13284 popover.current_signature -= 1;
13285 }
13286 cx.notify();
13287 }
13288 }
13289
13290 pub fn signature_help_next(
13291 &mut self,
13292 _: &SignatureHelpNext,
13293 _: &mut Window,
13294 cx: &mut Context<Self>,
13295 ) {
13296 if let Some(popover) = self.signature_help_state.popover_mut() {
13297 if popover.current_signature + 1 == popover.signatures.len() {
13298 popover.current_signature = 0;
13299 } else {
13300 popover.current_signature += 1;
13301 }
13302 cx.notify();
13303 }
13304 }
13305
13306 pub fn move_to_previous_word_start(
13307 &mut self,
13308 _: &MoveToPreviousWordStart,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13313 self.change_selections(Default::default(), window, cx, |s| {
13314 s.move_cursors_with(|map, head, _| {
13315 (
13316 movement::previous_word_start(map, head),
13317 SelectionGoal::None,
13318 )
13319 });
13320 })
13321 }
13322
13323 pub fn move_to_previous_subword_start(
13324 &mut self,
13325 _: &MoveToPreviousSubwordStart,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13330 self.change_selections(Default::default(), window, cx, |s| {
13331 s.move_cursors_with(|map, head, _| {
13332 (
13333 movement::previous_subword_start(map, head),
13334 SelectionGoal::None,
13335 )
13336 });
13337 })
13338 }
13339
13340 pub fn select_to_previous_word_start(
13341 &mut self,
13342 _: &SelectToPreviousWordStart,
13343 window: &mut Window,
13344 cx: &mut Context<Self>,
13345 ) {
13346 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13347 self.change_selections(Default::default(), window, cx, |s| {
13348 s.move_heads_with(|map, head, _| {
13349 (
13350 movement::previous_word_start(map, head),
13351 SelectionGoal::None,
13352 )
13353 });
13354 })
13355 }
13356
13357 pub fn select_to_previous_subword_start(
13358 &mut self,
13359 _: &SelectToPreviousSubwordStart,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) {
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 self.change_selections(Default::default(), window, cx, |s| {
13365 s.move_heads_with(|map, head, _| {
13366 (
13367 movement::previous_subword_start(map, head),
13368 SelectionGoal::None,
13369 )
13370 });
13371 })
13372 }
13373
13374 pub fn delete_to_previous_word_start(
13375 &mut self,
13376 action: &DeleteToPreviousWordStart,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13381 self.transact(window, cx, |this, window, cx| {
13382 this.select_autoclose_pair(window, cx);
13383 this.change_selections(Default::default(), window, cx, |s| {
13384 s.move_with(|map, selection| {
13385 if selection.is_empty() {
13386 let mut cursor = if action.ignore_newlines {
13387 movement::previous_word_start(map, selection.head())
13388 } else {
13389 movement::previous_word_start_or_newline(map, selection.head())
13390 };
13391 cursor = movement::adjust_greedy_deletion(
13392 map,
13393 selection.head(),
13394 cursor,
13395 action.ignore_brackets,
13396 );
13397 selection.set_head(cursor, SelectionGoal::None);
13398 }
13399 });
13400 });
13401 this.insert("", window, cx);
13402 });
13403 }
13404
13405 pub fn delete_to_previous_subword_start(
13406 &mut self,
13407 _: &DeleteToPreviousSubwordStart,
13408 window: &mut Window,
13409 cx: &mut Context<Self>,
13410 ) {
13411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13412 self.transact(window, cx, |this, window, cx| {
13413 this.select_autoclose_pair(window, cx);
13414 this.change_selections(Default::default(), window, cx, |s| {
13415 s.move_with(|map, selection| {
13416 if selection.is_empty() {
13417 let mut cursor = movement::previous_subword_start(map, selection.head());
13418 cursor =
13419 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13420 selection.set_head(cursor, SelectionGoal::None);
13421 }
13422 });
13423 });
13424 this.insert("", window, cx);
13425 });
13426 }
13427
13428 pub fn move_to_next_word_end(
13429 &mut self,
13430 _: &MoveToNextWordEnd,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13435 self.change_selections(Default::default(), window, cx, |s| {
13436 s.move_cursors_with(|map, head, _| {
13437 (movement::next_word_end(map, head), SelectionGoal::None)
13438 });
13439 })
13440 }
13441
13442 pub fn move_to_next_subword_end(
13443 &mut self,
13444 _: &MoveToNextSubwordEnd,
13445 window: &mut Window,
13446 cx: &mut Context<Self>,
13447 ) {
13448 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13449 self.change_selections(Default::default(), window, cx, |s| {
13450 s.move_cursors_with(|map, head, _| {
13451 (movement::next_subword_end(map, head), SelectionGoal::None)
13452 });
13453 })
13454 }
13455
13456 pub fn select_to_next_word_end(
13457 &mut self,
13458 _: &SelectToNextWordEnd,
13459 window: &mut Window,
13460 cx: &mut Context<Self>,
13461 ) {
13462 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13463 self.change_selections(Default::default(), window, cx, |s| {
13464 s.move_heads_with(|map, head, _| {
13465 (movement::next_word_end(map, head), SelectionGoal::None)
13466 });
13467 })
13468 }
13469
13470 pub fn select_to_next_subword_end(
13471 &mut self,
13472 _: &SelectToNextSubwordEnd,
13473 window: &mut Window,
13474 cx: &mut Context<Self>,
13475 ) {
13476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13477 self.change_selections(Default::default(), window, cx, |s| {
13478 s.move_heads_with(|map, head, _| {
13479 (movement::next_subword_end(map, head), SelectionGoal::None)
13480 });
13481 })
13482 }
13483
13484 pub fn delete_to_next_word_end(
13485 &mut self,
13486 action: &DeleteToNextWordEnd,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13491 self.transact(window, cx, |this, window, cx| {
13492 this.change_selections(Default::default(), window, cx, |s| {
13493 s.move_with(|map, selection| {
13494 if selection.is_empty() {
13495 let mut cursor = if action.ignore_newlines {
13496 movement::next_word_end(map, selection.head())
13497 } else {
13498 movement::next_word_end_or_newline(map, selection.head())
13499 };
13500 cursor = movement::adjust_greedy_deletion(
13501 map,
13502 selection.head(),
13503 cursor,
13504 action.ignore_brackets,
13505 );
13506 selection.set_head(cursor, SelectionGoal::None);
13507 }
13508 });
13509 });
13510 this.insert("", window, cx);
13511 });
13512 }
13513
13514 pub fn delete_to_next_subword_end(
13515 &mut self,
13516 _: &DeleteToNextSubwordEnd,
13517 window: &mut Window,
13518 cx: &mut Context<Self>,
13519 ) {
13520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13521 self.transact(window, cx, |this, window, cx| {
13522 this.change_selections(Default::default(), window, cx, |s| {
13523 s.move_with(|map, selection| {
13524 if selection.is_empty() {
13525 let mut cursor = movement::next_subword_end(map, selection.head());
13526 cursor =
13527 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13528 selection.set_head(cursor, SelectionGoal::None);
13529 }
13530 });
13531 });
13532 this.insert("", window, cx);
13533 });
13534 }
13535
13536 pub fn move_to_beginning_of_line(
13537 &mut self,
13538 action: &MoveToBeginningOfLine,
13539 window: &mut Window,
13540 cx: &mut Context<Self>,
13541 ) {
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13543 self.change_selections(Default::default(), window, cx, |s| {
13544 s.move_cursors_with(|map, head, _| {
13545 (
13546 movement::indented_line_beginning(
13547 map,
13548 head,
13549 action.stop_at_soft_wraps,
13550 action.stop_at_indent,
13551 ),
13552 SelectionGoal::None,
13553 )
13554 });
13555 })
13556 }
13557
13558 pub fn select_to_beginning_of_line(
13559 &mut self,
13560 action: &SelectToBeginningOfLine,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13565 self.change_selections(Default::default(), window, cx, |s| {
13566 s.move_heads_with(|map, head, _| {
13567 (
13568 movement::indented_line_beginning(
13569 map,
13570 head,
13571 action.stop_at_soft_wraps,
13572 action.stop_at_indent,
13573 ),
13574 SelectionGoal::None,
13575 )
13576 });
13577 });
13578 }
13579
13580 pub fn delete_to_beginning_of_line(
13581 &mut self,
13582 action: &DeleteToBeginningOfLine,
13583 window: &mut Window,
13584 cx: &mut Context<Self>,
13585 ) {
13586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13587 self.transact(window, cx, |this, window, cx| {
13588 this.change_selections(Default::default(), window, cx, |s| {
13589 s.move_with(|_, selection| {
13590 selection.reversed = true;
13591 });
13592 });
13593
13594 this.select_to_beginning_of_line(
13595 &SelectToBeginningOfLine {
13596 stop_at_soft_wraps: false,
13597 stop_at_indent: action.stop_at_indent,
13598 },
13599 window,
13600 cx,
13601 );
13602 this.backspace(&Backspace, window, cx);
13603 });
13604 }
13605
13606 pub fn move_to_end_of_line(
13607 &mut self,
13608 action: &MoveToEndOfLine,
13609 window: &mut Window,
13610 cx: &mut Context<Self>,
13611 ) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13613 self.change_selections(Default::default(), window, cx, |s| {
13614 s.move_cursors_with(|map, head, _| {
13615 (
13616 movement::line_end(map, head, action.stop_at_soft_wraps),
13617 SelectionGoal::None,
13618 )
13619 });
13620 })
13621 }
13622
13623 pub fn select_to_end_of_line(
13624 &mut self,
13625 action: &SelectToEndOfLine,
13626 window: &mut Window,
13627 cx: &mut Context<Self>,
13628 ) {
13629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13630 self.change_selections(Default::default(), window, cx, |s| {
13631 s.move_heads_with(|map, head, _| {
13632 (
13633 movement::line_end(map, head, action.stop_at_soft_wraps),
13634 SelectionGoal::None,
13635 )
13636 });
13637 })
13638 }
13639
13640 pub fn delete_to_end_of_line(
13641 &mut self,
13642 _: &DeleteToEndOfLine,
13643 window: &mut Window,
13644 cx: &mut Context<Self>,
13645 ) {
13646 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13647 self.transact(window, cx, |this, window, cx| {
13648 this.select_to_end_of_line(
13649 &SelectToEndOfLine {
13650 stop_at_soft_wraps: false,
13651 },
13652 window,
13653 cx,
13654 );
13655 this.delete(&Delete, window, cx);
13656 });
13657 }
13658
13659 pub fn cut_to_end_of_line(
13660 &mut self,
13661 action: &CutToEndOfLine,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13666 self.transact(window, cx, |this, window, cx| {
13667 this.select_to_end_of_line(
13668 &SelectToEndOfLine {
13669 stop_at_soft_wraps: false,
13670 },
13671 window,
13672 cx,
13673 );
13674 if !action.stop_at_newlines {
13675 this.change_selections(Default::default(), window, cx, |s| {
13676 s.move_with(|_, sel| {
13677 if sel.is_empty() {
13678 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13679 }
13680 });
13681 });
13682 }
13683 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13684 let item = this.cut_common(false, window, cx);
13685 cx.write_to_clipboard(item);
13686 });
13687 }
13688
13689 pub fn move_to_start_of_paragraph(
13690 &mut self,
13691 _: &MoveToStartOfParagraph,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 if matches!(self.mode, EditorMode::SingleLine) {
13696 cx.propagate();
13697 return;
13698 }
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13700 self.change_selections(Default::default(), window, cx, |s| {
13701 s.move_with(|map, selection| {
13702 selection.collapse_to(
13703 movement::start_of_paragraph(map, selection.head(), 1),
13704 SelectionGoal::None,
13705 )
13706 });
13707 })
13708 }
13709
13710 pub fn move_to_end_of_paragraph(
13711 &mut self,
13712 _: &MoveToEndOfParagraph,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 if matches!(self.mode, EditorMode::SingleLine) {
13717 cx.propagate();
13718 return;
13719 }
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13721 self.change_selections(Default::default(), window, cx, |s| {
13722 s.move_with(|map, selection| {
13723 selection.collapse_to(
13724 movement::end_of_paragraph(map, selection.head(), 1),
13725 SelectionGoal::None,
13726 )
13727 });
13728 })
13729 }
13730
13731 pub fn select_to_start_of_paragraph(
13732 &mut self,
13733 _: &SelectToStartOfParagraph,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 if matches!(self.mode, EditorMode::SingleLine) {
13738 cx.propagate();
13739 return;
13740 }
13741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13742 self.change_selections(Default::default(), window, cx, |s| {
13743 s.move_heads_with(|map, head, _| {
13744 (
13745 movement::start_of_paragraph(map, head, 1),
13746 SelectionGoal::None,
13747 )
13748 });
13749 })
13750 }
13751
13752 pub fn select_to_end_of_paragraph(
13753 &mut self,
13754 _: &SelectToEndOfParagraph,
13755 window: &mut Window,
13756 cx: &mut Context<Self>,
13757 ) {
13758 if matches!(self.mode, EditorMode::SingleLine) {
13759 cx.propagate();
13760 return;
13761 }
13762 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13763 self.change_selections(Default::default(), window, cx, |s| {
13764 s.move_heads_with(|map, head, _| {
13765 (
13766 movement::end_of_paragraph(map, head, 1),
13767 SelectionGoal::None,
13768 )
13769 });
13770 })
13771 }
13772
13773 pub fn move_to_start_of_excerpt(
13774 &mut self,
13775 _: &MoveToStartOfExcerpt,
13776 window: &mut Window,
13777 cx: &mut Context<Self>,
13778 ) {
13779 if matches!(self.mode, EditorMode::SingleLine) {
13780 cx.propagate();
13781 return;
13782 }
13783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_with(|map, selection| {
13786 selection.collapse_to(
13787 movement::start_of_excerpt(
13788 map,
13789 selection.head(),
13790 workspace::searchable::Direction::Prev,
13791 ),
13792 SelectionGoal::None,
13793 )
13794 });
13795 })
13796 }
13797
13798 pub fn move_to_start_of_next_excerpt(
13799 &mut self,
13800 _: &MoveToStartOfNextExcerpt,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 if matches!(self.mode, EditorMode::SingleLine) {
13805 cx.propagate();
13806 return;
13807 }
13808
13809 self.change_selections(Default::default(), window, cx, |s| {
13810 s.move_with(|map, selection| {
13811 selection.collapse_to(
13812 movement::start_of_excerpt(
13813 map,
13814 selection.head(),
13815 workspace::searchable::Direction::Next,
13816 ),
13817 SelectionGoal::None,
13818 )
13819 });
13820 })
13821 }
13822
13823 pub fn move_to_end_of_excerpt(
13824 &mut self,
13825 _: &MoveToEndOfExcerpt,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 if matches!(self.mode, EditorMode::SingleLine) {
13830 cx.propagate();
13831 return;
13832 }
13833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13834 self.change_selections(Default::default(), window, cx, |s| {
13835 s.move_with(|map, selection| {
13836 selection.collapse_to(
13837 movement::end_of_excerpt(
13838 map,
13839 selection.head(),
13840 workspace::searchable::Direction::Next,
13841 ),
13842 SelectionGoal::None,
13843 )
13844 });
13845 })
13846 }
13847
13848 pub fn move_to_end_of_previous_excerpt(
13849 &mut self,
13850 _: &MoveToEndOfPreviousExcerpt,
13851 window: &mut Window,
13852 cx: &mut Context<Self>,
13853 ) {
13854 if matches!(self.mode, EditorMode::SingleLine) {
13855 cx.propagate();
13856 return;
13857 }
13858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13859 self.change_selections(Default::default(), window, cx, |s| {
13860 s.move_with(|map, selection| {
13861 selection.collapse_to(
13862 movement::end_of_excerpt(
13863 map,
13864 selection.head(),
13865 workspace::searchable::Direction::Prev,
13866 ),
13867 SelectionGoal::None,
13868 )
13869 });
13870 })
13871 }
13872
13873 pub fn select_to_start_of_excerpt(
13874 &mut self,
13875 _: &SelectToStartOfExcerpt,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) {
13879 if matches!(self.mode, EditorMode::SingleLine) {
13880 cx.propagate();
13881 return;
13882 }
13883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13884 self.change_selections(Default::default(), window, cx, |s| {
13885 s.move_heads_with(|map, head, _| {
13886 (
13887 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13888 SelectionGoal::None,
13889 )
13890 });
13891 })
13892 }
13893
13894 pub fn select_to_start_of_next_excerpt(
13895 &mut self,
13896 _: &SelectToStartOfNextExcerpt,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 if matches!(self.mode, EditorMode::SingleLine) {
13901 cx.propagate();
13902 return;
13903 }
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 self.change_selections(Default::default(), window, cx, |s| {
13906 s.move_heads_with(|map, head, _| {
13907 (
13908 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn select_to_end_of_excerpt(
13916 &mut self,
13917 _: &SelectToEndOfExcerpt,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 if matches!(self.mode, EditorMode::SingleLine) {
13922 cx.propagate();
13923 return;
13924 }
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926 self.change_selections(Default::default(), window, cx, |s| {
13927 s.move_heads_with(|map, head, _| {
13928 (
13929 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn select_to_end_of_previous_excerpt(
13937 &mut self,
13938 _: &SelectToEndOfPreviousExcerpt,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_heads_with(|map, head, _| {
13949 (
13950 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13951 SelectionGoal::None,
13952 )
13953 });
13954 })
13955 }
13956
13957 pub fn move_to_beginning(
13958 &mut self,
13959 _: &MoveToBeginning,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) {
13963 if matches!(self.mode, EditorMode::SingleLine) {
13964 cx.propagate();
13965 return;
13966 }
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.select_ranges(vec![0..0]);
13970 });
13971 }
13972
13973 pub fn select_to_beginning(
13974 &mut self,
13975 _: &SelectToBeginning,
13976 window: &mut Window,
13977 cx: &mut Context<Self>,
13978 ) {
13979 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13980 selection.set_head(Point::zero(), SelectionGoal::None);
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13982 self.change_selections(Default::default(), window, cx, |s| {
13983 s.select(vec![selection]);
13984 });
13985 }
13986
13987 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13988 if matches!(self.mode, EditorMode::SingleLine) {
13989 cx.propagate();
13990 return;
13991 }
13992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13993 let cursor = self.buffer.read(cx).read(cx).len();
13994 self.change_selections(Default::default(), window, cx, |s| {
13995 s.select_ranges(vec![cursor..cursor])
13996 });
13997 }
13998
13999 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14000 self.nav_history = nav_history;
14001 }
14002
14003 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14004 self.nav_history.as_ref()
14005 }
14006
14007 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14008 self.push_to_nav_history(
14009 self.selections.newest_anchor().head(),
14010 None,
14011 false,
14012 true,
14013 cx,
14014 );
14015 }
14016
14017 fn push_to_nav_history(
14018 &mut self,
14019 cursor_anchor: Anchor,
14020 new_position: Option<Point>,
14021 is_deactivate: bool,
14022 always: bool,
14023 cx: &mut Context<Self>,
14024 ) {
14025 if let Some(nav_history) = self.nav_history.as_mut() {
14026 let buffer = self.buffer.read(cx).read(cx);
14027 let cursor_position = cursor_anchor.to_point(&buffer);
14028 let scroll_state = self.scroll_manager.anchor();
14029 let scroll_top_row = scroll_state.top_row(&buffer);
14030 drop(buffer);
14031
14032 if let Some(new_position) = new_position {
14033 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14034 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14035 return;
14036 }
14037 }
14038
14039 nav_history.push(
14040 Some(NavigationData {
14041 cursor_anchor,
14042 cursor_position,
14043 scroll_anchor: scroll_state,
14044 scroll_top_row,
14045 }),
14046 cx,
14047 );
14048 cx.emit(EditorEvent::PushedToNavHistory {
14049 anchor: cursor_anchor,
14050 is_deactivate,
14051 })
14052 }
14053 }
14054
14055 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14057 let buffer = self.buffer.read(cx).snapshot(cx);
14058 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14059 selection.set_head(buffer.len(), SelectionGoal::None);
14060 self.change_selections(Default::default(), window, cx, |s| {
14061 s.select(vec![selection]);
14062 });
14063 }
14064
14065 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14067 let end = self.buffer.read(cx).read(cx).len();
14068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14069 s.select_ranges(vec![0..end]);
14070 });
14071 }
14072
14073 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14076 let mut selections = self.selections.all::<Point>(&display_map);
14077 let max_point = display_map.buffer_snapshot().max_point();
14078 for selection in &mut selections {
14079 let rows = selection.spanned_rows(true, &display_map);
14080 selection.start = Point::new(rows.start.0, 0);
14081 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14082 selection.reversed = false;
14083 }
14084 self.change_selections(Default::default(), window, cx, |s| {
14085 s.select(selections);
14086 });
14087 }
14088
14089 pub fn split_selection_into_lines(
14090 &mut self,
14091 action: &SplitSelectionIntoLines,
14092 window: &mut Window,
14093 cx: &mut Context<Self>,
14094 ) {
14095 let selections = self
14096 .selections
14097 .all::<Point>(&self.display_snapshot(cx))
14098 .into_iter()
14099 .map(|selection| selection.start..selection.end)
14100 .collect::<Vec<_>>();
14101 self.unfold_ranges(&selections, true, true, cx);
14102
14103 let mut new_selection_ranges = Vec::new();
14104 {
14105 let buffer = self.buffer.read(cx).read(cx);
14106 for selection in selections {
14107 for row in selection.start.row..selection.end.row {
14108 let line_start = Point::new(row, 0);
14109 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14110
14111 if action.keep_selections {
14112 // Keep the selection range for each line
14113 let selection_start = if row == selection.start.row {
14114 selection.start
14115 } else {
14116 line_start
14117 };
14118 new_selection_ranges.push(selection_start..line_end);
14119 } else {
14120 // Collapse to cursor at end of line
14121 new_selection_ranges.push(line_end..line_end);
14122 }
14123 }
14124
14125 let is_multiline_selection = selection.start.row != selection.end.row;
14126 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14127 // so this action feels more ergonomic when paired with other selection operations
14128 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14129 if !should_skip_last {
14130 if action.keep_selections {
14131 if is_multiline_selection {
14132 let line_start = Point::new(selection.end.row, 0);
14133 new_selection_ranges.push(line_start..selection.end);
14134 } else {
14135 new_selection_ranges.push(selection.start..selection.end);
14136 }
14137 } else {
14138 new_selection_ranges.push(selection.end..selection.end);
14139 }
14140 }
14141 }
14142 }
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.select_ranges(new_selection_ranges);
14145 });
14146 }
14147
14148 pub fn add_selection_above(
14149 &mut self,
14150 action: &AddSelectionAbove,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) {
14154 self.add_selection(true, action.skip_soft_wrap, window, cx);
14155 }
14156
14157 pub fn add_selection_below(
14158 &mut self,
14159 action: &AddSelectionBelow,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 self.add_selection(false, action.skip_soft_wrap, window, cx);
14164 }
14165
14166 fn add_selection(
14167 &mut self,
14168 above: bool,
14169 skip_soft_wrap: bool,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) {
14173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14174
14175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14176 let all_selections = self.selections.all::<Point>(&display_map);
14177 let text_layout_details = self.text_layout_details(window);
14178
14179 let (mut columnar_selections, new_selections_to_columnarize) = {
14180 if let Some(state) = self.add_selections_state.as_ref() {
14181 let columnar_selection_ids: HashSet<_> = state
14182 .groups
14183 .iter()
14184 .flat_map(|group| group.stack.iter())
14185 .copied()
14186 .collect();
14187
14188 all_selections
14189 .into_iter()
14190 .partition(|s| columnar_selection_ids.contains(&s.id))
14191 } else {
14192 (Vec::new(), all_selections)
14193 }
14194 };
14195
14196 let mut state = self
14197 .add_selections_state
14198 .take()
14199 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14200
14201 for selection in new_selections_to_columnarize {
14202 let range = selection.display_range(&display_map).sorted();
14203 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14204 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14205 let positions = start_x.min(end_x)..start_x.max(end_x);
14206 let mut stack = Vec::new();
14207 for row in range.start.row().0..=range.end.row().0 {
14208 if let Some(selection) = self.selections.build_columnar_selection(
14209 &display_map,
14210 DisplayRow(row),
14211 &positions,
14212 selection.reversed,
14213 &text_layout_details,
14214 ) {
14215 stack.push(selection.id);
14216 columnar_selections.push(selection);
14217 }
14218 }
14219 if !stack.is_empty() {
14220 if above {
14221 stack.reverse();
14222 }
14223 state.groups.push(AddSelectionsGroup { above, stack });
14224 }
14225 }
14226
14227 let mut final_selections = Vec::new();
14228 let end_row = if above {
14229 DisplayRow(0)
14230 } else {
14231 display_map.max_point().row()
14232 };
14233
14234 let mut last_added_item_per_group = HashMap::default();
14235 for group in state.groups.iter_mut() {
14236 if let Some(last_id) = group.stack.last() {
14237 last_added_item_per_group.insert(*last_id, group);
14238 }
14239 }
14240
14241 for selection in columnar_selections {
14242 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14243 if above == group.above {
14244 let range = selection.display_range(&display_map).sorted();
14245 debug_assert_eq!(range.start.row(), range.end.row());
14246 let mut row = range.start.row();
14247 let positions =
14248 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14249 Pixels::from(start)..Pixels::from(end)
14250 } else {
14251 let start_x =
14252 display_map.x_for_display_point(range.start, &text_layout_details);
14253 let end_x =
14254 display_map.x_for_display_point(range.end, &text_layout_details);
14255 start_x.min(end_x)..start_x.max(end_x)
14256 };
14257
14258 let mut maybe_new_selection = None;
14259 let direction = if above { -1 } else { 1 };
14260
14261 while row != end_row {
14262 if skip_soft_wrap {
14263 row = display_map
14264 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14265 .row();
14266 } else if above {
14267 row.0 -= 1;
14268 } else {
14269 row.0 += 1;
14270 }
14271
14272 if let Some(new_selection) = self.selections.build_columnar_selection(
14273 &display_map,
14274 row,
14275 &positions,
14276 selection.reversed,
14277 &text_layout_details,
14278 ) {
14279 maybe_new_selection = Some(new_selection);
14280 break;
14281 }
14282 }
14283
14284 if let Some(new_selection) = maybe_new_selection {
14285 group.stack.push(new_selection.id);
14286 if above {
14287 final_selections.push(new_selection);
14288 final_selections.push(selection);
14289 } else {
14290 final_selections.push(selection);
14291 final_selections.push(new_selection);
14292 }
14293 } else {
14294 final_selections.push(selection);
14295 }
14296 } else {
14297 group.stack.pop();
14298 }
14299 } else {
14300 final_selections.push(selection);
14301 }
14302 }
14303
14304 self.change_selections(Default::default(), window, cx, |s| {
14305 s.select(final_selections);
14306 });
14307
14308 let final_selection_ids: HashSet<_> = self
14309 .selections
14310 .all::<Point>(&display_map)
14311 .iter()
14312 .map(|s| s.id)
14313 .collect();
14314 state.groups.retain_mut(|group| {
14315 // selections might get merged above so we remove invalid items from stacks
14316 group.stack.retain(|id| final_selection_ids.contains(id));
14317
14318 // single selection in stack can be treated as initial state
14319 group.stack.len() > 1
14320 });
14321
14322 if !state.groups.is_empty() {
14323 self.add_selections_state = Some(state);
14324 }
14325 }
14326
14327 fn select_match_ranges(
14328 &mut self,
14329 range: Range<usize>,
14330 reversed: bool,
14331 replace_newest: bool,
14332 auto_scroll: Option<Autoscroll>,
14333 window: &mut Window,
14334 cx: &mut Context<Editor>,
14335 ) {
14336 self.unfold_ranges(
14337 std::slice::from_ref(&range),
14338 false,
14339 auto_scroll.is_some(),
14340 cx,
14341 );
14342 let effects = if let Some(scroll) = auto_scroll {
14343 SelectionEffects::scroll(scroll)
14344 } else {
14345 SelectionEffects::no_scroll()
14346 };
14347 self.change_selections(effects, window, cx, |s| {
14348 if replace_newest {
14349 s.delete(s.newest_anchor().id);
14350 }
14351 if reversed {
14352 s.insert_range(range.end..range.start);
14353 } else {
14354 s.insert_range(range);
14355 }
14356 });
14357 }
14358
14359 pub fn select_next_match_internal(
14360 &mut self,
14361 display_map: &DisplaySnapshot,
14362 replace_newest: bool,
14363 autoscroll: Option<Autoscroll>,
14364 window: &mut Window,
14365 cx: &mut Context<Self>,
14366 ) -> Result<()> {
14367 let buffer = display_map.buffer_snapshot();
14368 let mut selections = self.selections.all::<usize>(&display_map);
14369 if let Some(mut select_next_state) = self.select_next_state.take() {
14370 let query = &select_next_state.query;
14371 if !select_next_state.done {
14372 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14373 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14374 let mut next_selected_range = None;
14375
14376 let bytes_after_last_selection =
14377 buffer.bytes_in_range(last_selection.end..buffer.len());
14378 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14379 let query_matches = query
14380 .stream_find_iter(bytes_after_last_selection)
14381 .map(|result| (last_selection.end, result))
14382 .chain(
14383 query
14384 .stream_find_iter(bytes_before_first_selection)
14385 .map(|result| (0, result)),
14386 );
14387
14388 for (start_offset, query_match) in query_matches {
14389 let query_match = query_match.unwrap(); // can only fail due to I/O
14390 let offset_range =
14391 start_offset + query_match.start()..start_offset + query_match.end();
14392
14393 if !select_next_state.wordwise
14394 || (!buffer.is_inside_word(offset_range.start, None)
14395 && !buffer.is_inside_word(offset_range.end, None))
14396 {
14397 let idx = selections
14398 .partition_point(|selection| selection.end <= offset_range.start);
14399 let overlaps = selections
14400 .get(idx)
14401 .map_or(false, |selection| selection.start < offset_range.end);
14402
14403 if !overlaps {
14404 next_selected_range = Some(offset_range);
14405 break;
14406 }
14407 }
14408 }
14409
14410 if let Some(next_selected_range) = next_selected_range {
14411 self.select_match_ranges(
14412 next_selected_range,
14413 last_selection.reversed,
14414 replace_newest,
14415 autoscroll,
14416 window,
14417 cx,
14418 );
14419 } else {
14420 select_next_state.done = true;
14421 }
14422 }
14423
14424 self.select_next_state = Some(select_next_state);
14425 } else {
14426 let mut only_carets = true;
14427 let mut same_text_selected = true;
14428 let mut selected_text = None;
14429
14430 let mut selections_iter = selections.iter().peekable();
14431 while let Some(selection) = selections_iter.next() {
14432 if selection.start != selection.end {
14433 only_carets = false;
14434 }
14435
14436 if same_text_selected {
14437 if selected_text.is_none() {
14438 selected_text =
14439 Some(buffer.text_for_range(selection.range()).collect::<String>());
14440 }
14441
14442 if let Some(next_selection) = selections_iter.peek() {
14443 if next_selection.range().len() == selection.range().len() {
14444 let next_selected_text = buffer
14445 .text_for_range(next_selection.range())
14446 .collect::<String>();
14447 if Some(next_selected_text) != selected_text {
14448 same_text_selected = false;
14449 selected_text = None;
14450 }
14451 } else {
14452 same_text_selected = false;
14453 selected_text = None;
14454 }
14455 }
14456 }
14457 }
14458
14459 if only_carets {
14460 for selection in &mut selections {
14461 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14462 selection.start = word_range.start;
14463 selection.end = word_range.end;
14464 selection.goal = SelectionGoal::None;
14465 selection.reversed = false;
14466 self.select_match_ranges(
14467 selection.start..selection.end,
14468 selection.reversed,
14469 replace_newest,
14470 autoscroll,
14471 window,
14472 cx,
14473 );
14474 }
14475
14476 if selections.len() == 1 {
14477 let selection = selections
14478 .last()
14479 .expect("ensured that there's only one selection");
14480 let query = buffer
14481 .text_for_range(selection.start..selection.end)
14482 .collect::<String>();
14483 let is_empty = query.is_empty();
14484 let select_state = SelectNextState {
14485 query: AhoCorasick::new(&[query])?,
14486 wordwise: true,
14487 done: is_empty,
14488 };
14489 self.select_next_state = Some(select_state);
14490 } else {
14491 self.select_next_state = None;
14492 }
14493 } else if let Some(selected_text) = selected_text {
14494 self.select_next_state = Some(SelectNextState {
14495 query: AhoCorasick::new(&[selected_text])?,
14496 wordwise: false,
14497 done: false,
14498 });
14499 self.select_next_match_internal(
14500 display_map,
14501 replace_newest,
14502 autoscroll,
14503 window,
14504 cx,
14505 )?;
14506 }
14507 }
14508 Ok(())
14509 }
14510
14511 pub fn select_all_matches(
14512 &mut self,
14513 _action: &SelectAllMatches,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) -> Result<()> {
14517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14518
14519 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14520
14521 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14522 let Some(select_next_state) = self.select_next_state.as_mut() else {
14523 return Ok(());
14524 };
14525 if select_next_state.done {
14526 return Ok(());
14527 }
14528
14529 let mut new_selections = Vec::new();
14530
14531 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14532 let buffer = display_map.buffer_snapshot();
14533 let query_matches = select_next_state
14534 .query
14535 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14536
14537 for query_match in query_matches.into_iter() {
14538 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14539 let offset_range = if reversed {
14540 query_match.end()..query_match.start()
14541 } else {
14542 query_match.start()..query_match.end()
14543 };
14544
14545 if !select_next_state.wordwise
14546 || (!buffer.is_inside_word(offset_range.start, None)
14547 && !buffer.is_inside_word(offset_range.end, None))
14548 {
14549 new_selections.push(offset_range.start..offset_range.end);
14550 }
14551 }
14552
14553 select_next_state.done = true;
14554
14555 if new_selections.is_empty() {
14556 log::error!("bug: new_selections is empty in select_all_matches");
14557 return Ok(());
14558 }
14559
14560 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14561 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14562 selections.select_ranges(new_selections)
14563 });
14564
14565 Ok(())
14566 }
14567
14568 pub fn select_next(
14569 &mut self,
14570 action: &SelectNext,
14571 window: &mut Window,
14572 cx: &mut Context<Self>,
14573 ) -> Result<()> {
14574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14575 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14576 self.select_next_match_internal(
14577 &display_map,
14578 action.replace_newest,
14579 Some(Autoscroll::newest()),
14580 window,
14581 cx,
14582 )?;
14583 Ok(())
14584 }
14585
14586 pub fn select_previous(
14587 &mut self,
14588 action: &SelectPrevious,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) -> Result<()> {
14592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14594 let buffer = display_map.buffer_snapshot();
14595 let mut selections = self.selections.all::<usize>(&display_map);
14596 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14597 let query = &select_prev_state.query;
14598 if !select_prev_state.done {
14599 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14600 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14601 let mut next_selected_range = None;
14602 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14603 let bytes_before_last_selection =
14604 buffer.reversed_bytes_in_range(0..last_selection.start);
14605 let bytes_after_first_selection =
14606 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14607 let query_matches = query
14608 .stream_find_iter(bytes_before_last_selection)
14609 .map(|result| (last_selection.start, result))
14610 .chain(
14611 query
14612 .stream_find_iter(bytes_after_first_selection)
14613 .map(|result| (buffer.len(), result)),
14614 );
14615 for (end_offset, query_match) in query_matches {
14616 let query_match = query_match.unwrap(); // can only fail due to I/O
14617 let offset_range =
14618 end_offset - query_match.end()..end_offset - query_match.start();
14619
14620 if !select_prev_state.wordwise
14621 || (!buffer.is_inside_word(offset_range.start, None)
14622 && !buffer.is_inside_word(offset_range.end, None))
14623 {
14624 next_selected_range = Some(offset_range);
14625 break;
14626 }
14627 }
14628
14629 if let Some(next_selected_range) = next_selected_range {
14630 self.select_match_ranges(
14631 next_selected_range,
14632 last_selection.reversed,
14633 action.replace_newest,
14634 Some(Autoscroll::newest()),
14635 window,
14636 cx,
14637 );
14638 } else {
14639 select_prev_state.done = true;
14640 }
14641 }
14642
14643 self.select_prev_state = Some(select_prev_state);
14644 } else {
14645 let mut only_carets = true;
14646 let mut same_text_selected = true;
14647 let mut selected_text = None;
14648
14649 let mut selections_iter = selections.iter().peekable();
14650 while let Some(selection) = selections_iter.next() {
14651 if selection.start != selection.end {
14652 only_carets = false;
14653 }
14654
14655 if same_text_selected {
14656 if selected_text.is_none() {
14657 selected_text =
14658 Some(buffer.text_for_range(selection.range()).collect::<String>());
14659 }
14660
14661 if let Some(next_selection) = selections_iter.peek() {
14662 if next_selection.range().len() == selection.range().len() {
14663 let next_selected_text = buffer
14664 .text_for_range(next_selection.range())
14665 .collect::<String>();
14666 if Some(next_selected_text) != selected_text {
14667 same_text_selected = false;
14668 selected_text = None;
14669 }
14670 } else {
14671 same_text_selected = false;
14672 selected_text = None;
14673 }
14674 }
14675 }
14676 }
14677
14678 if only_carets {
14679 for selection in &mut selections {
14680 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14681 selection.start = word_range.start;
14682 selection.end = word_range.end;
14683 selection.goal = SelectionGoal::None;
14684 selection.reversed = false;
14685 self.select_match_ranges(
14686 selection.start..selection.end,
14687 selection.reversed,
14688 action.replace_newest,
14689 Some(Autoscroll::newest()),
14690 window,
14691 cx,
14692 );
14693 }
14694 if selections.len() == 1 {
14695 let selection = selections
14696 .last()
14697 .expect("ensured that there's only one selection");
14698 let query = buffer
14699 .text_for_range(selection.start..selection.end)
14700 .collect::<String>();
14701 let is_empty = query.is_empty();
14702 let select_state = SelectNextState {
14703 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14704 wordwise: true,
14705 done: is_empty,
14706 };
14707 self.select_prev_state = Some(select_state);
14708 } else {
14709 self.select_prev_state = None;
14710 }
14711 } else if let Some(selected_text) = selected_text {
14712 self.select_prev_state = Some(SelectNextState {
14713 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14714 wordwise: false,
14715 done: false,
14716 });
14717 self.select_previous(action, window, cx)?;
14718 }
14719 }
14720 Ok(())
14721 }
14722
14723 pub fn find_next_match(
14724 &mut self,
14725 _: &FindNextMatch,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) -> Result<()> {
14729 let selections = self.selections.disjoint_anchors_arc();
14730 match selections.first() {
14731 Some(first) if selections.len() >= 2 => {
14732 self.change_selections(Default::default(), window, cx, |s| {
14733 s.select_ranges([first.range()]);
14734 });
14735 }
14736 _ => self.select_next(
14737 &SelectNext {
14738 replace_newest: true,
14739 },
14740 window,
14741 cx,
14742 )?,
14743 }
14744 Ok(())
14745 }
14746
14747 pub fn find_previous_match(
14748 &mut self,
14749 _: &FindPreviousMatch,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) -> Result<()> {
14753 let selections = self.selections.disjoint_anchors_arc();
14754 match selections.last() {
14755 Some(last) if selections.len() >= 2 => {
14756 self.change_selections(Default::default(), window, cx, |s| {
14757 s.select_ranges([last.range()]);
14758 });
14759 }
14760 _ => self.select_previous(
14761 &SelectPrevious {
14762 replace_newest: true,
14763 },
14764 window,
14765 cx,
14766 )?,
14767 }
14768 Ok(())
14769 }
14770
14771 pub fn toggle_comments(
14772 &mut self,
14773 action: &ToggleComments,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) {
14777 if self.read_only(cx) {
14778 return;
14779 }
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14781 let text_layout_details = &self.text_layout_details(window);
14782 self.transact(window, cx, |this, window, cx| {
14783 let mut selections = this
14784 .selections
14785 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14786 let mut edits = Vec::new();
14787 let mut selection_edit_ranges = Vec::new();
14788 let mut last_toggled_row = None;
14789 let snapshot = this.buffer.read(cx).read(cx);
14790 let empty_str: Arc<str> = Arc::default();
14791 let mut suffixes_inserted = Vec::new();
14792 let ignore_indent = action.ignore_indent;
14793
14794 fn comment_prefix_range(
14795 snapshot: &MultiBufferSnapshot,
14796 row: MultiBufferRow,
14797 comment_prefix: &str,
14798 comment_prefix_whitespace: &str,
14799 ignore_indent: bool,
14800 ) -> Range<Point> {
14801 let indent_size = if ignore_indent {
14802 0
14803 } else {
14804 snapshot.indent_size_for_line(row).len
14805 };
14806
14807 let start = Point::new(row.0, indent_size);
14808
14809 let mut line_bytes = snapshot
14810 .bytes_in_range(start..snapshot.max_point())
14811 .flatten()
14812 .copied();
14813
14814 // If this line currently begins with the line comment prefix, then record
14815 // the range containing the prefix.
14816 if line_bytes
14817 .by_ref()
14818 .take(comment_prefix.len())
14819 .eq(comment_prefix.bytes())
14820 {
14821 // Include any whitespace that matches the comment prefix.
14822 let matching_whitespace_len = line_bytes
14823 .zip(comment_prefix_whitespace.bytes())
14824 .take_while(|(a, b)| a == b)
14825 .count() as u32;
14826 let end = Point::new(
14827 start.row,
14828 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14829 );
14830 start..end
14831 } else {
14832 start..start
14833 }
14834 }
14835
14836 fn comment_suffix_range(
14837 snapshot: &MultiBufferSnapshot,
14838 row: MultiBufferRow,
14839 comment_suffix: &str,
14840 comment_suffix_has_leading_space: bool,
14841 ) -> Range<Point> {
14842 let end = Point::new(row.0, snapshot.line_len(row));
14843 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14844
14845 let mut line_end_bytes = snapshot
14846 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14847 .flatten()
14848 .copied();
14849
14850 let leading_space_len = if suffix_start_column > 0
14851 && line_end_bytes.next() == Some(b' ')
14852 && comment_suffix_has_leading_space
14853 {
14854 1
14855 } else {
14856 0
14857 };
14858
14859 // If this line currently begins with the line comment prefix, then record
14860 // the range containing the prefix.
14861 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14862 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14863 start..end
14864 } else {
14865 end..end
14866 }
14867 }
14868
14869 // TODO: Handle selections that cross excerpts
14870 for selection in &mut selections {
14871 let start_column = snapshot
14872 .indent_size_for_line(MultiBufferRow(selection.start.row))
14873 .len;
14874 let language = if let Some(language) =
14875 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14876 {
14877 language
14878 } else {
14879 continue;
14880 };
14881
14882 selection_edit_ranges.clear();
14883
14884 // If multiple selections contain a given row, avoid processing that
14885 // row more than once.
14886 let mut start_row = MultiBufferRow(selection.start.row);
14887 if last_toggled_row == Some(start_row) {
14888 start_row = start_row.next_row();
14889 }
14890 let end_row =
14891 if selection.end.row > selection.start.row && selection.end.column == 0 {
14892 MultiBufferRow(selection.end.row - 1)
14893 } else {
14894 MultiBufferRow(selection.end.row)
14895 };
14896 last_toggled_row = Some(end_row);
14897
14898 if start_row > end_row {
14899 continue;
14900 }
14901
14902 // If the language has line comments, toggle those.
14903 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14904
14905 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14906 if ignore_indent {
14907 full_comment_prefixes = full_comment_prefixes
14908 .into_iter()
14909 .map(|s| Arc::from(s.trim_end()))
14910 .collect();
14911 }
14912
14913 if !full_comment_prefixes.is_empty() {
14914 let first_prefix = full_comment_prefixes
14915 .first()
14916 .expect("prefixes is non-empty");
14917 let prefix_trimmed_lengths = full_comment_prefixes
14918 .iter()
14919 .map(|p| p.trim_end_matches(' ').len())
14920 .collect::<SmallVec<[usize; 4]>>();
14921
14922 let mut all_selection_lines_are_comments = true;
14923
14924 for row in start_row.0..=end_row.0 {
14925 let row = MultiBufferRow(row);
14926 if start_row < end_row && snapshot.is_line_blank(row) {
14927 continue;
14928 }
14929
14930 let prefix_range = full_comment_prefixes
14931 .iter()
14932 .zip(prefix_trimmed_lengths.iter().copied())
14933 .map(|(prefix, trimmed_prefix_len)| {
14934 comment_prefix_range(
14935 snapshot.deref(),
14936 row,
14937 &prefix[..trimmed_prefix_len],
14938 &prefix[trimmed_prefix_len..],
14939 ignore_indent,
14940 )
14941 })
14942 .max_by_key(|range| range.end.column - range.start.column)
14943 .expect("prefixes is non-empty");
14944
14945 if prefix_range.is_empty() {
14946 all_selection_lines_are_comments = false;
14947 }
14948
14949 selection_edit_ranges.push(prefix_range);
14950 }
14951
14952 if all_selection_lines_are_comments {
14953 edits.extend(
14954 selection_edit_ranges
14955 .iter()
14956 .cloned()
14957 .map(|range| (range, empty_str.clone())),
14958 );
14959 } else {
14960 let min_column = selection_edit_ranges
14961 .iter()
14962 .map(|range| range.start.column)
14963 .min()
14964 .unwrap_or(0);
14965 edits.extend(selection_edit_ranges.iter().map(|range| {
14966 let position = Point::new(range.start.row, min_column);
14967 (position..position, first_prefix.clone())
14968 }));
14969 }
14970 } else if let Some(BlockCommentConfig {
14971 start: full_comment_prefix,
14972 end: comment_suffix,
14973 ..
14974 }) = language.block_comment()
14975 {
14976 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14977 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14978 let prefix_range = comment_prefix_range(
14979 snapshot.deref(),
14980 start_row,
14981 comment_prefix,
14982 comment_prefix_whitespace,
14983 ignore_indent,
14984 );
14985 let suffix_range = comment_suffix_range(
14986 snapshot.deref(),
14987 end_row,
14988 comment_suffix.trim_start_matches(' '),
14989 comment_suffix.starts_with(' '),
14990 );
14991
14992 if prefix_range.is_empty() || suffix_range.is_empty() {
14993 edits.push((
14994 prefix_range.start..prefix_range.start,
14995 full_comment_prefix.clone(),
14996 ));
14997 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14998 suffixes_inserted.push((end_row, comment_suffix.len()));
14999 } else {
15000 edits.push((prefix_range, empty_str.clone()));
15001 edits.push((suffix_range, empty_str.clone()));
15002 }
15003 } else {
15004 continue;
15005 }
15006 }
15007
15008 drop(snapshot);
15009 this.buffer.update(cx, |buffer, cx| {
15010 buffer.edit(edits, None, cx);
15011 });
15012
15013 // Adjust selections so that they end before any comment suffixes that
15014 // were inserted.
15015 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15016 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15017 let snapshot = this.buffer.read(cx).read(cx);
15018 for selection in &mut selections {
15019 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15020 match row.cmp(&MultiBufferRow(selection.end.row)) {
15021 Ordering::Less => {
15022 suffixes_inserted.next();
15023 continue;
15024 }
15025 Ordering::Greater => break,
15026 Ordering::Equal => {
15027 if selection.end.column == snapshot.line_len(row) {
15028 if selection.is_empty() {
15029 selection.start.column -= suffix_len as u32;
15030 }
15031 selection.end.column -= suffix_len as u32;
15032 }
15033 break;
15034 }
15035 }
15036 }
15037 }
15038
15039 drop(snapshot);
15040 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15041
15042 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15043 let selections_on_single_row = selections.windows(2).all(|selections| {
15044 selections[0].start.row == selections[1].start.row
15045 && selections[0].end.row == selections[1].end.row
15046 && selections[0].start.row == selections[0].end.row
15047 });
15048 let selections_selecting = selections
15049 .iter()
15050 .any(|selection| selection.start != selection.end);
15051 let advance_downwards = action.advance_downwards
15052 && selections_on_single_row
15053 && !selections_selecting
15054 && !matches!(this.mode, EditorMode::SingleLine);
15055
15056 if advance_downwards {
15057 let snapshot = this.buffer.read(cx).snapshot(cx);
15058
15059 this.change_selections(Default::default(), window, cx, |s| {
15060 s.move_cursors_with(|display_snapshot, display_point, _| {
15061 let mut point = display_point.to_point(display_snapshot);
15062 point.row += 1;
15063 point = snapshot.clip_point(point, Bias::Left);
15064 let display_point = point.to_display_point(display_snapshot);
15065 let goal = SelectionGoal::HorizontalPosition(
15066 display_snapshot
15067 .x_for_display_point(display_point, text_layout_details)
15068 .into(),
15069 );
15070 (display_point, goal)
15071 })
15072 });
15073 }
15074 });
15075 }
15076
15077 pub fn select_enclosing_symbol(
15078 &mut self,
15079 _: &SelectEnclosingSymbol,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) {
15083 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15084
15085 let buffer = self.buffer.read(cx).snapshot(cx);
15086 let old_selections = self
15087 .selections
15088 .all::<usize>(&self.display_snapshot(cx))
15089 .into_boxed_slice();
15090
15091 fn update_selection(
15092 selection: &Selection<usize>,
15093 buffer_snap: &MultiBufferSnapshot,
15094 ) -> Option<Selection<usize>> {
15095 let cursor = selection.head();
15096 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15097 for symbol in symbols.iter().rev() {
15098 let start = symbol.range.start.to_offset(buffer_snap);
15099 let end = symbol.range.end.to_offset(buffer_snap);
15100 let new_range = start..end;
15101 if start < selection.start || end > selection.end {
15102 return Some(Selection {
15103 id: selection.id,
15104 start: new_range.start,
15105 end: new_range.end,
15106 goal: SelectionGoal::None,
15107 reversed: selection.reversed,
15108 });
15109 }
15110 }
15111 None
15112 }
15113
15114 let mut selected_larger_symbol = false;
15115 let new_selections = old_selections
15116 .iter()
15117 .map(|selection| match update_selection(selection, &buffer) {
15118 Some(new_selection) => {
15119 if new_selection.range() != selection.range() {
15120 selected_larger_symbol = true;
15121 }
15122 new_selection
15123 }
15124 None => selection.clone(),
15125 })
15126 .collect::<Vec<_>>();
15127
15128 if selected_larger_symbol {
15129 self.change_selections(Default::default(), window, cx, |s| {
15130 s.select(new_selections);
15131 });
15132 }
15133 }
15134
15135 pub fn select_larger_syntax_node(
15136 &mut self,
15137 _: &SelectLargerSyntaxNode,
15138 window: &mut Window,
15139 cx: &mut Context<Self>,
15140 ) {
15141 let Some(visible_row_count) = self.visible_row_count() else {
15142 return;
15143 };
15144 let old_selections: Box<[_]> = self
15145 .selections
15146 .all::<usize>(&self.display_snapshot(cx))
15147 .into();
15148 if old_selections.is_empty() {
15149 return;
15150 }
15151
15152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15153
15154 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15155 let buffer = self.buffer.read(cx).snapshot(cx);
15156
15157 let mut selected_larger_node = false;
15158 let mut new_selections = old_selections
15159 .iter()
15160 .map(|selection| {
15161 let old_range = selection.start..selection.end;
15162
15163 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15164 // manually select word at selection
15165 if ["string_content", "inline"].contains(&node.kind()) {
15166 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15167 // ignore if word is already selected
15168 if !word_range.is_empty() && old_range != word_range {
15169 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15170 // only select word if start and end point belongs to same word
15171 if word_range == last_word_range {
15172 selected_larger_node = true;
15173 return Selection {
15174 id: selection.id,
15175 start: word_range.start,
15176 end: word_range.end,
15177 goal: SelectionGoal::None,
15178 reversed: selection.reversed,
15179 };
15180 }
15181 }
15182 }
15183 }
15184
15185 let mut new_range = old_range.clone();
15186 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15187 new_range = range;
15188 if !node.is_named() {
15189 continue;
15190 }
15191 if !display_map.intersects_fold(new_range.start)
15192 && !display_map.intersects_fold(new_range.end)
15193 {
15194 break;
15195 }
15196 }
15197
15198 selected_larger_node |= new_range != old_range;
15199 Selection {
15200 id: selection.id,
15201 start: new_range.start,
15202 end: new_range.end,
15203 goal: SelectionGoal::None,
15204 reversed: selection.reversed,
15205 }
15206 })
15207 .collect::<Vec<_>>();
15208
15209 if !selected_larger_node {
15210 return; // don't put this call in the history
15211 }
15212
15213 // scroll based on transformation done to the last selection created by the user
15214 let (last_old, last_new) = old_selections
15215 .last()
15216 .zip(new_selections.last().cloned())
15217 .expect("old_selections isn't empty");
15218
15219 // revert selection
15220 let is_selection_reversed = {
15221 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15222 new_selections.last_mut().expect("checked above").reversed =
15223 should_newest_selection_be_reversed;
15224 should_newest_selection_be_reversed
15225 };
15226
15227 if selected_larger_node {
15228 self.select_syntax_node_history.disable_clearing = true;
15229 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15230 s.select(new_selections.clone());
15231 });
15232 self.select_syntax_node_history.disable_clearing = false;
15233 }
15234
15235 let start_row = last_new.start.to_display_point(&display_map).row().0;
15236 let end_row = last_new.end.to_display_point(&display_map).row().0;
15237 let selection_height = end_row - start_row + 1;
15238 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15239
15240 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15241 let scroll_behavior = if fits_on_the_screen {
15242 self.request_autoscroll(Autoscroll::fit(), cx);
15243 SelectSyntaxNodeScrollBehavior::FitSelection
15244 } else if is_selection_reversed {
15245 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15246 SelectSyntaxNodeScrollBehavior::CursorTop
15247 } else {
15248 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15249 SelectSyntaxNodeScrollBehavior::CursorBottom
15250 };
15251
15252 self.select_syntax_node_history.push((
15253 old_selections,
15254 scroll_behavior,
15255 is_selection_reversed,
15256 ));
15257 }
15258
15259 pub fn select_smaller_syntax_node(
15260 &mut self,
15261 _: &SelectSmallerSyntaxNode,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266
15267 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15268 self.select_syntax_node_history.pop()
15269 {
15270 if let Some(selection) = selections.last_mut() {
15271 selection.reversed = is_selection_reversed;
15272 }
15273
15274 self.select_syntax_node_history.disable_clearing = true;
15275 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15276 s.select(selections.to_vec());
15277 });
15278 self.select_syntax_node_history.disable_clearing = false;
15279
15280 match scroll_behavior {
15281 SelectSyntaxNodeScrollBehavior::CursorTop => {
15282 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15283 }
15284 SelectSyntaxNodeScrollBehavior::FitSelection => {
15285 self.request_autoscroll(Autoscroll::fit(), cx);
15286 }
15287 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15288 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15289 }
15290 }
15291 }
15292 }
15293
15294 pub fn unwrap_syntax_node(
15295 &mut self,
15296 _: &UnwrapSyntaxNode,
15297 window: &mut Window,
15298 cx: &mut Context<Self>,
15299 ) {
15300 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15301
15302 let buffer = self.buffer.read(cx).snapshot(cx);
15303 let selections = self
15304 .selections
15305 .all::<usize>(&self.display_snapshot(cx))
15306 .into_iter()
15307 // subtracting the offset requires sorting
15308 .sorted_by_key(|i| i.start);
15309
15310 let full_edits = selections
15311 .into_iter()
15312 .filter_map(|selection| {
15313 let child = if selection.is_empty()
15314 && let Some((_, ancestor_range)) =
15315 buffer.syntax_ancestor(selection.start..selection.end)
15316 {
15317 ancestor_range
15318 } else {
15319 selection.range()
15320 };
15321
15322 let mut parent = child.clone();
15323 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15324 parent = ancestor_range;
15325 if parent.start < child.start || parent.end > child.end {
15326 break;
15327 }
15328 }
15329
15330 if parent == child {
15331 return None;
15332 }
15333 let text = buffer.text_for_range(child).collect::<String>();
15334 Some((selection.id, parent, text))
15335 })
15336 .collect::<Vec<_>>();
15337 if full_edits.is_empty() {
15338 return;
15339 }
15340
15341 self.transact(window, cx, |this, window, cx| {
15342 this.buffer.update(cx, |buffer, cx| {
15343 buffer.edit(
15344 full_edits
15345 .iter()
15346 .map(|(_, p, t)| (p.clone(), t.clone()))
15347 .collect::<Vec<_>>(),
15348 None,
15349 cx,
15350 );
15351 });
15352 this.change_selections(Default::default(), window, cx, |s| {
15353 let mut offset = 0;
15354 let mut selections = vec![];
15355 for (id, parent, text) in full_edits {
15356 let start = parent.start - offset;
15357 offset += parent.len() - text.len();
15358 selections.push(Selection {
15359 id,
15360 start,
15361 end: start + text.len(),
15362 reversed: false,
15363 goal: Default::default(),
15364 });
15365 }
15366 s.select(selections);
15367 });
15368 });
15369 }
15370
15371 pub fn select_next_syntax_node(
15372 &mut self,
15373 _: &SelectNextSyntaxNode,
15374 window: &mut Window,
15375 cx: &mut Context<Self>,
15376 ) {
15377 let old_selections: Box<[_]> = self
15378 .selections
15379 .all::<usize>(&self.display_snapshot(cx))
15380 .into();
15381 if old_selections.is_empty() {
15382 return;
15383 }
15384
15385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15386
15387 let buffer = self.buffer.read(cx).snapshot(cx);
15388 let mut selected_sibling = false;
15389
15390 let new_selections = old_selections
15391 .iter()
15392 .map(|selection| {
15393 let old_range = selection.start..selection.end;
15394
15395 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15396 let new_range = node.byte_range();
15397 selected_sibling = true;
15398 Selection {
15399 id: selection.id,
15400 start: new_range.start,
15401 end: new_range.end,
15402 goal: SelectionGoal::None,
15403 reversed: selection.reversed,
15404 }
15405 } else {
15406 selection.clone()
15407 }
15408 })
15409 .collect::<Vec<_>>();
15410
15411 if selected_sibling {
15412 self.change_selections(
15413 SelectionEffects::scroll(Autoscroll::fit()),
15414 window,
15415 cx,
15416 |s| {
15417 s.select(new_selections);
15418 },
15419 );
15420 }
15421 }
15422
15423 pub fn select_prev_syntax_node(
15424 &mut self,
15425 _: &SelectPreviousSyntaxNode,
15426 window: &mut Window,
15427 cx: &mut Context<Self>,
15428 ) {
15429 let old_selections: Box<[_]> = self
15430 .selections
15431 .all::<usize>(&self.display_snapshot(cx))
15432 .into();
15433 if old_selections.is_empty() {
15434 return;
15435 }
15436
15437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15438
15439 let buffer = self.buffer.read(cx).snapshot(cx);
15440 let mut selected_sibling = false;
15441
15442 let new_selections = old_selections
15443 .iter()
15444 .map(|selection| {
15445 let old_range = selection.start..selection.end;
15446
15447 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15448 let new_range = node.byte_range();
15449 selected_sibling = true;
15450 Selection {
15451 id: selection.id,
15452 start: new_range.start,
15453 end: new_range.end,
15454 goal: SelectionGoal::None,
15455 reversed: selection.reversed,
15456 }
15457 } else {
15458 selection.clone()
15459 }
15460 })
15461 .collect::<Vec<_>>();
15462
15463 if selected_sibling {
15464 self.change_selections(
15465 SelectionEffects::scroll(Autoscroll::fit()),
15466 window,
15467 cx,
15468 |s| {
15469 s.select(new_selections);
15470 },
15471 );
15472 }
15473 }
15474
15475 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15476 if !EditorSettings::get_global(cx).gutter.runnables {
15477 self.clear_tasks();
15478 return Task::ready(());
15479 }
15480 let project = self.project().map(Entity::downgrade);
15481 let task_sources = self.lsp_task_sources(cx);
15482 let multi_buffer = self.buffer.downgrade();
15483 cx.spawn_in(window, async move |editor, cx| {
15484 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15485 let Some(project) = project.and_then(|p| p.upgrade()) else {
15486 return;
15487 };
15488 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15489 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15490 }) else {
15491 return;
15492 };
15493
15494 let hide_runnables = project
15495 .update(cx, |project, _| project.is_via_collab())
15496 .unwrap_or(true);
15497 if hide_runnables {
15498 return;
15499 }
15500 let new_rows =
15501 cx.background_spawn({
15502 let snapshot = display_snapshot.clone();
15503 async move {
15504 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15505 }
15506 })
15507 .await;
15508 let Ok(lsp_tasks) =
15509 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15510 else {
15511 return;
15512 };
15513 let lsp_tasks = lsp_tasks.await;
15514
15515 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15516 lsp_tasks
15517 .into_iter()
15518 .flat_map(|(kind, tasks)| {
15519 tasks.into_iter().filter_map(move |(location, task)| {
15520 Some((kind.clone(), location?, task))
15521 })
15522 })
15523 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15524 let buffer = location.target.buffer;
15525 let buffer_snapshot = buffer.read(cx).snapshot();
15526 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15527 |(excerpt_id, snapshot, _)| {
15528 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15529 display_snapshot
15530 .buffer_snapshot()
15531 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15532 } else {
15533 None
15534 }
15535 },
15536 );
15537 if let Some(offset) = offset {
15538 let task_buffer_range =
15539 location.target.range.to_point(&buffer_snapshot);
15540 let context_buffer_range =
15541 task_buffer_range.to_offset(&buffer_snapshot);
15542 let context_range = BufferOffset(context_buffer_range.start)
15543 ..BufferOffset(context_buffer_range.end);
15544
15545 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15546 .or_insert_with(|| RunnableTasks {
15547 templates: Vec::new(),
15548 offset,
15549 column: task_buffer_range.start.column,
15550 extra_variables: HashMap::default(),
15551 context_range,
15552 })
15553 .templates
15554 .push((kind, task.original_task().clone()));
15555 }
15556
15557 acc
15558 })
15559 }) else {
15560 return;
15561 };
15562
15563 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15564 buffer.language_settings(cx).tasks.prefer_lsp
15565 }) else {
15566 return;
15567 };
15568
15569 let rows = Self::runnable_rows(
15570 project,
15571 display_snapshot,
15572 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15573 new_rows,
15574 cx.clone(),
15575 )
15576 .await;
15577 editor
15578 .update(cx, |editor, _| {
15579 editor.clear_tasks();
15580 for (key, mut value) in rows {
15581 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15582 value.templates.extend(lsp_tasks.templates);
15583 }
15584
15585 editor.insert_tasks(key, value);
15586 }
15587 for (key, value) in lsp_tasks_by_rows {
15588 editor.insert_tasks(key, value);
15589 }
15590 })
15591 .ok();
15592 })
15593 }
15594 fn fetch_runnable_ranges(
15595 snapshot: &DisplaySnapshot,
15596 range: Range<Anchor>,
15597 ) -> Vec<language::RunnableRange> {
15598 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15599 }
15600
15601 fn runnable_rows(
15602 project: Entity<Project>,
15603 snapshot: DisplaySnapshot,
15604 prefer_lsp: bool,
15605 runnable_ranges: Vec<RunnableRange>,
15606 cx: AsyncWindowContext,
15607 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15608 cx.spawn(async move |cx| {
15609 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15610 for mut runnable in runnable_ranges {
15611 let Some(tasks) = cx
15612 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15613 .ok()
15614 else {
15615 continue;
15616 };
15617 let mut tasks = tasks.await;
15618
15619 if prefer_lsp {
15620 tasks.retain(|(task_kind, _)| {
15621 !matches!(task_kind, TaskSourceKind::Language { .. })
15622 });
15623 }
15624 if tasks.is_empty() {
15625 continue;
15626 }
15627
15628 let point = runnable
15629 .run_range
15630 .start
15631 .to_point(&snapshot.buffer_snapshot());
15632 let Some(row) = snapshot
15633 .buffer_snapshot()
15634 .buffer_line_for_row(MultiBufferRow(point.row))
15635 .map(|(_, range)| range.start.row)
15636 else {
15637 continue;
15638 };
15639
15640 let context_range =
15641 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15642 runnable_rows.push((
15643 (runnable.buffer_id, row),
15644 RunnableTasks {
15645 templates: tasks,
15646 offset: snapshot
15647 .buffer_snapshot()
15648 .anchor_before(runnable.run_range.start),
15649 context_range,
15650 column: point.column,
15651 extra_variables: runnable.extra_captures,
15652 },
15653 ));
15654 }
15655 runnable_rows
15656 })
15657 }
15658
15659 fn templates_with_tags(
15660 project: &Entity<Project>,
15661 runnable: &mut Runnable,
15662 cx: &mut App,
15663 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15664 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15665 let (worktree_id, file) = project
15666 .buffer_for_id(runnable.buffer, cx)
15667 .and_then(|buffer| buffer.read(cx).file())
15668 .map(|file| (file.worktree_id(cx), file.clone()))
15669 .unzip();
15670
15671 (
15672 project.task_store().read(cx).task_inventory().cloned(),
15673 worktree_id,
15674 file,
15675 )
15676 });
15677
15678 let tags = mem::take(&mut runnable.tags);
15679 let language = runnable.language.clone();
15680 cx.spawn(async move |cx| {
15681 let mut templates_with_tags = Vec::new();
15682 if let Some(inventory) = inventory {
15683 for RunnableTag(tag) in tags {
15684 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15685 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15686 }) else {
15687 return templates_with_tags;
15688 };
15689 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15690 move |(_, template)| {
15691 template.tags.iter().any(|source_tag| source_tag == &tag)
15692 },
15693 ));
15694 }
15695 }
15696 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15697
15698 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15699 // Strongest source wins; if we have worktree tag binding, prefer that to
15700 // global and language bindings;
15701 // if we have a global binding, prefer that to language binding.
15702 let first_mismatch = templates_with_tags
15703 .iter()
15704 .position(|(tag_source, _)| tag_source != leading_tag_source);
15705 if let Some(index) = first_mismatch {
15706 templates_with_tags.truncate(index);
15707 }
15708 }
15709
15710 templates_with_tags
15711 })
15712 }
15713
15714 pub fn move_to_enclosing_bracket(
15715 &mut self,
15716 _: &MoveToEnclosingBracket,
15717 window: &mut Window,
15718 cx: &mut Context<Self>,
15719 ) {
15720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15721 self.change_selections(Default::default(), window, cx, |s| {
15722 s.move_offsets_with(|snapshot, selection| {
15723 let Some(enclosing_bracket_ranges) =
15724 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15725 else {
15726 return;
15727 };
15728
15729 let mut best_length = usize::MAX;
15730 let mut best_inside = false;
15731 let mut best_in_bracket_range = false;
15732 let mut best_destination = None;
15733 for (open, close) in enclosing_bracket_ranges {
15734 let close = close.to_inclusive();
15735 let length = close.end() - open.start;
15736 let inside = selection.start >= open.end && selection.end <= *close.start();
15737 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15738 || close.contains(&selection.head());
15739
15740 // If best is next to a bracket and current isn't, skip
15741 if !in_bracket_range && best_in_bracket_range {
15742 continue;
15743 }
15744
15745 // Prefer smaller lengths unless best is inside and current isn't
15746 if length > best_length && (best_inside || !inside) {
15747 continue;
15748 }
15749
15750 best_length = length;
15751 best_inside = inside;
15752 best_in_bracket_range = in_bracket_range;
15753 best_destination = Some(
15754 if close.contains(&selection.start) && close.contains(&selection.end) {
15755 if inside { open.end } else { open.start }
15756 } else if inside {
15757 *close.start()
15758 } else {
15759 *close.end()
15760 },
15761 );
15762 }
15763
15764 if let Some(destination) = best_destination {
15765 selection.collapse_to(destination, SelectionGoal::None);
15766 }
15767 })
15768 });
15769 }
15770
15771 pub fn undo_selection(
15772 &mut self,
15773 _: &UndoSelection,
15774 window: &mut Window,
15775 cx: &mut Context<Self>,
15776 ) {
15777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15778 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15779 self.selection_history.mode = SelectionHistoryMode::Undoing;
15780 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15781 this.end_selection(window, cx);
15782 this.change_selections(
15783 SelectionEffects::scroll(Autoscroll::newest()),
15784 window,
15785 cx,
15786 |s| s.select_anchors(entry.selections.to_vec()),
15787 );
15788 });
15789 self.selection_history.mode = SelectionHistoryMode::Normal;
15790
15791 self.select_next_state = entry.select_next_state;
15792 self.select_prev_state = entry.select_prev_state;
15793 self.add_selections_state = entry.add_selections_state;
15794 }
15795 }
15796
15797 pub fn redo_selection(
15798 &mut self,
15799 _: &RedoSelection,
15800 window: &mut Window,
15801 cx: &mut Context<Self>,
15802 ) {
15803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15804 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15805 self.selection_history.mode = SelectionHistoryMode::Redoing;
15806 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15807 this.end_selection(window, cx);
15808 this.change_selections(
15809 SelectionEffects::scroll(Autoscroll::newest()),
15810 window,
15811 cx,
15812 |s| s.select_anchors(entry.selections.to_vec()),
15813 );
15814 });
15815 self.selection_history.mode = SelectionHistoryMode::Normal;
15816
15817 self.select_next_state = entry.select_next_state;
15818 self.select_prev_state = entry.select_prev_state;
15819 self.add_selections_state = entry.add_selections_state;
15820 }
15821 }
15822
15823 pub fn expand_excerpts(
15824 &mut self,
15825 action: &ExpandExcerpts,
15826 _: &mut Window,
15827 cx: &mut Context<Self>,
15828 ) {
15829 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15830 }
15831
15832 pub fn expand_excerpts_down(
15833 &mut self,
15834 action: &ExpandExcerptsDown,
15835 _: &mut Window,
15836 cx: &mut Context<Self>,
15837 ) {
15838 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15839 }
15840
15841 pub fn expand_excerpts_up(
15842 &mut self,
15843 action: &ExpandExcerptsUp,
15844 _: &mut Window,
15845 cx: &mut Context<Self>,
15846 ) {
15847 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15848 }
15849
15850 pub fn expand_excerpts_for_direction(
15851 &mut self,
15852 lines: u32,
15853 direction: ExpandExcerptDirection,
15854
15855 cx: &mut Context<Self>,
15856 ) {
15857 let selections = self.selections.disjoint_anchors_arc();
15858
15859 let lines = if lines == 0 {
15860 EditorSettings::get_global(cx).expand_excerpt_lines
15861 } else {
15862 lines
15863 };
15864
15865 self.buffer.update(cx, |buffer, cx| {
15866 let snapshot = buffer.snapshot(cx);
15867 let mut excerpt_ids = selections
15868 .iter()
15869 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15870 .collect::<Vec<_>>();
15871 excerpt_ids.sort();
15872 excerpt_ids.dedup();
15873 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15874 })
15875 }
15876
15877 pub fn expand_excerpt(
15878 &mut self,
15879 excerpt: ExcerptId,
15880 direction: ExpandExcerptDirection,
15881 window: &mut Window,
15882 cx: &mut Context<Self>,
15883 ) {
15884 let current_scroll_position = self.scroll_position(cx);
15885 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15886 let mut scroll = None;
15887
15888 if direction == ExpandExcerptDirection::Down {
15889 let multi_buffer = self.buffer.read(cx);
15890 let snapshot = multi_buffer.snapshot(cx);
15891 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15892 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15893 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15894 {
15895 let buffer_snapshot = buffer.read(cx).snapshot();
15896 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15897 let last_row = buffer_snapshot.max_point().row;
15898 let lines_below = last_row.saturating_sub(excerpt_end_row);
15899 if lines_below >= lines_to_expand {
15900 scroll = Some(
15901 current_scroll_position
15902 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15903 );
15904 }
15905 }
15906 }
15907 if direction == ExpandExcerptDirection::Up
15908 && self
15909 .buffer
15910 .read(cx)
15911 .snapshot(cx)
15912 .excerpt_before(excerpt)
15913 .is_none()
15914 {
15915 scroll = Some(current_scroll_position);
15916 }
15917
15918 self.buffer.update(cx, |buffer, cx| {
15919 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15920 });
15921
15922 if let Some(new_scroll_position) = scroll {
15923 self.set_scroll_position(new_scroll_position, window, cx);
15924 }
15925 }
15926
15927 pub fn go_to_singleton_buffer_point(
15928 &mut self,
15929 point: Point,
15930 window: &mut Window,
15931 cx: &mut Context<Self>,
15932 ) {
15933 self.go_to_singleton_buffer_range(point..point, window, cx);
15934 }
15935
15936 pub fn go_to_singleton_buffer_range(
15937 &mut self,
15938 range: Range<Point>,
15939 window: &mut Window,
15940 cx: &mut Context<Self>,
15941 ) {
15942 let multibuffer = self.buffer().read(cx);
15943 let Some(buffer) = multibuffer.as_singleton() else {
15944 return;
15945 };
15946 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15947 return;
15948 };
15949 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15950 return;
15951 };
15952 self.change_selections(
15953 SelectionEffects::default().nav_history(true),
15954 window,
15955 cx,
15956 |s| s.select_anchor_ranges([start..end]),
15957 );
15958 }
15959
15960 pub fn go_to_diagnostic(
15961 &mut self,
15962 action: &GoToDiagnostic,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 if !self.diagnostics_enabled() {
15967 return;
15968 }
15969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15970 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15971 }
15972
15973 pub fn go_to_prev_diagnostic(
15974 &mut self,
15975 action: &GoToPreviousDiagnostic,
15976 window: &mut Window,
15977 cx: &mut Context<Self>,
15978 ) {
15979 if !self.diagnostics_enabled() {
15980 return;
15981 }
15982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15983 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15984 }
15985
15986 pub fn go_to_diagnostic_impl(
15987 &mut self,
15988 direction: Direction,
15989 severity: GoToDiagnosticSeverityFilter,
15990 window: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 let buffer = self.buffer.read(cx).snapshot(cx);
15994 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15995
15996 let mut active_group_id = None;
15997 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15998 && active_group.active_range.start.to_offset(&buffer) == selection.start
15999 {
16000 active_group_id = Some(active_group.group_id);
16001 }
16002
16003 fn filtered<'a>(
16004 snapshot: EditorSnapshot,
16005 severity: GoToDiagnosticSeverityFilter,
16006 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16007 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16008 diagnostics
16009 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16010 .filter(|entry| entry.range.start != entry.range.end)
16011 .filter(|entry| !entry.diagnostic.is_unnecessary)
16012 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16013 }
16014
16015 let snapshot = self.snapshot(window, cx);
16016 let before = filtered(
16017 snapshot.clone(),
16018 severity,
16019 buffer
16020 .diagnostics_in_range(0..selection.start)
16021 .filter(|entry| entry.range.start <= selection.start),
16022 );
16023 let after = filtered(
16024 snapshot,
16025 severity,
16026 buffer
16027 .diagnostics_in_range(selection.start..buffer.len())
16028 .filter(|entry| entry.range.start >= selection.start),
16029 );
16030
16031 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16032 if direction == Direction::Prev {
16033 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16034 {
16035 for diagnostic in prev_diagnostics.into_iter().rev() {
16036 if diagnostic.range.start != selection.start
16037 || active_group_id
16038 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16039 {
16040 found = Some(diagnostic);
16041 break 'outer;
16042 }
16043 }
16044 }
16045 } else {
16046 for diagnostic in after.chain(before) {
16047 if diagnostic.range.start != selection.start
16048 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16049 {
16050 found = Some(diagnostic);
16051 break;
16052 }
16053 }
16054 }
16055 let Some(next_diagnostic) = found else {
16056 return;
16057 };
16058
16059 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16060 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16061 return;
16062 };
16063 self.change_selections(Default::default(), window, cx, |s| {
16064 s.select_ranges(vec![
16065 next_diagnostic.range.start..next_diagnostic.range.start,
16066 ])
16067 });
16068 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16069 self.refresh_edit_prediction(false, true, window, cx);
16070 }
16071
16072 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16074 let snapshot = self.snapshot(window, cx);
16075 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16076 self.go_to_hunk_before_or_after_position(
16077 &snapshot,
16078 selection.head(),
16079 Direction::Next,
16080 window,
16081 cx,
16082 );
16083 }
16084
16085 pub fn go_to_hunk_before_or_after_position(
16086 &mut self,
16087 snapshot: &EditorSnapshot,
16088 position: Point,
16089 direction: Direction,
16090 window: &mut Window,
16091 cx: &mut Context<Editor>,
16092 ) {
16093 let row = if direction == Direction::Next {
16094 self.hunk_after_position(snapshot, position)
16095 .map(|hunk| hunk.row_range.start)
16096 } else {
16097 self.hunk_before_position(snapshot, position)
16098 };
16099
16100 if let Some(row) = row {
16101 let destination = Point::new(row.0, 0);
16102 let autoscroll = Autoscroll::center();
16103
16104 self.unfold_ranges(&[destination..destination], false, false, cx);
16105 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16106 s.select_ranges([destination..destination]);
16107 });
16108 }
16109 }
16110
16111 fn hunk_after_position(
16112 &mut self,
16113 snapshot: &EditorSnapshot,
16114 position: Point,
16115 ) -> Option<MultiBufferDiffHunk> {
16116 snapshot
16117 .buffer_snapshot()
16118 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16119 .find(|hunk| hunk.row_range.start.0 > position.row)
16120 .or_else(|| {
16121 snapshot
16122 .buffer_snapshot()
16123 .diff_hunks_in_range(Point::zero()..position)
16124 .find(|hunk| hunk.row_range.end.0 < position.row)
16125 })
16126 }
16127
16128 fn go_to_prev_hunk(
16129 &mut self,
16130 _: &GoToPreviousHunk,
16131 window: &mut Window,
16132 cx: &mut Context<Self>,
16133 ) {
16134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16135 let snapshot = self.snapshot(window, cx);
16136 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16137 self.go_to_hunk_before_or_after_position(
16138 &snapshot,
16139 selection.head(),
16140 Direction::Prev,
16141 window,
16142 cx,
16143 );
16144 }
16145
16146 fn hunk_before_position(
16147 &mut self,
16148 snapshot: &EditorSnapshot,
16149 position: Point,
16150 ) -> Option<MultiBufferRow> {
16151 snapshot
16152 .buffer_snapshot()
16153 .diff_hunk_before(position)
16154 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16155 }
16156
16157 fn go_to_next_change(
16158 &mut self,
16159 _: &GoToNextChange,
16160 window: &mut Window,
16161 cx: &mut Context<Self>,
16162 ) {
16163 if let Some(selections) = self
16164 .change_list
16165 .next_change(1, Direction::Next)
16166 .map(|s| s.to_vec())
16167 {
16168 self.change_selections(Default::default(), window, cx, |s| {
16169 let map = s.display_map();
16170 s.select_display_ranges(selections.iter().map(|a| {
16171 let point = a.to_display_point(&map);
16172 point..point
16173 }))
16174 })
16175 }
16176 }
16177
16178 fn go_to_previous_change(
16179 &mut self,
16180 _: &GoToPreviousChange,
16181 window: &mut Window,
16182 cx: &mut Context<Self>,
16183 ) {
16184 if let Some(selections) = self
16185 .change_list
16186 .next_change(1, Direction::Prev)
16187 .map(|s| s.to_vec())
16188 {
16189 self.change_selections(Default::default(), window, cx, |s| {
16190 let map = s.display_map();
16191 s.select_display_ranges(selections.iter().map(|a| {
16192 let point = a.to_display_point(&map);
16193 point..point
16194 }))
16195 })
16196 }
16197 }
16198
16199 pub fn go_to_next_document_highlight(
16200 &mut self,
16201 _: &GoToNextDocumentHighlight,
16202 window: &mut Window,
16203 cx: &mut Context<Self>,
16204 ) {
16205 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16206 }
16207
16208 pub fn go_to_prev_document_highlight(
16209 &mut self,
16210 _: &GoToPreviousDocumentHighlight,
16211 window: &mut Window,
16212 cx: &mut Context<Self>,
16213 ) {
16214 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16215 }
16216
16217 pub fn go_to_document_highlight_before_or_after_position(
16218 &mut self,
16219 direction: Direction,
16220 window: &mut Window,
16221 cx: &mut Context<Editor>,
16222 ) {
16223 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16224 let snapshot = self.snapshot(window, cx);
16225 let buffer = &snapshot.buffer_snapshot();
16226 let position = self
16227 .selections
16228 .newest::<Point>(&snapshot.display_snapshot)
16229 .head();
16230 let anchor_position = buffer.anchor_after(position);
16231
16232 // Get all document highlights (both read and write)
16233 let mut all_highlights = Vec::new();
16234
16235 if let Some((_, read_highlights)) = self
16236 .background_highlights
16237 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16238 {
16239 all_highlights.extend(read_highlights.iter());
16240 }
16241
16242 if let Some((_, write_highlights)) = self
16243 .background_highlights
16244 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16245 {
16246 all_highlights.extend(write_highlights.iter());
16247 }
16248
16249 if all_highlights.is_empty() {
16250 return;
16251 }
16252
16253 // Sort highlights by position
16254 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16255
16256 let target_highlight = match direction {
16257 Direction::Next => {
16258 // Find the first highlight after the current position
16259 all_highlights
16260 .iter()
16261 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16262 }
16263 Direction::Prev => {
16264 // Find the last highlight before the current position
16265 all_highlights
16266 .iter()
16267 .rev()
16268 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16269 }
16270 };
16271
16272 if let Some(highlight) = target_highlight {
16273 let destination = highlight.start.to_point(buffer);
16274 let autoscroll = Autoscroll::center();
16275
16276 self.unfold_ranges(&[destination..destination], false, false, cx);
16277 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16278 s.select_ranges([destination..destination]);
16279 });
16280 }
16281 }
16282
16283 fn go_to_line<T: 'static>(
16284 &mut self,
16285 position: Anchor,
16286 highlight_color: Option<Hsla>,
16287 window: &mut Window,
16288 cx: &mut Context<Self>,
16289 ) {
16290 let snapshot = self.snapshot(window, cx).display_snapshot;
16291 let position = position.to_point(&snapshot.buffer_snapshot());
16292 let start = snapshot
16293 .buffer_snapshot()
16294 .clip_point(Point::new(position.row, 0), Bias::Left);
16295 let end = start + Point::new(1, 0);
16296 let start = snapshot.buffer_snapshot().anchor_before(start);
16297 let end = snapshot.buffer_snapshot().anchor_before(end);
16298
16299 self.highlight_rows::<T>(
16300 start..end,
16301 highlight_color
16302 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16303 Default::default(),
16304 cx,
16305 );
16306
16307 if self.buffer.read(cx).is_singleton() {
16308 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16309 }
16310 }
16311
16312 pub fn go_to_definition(
16313 &mut self,
16314 _: &GoToDefinition,
16315 window: &mut Window,
16316 cx: &mut Context<Self>,
16317 ) -> Task<Result<Navigated>> {
16318 let definition =
16319 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16320 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16321 cx.spawn_in(window, async move |editor, cx| {
16322 if definition.await? == Navigated::Yes {
16323 return Ok(Navigated::Yes);
16324 }
16325 match fallback_strategy {
16326 GoToDefinitionFallback::None => Ok(Navigated::No),
16327 GoToDefinitionFallback::FindAllReferences => {
16328 match editor.update_in(cx, |editor, window, cx| {
16329 editor.find_all_references(&FindAllReferences, window, cx)
16330 })? {
16331 Some(references) => references.await,
16332 None => Ok(Navigated::No),
16333 }
16334 }
16335 }
16336 })
16337 }
16338
16339 pub fn go_to_declaration(
16340 &mut self,
16341 _: &GoToDeclaration,
16342 window: &mut Window,
16343 cx: &mut Context<Self>,
16344 ) -> Task<Result<Navigated>> {
16345 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16346 }
16347
16348 pub fn go_to_declaration_split(
16349 &mut self,
16350 _: &GoToDeclaration,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) -> Task<Result<Navigated>> {
16354 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16355 }
16356
16357 pub fn go_to_implementation(
16358 &mut self,
16359 _: &GoToImplementation,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) -> Task<Result<Navigated>> {
16363 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16364 }
16365
16366 pub fn go_to_implementation_split(
16367 &mut self,
16368 _: &GoToImplementationSplit,
16369 window: &mut Window,
16370 cx: &mut Context<Self>,
16371 ) -> Task<Result<Navigated>> {
16372 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16373 }
16374
16375 pub fn go_to_type_definition(
16376 &mut self,
16377 _: &GoToTypeDefinition,
16378 window: &mut Window,
16379 cx: &mut Context<Self>,
16380 ) -> Task<Result<Navigated>> {
16381 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16382 }
16383
16384 pub fn go_to_definition_split(
16385 &mut self,
16386 _: &GoToDefinitionSplit,
16387 window: &mut Window,
16388 cx: &mut Context<Self>,
16389 ) -> Task<Result<Navigated>> {
16390 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16391 }
16392
16393 pub fn go_to_type_definition_split(
16394 &mut self,
16395 _: &GoToTypeDefinitionSplit,
16396 window: &mut Window,
16397 cx: &mut Context<Self>,
16398 ) -> Task<Result<Navigated>> {
16399 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16400 }
16401
16402 fn go_to_definition_of_kind(
16403 &mut self,
16404 kind: GotoDefinitionKind,
16405 split: bool,
16406 window: &mut Window,
16407 cx: &mut Context<Self>,
16408 ) -> Task<Result<Navigated>> {
16409 let Some(provider) = self.semantics_provider.clone() else {
16410 return Task::ready(Ok(Navigated::No));
16411 };
16412 let head = self
16413 .selections
16414 .newest::<usize>(&self.display_snapshot(cx))
16415 .head();
16416 let buffer = self.buffer.read(cx);
16417 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16418 return Task::ready(Ok(Navigated::No));
16419 };
16420 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16421 return Task::ready(Ok(Navigated::No));
16422 };
16423
16424 cx.spawn_in(window, async move |editor, cx| {
16425 let Some(definitions) = definitions.await? else {
16426 return Ok(Navigated::No);
16427 };
16428 let navigated = editor
16429 .update_in(cx, |editor, window, cx| {
16430 editor.navigate_to_hover_links(
16431 Some(kind),
16432 definitions
16433 .into_iter()
16434 .filter(|location| {
16435 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16436 })
16437 .map(HoverLink::Text)
16438 .collect::<Vec<_>>(),
16439 split,
16440 window,
16441 cx,
16442 )
16443 })?
16444 .await?;
16445 anyhow::Ok(navigated)
16446 })
16447 }
16448
16449 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16450 let selection = self.selections.newest_anchor();
16451 let head = selection.head();
16452 let tail = selection.tail();
16453
16454 let Some((buffer, start_position)) =
16455 self.buffer.read(cx).text_anchor_for_position(head, cx)
16456 else {
16457 return;
16458 };
16459
16460 let end_position = if head != tail {
16461 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16462 return;
16463 };
16464 Some(pos)
16465 } else {
16466 None
16467 };
16468
16469 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16470 let url = if let Some(end_pos) = end_position {
16471 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16472 } else {
16473 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16474 };
16475
16476 if let Some(url) = url {
16477 cx.update(|window, cx| {
16478 if parse_zed_link(&url, cx).is_some() {
16479 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16480 } else {
16481 cx.open_url(&url);
16482 }
16483 })?;
16484 }
16485
16486 anyhow::Ok(())
16487 });
16488
16489 url_finder.detach();
16490 }
16491
16492 pub fn open_selected_filename(
16493 &mut self,
16494 _: &OpenSelectedFilename,
16495 window: &mut Window,
16496 cx: &mut Context<Self>,
16497 ) {
16498 let Some(workspace) = self.workspace() else {
16499 return;
16500 };
16501
16502 let position = self.selections.newest_anchor().head();
16503
16504 let Some((buffer, buffer_position)) =
16505 self.buffer.read(cx).text_anchor_for_position(position, cx)
16506 else {
16507 return;
16508 };
16509
16510 let project = self.project.clone();
16511
16512 cx.spawn_in(window, async move |_, cx| {
16513 let result = find_file(&buffer, project, buffer_position, cx).await;
16514
16515 if let Some((_, path)) = result {
16516 workspace
16517 .update_in(cx, |workspace, window, cx| {
16518 workspace.open_resolved_path(path, window, cx)
16519 })?
16520 .await?;
16521 }
16522 anyhow::Ok(())
16523 })
16524 .detach();
16525 }
16526
16527 pub(crate) fn navigate_to_hover_links(
16528 &mut self,
16529 kind: Option<GotoDefinitionKind>,
16530 definitions: Vec<HoverLink>,
16531 split: bool,
16532 window: &mut Window,
16533 cx: &mut Context<Editor>,
16534 ) -> Task<Result<Navigated>> {
16535 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16536 let mut first_url_or_file = None;
16537 let definitions: Vec<_> = definitions
16538 .into_iter()
16539 .filter_map(|def| match def {
16540 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16541 HoverLink::InlayHint(lsp_location, server_id) => {
16542 let computation =
16543 self.compute_target_location(lsp_location, server_id, window, cx);
16544 Some(cx.background_spawn(computation))
16545 }
16546 HoverLink::Url(url) => {
16547 first_url_or_file = Some(Either::Left(url));
16548 None
16549 }
16550 HoverLink::File(path) => {
16551 first_url_or_file = Some(Either::Right(path));
16552 None
16553 }
16554 })
16555 .collect();
16556
16557 let workspace = self.workspace();
16558
16559 cx.spawn_in(window, async move |editor, cx| {
16560 let locations: Vec<Location> = future::join_all(definitions)
16561 .await
16562 .into_iter()
16563 .filter_map(|location| location.transpose())
16564 .collect::<Result<_>>()
16565 .context("location tasks")?;
16566 let mut locations = cx.update(|_, cx| {
16567 locations
16568 .into_iter()
16569 .map(|location| {
16570 let buffer = location.buffer.read(cx);
16571 (location.buffer, location.range.to_point(buffer))
16572 })
16573 .into_group_map()
16574 })?;
16575 let mut num_locations = 0;
16576 for ranges in locations.values_mut() {
16577 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16578 ranges.dedup();
16579 num_locations += ranges.len();
16580 }
16581
16582 if num_locations > 1 {
16583 let Some(workspace) = workspace else {
16584 return Ok(Navigated::No);
16585 };
16586
16587 let tab_kind = match kind {
16588 Some(GotoDefinitionKind::Implementation) => "Implementations",
16589 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16590 Some(GotoDefinitionKind::Declaration) => "Declarations",
16591 Some(GotoDefinitionKind::Type) => "Types",
16592 };
16593 let title = editor
16594 .update_in(cx, |_, _, cx| {
16595 let target = locations
16596 .iter()
16597 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16598 .map(|(buffer, location)| {
16599 buffer
16600 .read(cx)
16601 .text_for_range(location.clone())
16602 .collect::<String>()
16603 })
16604 .filter(|text| !text.contains('\n'))
16605 .unique()
16606 .take(3)
16607 .join(", ");
16608 if target.is_empty() {
16609 tab_kind.to_owned()
16610 } else {
16611 format!("{tab_kind} for {target}")
16612 }
16613 })
16614 .context("buffer title")?;
16615
16616 let opened = workspace
16617 .update_in(cx, |workspace, window, cx| {
16618 Self::open_locations_in_multibuffer(
16619 workspace,
16620 locations,
16621 title,
16622 split,
16623 MultibufferSelectionMode::First,
16624 window,
16625 cx,
16626 )
16627 })
16628 .is_ok();
16629
16630 anyhow::Ok(Navigated::from_bool(opened))
16631 } else if num_locations == 0 {
16632 // If there is one url or file, open it directly
16633 match first_url_or_file {
16634 Some(Either::Left(url)) => {
16635 cx.update(|_, cx| cx.open_url(&url))?;
16636 Ok(Navigated::Yes)
16637 }
16638 Some(Either::Right(path)) => {
16639 let Some(workspace) = workspace else {
16640 return Ok(Navigated::No);
16641 };
16642
16643 workspace
16644 .update_in(cx, |workspace, window, cx| {
16645 workspace.open_resolved_path(path, window, cx)
16646 })?
16647 .await?;
16648 Ok(Navigated::Yes)
16649 }
16650 None => Ok(Navigated::No),
16651 }
16652 } else {
16653 let Some(workspace) = workspace else {
16654 return Ok(Navigated::No);
16655 };
16656
16657 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16658 let target_range = target_ranges.first().unwrap().clone();
16659
16660 editor.update_in(cx, |editor, window, cx| {
16661 let range = target_range.to_point(target_buffer.read(cx));
16662 let range = editor.range_for_match(&range, false);
16663 let range = collapse_multiline_range(range);
16664
16665 if !split
16666 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16667 {
16668 editor.go_to_singleton_buffer_range(range, window, cx);
16669 } else {
16670 let pane = workspace.read(cx).active_pane().clone();
16671 window.defer(cx, move |window, cx| {
16672 let target_editor: Entity<Self> =
16673 workspace.update(cx, |workspace, cx| {
16674 let pane = if split {
16675 workspace.adjacent_pane(window, cx)
16676 } else {
16677 workspace.active_pane().clone()
16678 };
16679
16680 workspace.open_project_item(
16681 pane,
16682 target_buffer.clone(),
16683 true,
16684 true,
16685 window,
16686 cx,
16687 )
16688 });
16689 target_editor.update(cx, |target_editor, cx| {
16690 // When selecting a definition in a different buffer, disable the nav history
16691 // to avoid creating a history entry at the previous cursor location.
16692 pane.update(cx, |pane, _| pane.disable_history());
16693 target_editor.go_to_singleton_buffer_range(range, window, cx);
16694 pane.update(cx, |pane, _| pane.enable_history());
16695 });
16696 });
16697 }
16698 Navigated::Yes
16699 })
16700 }
16701 })
16702 }
16703
16704 fn compute_target_location(
16705 &self,
16706 lsp_location: lsp::Location,
16707 server_id: LanguageServerId,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) -> Task<anyhow::Result<Option<Location>>> {
16711 let Some(project) = self.project.clone() else {
16712 return Task::ready(Ok(None));
16713 };
16714
16715 cx.spawn_in(window, async move |editor, cx| {
16716 let location_task = editor.update(cx, |_, cx| {
16717 project.update(cx, |project, cx| {
16718 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16719 })
16720 })?;
16721 let location = Some({
16722 let target_buffer_handle = location_task.await.context("open local buffer")?;
16723 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16724 let target_start = target_buffer
16725 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16726 let target_end = target_buffer
16727 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16728 target_buffer.anchor_after(target_start)
16729 ..target_buffer.anchor_before(target_end)
16730 })?;
16731 Location {
16732 buffer: target_buffer_handle,
16733 range,
16734 }
16735 });
16736 Ok(location)
16737 })
16738 }
16739
16740 fn go_to_next_reference(
16741 &mut self,
16742 _: &GoToNextReference,
16743 window: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) {
16746 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16747 if let Some(task) = task {
16748 task.detach();
16749 };
16750 }
16751
16752 fn go_to_prev_reference(
16753 &mut self,
16754 _: &GoToPreviousReference,
16755 window: &mut Window,
16756 cx: &mut Context<Self>,
16757 ) {
16758 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16759 if let Some(task) = task {
16760 task.detach();
16761 };
16762 }
16763
16764 pub fn go_to_reference_before_or_after_position(
16765 &mut self,
16766 direction: Direction,
16767 count: usize,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) -> Option<Task<Result<()>>> {
16771 let selection = self.selections.newest_anchor();
16772 let head = selection.head();
16773
16774 let multi_buffer = self.buffer.read(cx);
16775
16776 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16777 let workspace = self.workspace()?;
16778 let project = workspace.read(cx).project().clone();
16779 let references =
16780 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16781 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16782 let Some(locations) = references.await? else {
16783 return Ok(());
16784 };
16785
16786 if locations.is_empty() {
16787 // totally normal - the cursor may be on something which is not
16788 // a symbol (e.g. a keyword)
16789 log::info!("no references found under cursor");
16790 return Ok(());
16791 }
16792
16793 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16794
16795 let multi_buffer_snapshot =
16796 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16797
16798 let (locations, current_location_index) =
16799 multi_buffer.update(cx, |multi_buffer, cx| {
16800 let mut locations = locations
16801 .into_iter()
16802 .filter_map(|loc| {
16803 let start = multi_buffer.buffer_anchor_to_anchor(
16804 &loc.buffer,
16805 loc.range.start,
16806 cx,
16807 )?;
16808 let end = multi_buffer.buffer_anchor_to_anchor(
16809 &loc.buffer,
16810 loc.range.end,
16811 cx,
16812 )?;
16813 Some(start..end)
16814 })
16815 .collect::<Vec<_>>();
16816
16817 // There is an O(n) implementation, but given this list will be
16818 // small (usually <100 items), the extra O(log(n)) factor isn't
16819 // worth the (surprisingly large amount of) extra complexity.
16820 locations
16821 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16822
16823 let head_offset = head.to_offset(&multi_buffer_snapshot);
16824
16825 let current_location_index = locations.iter().position(|loc| {
16826 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16827 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16828 });
16829
16830 (locations, current_location_index)
16831 })?;
16832
16833 let Some(current_location_index) = current_location_index else {
16834 // This indicates something has gone wrong, because we already
16835 // handle the "no references" case above
16836 log::error!(
16837 "failed to find current reference under cursor. Total references: {}",
16838 locations.len()
16839 );
16840 return Ok(());
16841 };
16842
16843 let destination_location_index = match direction {
16844 Direction::Next => (current_location_index + count) % locations.len(),
16845 Direction::Prev => {
16846 (current_location_index + locations.len() - count % locations.len())
16847 % locations.len()
16848 }
16849 };
16850
16851 // TODO(cameron): is this needed?
16852 // the thinking is to avoid "jumping to the current location" (avoid
16853 // polluting "jumplist" in vim terms)
16854 if current_location_index == destination_location_index {
16855 return Ok(());
16856 }
16857
16858 let Range { start, end } = locations[destination_location_index];
16859
16860 editor.update_in(cx, |editor, window, cx| {
16861 let effects = SelectionEffects::default();
16862
16863 editor.unfold_ranges(&[start..end], false, false, cx);
16864 editor.change_selections(effects, window, cx, |s| {
16865 s.select_ranges([start..start]);
16866 });
16867 })?;
16868
16869 Ok(())
16870 }))
16871 }
16872
16873 pub fn find_all_references(
16874 &mut self,
16875 _: &FindAllReferences,
16876 window: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) -> Option<Task<Result<Navigated>>> {
16879 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16880 let multi_buffer = self.buffer.read(cx);
16881 let head = selection.head();
16882
16883 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16884 let head_anchor = multi_buffer_snapshot.anchor_at(
16885 head,
16886 if head < selection.tail() {
16887 Bias::Right
16888 } else {
16889 Bias::Left
16890 },
16891 );
16892
16893 match self
16894 .find_all_references_task_sources
16895 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16896 {
16897 Ok(_) => {
16898 log::info!(
16899 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16900 );
16901 return None;
16902 }
16903 Err(i) => {
16904 self.find_all_references_task_sources.insert(i, head_anchor);
16905 }
16906 }
16907
16908 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16909 let workspace = self.workspace()?;
16910 let project = workspace.read(cx).project().clone();
16911 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16912 Some(cx.spawn_in(window, async move |editor, cx| {
16913 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16914 if let Ok(i) = editor
16915 .find_all_references_task_sources
16916 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16917 {
16918 editor.find_all_references_task_sources.remove(i);
16919 }
16920 });
16921
16922 let Some(locations) = references.await? else {
16923 return anyhow::Ok(Navigated::No);
16924 };
16925 let mut locations = cx.update(|_, cx| {
16926 locations
16927 .into_iter()
16928 .map(|location| {
16929 let buffer = location.buffer.read(cx);
16930 (location.buffer, location.range.to_point(buffer))
16931 })
16932 .into_group_map()
16933 })?;
16934 if locations.is_empty() {
16935 return anyhow::Ok(Navigated::No);
16936 }
16937 for ranges in locations.values_mut() {
16938 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16939 ranges.dedup();
16940 }
16941
16942 workspace.update_in(cx, |workspace, window, cx| {
16943 let target = locations
16944 .iter()
16945 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16946 .map(|(buffer, location)| {
16947 buffer
16948 .read(cx)
16949 .text_for_range(location.clone())
16950 .collect::<String>()
16951 })
16952 .filter(|text| !text.contains('\n'))
16953 .unique()
16954 .take(3)
16955 .join(", ");
16956 let title = if target.is_empty() {
16957 "References".to_owned()
16958 } else {
16959 format!("References to {target}")
16960 };
16961 Self::open_locations_in_multibuffer(
16962 workspace,
16963 locations,
16964 title,
16965 false,
16966 MultibufferSelectionMode::First,
16967 window,
16968 cx,
16969 );
16970 Navigated::Yes
16971 })
16972 }))
16973 }
16974
16975 /// Opens a multibuffer with the given project locations in it
16976 pub fn open_locations_in_multibuffer(
16977 workspace: &mut Workspace,
16978 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16979 title: String,
16980 split: bool,
16981 multibuffer_selection_mode: MultibufferSelectionMode,
16982 window: &mut Window,
16983 cx: &mut Context<Workspace>,
16984 ) {
16985 if locations.is_empty() {
16986 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16987 return;
16988 }
16989
16990 let capability = workspace.project().read(cx).capability();
16991 let mut ranges = <Vec<Range<Anchor>>>::new();
16992
16993 // a key to find existing multibuffer editors with the same set of locations
16994 // to prevent us from opening more and more multibuffer tabs for searches and the like
16995 let mut key = (title.clone(), vec![]);
16996 let excerpt_buffer = cx.new(|cx| {
16997 let key = &mut key.1;
16998 let mut multibuffer = MultiBuffer::new(capability);
16999 for (buffer, mut ranges_for_buffer) in locations {
17000 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17001 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17002 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17003 PathKey::for_buffer(&buffer, cx),
17004 buffer.clone(),
17005 ranges_for_buffer,
17006 multibuffer_context_lines(cx),
17007 cx,
17008 );
17009 ranges.extend(new_ranges)
17010 }
17011
17012 multibuffer.with_title(title)
17013 });
17014 let existing = workspace.active_pane().update(cx, |pane, cx| {
17015 pane.items()
17016 .filter_map(|item| item.downcast::<Editor>())
17017 .find(|editor| {
17018 editor
17019 .read(cx)
17020 .lookup_key
17021 .as_ref()
17022 .and_then(|it| {
17023 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17024 })
17025 .is_some_and(|it| *it == key)
17026 })
17027 });
17028 let editor = existing.unwrap_or_else(|| {
17029 cx.new(|cx| {
17030 let mut editor = Editor::for_multibuffer(
17031 excerpt_buffer,
17032 Some(workspace.project().clone()),
17033 window,
17034 cx,
17035 );
17036 editor.lookup_key = Some(Box::new(key));
17037 editor
17038 })
17039 });
17040 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17041 MultibufferSelectionMode::First => {
17042 if let Some(first_range) = ranges.first() {
17043 editor.change_selections(
17044 SelectionEffects::no_scroll(),
17045 window,
17046 cx,
17047 |selections| {
17048 selections.clear_disjoint();
17049 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17050 },
17051 );
17052 }
17053 editor.highlight_background::<Self>(
17054 &ranges,
17055 |theme| theme.colors().editor_highlighted_line_background,
17056 cx,
17057 );
17058 }
17059 MultibufferSelectionMode::All => {
17060 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17061 selections.clear_disjoint();
17062 selections.select_anchor_ranges(ranges);
17063 });
17064 }
17065 });
17066
17067 let item = Box::new(editor);
17068 let item_id = item.item_id();
17069
17070 if split {
17071 let pane = workspace.adjacent_pane(window, cx);
17072 workspace.add_item(pane, item, None, true, true, window, cx);
17073 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17074 let (preview_item_id, preview_item_idx) =
17075 workspace.active_pane().read_with(cx, |pane, _| {
17076 (pane.preview_item_id(), pane.preview_item_idx())
17077 });
17078
17079 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17080
17081 if let Some(preview_item_id) = preview_item_id {
17082 workspace.active_pane().update(cx, |pane, cx| {
17083 pane.remove_item(preview_item_id, false, false, window, cx);
17084 });
17085 }
17086 } else {
17087 workspace.add_item_to_active_pane(item, None, true, window, cx);
17088 }
17089 workspace.active_pane().update(cx, |pane, cx| {
17090 pane.set_preview_item_id(Some(item_id), cx);
17091 });
17092 }
17093
17094 pub fn rename(
17095 &mut self,
17096 _: &Rename,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) -> Option<Task<Result<()>>> {
17100 use language::ToOffset as _;
17101
17102 let provider = self.semantics_provider.clone()?;
17103 let selection = self.selections.newest_anchor().clone();
17104 let (cursor_buffer, cursor_buffer_position) = self
17105 .buffer
17106 .read(cx)
17107 .text_anchor_for_position(selection.head(), cx)?;
17108 let (tail_buffer, cursor_buffer_position_end) = self
17109 .buffer
17110 .read(cx)
17111 .text_anchor_for_position(selection.tail(), cx)?;
17112 if tail_buffer != cursor_buffer {
17113 return None;
17114 }
17115
17116 let snapshot = cursor_buffer.read(cx).snapshot();
17117 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17118 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17119 let prepare_rename = provider
17120 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17121 .unwrap_or_else(|| Task::ready(Ok(None)));
17122 drop(snapshot);
17123
17124 Some(cx.spawn_in(window, async move |this, cx| {
17125 let rename_range = if let Some(range) = prepare_rename.await? {
17126 Some(range)
17127 } else {
17128 this.update(cx, |this, cx| {
17129 let buffer = this.buffer.read(cx).snapshot(cx);
17130 let mut buffer_highlights = this
17131 .document_highlights_for_position(selection.head(), &buffer)
17132 .filter(|highlight| {
17133 highlight.start.excerpt_id == selection.head().excerpt_id
17134 && highlight.end.excerpt_id == selection.head().excerpt_id
17135 });
17136 buffer_highlights
17137 .next()
17138 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17139 })?
17140 };
17141 if let Some(rename_range) = rename_range {
17142 this.update_in(cx, |this, window, cx| {
17143 let snapshot = cursor_buffer.read(cx).snapshot();
17144 let rename_buffer_range = rename_range.to_offset(&snapshot);
17145 let cursor_offset_in_rename_range =
17146 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17147 let cursor_offset_in_rename_range_end =
17148 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17149
17150 this.take_rename(false, window, cx);
17151 let buffer = this.buffer.read(cx).read(cx);
17152 let cursor_offset = selection.head().to_offset(&buffer);
17153 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17154 let rename_end = rename_start + rename_buffer_range.len();
17155 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17156 let mut old_highlight_id = None;
17157 let old_name: Arc<str> = buffer
17158 .chunks(rename_start..rename_end, true)
17159 .map(|chunk| {
17160 if old_highlight_id.is_none() {
17161 old_highlight_id = chunk.syntax_highlight_id;
17162 }
17163 chunk.text
17164 })
17165 .collect::<String>()
17166 .into();
17167
17168 drop(buffer);
17169
17170 // Position the selection in the rename editor so that it matches the current selection.
17171 this.show_local_selections = false;
17172 let rename_editor = cx.new(|cx| {
17173 let mut editor = Editor::single_line(window, cx);
17174 editor.buffer.update(cx, |buffer, cx| {
17175 buffer.edit([(0..0, old_name.clone())], None, cx)
17176 });
17177 let rename_selection_range = match cursor_offset_in_rename_range
17178 .cmp(&cursor_offset_in_rename_range_end)
17179 {
17180 Ordering::Equal => {
17181 editor.select_all(&SelectAll, window, cx);
17182 return editor;
17183 }
17184 Ordering::Less => {
17185 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17186 }
17187 Ordering::Greater => {
17188 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17189 }
17190 };
17191 if rename_selection_range.end > old_name.len() {
17192 editor.select_all(&SelectAll, window, cx);
17193 } else {
17194 editor.change_selections(Default::default(), window, cx, |s| {
17195 s.select_ranges([rename_selection_range]);
17196 });
17197 }
17198 editor
17199 });
17200 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17201 if e == &EditorEvent::Focused {
17202 cx.emit(EditorEvent::FocusedIn)
17203 }
17204 })
17205 .detach();
17206
17207 let write_highlights =
17208 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17209 let read_highlights =
17210 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17211 let ranges = write_highlights
17212 .iter()
17213 .flat_map(|(_, ranges)| ranges.iter())
17214 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17215 .cloned()
17216 .collect();
17217
17218 this.highlight_text::<Rename>(
17219 ranges,
17220 HighlightStyle {
17221 fade_out: Some(0.6),
17222 ..Default::default()
17223 },
17224 cx,
17225 );
17226 let rename_focus_handle = rename_editor.focus_handle(cx);
17227 window.focus(&rename_focus_handle);
17228 let block_id = this.insert_blocks(
17229 [BlockProperties {
17230 style: BlockStyle::Flex,
17231 placement: BlockPlacement::Below(range.start),
17232 height: Some(1),
17233 render: Arc::new({
17234 let rename_editor = rename_editor.clone();
17235 move |cx: &mut BlockContext| {
17236 let mut text_style = cx.editor_style.text.clone();
17237 if let Some(highlight_style) = old_highlight_id
17238 .and_then(|h| h.style(&cx.editor_style.syntax))
17239 {
17240 text_style = text_style.highlight(highlight_style);
17241 }
17242 div()
17243 .block_mouse_except_scroll()
17244 .pl(cx.anchor_x)
17245 .child(EditorElement::new(
17246 &rename_editor,
17247 EditorStyle {
17248 background: cx.theme().system().transparent,
17249 local_player: cx.editor_style.local_player,
17250 text: text_style,
17251 scrollbar_width: cx.editor_style.scrollbar_width,
17252 syntax: cx.editor_style.syntax.clone(),
17253 status: cx.editor_style.status.clone(),
17254 inlay_hints_style: HighlightStyle {
17255 font_weight: Some(FontWeight::BOLD),
17256 ..make_inlay_hints_style(cx.app)
17257 },
17258 edit_prediction_styles: make_suggestion_styles(
17259 cx.app,
17260 ),
17261 ..EditorStyle::default()
17262 },
17263 ))
17264 .into_any_element()
17265 }
17266 }),
17267 priority: 0,
17268 }],
17269 Some(Autoscroll::fit()),
17270 cx,
17271 )[0];
17272 this.pending_rename = Some(RenameState {
17273 range,
17274 old_name,
17275 editor: rename_editor,
17276 block_id,
17277 });
17278 })?;
17279 }
17280
17281 Ok(())
17282 }))
17283 }
17284
17285 pub fn confirm_rename(
17286 &mut self,
17287 _: &ConfirmRename,
17288 window: &mut Window,
17289 cx: &mut Context<Self>,
17290 ) -> Option<Task<Result<()>>> {
17291 let rename = self.take_rename(false, window, cx)?;
17292 let workspace = self.workspace()?.downgrade();
17293 let (buffer, start) = self
17294 .buffer
17295 .read(cx)
17296 .text_anchor_for_position(rename.range.start, cx)?;
17297 let (end_buffer, _) = self
17298 .buffer
17299 .read(cx)
17300 .text_anchor_for_position(rename.range.end, cx)?;
17301 if buffer != end_buffer {
17302 return None;
17303 }
17304
17305 let old_name = rename.old_name;
17306 let new_name = rename.editor.read(cx).text(cx);
17307
17308 let rename = self.semantics_provider.as_ref()?.perform_rename(
17309 &buffer,
17310 start,
17311 new_name.clone(),
17312 cx,
17313 )?;
17314
17315 Some(cx.spawn_in(window, async move |editor, cx| {
17316 let project_transaction = rename.await?;
17317 Self::open_project_transaction(
17318 &editor,
17319 workspace,
17320 project_transaction,
17321 format!("Rename: {} → {}", old_name, new_name),
17322 cx,
17323 )
17324 .await?;
17325
17326 editor.update(cx, |editor, cx| {
17327 editor.refresh_document_highlights(cx);
17328 })?;
17329 Ok(())
17330 }))
17331 }
17332
17333 fn take_rename(
17334 &mut self,
17335 moving_cursor: bool,
17336 window: &mut Window,
17337 cx: &mut Context<Self>,
17338 ) -> Option<RenameState> {
17339 let rename = self.pending_rename.take()?;
17340 if rename.editor.focus_handle(cx).is_focused(window) {
17341 window.focus(&self.focus_handle);
17342 }
17343
17344 self.remove_blocks(
17345 [rename.block_id].into_iter().collect(),
17346 Some(Autoscroll::fit()),
17347 cx,
17348 );
17349 self.clear_highlights::<Rename>(cx);
17350 self.show_local_selections = true;
17351
17352 if moving_cursor {
17353 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17354 editor
17355 .selections
17356 .newest::<usize>(&editor.display_snapshot(cx))
17357 .head()
17358 });
17359
17360 // Update the selection to match the position of the selection inside
17361 // the rename editor.
17362 let snapshot = self.buffer.read(cx).read(cx);
17363 let rename_range = rename.range.to_offset(&snapshot);
17364 let cursor_in_editor = snapshot
17365 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17366 .min(rename_range.end);
17367 drop(snapshot);
17368
17369 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17370 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17371 });
17372 } else {
17373 self.refresh_document_highlights(cx);
17374 }
17375
17376 Some(rename)
17377 }
17378
17379 pub fn pending_rename(&self) -> Option<&RenameState> {
17380 self.pending_rename.as_ref()
17381 }
17382
17383 fn format(
17384 &mut self,
17385 _: &Format,
17386 window: &mut Window,
17387 cx: &mut Context<Self>,
17388 ) -> Option<Task<Result<()>>> {
17389 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17390
17391 let project = match &self.project {
17392 Some(project) => project.clone(),
17393 None => return None,
17394 };
17395
17396 Some(self.perform_format(
17397 project,
17398 FormatTrigger::Manual,
17399 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17400 window,
17401 cx,
17402 ))
17403 }
17404
17405 fn format_selections(
17406 &mut self,
17407 _: &FormatSelections,
17408 window: &mut Window,
17409 cx: &mut Context<Self>,
17410 ) -> Option<Task<Result<()>>> {
17411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17412
17413 let project = match &self.project {
17414 Some(project) => project.clone(),
17415 None => return None,
17416 };
17417
17418 let ranges = self
17419 .selections
17420 .all_adjusted(&self.display_snapshot(cx))
17421 .into_iter()
17422 .map(|selection| selection.range())
17423 .collect_vec();
17424
17425 Some(self.perform_format(
17426 project,
17427 FormatTrigger::Manual,
17428 FormatTarget::Ranges(ranges),
17429 window,
17430 cx,
17431 ))
17432 }
17433
17434 fn perform_format(
17435 &mut self,
17436 project: Entity<Project>,
17437 trigger: FormatTrigger,
17438 target: FormatTarget,
17439 window: &mut Window,
17440 cx: &mut Context<Self>,
17441 ) -> Task<Result<()>> {
17442 let buffer = self.buffer.clone();
17443 let (buffers, target) = match target {
17444 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17445 FormatTarget::Ranges(selection_ranges) => {
17446 let multi_buffer = buffer.read(cx);
17447 let snapshot = multi_buffer.read(cx);
17448 let mut buffers = HashSet::default();
17449 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17450 BTreeMap::new();
17451 for selection_range in selection_ranges {
17452 for (buffer, buffer_range, _) in
17453 snapshot.range_to_buffer_ranges(selection_range)
17454 {
17455 let buffer_id = buffer.remote_id();
17456 let start = buffer.anchor_before(buffer_range.start);
17457 let end = buffer.anchor_after(buffer_range.end);
17458 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17459 buffer_id_to_ranges
17460 .entry(buffer_id)
17461 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17462 .or_insert_with(|| vec![start..end]);
17463 }
17464 }
17465 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17466 }
17467 };
17468
17469 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17470 let selections_prev = transaction_id_prev
17471 .and_then(|transaction_id_prev| {
17472 // default to selections as they were after the last edit, if we have them,
17473 // instead of how they are now.
17474 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17475 // will take you back to where you made the last edit, instead of staying where you scrolled
17476 self.selection_history
17477 .transaction(transaction_id_prev)
17478 .map(|t| t.0.clone())
17479 })
17480 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17481
17482 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17483 let format = project.update(cx, |project, cx| {
17484 project.format(buffers, target, true, trigger, cx)
17485 });
17486
17487 cx.spawn_in(window, async move |editor, cx| {
17488 let transaction = futures::select_biased! {
17489 transaction = format.log_err().fuse() => transaction,
17490 () = timeout => {
17491 log::warn!("timed out waiting for formatting");
17492 None
17493 }
17494 };
17495
17496 buffer
17497 .update(cx, |buffer, cx| {
17498 if let Some(transaction) = transaction
17499 && !buffer.is_singleton()
17500 {
17501 buffer.push_transaction(&transaction.0, cx);
17502 }
17503 cx.notify();
17504 })
17505 .ok();
17506
17507 if let Some(transaction_id_now) =
17508 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17509 {
17510 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17511 if has_new_transaction {
17512 _ = editor.update(cx, |editor, _| {
17513 editor
17514 .selection_history
17515 .insert_transaction(transaction_id_now, selections_prev);
17516 });
17517 }
17518 }
17519
17520 Ok(())
17521 })
17522 }
17523
17524 fn organize_imports(
17525 &mut self,
17526 _: &OrganizeImports,
17527 window: &mut Window,
17528 cx: &mut Context<Self>,
17529 ) -> Option<Task<Result<()>>> {
17530 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17531 let project = match &self.project {
17532 Some(project) => project.clone(),
17533 None => return None,
17534 };
17535 Some(self.perform_code_action_kind(
17536 project,
17537 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17538 window,
17539 cx,
17540 ))
17541 }
17542
17543 fn perform_code_action_kind(
17544 &mut self,
17545 project: Entity<Project>,
17546 kind: CodeActionKind,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) -> Task<Result<()>> {
17550 let buffer = self.buffer.clone();
17551 let buffers = buffer.read(cx).all_buffers();
17552 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17553 let apply_action = project.update(cx, |project, cx| {
17554 project.apply_code_action_kind(buffers, kind, true, cx)
17555 });
17556 cx.spawn_in(window, async move |_, cx| {
17557 let transaction = futures::select_biased! {
17558 () = timeout => {
17559 log::warn!("timed out waiting for executing code action");
17560 None
17561 }
17562 transaction = apply_action.log_err().fuse() => transaction,
17563 };
17564 buffer
17565 .update(cx, |buffer, cx| {
17566 // check if we need this
17567 if let Some(transaction) = transaction
17568 && !buffer.is_singleton()
17569 {
17570 buffer.push_transaction(&transaction.0, cx);
17571 }
17572 cx.notify();
17573 })
17574 .ok();
17575 Ok(())
17576 })
17577 }
17578
17579 pub fn restart_language_server(
17580 &mut self,
17581 _: &RestartLanguageServer,
17582 _: &mut Window,
17583 cx: &mut Context<Self>,
17584 ) {
17585 if let Some(project) = self.project.clone() {
17586 self.buffer.update(cx, |multi_buffer, cx| {
17587 project.update(cx, |project, cx| {
17588 project.restart_language_servers_for_buffers(
17589 multi_buffer.all_buffers().into_iter().collect(),
17590 HashSet::default(),
17591 cx,
17592 );
17593 });
17594 })
17595 }
17596 }
17597
17598 pub fn stop_language_server(
17599 &mut self,
17600 _: &StopLanguageServer,
17601 _: &mut Window,
17602 cx: &mut Context<Self>,
17603 ) {
17604 if let Some(project) = self.project.clone() {
17605 self.buffer.update(cx, |multi_buffer, cx| {
17606 project.update(cx, |project, cx| {
17607 project.stop_language_servers_for_buffers(
17608 multi_buffer.all_buffers().into_iter().collect(),
17609 HashSet::default(),
17610 cx,
17611 );
17612 });
17613 });
17614 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17615 }
17616 }
17617
17618 fn cancel_language_server_work(
17619 workspace: &mut Workspace,
17620 _: &actions::CancelLanguageServerWork,
17621 _: &mut Window,
17622 cx: &mut Context<Workspace>,
17623 ) {
17624 let project = workspace.project();
17625 let buffers = workspace
17626 .active_item(cx)
17627 .and_then(|item| item.act_as::<Editor>(cx))
17628 .map_or(HashSet::default(), |editor| {
17629 editor.read(cx).buffer.read(cx).all_buffers()
17630 });
17631 project.update(cx, |project, cx| {
17632 project.cancel_language_server_work_for_buffers(buffers, cx);
17633 });
17634 }
17635
17636 fn show_character_palette(
17637 &mut self,
17638 _: &ShowCharacterPalette,
17639 window: &mut Window,
17640 _: &mut Context<Self>,
17641 ) {
17642 window.show_character_palette();
17643 }
17644
17645 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17646 if !self.diagnostics_enabled() {
17647 return;
17648 }
17649
17650 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17651 let buffer = self.buffer.read(cx).snapshot(cx);
17652 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17653 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17654 let is_valid = buffer
17655 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17656 .any(|entry| {
17657 entry.diagnostic.is_primary
17658 && !entry.range.is_empty()
17659 && entry.range.start == primary_range_start
17660 && entry.diagnostic.message == active_diagnostics.active_message
17661 });
17662
17663 if !is_valid {
17664 self.dismiss_diagnostics(cx);
17665 }
17666 }
17667 }
17668
17669 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17670 match &self.active_diagnostics {
17671 ActiveDiagnostic::Group(group) => Some(group),
17672 _ => None,
17673 }
17674 }
17675
17676 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17677 if !self.diagnostics_enabled() {
17678 return;
17679 }
17680 self.dismiss_diagnostics(cx);
17681 self.active_diagnostics = ActiveDiagnostic::All;
17682 }
17683
17684 fn activate_diagnostics(
17685 &mut self,
17686 buffer_id: BufferId,
17687 diagnostic: DiagnosticEntryRef<'_, usize>,
17688 window: &mut Window,
17689 cx: &mut Context<Self>,
17690 ) {
17691 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17692 return;
17693 }
17694 self.dismiss_diagnostics(cx);
17695 let snapshot = self.snapshot(window, cx);
17696 let buffer = self.buffer.read(cx).snapshot(cx);
17697 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17698 return;
17699 };
17700
17701 let diagnostic_group = buffer
17702 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17703 .collect::<Vec<_>>();
17704
17705 let blocks =
17706 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17707
17708 let blocks = self.display_map.update(cx, |display_map, cx| {
17709 display_map.insert_blocks(blocks, cx).into_iter().collect()
17710 });
17711 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17712 active_range: buffer.anchor_before(diagnostic.range.start)
17713 ..buffer.anchor_after(diagnostic.range.end),
17714 active_message: diagnostic.diagnostic.message.clone(),
17715 group_id: diagnostic.diagnostic.group_id,
17716 blocks,
17717 });
17718 cx.notify();
17719 }
17720
17721 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17722 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17723 return;
17724 };
17725
17726 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17727 if let ActiveDiagnostic::Group(group) = prev {
17728 self.display_map.update(cx, |display_map, cx| {
17729 display_map.remove_blocks(group.blocks, cx);
17730 });
17731 cx.notify();
17732 }
17733 }
17734
17735 /// Disable inline diagnostics rendering for this editor.
17736 pub fn disable_inline_diagnostics(&mut self) {
17737 self.inline_diagnostics_enabled = false;
17738 self.inline_diagnostics_update = Task::ready(());
17739 self.inline_diagnostics.clear();
17740 }
17741
17742 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17743 self.diagnostics_enabled = false;
17744 self.dismiss_diagnostics(cx);
17745 self.inline_diagnostics_update = Task::ready(());
17746 self.inline_diagnostics.clear();
17747 }
17748
17749 pub fn disable_word_completions(&mut self) {
17750 self.word_completions_enabled = false;
17751 }
17752
17753 pub fn diagnostics_enabled(&self) -> bool {
17754 self.diagnostics_enabled && self.mode.is_full()
17755 }
17756
17757 pub fn inline_diagnostics_enabled(&self) -> bool {
17758 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17759 }
17760
17761 pub fn show_inline_diagnostics(&self) -> bool {
17762 self.show_inline_diagnostics
17763 }
17764
17765 pub fn toggle_inline_diagnostics(
17766 &mut self,
17767 _: &ToggleInlineDiagnostics,
17768 window: &mut Window,
17769 cx: &mut Context<Editor>,
17770 ) {
17771 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17772 self.refresh_inline_diagnostics(false, window, cx);
17773 }
17774
17775 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17776 self.diagnostics_max_severity = severity;
17777 self.display_map.update(cx, |display_map, _| {
17778 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17779 });
17780 }
17781
17782 pub fn toggle_diagnostics(
17783 &mut self,
17784 _: &ToggleDiagnostics,
17785 window: &mut Window,
17786 cx: &mut Context<Editor>,
17787 ) {
17788 if !self.diagnostics_enabled() {
17789 return;
17790 }
17791
17792 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17793 EditorSettings::get_global(cx)
17794 .diagnostics_max_severity
17795 .filter(|severity| severity != &DiagnosticSeverity::Off)
17796 .unwrap_or(DiagnosticSeverity::Hint)
17797 } else {
17798 DiagnosticSeverity::Off
17799 };
17800 self.set_max_diagnostics_severity(new_severity, cx);
17801 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17802 self.active_diagnostics = ActiveDiagnostic::None;
17803 self.inline_diagnostics_update = Task::ready(());
17804 self.inline_diagnostics.clear();
17805 } else {
17806 self.refresh_inline_diagnostics(false, window, cx);
17807 }
17808
17809 cx.notify();
17810 }
17811
17812 pub fn toggle_minimap(
17813 &mut self,
17814 _: &ToggleMinimap,
17815 window: &mut Window,
17816 cx: &mut Context<Editor>,
17817 ) {
17818 if self.supports_minimap(cx) {
17819 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17820 }
17821 }
17822
17823 fn refresh_inline_diagnostics(
17824 &mut self,
17825 debounce: bool,
17826 window: &mut Window,
17827 cx: &mut Context<Self>,
17828 ) {
17829 let max_severity = ProjectSettings::get_global(cx)
17830 .diagnostics
17831 .inline
17832 .max_severity
17833 .unwrap_or(self.diagnostics_max_severity);
17834
17835 if !self.inline_diagnostics_enabled()
17836 || !self.show_inline_diagnostics
17837 || max_severity == DiagnosticSeverity::Off
17838 {
17839 self.inline_diagnostics_update = Task::ready(());
17840 self.inline_diagnostics.clear();
17841 return;
17842 }
17843
17844 let debounce_ms = ProjectSettings::get_global(cx)
17845 .diagnostics
17846 .inline
17847 .update_debounce_ms;
17848 let debounce = if debounce && debounce_ms > 0 {
17849 Some(Duration::from_millis(debounce_ms))
17850 } else {
17851 None
17852 };
17853 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17854 if let Some(debounce) = debounce {
17855 cx.background_executor().timer(debounce).await;
17856 }
17857 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17858 editor
17859 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17860 .ok()
17861 }) else {
17862 return;
17863 };
17864
17865 let new_inline_diagnostics = cx
17866 .background_spawn(async move {
17867 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17868 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17869 let message = diagnostic_entry
17870 .diagnostic
17871 .message
17872 .split_once('\n')
17873 .map(|(line, _)| line)
17874 .map(SharedString::new)
17875 .unwrap_or_else(|| {
17876 SharedString::new(&*diagnostic_entry.diagnostic.message)
17877 });
17878 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17879 let (Ok(i) | Err(i)) = inline_diagnostics
17880 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17881 inline_diagnostics.insert(
17882 i,
17883 (
17884 start_anchor,
17885 InlineDiagnostic {
17886 message,
17887 group_id: diagnostic_entry.diagnostic.group_id,
17888 start: diagnostic_entry.range.start.to_point(&snapshot),
17889 is_primary: diagnostic_entry.diagnostic.is_primary,
17890 severity: diagnostic_entry.diagnostic.severity,
17891 },
17892 ),
17893 );
17894 }
17895 inline_diagnostics
17896 })
17897 .await;
17898
17899 editor
17900 .update(cx, |editor, cx| {
17901 editor.inline_diagnostics = new_inline_diagnostics;
17902 cx.notify();
17903 })
17904 .ok();
17905 });
17906 }
17907
17908 fn pull_diagnostics(
17909 &mut self,
17910 buffer_id: Option<BufferId>,
17911 window: &Window,
17912 cx: &mut Context<Self>,
17913 ) -> Option<()> {
17914 if self.ignore_lsp_data() {
17915 return None;
17916 }
17917 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17918 .diagnostics
17919 .lsp_pull_diagnostics;
17920 if !pull_diagnostics_settings.enabled {
17921 return None;
17922 }
17923 let project = self.project()?.downgrade();
17924 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17925 let mut buffers = self.buffer.read(cx).all_buffers();
17926 buffers.retain(|buffer| {
17927 let buffer_id_to_retain = buffer.read(cx).remote_id();
17928 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17929 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17930 });
17931 if buffers.is_empty() {
17932 self.pull_diagnostics_task = Task::ready(());
17933 return None;
17934 }
17935
17936 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17937 cx.background_executor().timer(debounce).await;
17938
17939 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17940 buffers
17941 .into_iter()
17942 .filter_map(|buffer| {
17943 project
17944 .update(cx, |project, cx| {
17945 project.lsp_store().update(cx, |lsp_store, cx| {
17946 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17947 })
17948 })
17949 .ok()
17950 })
17951 .collect::<FuturesUnordered<_>>()
17952 }) else {
17953 return;
17954 };
17955
17956 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17957 match pull_task {
17958 Ok(()) => {
17959 if editor
17960 .update_in(cx, |editor, window, cx| {
17961 editor.update_diagnostics_state(window, cx);
17962 })
17963 .is_err()
17964 {
17965 return;
17966 }
17967 }
17968 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17969 }
17970 }
17971 });
17972
17973 Some(())
17974 }
17975
17976 pub fn set_selections_from_remote(
17977 &mut self,
17978 selections: Vec<Selection<Anchor>>,
17979 pending_selection: Option<Selection<Anchor>>,
17980 window: &mut Window,
17981 cx: &mut Context<Self>,
17982 ) {
17983 let old_cursor_position = self.selections.newest_anchor().head();
17984 self.selections.change_with(cx, |s| {
17985 s.select_anchors(selections);
17986 if let Some(pending_selection) = pending_selection {
17987 s.set_pending(pending_selection, SelectMode::Character);
17988 } else {
17989 s.clear_pending();
17990 }
17991 });
17992 self.selections_did_change(
17993 false,
17994 &old_cursor_position,
17995 SelectionEffects::default(),
17996 window,
17997 cx,
17998 );
17999 }
18000
18001 pub fn transact(
18002 &mut self,
18003 window: &mut Window,
18004 cx: &mut Context<Self>,
18005 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18006 ) -> Option<TransactionId> {
18007 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18008 this.start_transaction_at(Instant::now(), window, cx);
18009 update(this, window, cx);
18010 this.end_transaction_at(Instant::now(), cx)
18011 })
18012 }
18013
18014 pub fn start_transaction_at(
18015 &mut self,
18016 now: Instant,
18017 window: &mut Window,
18018 cx: &mut Context<Self>,
18019 ) -> Option<TransactionId> {
18020 self.end_selection(window, cx);
18021 if let Some(tx_id) = self
18022 .buffer
18023 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18024 {
18025 self.selection_history
18026 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18027 cx.emit(EditorEvent::TransactionBegun {
18028 transaction_id: tx_id,
18029 });
18030 Some(tx_id)
18031 } else {
18032 None
18033 }
18034 }
18035
18036 pub fn end_transaction_at(
18037 &mut self,
18038 now: Instant,
18039 cx: &mut Context<Self>,
18040 ) -> Option<TransactionId> {
18041 if let Some(transaction_id) = self
18042 .buffer
18043 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18044 {
18045 if let Some((_, end_selections)) =
18046 self.selection_history.transaction_mut(transaction_id)
18047 {
18048 *end_selections = Some(self.selections.disjoint_anchors_arc());
18049 } else {
18050 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18051 }
18052
18053 cx.emit(EditorEvent::Edited { transaction_id });
18054 Some(transaction_id)
18055 } else {
18056 None
18057 }
18058 }
18059
18060 pub fn modify_transaction_selection_history(
18061 &mut self,
18062 transaction_id: TransactionId,
18063 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18064 ) -> bool {
18065 self.selection_history
18066 .transaction_mut(transaction_id)
18067 .map(modify)
18068 .is_some()
18069 }
18070
18071 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18072 if self.selection_mark_mode {
18073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18074 s.move_with(|_, sel| {
18075 sel.collapse_to(sel.head(), SelectionGoal::None);
18076 });
18077 })
18078 }
18079 self.selection_mark_mode = true;
18080 cx.notify();
18081 }
18082
18083 pub fn swap_selection_ends(
18084 &mut self,
18085 _: &actions::SwapSelectionEnds,
18086 window: &mut Window,
18087 cx: &mut Context<Self>,
18088 ) {
18089 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18090 s.move_with(|_, sel| {
18091 if sel.start != sel.end {
18092 sel.reversed = !sel.reversed
18093 }
18094 });
18095 });
18096 self.request_autoscroll(Autoscroll::newest(), cx);
18097 cx.notify();
18098 }
18099
18100 pub fn toggle_focus(
18101 workspace: &mut Workspace,
18102 _: &actions::ToggleFocus,
18103 window: &mut Window,
18104 cx: &mut Context<Workspace>,
18105 ) {
18106 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18107 return;
18108 };
18109 workspace.activate_item(&item, true, true, window, cx);
18110 }
18111
18112 pub fn toggle_fold(
18113 &mut self,
18114 _: &actions::ToggleFold,
18115 window: &mut Window,
18116 cx: &mut Context<Self>,
18117 ) {
18118 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18120 let selection = self.selections.newest::<Point>(&display_map);
18121
18122 let range = if selection.is_empty() {
18123 let point = selection.head().to_display_point(&display_map);
18124 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18125 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18126 .to_point(&display_map);
18127 start..end
18128 } else {
18129 selection.range()
18130 };
18131 if display_map.folds_in_range(range).next().is_some() {
18132 self.unfold_lines(&Default::default(), window, cx)
18133 } else {
18134 self.fold(&Default::default(), window, cx)
18135 }
18136 } else {
18137 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18138 let buffer_ids: HashSet<_> = self
18139 .selections
18140 .disjoint_anchor_ranges()
18141 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18142 .collect();
18143
18144 let should_unfold = buffer_ids
18145 .iter()
18146 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18147
18148 for buffer_id in buffer_ids {
18149 if should_unfold {
18150 self.unfold_buffer(buffer_id, cx);
18151 } else {
18152 self.fold_buffer(buffer_id, cx);
18153 }
18154 }
18155 }
18156 }
18157
18158 pub fn toggle_fold_recursive(
18159 &mut self,
18160 _: &actions::ToggleFoldRecursive,
18161 window: &mut Window,
18162 cx: &mut Context<Self>,
18163 ) {
18164 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18165
18166 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18167 let range = if selection.is_empty() {
18168 let point = selection.head().to_display_point(&display_map);
18169 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18170 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18171 .to_point(&display_map);
18172 start..end
18173 } else {
18174 selection.range()
18175 };
18176 if display_map.folds_in_range(range).next().is_some() {
18177 self.unfold_recursive(&Default::default(), window, cx)
18178 } else {
18179 self.fold_recursive(&Default::default(), window, cx)
18180 }
18181 }
18182
18183 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18184 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18185 let mut to_fold = Vec::new();
18186 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18187 let selections = self.selections.all_adjusted(&display_map);
18188
18189 for selection in selections {
18190 let range = selection.range().sorted();
18191 let buffer_start_row = range.start.row;
18192
18193 if range.start.row != range.end.row {
18194 let mut found = false;
18195 let mut row = range.start.row;
18196 while row <= range.end.row {
18197 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18198 {
18199 found = true;
18200 row = crease.range().end.row + 1;
18201 to_fold.push(crease);
18202 } else {
18203 row += 1
18204 }
18205 }
18206 if found {
18207 continue;
18208 }
18209 }
18210
18211 for row in (0..=range.start.row).rev() {
18212 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18213 && crease.range().end.row >= buffer_start_row
18214 {
18215 to_fold.push(crease);
18216 if row <= range.start.row {
18217 break;
18218 }
18219 }
18220 }
18221 }
18222
18223 self.fold_creases(to_fold, true, window, cx);
18224 } else {
18225 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18226 let buffer_ids = self
18227 .selections
18228 .disjoint_anchor_ranges()
18229 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18230 .collect::<HashSet<_>>();
18231 for buffer_id in buffer_ids {
18232 self.fold_buffer(buffer_id, cx);
18233 }
18234 }
18235 }
18236
18237 pub fn toggle_fold_all(
18238 &mut self,
18239 _: &actions::ToggleFoldAll,
18240 window: &mut Window,
18241 cx: &mut Context<Self>,
18242 ) {
18243 if self.buffer.read(cx).is_singleton() {
18244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18245 let has_folds = display_map
18246 .folds_in_range(0..display_map.buffer_snapshot().len())
18247 .next()
18248 .is_some();
18249
18250 if has_folds {
18251 self.unfold_all(&actions::UnfoldAll, window, cx);
18252 } else {
18253 self.fold_all(&actions::FoldAll, window, cx);
18254 }
18255 } else {
18256 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18257 let should_unfold = buffer_ids
18258 .iter()
18259 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18260
18261 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18262 editor
18263 .update_in(cx, |editor, _, cx| {
18264 for buffer_id in buffer_ids {
18265 if should_unfold {
18266 editor.unfold_buffer(buffer_id, cx);
18267 } else {
18268 editor.fold_buffer(buffer_id, cx);
18269 }
18270 }
18271 })
18272 .ok();
18273 });
18274 }
18275 }
18276
18277 fn fold_at_level(
18278 &mut self,
18279 fold_at: &FoldAtLevel,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) {
18283 if !self.buffer.read(cx).is_singleton() {
18284 return;
18285 }
18286
18287 let fold_at_level = fold_at.0;
18288 let snapshot = self.buffer.read(cx).snapshot(cx);
18289 let mut to_fold = Vec::new();
18290 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18291
18292 let row_ranges_to_keep: Vec<Range<u32>> = self
18293 .selections
18294 .all::<Point>(&self.display_snapshot(cx))
18295 .into_iter()
18296 .map(|sel| sel.start.row..sel.end.row)
18297 .collect();
18298
18299 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18300 while start_row < end_row {
18301 match self
18302 .snapshot(window, cx)
18303 .crease_for_buffer_row(MultiBufferRow(start_row))
18304 {
18305 Some(crease) => {
18306 let nested_start_row = crease.range().start.row + 1;
18307 let nested_end_row = crease.range().end.row;
18308
18309 if current_level < fold_at_level {
18310 stack.push((nested_start_row, nested_end_row, current_level + 1));
18311 } else if current_level == fold_at_level {
18312 // Fold iff there is no selection completely contained within the fold region
18313 if !row_ranges_to_keep.iter().any(|selection| {
18314 selection.end >= nested_start_row
18315 && selection.start <= nested_end_row
18316 }) {
18317 to_fold.push(crease);
18318 }
18319 }
18320
18321 start_row = nested_end_row + 1;
18322 }
18323 None => start_row += 1,
18324 }
18325 }
18326 }
18327
18328 self.fold_creases(to_fold, true, window, cx);
18329 }
18330
18331 pub fn fold_at_level_1(
18332 &mut self,
18333 _: &actions::FoldAtLevel1,
18334 window: &mut Window,
18335 cx: &mut Context<Self>,
18336 ) {
18337 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18338 }
18339
18340 pub fn fold_at_level_2(
18341 &mut self,
18342 _: &actions::FoldAtLevel2,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18347 }
18348
18349 pub fn fold_at_level_3(
18350 &mut self,
18351 _: &actions::FoldAtLevel3,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18356 }
18357
18358 pub fn fold_at_level_4(
18359 &mut self,
18360 _: &actions::FoldAtLevel4,
18361 window: &mut Window,
18362 cx: &mut Context<Self>,
18363 ) {
18364 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18365 }
18366
18367 pub fn fold_at_level_5(
18368 &mut self,
18369 _: &actions::FoldAtLevel5,
18370 window: &mut Window,
18371 cx: &mut Context<Self>,
18372 ) {
18373 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18374 }
18375
18376 pub fn fold_at_level_6(
18377 &mut self,
18378 _: &actions::FoldAtLevel6,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18383 }
18384
18385 pub fn fold_at_level_7(
18386 &mut self,
18387 _: &actions::FoldAtLevel7,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18392 }
18393
18394 pub fn fold_at_level_8(
18395 &mut self,
18396 _: &actions::FoldAtLevel8,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18401 }
18402
18403 pub fn fold_at_level_9(
18404 &mut self,
18405 _: &actions::FoldAtLevel9,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18410 }
18411
18412 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18413 if self.buffer.read(cx).is_singleton() {
18414 let mut fold_ranges = Vec::new();
18415 let snapshot = self.buffer.read(cx).snapshot(cx);
18416
18417 for row in 0..snapshot.max_row().0 {
18418 if let Some(foldable_range) = self
18419 .snapshot(window, cx)
18420 .crease_for_buffer_row(MultiBufferRow(row))
18421 {
18422 fold_ranges.push(foldable_range);
18423 }
18424 }
18425
18426 self.fold_creases(fold_ranges, true, window, cx);
18427 } else {
18428 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18429 editor
18430 .update_in(cx, |editor, _, cx| {
18431 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18432 editor.fold_buffer(buffer_id, cx);
18433 }
18434 })
18435 .ok();
18436 });
18437 }
18438 }
18439
18440 pub fn fold_function_bodies(
18441 &mut self,
18442 _: &actions::FoldFunctionBodies,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) {
18446 let snapshot = self.buffer.read(cx).snapshot(cx);
18447
18448 let ranges = snapshot
18449 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18450 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18451 .collect::<Vec<_>>();
18452
18453 let creases = ranges
18454 .into_iter()
18455 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18456 .collect();
18457
18458 self.fold_creases(creases, true, window, cx);
18459 }
18460
18461 pub fn fold_recursive(
18462 &mut self,
18463 _: &actions::FoldRecursive,
18464 window: &mut Window,
18465 cx: &mut Context<Self>,
18466 ) {
18467 let mut to_fold = Vec::new();
18468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18469 let selections = self.selections.all_adjusted(&display_map);
18470
18471 for selection in selections {
18472 let range = selection.range().sorted();
18473 let buffer_start_row = range.start.row;
18474
18475 if range.start.row != range.end.row {
18476 let mut found = false;
18477 for row in range.start.row..=range.end.row {
18478 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18479 found = true;
18480 to_fold.push(crease);
18481 }
18482 }
18483 if found {
18484 continue;
18485 }
18486 }
18487
18488 for row in (0..=range.start.row).rev() {
18489 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18490 if crease.range().end.row >= buffer_start_row {
18491 to_fold.push(crease);
18492 } else {
18493 break;
18494 }
18495 }
18496 }
18497 }
18498
18499 self.fold_creases(to_fold, true, window, cx);
18500 }
18501
18502 pub fn fold_at(
18503 &mut self,
18504 buffer_row: MultiBufferRow,
18505 window: &mut Window,
18506 cx: &mut Context<Self>,
18507 ) {
18508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18509
18510 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18511 let autoscroll = self
18512 .selections
18513 .all::<Point>(&display_map)
18514 .iter()
18515 .any(|selection| crease.range().overlaps(&selection.range()));
18516
18517 self.fold_creases(vec![crease], autoscroll, window, cx);
18518 }
18519 }
18520
18521 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18522 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18523 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18524 let buffer = display_map.buffer_snapshot();
18525 let selections = self.selections.all::<Point>(&display_map);
18526 let ranges = selections
18527 .iter()
18528 .map(|s| {
18529 let range = s.display_range(&display_map).sorted();
18530 let mut start = range.start.to_point(&display_map);
18531 let mut end = range.end.to_point(&display_map);
18532 start.column = 0;
18533 end.column = buffer.line_len(MultiBufferRow(end.row));
18534 start..end
18535 })
18536 .collect::<Vec<_>>();
18537
18538 self.unfold_ranges(&ranges, true, true, cx);
18539 } else {
18540 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18541 let buffer_ids = self
18542 .selections
18543 .disjoint_anchor_ranges()
18544 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18545 .collect::<HashSet<_>>();
18546 for buffer_id in buffer_ids {
18547 self.unfold_buffer(buffer_id, cx);
18548 }
18549 }
18550 }
18551
18552 pub fn unfold_recursive(
18553 &mut self,
18554 _: &UnfoldRecursive,
18555 _window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18559 let selections = self.selections.all::<Point>(&display_map);
18560 let ranges = selections
18561 .iter()
18562 .map(|s| {
18563 let mut range = s.display_range(&display_map).sorted();
18564 *range.start.column_mut() = 0;
18565 *range.end.column_mut() = display_map.line_len(range.end.row());
18566 let start = range.start.to_point(&display_map);
18567 let end = range.end.to_point(&display_map);
18568 start..end
18569 })
18570 .collect::<Vec<_>>();
18571
18572 self.unfold_ranges(&ranges, true, true, cx);
18573 }
18574
18575 pub fn unfold_at(
18576 &mut self,
18577 buffer_row: MultiBufferRow,
18578 _window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) {
18581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18582
18583 let intersection_range = Point::new(buffer_row.0, 0)
18584 ..Point::new(
18585 buffer_row.0,
18586 display_map.buffer_snapshot().line_len(buffer_row),
18587 );
18588
18589 let autoscroll = self
18590 .selections
18591 .all::<Point>(&display_map)
18592 .iter()
18593 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18594
18595 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18596 }
18597
18598 pub fn unfold_all(
18599 &mut self,
18600 _: &actions::UnfoldAll,
18601 _window: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 if self.buffer.read(cx).is_singleton() {
18605 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18606 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18607 } else {
18608 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18609 editor
18610 .update(cx, |editor, cx| {
18611 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18612 editor.unfold_buffer(buffer_id, cx);
18613 }
18614 })
18615 .ok();
18616 });
18617 }
18618 }
18619
18620 pub fn fold_selected_ranges(
18621 &mut self,
18622 _: &FoldSelectedRanges,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18627 let selections = self.selections.all_adjusted(&display_map);
18628 let ranges = selections
18629 .into_iter()
18630 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18631 .collect::<Vec<_>>();
18632 self.fold_creases(ranges, true, window, cx);
18633 }
18634
18635 pub fn fold_ranges<T: ToOffset + Clone>(
18636 &mut self,
18637 ranges: Vec<Range<T>>,
18638 auto_scroll: bool,
18639 window: &mut Window,
18640 cx: &mut Context<Self>,
18641 ) {
18642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18643 let ranges = ranges
18644 .into_iter()
18645 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18646 .collect::<Vec<_>>();
18647 self.fold_creases(ranges, auto_scroll, window, cx);
18648 }
18649
18650 pub fn fold_creases<T: ToOffset + Clone>(
18651 &mut self,
18652 creases: Vec<Crease<T>>,
18653 auto_scroll: bool,
18654 _window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 if creases.is_empty() {
18658 return;
18659 }
18660
18661 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18662
18663 if auto_scroll {
18664 self.request_autoscroll(Autoscroll::fit(), cx);
18665 }
18666
18667 cx.notify();
18668
18669 self.scrollbar_marker_state.dirty = true;
18670 self.folds_did_change(cx);
18671 }
18672
18673 /// Removes any folds whose ranges intersect any of the given ranges.
18674 pub fn unfold_ranges<T: ToOffset + Clone>(
18675 &mut self,
18676 ranges: &[Range<T>],
18677 inclusive: bool,
18678 auto_scroll: bool,
18679 cx: &mut Context<Self>,
18680 ) {
18681 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18682 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18683 });
18684 self.folds_did_change(cx);
18685 }
18686
18687 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18688 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18689 return;
18690 }
18691 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18692 self.display_map.update(cx, |display_map, cx| {
18693 display_map.fold_buffers([buffer_id], cx)
18694 });
18695 cx.emit(EditorEvent::BufferFoldToggled {
18696 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18697 folded: true,
18698 });
18699 cx.notify();
18700 }
18701
18702 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18703 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18704 return;
18705 }
18706 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18707 self.display_map.update(cx, |display_map, cx| {
18708 display_map.unfold_buffers([buffer_id], cx);
18709 });
18710 cx.emit(EditorEvent::BufferFoldToggled {
18711 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18712 folded: false,
18713 });
18714 cx.notify();
18715 }
18716
18717 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18718 self.display_map.read(cx).is_buffer_folded(buffer)
18719 }
18720
18721 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18722 self.display_map.read(cx).folded_buffers()
18723 }
18724
18725 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18726 self.display_map.update(cx, |display_map, cx| {
18727 display_map.disable_header_for_buffer(buffer_id, cx);
18728 });
18729 cx.notify();
18730 }
18731
18732 /// Removes any folds with the given ranges.
18733 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18734 &mut self,
18735 ranges: &[Range<T>],
18736 type_id: TypeId,
18737 auto_scroll: bool,
18738 cx: &mut Context<Self>,
18739 ) {
18740 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18741 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18742 });
18743 self.folds_did_change(cx);
18744 }
18745
18746 fn remove_folds_with<T: ToOffset + Clone>(
18747 &mut self,
18748 ranges: &[Range<T>],
18749 auto_scroll: bool,
18750 cx: &mut Context<Self>,
18751 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18752 ) {
18753 if ranges.is_empty() {
18754 return;
18755 }
18756
18757 let mut buffers_affected = HashSet::default();
18758 let multi_buffer = self.buffer().read(cx);
18759 for range in ranges {
18760 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18761 buffers_affected.insert(buffer.read(cx).remote_id());
18762 };
18763 }
18764
18765 self.display_map.update(cx, update);
18766
18767 if auto_scroll {
18768 self.request_autoscroll(Autoscroll::fit(), cx);
18769 }
18770
18771 cx.notify();
18772 self.scrollbar_marker_state.dirty = true;
18773 self.active_indent_guides_state.dirty = true;
18774 }
18775
18776 pub fn update_renderer_widths(
18777 &mut self,
18778 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18779 cx: &mut Context<Self>,
18780 ) -> bool {
18781 self.display_map
18782 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18783 }
18784
18785 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18786 self.display_map.read(cx).fold_placeholder.clone()
18787 }
18788
18789 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18790 self.buffer.update(cx, |buffer, cx| {
18791 buffer.set_all_diff_hunks_expanded(cx);
18792 });
18793 }
18794
18795 pub fn expand_all_diff_hunks(
18796 &mut self,
18797 _: &ExpandAllDiffHunks,
18798 _window: &mut Window,
18799 cx: &mut Context<Self>,
18800 ) {
18801 self.buffer.update(cx, |buffer, cx| {
18802 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18803 });
18804 }
18805
18806 pub fn collapse_all_diff_hunks(
18807 &mut self,
18808 _: &CollapseAllDiffHunks,
18809 _window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.buffer.update(cx, |buffer, cx| {
18813 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18814 });
18815 }
18816
18817 pub fn toggle_selected_diff_hunks(
18818 &mut self,
18819 _: &ToggleSelectedDiffHunks,
18820 _window: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) {
18823 let ranges: Vec<_> = self
18824 .selections
18825 .disjoint_anchors()
18826 .iter()
18827 .map(|s| s.range())
18828 .collect();
18829 self.toggle_diff_hunks_in_ranges(ranges, cx);
18830 }
18831
18832 pub fn diff_hunks_in_ranges<'a>(
18833 &'a self,
18834 ranges: &'a [Range<Anchor>],
18835 buffer: &'a MultiBufferSnapshot,
18836 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18837 ranges.iter().flat_map(move |range| {
18838 let end_excerpt_id = range.end.excerpt_id;
18839 let range = range.to_point(buffer);
18840 let mut peek_end = range.end;
18841 if range.end.row < buffer.max_row().0 {
18842 peek_end = Point::new(range.end.row + 1, 0);
18843 }
18844 buffer
18845 .diff_hunks_in_range(range.start..peek_end)
18846 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18847 })
18848 }
18849
18850 pub fn has_stageable_diff_hunks_in_ranges(
18851 &self,
18852 ranges: &[Range<Anchor>],
18853 snapshot: &MultiBufferSnapshot,
18854 ) -> bool {
18855 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18856 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18857 }
18858
18859 pub fn toggle_staged_selected_diff_hunks(
18860 &mut self,
18861 _: &::git::ToggleStaged,
18862 _: &mut Window,
18863 cx: &mut Context<Self>,
18864 ) {
18865 let snapshot = self.buffer.read(cx).snapshot(cx);
18866 let ranges: Vec<_> = self
18867 .selections
18868 .disjoint_anchors()
18869 .iter()
18870 .map(|s| s.range())
18871 .collect();
18872 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18873 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18874 }
18875
18876 pub fn set_render_diff_hunk_controls(
18877 &mut self,
18878 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18879 cx: &mut Context<Self>,
18880 ) {
18881 self.render_diff_hunk_controls = render_diff_hunk_controls;
18882 cx.notify();
18883 }
18884
18885 pub fn stage_and_next(
18886 &mut self,
18887 _: &::git::StageAndNext,
18888 window: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 self.do_stage_or_unstage_and_next(true, window, cx);
18892 }
18893
18894 pub fn unstage_and_next(
18895 &mut self,
18896 _: &::git::UnstageAndNext,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.do_stage_or_unstage_and_next(false, window, cx);
18901 }
18902
18903 pub fn stage_or_unstage_diff_hunks(
18904 &mut self,
18905 stage: bool,
18906 ranges: Vec<Range<Anchor>>,
18907 cx: &mut Context<Self>,
18908 ) {
18909 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18910 cx.spawn(async move |this, cx| {
18911 task.await?;
18912 this.update(cx, |this, cx| {
18913 let snapshot = this.buffer.read(cx).snapshot(cx);
18914 let chunk_by = this
18915 .diff_hunks_in_ranges(&ranges, &snapshot)
18916 .chunk_by(|hunk| hunk.buffer_id);
18917 for (buffer_id, hunks) in &chunk_by {
18918 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18919 }
18920 })
18921 })
18922 .detach_and_log_err(cx);
18923 }
18924
18925 fn save_buffers_for_ranges_if_needed(
18926 &mut self,
18927 ranges: &[Range<Anchor>],
18928 cx: &mut Context<Editor>,
18929 ) -> Task<Result<()>> {
18930 let multibuffer = self.buffer.read(cx);
18931 let snapshot = multibuffer.read(cx);
18932 let buffer_ids: HashSet<_> = ranges
18933 .iter()
18934 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18935 .collect();
18936 drop(snapshot);
18937
18938 let mut buffers = HashSet::default();
18939 for buffer_id in buffer_ids {
18940 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18941 let buffer = buffer_entity.read(cx);
18942 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18943 {
18944 buffers.insert(buffer_entity);
18945 }
18946 }
18947 }
18948
18949 if let Some(project) = &self.project {
18950 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18951 } else {
18952 Task::ready(Ok(()))
18953 }
18954 }
18955
18956 fn do_stage_or_unstage_and_next(
18957 &mut self,
18958 stage: bool,
18959 window: &mut Window,
18960 cx: &mut Context<Self>,
18961 ) {
18962 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18963
18964 if ranges.iter().any(|range| range.start != range.end) {
18965 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18966 return;
18967 }
18968
18969 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18970 let snapshot = self.snapshot(window, cx);
18971 let position = self
18972 .selections
18973 .newest::<Point>(&snapshot.display_snapshot)
18974 .head();
18975 let mut row = snapshot
18976 .buffer_snapshot()
18977 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18978 .find(|hunk| hunk.row_range.start.0 > position.row)
18979 .map(|hunk| hunk.row_range.start);
18980
18981 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18982 // Outside of the project diff editor, wrap around to the beginning.
18983 if !all_diff_hunks_expanded {
18984 row = row.or_else(|| {
18985 snapshot
18986 .buffer_snapshot()
18987 .diff_hunks_in_range(Point::zero()..position)
18988 .find(|hunk| hunk.row_range.end.0 < position.row)
18989 .map(|hunk| hunk.row_range.start)
18990 });
18991 }
18992
18993 if let Some(row) = row {
18994 let destination = Point::new(row.0, 0);
18995 let autoscroll = Autoscroll::center();
18996
18997 self.unfold_ranges(&[destination..destination], false, false, cx);
18998 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18999 s.select_ranges([destination..destination]);
19000 });
19001 }
19002 }
19003
19004 fn do_stage_or_unstage(
19005 &self,
19006 stage: bool,
19007 buffer_id: BufferId,
19008 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19009 cx: &mut App,
19010 ) -> Option<()> {
19011 let project = self.project()?;
19012 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19013 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19014 let buffer_snapshot = buffer.read(cx).snapshot();
19015 let file_exists = buffer_snapshot
19016 .file()
19017 .is_some_and(|file| file.disk_state().exists());
19018 diff.update(cx, |diff, cx| {
19019 diff.stage_or_unstage_hunks(
19020 stage,
19021 &hunks
19022 .map(|hunk| buffer_diff::DiffHunk {
19023 buffer_range: hunk.buffer_range,
19024 diff_base_byte_range: hunk.diff_base_byte_range,
19025 secondary_status: hunk.secondary_status,
19026 range: Point::zero()..Point::zero(), // unused
19027 })
19028 .collect::<Vec<_>>(),
19029 &buffer_snapshot,
19030 file_exists,
19031 cx,
19032 )
19033 });
19034 None
19035 }
19036
19037 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19038 let ranges: Vec<_> = self
19039 .selections
19040 .disjoint_anchors()
19041 .iter()
19042 .map(|s| s.range())
19043 .collect();
19044 self.buffer
19045 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19046 }
19047
19048 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19049 self.buffer.update(cx, |buffer, cx| {
19050 let ranges = vec![Anchor::min()..Anchor::max()];
19051 if !buffer.all_diff_hunks_expanded()
19052 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19053 {
19054 buffer.collapse_diff_hunks(ranges, cx);
19055 true
19056 } else {
19057 false
19058 }
19059 })
19060 }
19061
19062 fn toggle_diff_hunks_in_ranges(
19063 &mut self,
19064 ranges: Vec<Range<Anchor>>,
19065 cx: &mut Context<Editor>,
19066 ) {
19067 self.buffer.update(cx, |buffer, cx| {
19068 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19069 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19070 })
19071 }
19072
19073 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19074 self.buffer.update(cx, |buffer, cx| {
19075 let snapshot = buffer.snapshot(cx);
19076 let excerpt_id = range.end.excerpt_id;
19077 let point_range = range.to_point(&snapshot);
19078 let expand = !buffer.single_hunk_is_expanded(range, cx);
19079 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19080 })
19081 }
19082
19083 pub(crate) fn apply_all_diff_hunks(
19084 &mut self,
19085 _: &ApplyAllDiffHunks,
19086 window: &mut Window,
19087 cx: &mut Context<Self>,
19088 ) {
19089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19090
19091 let buffers = self.buffer.read(cx).all_buffers();
19092 for branch_buffer in buffers {
19093 branch_buffer.update(cx, |branch_buffer, cx| {
19094 branch_buffer.merge_into_base(Vec::new(), cx);
19095 });
19096 }
19097
19098 if let Some(project) = self.project.clone() {
19099 self.save(
19100 SaveOptions {
19101 format: true,
19102 autosave: false,
19103 },
19104 project,
19105 window,
19106 cx,
19107 )
19108 .detach_and_log_err(cx);
19109 }
19110 }
19111
19112 pub(crate) fn apply_selected_diff_hunks(
19113 &mut self,
19114 _: &ApplyDiffHunk,
19115 window: &mut Window,
19116 cx: &mut Context<Self>,
19117 ) {
19118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19119 let snapshot = self.snapshot(window, cx);
19120 let hunks = snapshot.hunks_for_ranges(
19121 self.selections
19122 .all(&snapshot.display_snapshot)
19123 .into_iter()
19124 .map(|selection| selection.range()),
19125 );
19126 let mut ranges_by_buffer = HashMap::default();
19127 self.transact(window, cx, |editor, _window, cx| {
19128 for hunk in hunks {
19129 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19130 ranges_by_buffer
19131 .entry(buffer.clone())
19132 .or_insert_with(Vec::new)
19133 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19134 }
19135 }
19136
19137 for (buffer, ranges) in ranges_by_buffer {
19138 buffer.update(cx, |buffer, cx| {
19139 buffer.merge_into_base(ranges, cx);
19140 });
19141 }
19142 });
19143
19144 if let Some(project) = self.project.clone() {
19145 self.save(
19146 SaveOptions {
19147 format: true,
19148 autosave: false,
19149 },
19150 project,
19151 window,
19152 cx,
19153 )
19154 .detach_and_log_err(cx);
19155 }
19156 }
19157
19158 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19159 if hovered != self.gutter_hovered {
19160 self.gutter_hovered = hovered;
19161 cx.notify();
19162 }
19163 }
19164
19165 pub fn insert_blocks(
19166 &mut self,
19167 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19168 autoscroll: Option<Autoscroll>,
19169 cx: &mut Context<Self>,
19170 ) -> Vec<CustomBlockId> {
19171 let blocks = self
19172 .display_map
19173 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19174 if let Some(autoscroll) = autoscroll {
19175 self.request_autoscroll(autoscroll, cx);
19176 }
19177 cx.notify();
19178 blocks
19179 }
19180
19181 pub fn resize_blocks(
19182 &mut self,
19183 heights: HashMap<CustomBlockId, u32>,
19184 autoscroll: Option<Autoscroll>,
19185 cx: &mut Context<Self>,
19186 ) {
19187 self.display_map
19188 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19189 if let Some(autoscroll) = autoscroll {
19190 self.request_autoscroll(autoscroll, cx);
19191 }
19192 cx.notify();
19193 }
19194
19195 pub fn replace_blocks(
19196 &mut self,
19197 renderers: HashMap<CustomBlockId, RenderBlock>,
19198 autoscroll: Option<Autoscroll>,
19199 cx: &mut Context<Self>,
19200 ) {
19201 self.display_map
19202 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19203 if let Some(autoscroll) = autoscroll {
19204 self.request_autoscroll(autoscroll, cx);
19205 }
19206 cx.notify();
19207 }
19208
19209 pub fn remove_blocks(
19210 &mut self,
19211 block_ids: HashSet<CustomBlockId>,
19212 autoscroll: Option<Autoscroll>,
19213 cx: &mut Context<Self>,
19214 ) {
19215 self.display_map.update(cx, |display_map, cx| {
19216 display_map.remove_blocks(block_ids, cx)
19217 });
19218 if let Some(autoscroll) = autoscroll {
19219 self.request_autoscroll(autoscroll, cx);
19220 }
19221 cx.notify();
19222 }
19223
19224 pub fn row_for_block(
19225 &self,
19226 block_id: CustomBlockId,
19227 cx: &mut Context<Self>,
19228 ) -> Option<DisplayRow> {
19229 self.display_map
19230 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19231 }
19232
19233 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19234 self.focused_block = Some(focused_block);
19235 }
19236
19237 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19238 self.focused_block.take()
19239 }
19240
19241 pub fn insert_creases(
19242 &mut self,
19243 creases: impl IntoIterator<Item = Crease<Anchor>>,
19244 cx: &mut Context<Self>,
19245 ) -> Vec<CreaseId> {
19246 self.display_map
19247 .update(cx, |map, cx| map.insert_creases(creases, cx))
19248 }
19249
19250 pub fn remove_creases(
19251 &mut self,
19252 ids: impl IntoIterator<Item = CreaseId>,
19253 cx: &mut Context<Self>,
19254 ) -> Vec<(CreaseId, Range<Anchor>)> {
19255 self.display_map
19256 .update(cx, |map, cx| map.remove_creases(ids, cx))
19257 }
19258
19259 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19260 self.display_map
19261 .update(cx, |map, cx| map.snapshot(cx))
19262 .longest_row()
19263 }
19264
19265 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19266 self.display_map
19267 .update(cx, |map, cx| map.snapshot(cx))
19268 .max_point()
19269 }
19270
19271 pub fn text(&self, cx: &App) -> String {
19272 self.buffer.read(cx).read(cx).text()
19273 }
19274
19275 pub fn is_empty(&self, cx: &App) -> bool {
19276 self.buffer.read(cx).read(cx).is_empty()
19277 }
19278
19279 pub fn text_option(&self, cx: &App) -> Option<String> {
19280 let text = self.text(cx);
19281 let text = text.trim();
19282
19283 if text.is_empty() {
19284 return None;
19285 }
19286
19287 Some(text.to_string())
19288 }
19289
19290 pub fn set_text(
19291 &mut self,
19292 text: impl Into<Arc<str>>,
19293 window: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) {
19296 self.transact(window, cx, |this, _, cx| {
19297 this.buffer
19298 .read(cx)
19299 .as_singleton()
19300 .expect("you can only call set_text on editors for singleton buffers")
19301 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19302 });
19303 }
19304
19305 pub fn display_text(&self, cx: &mut App) -> String {
19306 self.display_map
19307 .update(cx, |map, cx| map.snapshot(cx))
19308 .text()
19309 }
19310
19311 fn create_minimap(
19312 &self,
19313 minimap_settings: MinimapSettings,
19314 window: &mut Window,
19315 cx: &mut Context<Self>,
19316 ) -> Option<Entity<Self>> {
19317 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19318 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19319 }
19320
19321 fn initialize_new_minimap(
19322 &self,
19323 minimap_settings: MinimapSettings,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) -> Entity<Self> {
19327 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19328
19329 let mut minimap = Editor::new_internal(
19330 EditorMode::Minimap {
19331 parent: cx.weak_entity(),
19332 },
19333 self.buffer.clone(),
19334 None,
19335 Some(self.display_map.clone()),
19336 window,
19337 cx,
19338 );
19339 minimap.scroll_manager.clone_state(&self.scroll_manager);
19340 minimap.set_text_style_refinement(TextStyleRefinement {
19341 font_size: Some(MINIMAP_FONT_SIZE),
19342 font_weight: Some(MINIMAP_FONT_WEIGHT),
19343 ..Default::default()
19344 });
19345 minimap.update_minimap_configuration(minimap_settings, cx);
19346 cx.new(|_| minimap)
19347 }
19348
19349 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19350 let current_line_highlight = minimap_settings
19351 .current_line_highlight
19352 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19353 self.set_current_line_highlight(Some(current_line_highlight));
19354 }
19355
19356 pub fn minimap(&self) -> Option<&Entity<Self>> {
19357 self.minimap
19358 .as_ref()
19359 .filter(|_| self.minimap_visibility.visible())
19360 }
19361
19362 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19363 let mut wrap_guides = smallvec![];
19364
19365 if self.show_wrap_guides == Some(false) {
19366 return wrap_guides;
19367 }
19368
19369 let settings = self.buffer.read(cx).language_settings(cx);
19370 if settings.show_wrap_guides {
19371 match self.soft_wrap_mode(cx) {
19372 SoftWrap::Column(soft_wrap) => {
19373 wrap_guides.push((soft_wrap as usize, true));
19374 }
19375 SoftWrap::Bounded(soft_wrap) => {
19376 wrap_guides.push((soft_wrap as usize, true));
19377 }
19378 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19379 }
19380 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19381 }
19382
19383 wrap_guides
19384 }
19385
19386 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19387 let settings = self.buffer.read(cx).language_settings(cx);
19388 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19389 match mode {
19390 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19391 SoftWrap::None
19392 }
19393 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19394 language_settings::SoftWrap::PreferredLineLength => {
19395 SoftWrap::Column(settings.preferred_line_length)
19396 }
19397 language_settings::SoftWrap::Bounded => {
19398 SoftWrap::Bounded(settings.preferred_line_length)
19399 }
19400 }
19401 }
19402
19403 pub fn set_soft_wrap_mode(
19404 &mut self,
19405 mode: language_settings::SoftWrap,
19406
19407 cx: &mut Context<Self>,
19408 ) {
19409 self.soft_wrap_mode_override = Some(mode);
19410 cx.notify();
19411 }
19412
19413 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19414 self.hard_wrap = hard_wrap;
19415 cx.notify();
19416 }
19417
19418 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19419 self.text_style_refinement = Some(style);
19420 }
19421
19422 /// called by the Element so we know what style we were most recently rendered with.
19423 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19424 // We intentionally do not inform the display map about the minimap style
19425 // so that wrapping is not recalculated and stays consistent for the editor
19426 // and its linked minimap.
19427 if !self.mode.is_minimap() {
19428 let font = style.text.font();
19429 let font_size = style.text.font_size.to_pixels(window.rem_size());
19430 let display_map = self
19431 .placeholder_display_map
19432 .as_ref()
19433 .filter(|_| self.is_empty(cx))
19434 .unwrap_or(&self.display_map);
19435
19436 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19437 }
19438 self.style = Some(style);
19439 }
19440
19441 pub fn style(&self) -> Option<&EditorStyle> {
19442 self.style.as_ref()
19443 }
19444
19445 // Called by the element. This method is not designed to be called outside of the editor
19446 // element's layout code because it does not notify when rewrapping is computed synchronously.
19447 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19448 if self.is_empty(cx) {
19449 self.placeholder_display_map
19450 .as_ref()
19451 .map_or(false, |display_map| {
19452 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19453 })
19454 } else {
19455 self.display_map
19456 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19457 }
19458 }
19459
19460 pub fn set_soft_wrap(&mut self) {
19461 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19462 }
19463
19464 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19465 if self.soft_wrap_mode_override.is_some() {
19466 self.soft_wrap_mode_override.take();
19467 } else {
19468 let soft_wrap = match self.soft_wrap_mode(cx) {
19469 SoftWrap::GitDiff => return,
19470 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19471 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19472 language_settings::SoftWrap::None
19473 }
19474 };
19475 self.soft_wrap_mode_override = Some(soft_wrap);
19476 }
19477 cx.notify();
19478 }
19479
19480 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19481 let Some(workspace) = self.workspace() else {
19482 return;
19483 };
19484 let fs = workspace.read(cx).app_state().fs.clone();
19485 let current_show = TabBarSettings::get_global(cx).show;
19486 update_settings_file(fs, cx, move |setting, _| {
19487 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19488 });
19489 }
19490
19491 pub fn toggle_indent_guides(
19492 &mut self,
19493 _: &ToggleIndentGuides,
19494 _: &mut Window,
19495 cx: &mut Context<Self>,
19496 ) {
19497 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19498 self.buffer
19499 .read(cx)
19500 .language_settings(cx)
19501 .indent_guides
19502 .enabled
19503 });
19504 self.show_indent_guides = Some(!currently_enabled);
19505 cx.notify();
19506 }
19507
19508 fn should_show_indent_guides(&self) -> Option<bool> {
19509 self.show_indent_guides
19510 }
19511
19512 pub fn toggle_line_numbers(
19513 &mut self,
19514 _: &ToggleLineNumbers,
19515 _: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) {
19518 let mut editor_settings = EditorSettings::get_global(cx).clone();
19519 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19520 EditorSettings::override_global(editor_settings, cx);
19521 }
19522
19523 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19524 if let Some(show_line_numbers) = self.show_line_numbers {
19525 return show_line_numbers;
19526 }
19527 EditorSettings::get_global(cx).gutter.line_numbers
19528 }
19529
19530 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19531 self.use_relative_line_numbers
19532 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19533 }
19534
19535 pub fn toggle_relative_line_numbers(
19536 &mut self,
19537 _: &ToggleRelativeLineNumbers,
19538 _: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 let is_relative = self.should_use_relative_line_numbers(cx);
19542 self.set_relative_line_number(Some(!is_relative), cx)
19543 }
19544
19545 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19546 self.use_relative_line_numbers = is_relative;
19547 cx.notify();
19548 }
19549
19550 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19551 self.show_gutter = show_gutter;
19552 cx.notify();
19553 }
19554
19555 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19556 self.show_scrollbars = ScrollbarAxes {
19557 horizontal: show,
19558 vertical: show,
19559 };
19560 cx.notify();
19561 }
19562
19563 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19564 self.show_scrollbars.vertical = show;
19565 cx.notify();
19566 }
19567
19568 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19569 self.show_scrollbars.horizontal = show;
19570 cx.notify();
19571 }
19572
19573 pub fn set_minimap_visibility(
19574 &mut self,
19575 minimap_visibility: MinimapVisibility,
19576 window: &mut Window,
19577 cx: &mut Context<Self>,
19578 ) {
19579 if self.minimap_visibility != minimap_visibility {
19580 if minimap_visibility.visible() && self.minimap.is_none() {
19581 let minimap_settings = EditorSettings::get_global(cx).minimap;
19582 self.minimap =
19583 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19584 }
19585 self.minimap_visibility = minimap_visibility;
19586 cx.notify();
19587 }
19588 }
19589
19590 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19591 self.set_show_scrollbars(false, cx);
19592 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19593 }
19594
19595 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19596 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19597 }
19598
19599 /// Normally the text in full mode and auto height editors is padded on the
19600 /// left side by roughly half a character width for improved hit testing.
19601 ///
19602 /// Use this method to disable this for cases where this is not wanted (e.g.
19603 /// if you want to align the editor text with some other text above or below)
19604 /// or if you want to add this padding to single-line editors.
19605 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19606 self.offset_content = offset_content;
19607 cx.notify();
19608 }
19609
19610 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19611 self.show_line_numbers = Some(show_line_numbers);
19612 cx.notify();
19613 }
19614
19615 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19616 self.disable_expand_excerpt_buttons = true;
19617 cx.notify();
19618 }
19619
19620 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19621 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19622 cx.notify();
19623 }
19624
19625 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19626 self.show_code_actions = Some(show_code_actions);
19627 cx.notify();
19628 }
19629
19630 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19631 self.show_runnables = Some(show_runnables);
19632 cx.notify();
19633 }
19634
19635 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19636 self.show_breakpoints = Some(show_breakpoints);
19637 cx.notify();
19638 }
19639
19640 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19641 if self.display_map.read(cx).masked != masked {
19642 self.display_map.update(cx, |map, _| map.masked = masked);
19643 }
19644 cx.notify()
19645 }
19646
19647 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19648 self.show_wrap_guides = Some(show_wrap_guides);
19649 cx.notify();
19650 }
19651
19652 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19653 self.show_indent_guides = Some(show_indent_guides);
19654 cx.notify();
19655 }
19656
19657 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19658 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19659 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19660 && let Some(dir) = file.abs_path(cx).parent()
19661 {
19662 return Some(dir.to_owned());
19663 }
19664 }
19665
19666 None
19667 }
19668
19669 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19670 self.active_excerpt(cx)?
19671 .1
19672 .read(cx)
19673 .file()
19674 .and_then(|f| f.as_local())
19675 }
19676
19677 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19678 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19679 let buffer = buffer.read(cx);
19680 if let Some(project_path) = buffer.project_path(cx) {
19681 let project = self.project()?.read(cx);
19682 project.absolute_path(&project_path, cx)
19683 } else {
19684 buffer
19685 .file()
19686 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19687 }
19688 })
19689 }
19690
19691 pub fn reveal_in_finder(
19692 &mut self,
19693 _: &RevealInFileManager,
19694 _window: &mut Window,
19695 cx: &mut Context<Self>,
19696 ) {
19697 if let Some(target) = self.target_file(cx) {
19698 cx.reveal_path(&target.abs_path(cx));
19699 }
19700 }
19701
19702 pub fn copy_path(
19703 &mut self,
19704 _: &zed_actions::workspace::CopyPath,
19705 _window: &mut Window,
19706 cx: &mut Context<Self>,
19707 ) {
19708 if let Some(path) = self.target_file_abs_path(cx)
19709 && let Some(path) = path.to_str()
19710 {
19711 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19712 } else {
19713 cx.propagate();
19714 }
19715 }
19716
19717 pub fn copy_relative_path(
19718 &mut self,
19719 _: &zed_actions::workspace::CopyRelativePath,
19720 _window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) {
19723 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19724 let project = self.project()?.read(cx);
19725 let path = buffer.read(cx).file()?.path();
19726 let path = path.display(project.path_style(cx));
19727 Some(path)
19728 }) {
19729 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19730 } else {
19731 cx.propagate();
19732 }
19733 }
19734
19735 /// Returns the project path for the editor's buffer, if any buffer is
19736 /// opened in the editor.
19737 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19738 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19739 buffer.read(cx).project_path(cx)
19740 } else {
19741 None
19742 }
19743 }
19744
19745 // Returns true if the editor handled a go-to-line request
19746 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19747 maybe!({
19748 let breakpoint_store = self.breakpoint_store.as_ref()?;
19749
19750 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19751 else {
19752 self.clear_row_highlights::<ActiveDebugLine>();
19753 return None;
19754 };
19755
19756 let position = active_stack_frame.position;
19757 let buffer_id = position.buffer_id?;
19758 let snapshot = self
19759 .project
19760 .as_ref()?
19761 .read(cx)
19762 .buffer_for_id(buffer_id, cx)?
19763 .read(cx)
19764 .snapshot();
19765
19766 let mut handled = false;
19767 for (id, ExcerptRange { context, .. }) in
19768 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19769 {
19770 if context.start.cmp(&position, &snapshot).is_ge()
19771 || context.end.cmp(&position, &snapshot).is_lt()
19772 {
19773 continue;
19774 }
19775 let snapshot = self.buffer.read(cx).snapshot(cx);
19776 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19777
19778 handled = true;
19779 self.clear_row_highlights::<ActiveDebugLine>();
19780
19781 self.go_to_line::<ActiveDebugLine>(
19782 multibuffer_anchor,
19783 Some(cx.theme().colors().editor_debugger_active_line_background),
19784 window,
19785 cx,
19786 );
19787
19788 cx.notify();
19789 }
19790
19791 handled.then_some(())
19792 })
19793 .is_some()
19794 }
19795
19796 pub fn copy_file_name_without_extension(
19797 &mut self,
19798 _: &CopyFileNameWithoutExtension,
19799 _: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 if let Some(file) = self.target_file(cx)
19803 && let Some(file_stem) = file.path().file_stem()
19804 {
19805 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19806 }
19807 }
19808
19809 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19810 if let Some(file) = self.target_file(cx)
19811 && let Some(name) = file.path().file_name()
19812 {
19813 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19814 }
19815 }
19816
19817 pub fn toggle_git_blame(
19818 &mut self,
19819 _: &::git::Blame,
19820 window: &mut Window,
19821 cx: &mut Context<Self>,
19822 ) {
19823 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19824
19825 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19826 self.start_git_blame(true, window, cx);
19827 }
19828
19829 cx.notify();
19830 }
19831
19832 pub fn toggle_git_blame_inline(
19833 &mut self,
19834 _: &ToggleGitBlameInline,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 self.toggle_git_blame_inline_internal(true, window, cx);
19839 cx.notify();
19840 }
19841
19842 pub fn open_git_blame_commit(
19843 &mut self,
19844 _: &OpenGitBlameCommit,
19845 window: &mut Window,
19846 cx: &mut Context<Self>,
19847 ) {
19848 self.open_git_blame_commit_internal(window, cx);
19849 }
19850
19851 fn open_git_blame_commit_internal(
19852 &mut self,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) -> Option<()> {
19856 let blame = self.blame.as_ref()?;
19857 let snapshot = self.snapshot(window, cx);
19858 let cursor = self
19859 .selections
19860 .newest::<Point>(&snapshot.display_snapshot)
19861 .head();
19862 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19863 let (_, blame_entry) = blame
19864 .update(cx, |blame, cx| {
19865 blame
19866 .blame_for_rows(
19867 &[RowInfo {
19868 buffer_id: Some(buffer.remote_id()),
19869 buffer_row: Some(point.row),
19870 ..Default::default()
19871 }],
19872 cx,
19873 )
19874 .next()
19875 })
19876 .flatten()?;
19877 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19878 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19879 let workspace = self.workspace()?.downgrade();
19880 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19881 None
19882 }
19883
19884 pub fn git_blame_inline_enabled(&self) -> bool {
19885 self.git_blame_inline_enabled
19886 }
19887
19888 pub fn toggle_selection_menu(
19889 &mut self,
19890 _: &ToggleSelectionMenu,
19891 _: &mut Window,
19892 cx: &mut Context<Self>,
19893 ) {
19894 self.show_selection_menu = self
19895 .show_selection_menu
19896 .map(|show_selections_menu| !show_selections_menu)
19897 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19898
19899 cx.notify();
19900 }
19901
19902 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19903 self.show_selection_menu
19904 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19905 }
19906
19907 fn start_git_blame(
19908 &mut self,
19909 user_triggered: bool,
19910 window: &mut Window,
19911 cx: &mut Context<Self>,
19912 ) {
19913 if let Some(project) = self.project() {
19914 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19915 && buffer.read(cx).file().is_none()
19916 {
19917 return;
19918 }
19919
19920 let focused = self.focus_handle(cx).contains_focused(window, cx);
19921
19922 let project = project.clone();
19923 let blame = cx
19924 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19925 self.blame_subscription =
19926 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19927 self.blame = Some(blame);
19928 }
19929 }
19930
19931 fn toggle_git_blame_inline_internal(
19932 &mut self,
19933 user_triggered: bool,
19934 window: &mut Window,
19935 cx: &mut Context<Self>,
19936 ) {
19937 if self.git_blame_inline_enabled {
19938 self.git_blame_inline_enabled = false;
19939 self.show_git_blame_inline = false;
19940 self.show_git_blame_inline_delay_task.take();
19941 } else {
19942 self.git_blame_inline_enabled = true;
19943 self.start_git_blame_inline(user_triggered, window, cx);
19944 }
19945
19946 cx.notify();
19947 }
19948
19949 fn start_git_blame_inline(
19950 &mut self,
19951 user_triggered: bool,
19952 window: &mut Window,
19953 cx: &mut Context<Self>,
19954 ) {
19955 self.start_git_blame(user_triggered, window, cx);
19956
19957 if ProjectSettings::get_global(cx)
19958 .git
19959 .inline_blame_delay()
19960 .is_some()
19961 {
19962 self.start_inline_blame_timer(window, cx);
19963 } else {
19964 self.show_git_blame_inline = true
19965 }
19966 }
19967
19968 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19969 self.blame.as_ref()
19970 }
19971
19972 pub fn show_git_blame_gutter(&self) -> bool {
19973 self.show_git_blame_gutter
19974 }
19975
19976 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19977 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19978 }
19979
19980 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19981 self.show_git_blame_inline
19982 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19983 && !self.newest_selection_head_on_empty_line(cx)
19984 && self.has_blame_entries(cx)
19985 }
19986
19987 fn has_blame_entries(&self, cx: &App) -> bool {
19988 self.blame()
19989 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19990 }
19991
19992 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19993 let cursor_anchor = self.selections.newest_anchor().head();
19994
19995 let snapshot = self.buffer.read(cx).snapshot(cx);
19996 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19997
19998 snapshot.line_len(buffer_row) == 0
19999 }
20000
20001 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20002 let buffer_and_selection = maybe!({
20003 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20004 let selection_range = selection.range();
20005
20006 let multi_buffer = self.buffer().read(cx);
20007 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20008 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20009
20010 let (buffer, range, _) = if selection.reversed {
20011 buffer_ranges.first()
20012 } else {
20013 buffer_ranges.last()
20014 }?;
20015
20016 let selection = text::ToPoint::to_point(&range.start, buffer).row
20017 ..text::ToPoint::to_point(&range.end, buffer).row;
20018 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20019 });
20020
20021 let Some((buffer, selection)) = buffer_and_selection else {
20022 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20023 };
20024
20025 let Some(project) = self.project() else {
20026 return Task::ready(Err(anyhow!("editor does not have project")));
20027 };
20028
20029 project.update(cx, |project, cx| {
20030 project.get_permalink_to_line(&buffer, selection, cx)
20031 })
20032 }
20033
20034 pub fn copy_permalink_to_line(
20035 &mut self,
20036 _: &CopyPermalinkToLine,
20037 window: &mut Window,
20038 cx: &mut Context<Self>,
20039 ) {
20040 let permalink_task = self.get_permalink_to_line(cx);
20041 let workspace = self.workspace();
20042
20043 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20044 Ok(permalink) => {
20045 cx.update(|_, cx| {
20046 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20047 })
20048 .ok();
20049 }
20050 Err(err) => {
20051 let message = format!("Failed to copy permalink: {err}");
20052
20053 anyhow::Result::<()>::Err(err).log_err();
20054
20055 if let Some(workspace) = workspace {
20056 workspace
20057 .update_in(cx, |workspace, _, cx| {
20058 struct CopyPermalinkToLine;
20059
20060 workspace.show_toast(
20061 Toast::new(
20062 NotificationId::unique::<CopyPermalinkToLine>(),
20063 message,
20064 ),
20065 cx,
20066 )
20067 })
20068 .ok();
20069 }
20070 }
20071 })
20072 .detach();
20073 }
20074
20075 pub fn copy_file_location(
20076 &mut self,
20077 _: &CopyFileLocation,
20078 _: &mut Window,
20079 cx: &mut Context<Self>,
20080 ) {
20081 let selection = self
20082 .selections
20083 .newest::<Point>(&self.display_snapshot(cx))
20084 .start
20085 .row
20086 + 1;
20087 if let Some(file) = self.target_file(cx) {
20088 let path = file.path().display(file.path_style(cx));
20089 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20090 }
20091 }
20092
20093 pub fn open_permalink_to_line(
20094 &mut self,
20095 _: &OpenPermalinkToLine,
20096 window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 let permalink_task = self.get_permalink_to_line(cx);
20100 let workspace = self.workspace();
20101
20102 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20103 Ok(permalink) => {
20104 cx.update(|_, cx| {
20105 cx.open_url(permalink.as_ref());
20106 })
20107 .ok();
20108 }
20109 Err(err) => {
20110 let message = format!("Failed to open permalink: {err}");
20111
20112 anyhow::Result::<()>::Err(err).log_err();
20113
20114 if let Some(workspace) = workspace {
20115 workspace
20116 .update(cx, |workspace, cx| {
20117 struct OpenPermalinkToLine;
20118
20119 workspace.show_toast(
20120 Toast::new(
20121 NotificationId::unique::<OpenPermalinkToLine>(),
20122 message,
20123 ),
20124 cx,
20125 )
20126 })
20127 .ok();
20128 }
20129 }
20130 })
20131 .detach();
20132 }
20133
20134 pub fn insert_uuid_v4(
20135 &mut self,
20136 _: &InsertUuidV4,
20137 window: &mut Window,
20138 cx: &mut Context<Self>,
20139 ) {
20140 self.insert_uuid(UuidVersion::V4, window, cx);
20141 }
20142
20143 pub fn insert_uuid_v7(
20144 &mut self,
20145 _: &InsertUuidV7,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 self.insert_uuid(UuidVersion::V7, window, cx);
20150 }
20151
20152 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20153 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20154 self.transact(window, cx, |this, window, cx| {
20155 let edits = this
20156 .selections
20157 .all::<Point>(&this.display_snapshot(cx))
20158 .into_iter()
20159 .map(|selection| {
20160 let uuid = match version {
20161 UuidVersion::V4 => uuid::Uuid::new_v4(),
20162 UuidVersion::V7 => uuid::Uuid::now_v7(),
20163 };
20164
20165 (selection.range(), uuid.to_string())
20166 });
20167 this.edit(edits, cx);
20168 this.refresh_edit_prediction(true, false, window, cx);
20169 });
20170 }
20171
20172 pub fn open_selections_in_multibuffer(
20173 &mut self,
20174 _: &OpenSelectionsInMultibuffer,
20175 window: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) {
20178 let multibuffer = self.buffer.read(cx);
20179
20180 let Some(buffer) = multibuffer.as_singleton() else {
20181 return;
20182 };
20183
20184 let Some(workspace) = self.workspace() else {
20185 return;
20186 };
20187
20188 let title = multibuffer.title(cx).to_string();
20189
20190 let locations = self
20191 .selections
20192 .all_anchors(cx)
20193 .iter()
20194 .map(|selection| {
20195 (
20196 buffer.clone(),
20197 (selection.start.text_anchor..selection.end.text_anchor)
20198 .to_point(buffer.read(cx)),
20199 )
20200 })
20201 .into_group_map();
20202
20203 cx.spawn_in(window, async move |_, cx| {
20204 workspace.update_in(cx, |workspace, window, cx| {
20205 Self::open_locations_in_multibuffer(
20206 workspace,
20207 locations,
20208 format!("Selections for '{title}'"),
20209 false,
20210 MultibufferSelectionMode::All,
20211 window,
20212 cx,
20213 );
20214 })
20215 })
20216 .detach();
20217 }
20218
20219 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20220 /// last highlight added will be used.
20221 ///
20222 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20223 pub fn highlight_rows<T: 'static>(
20224 &mut self,
20225 range: Range<Anchor>,
20226 color: Hsla,
20227 options: RowHighlightOptions,
20228 cx: &mut Context<Self>,
20229 ) {
20230 let snapshot = self.buffer().read(cx).snapshot(cx);
20231 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20232 let ix = row_highlights.binary_search_by(|highlight| {
20233 Ordering::Equal
20234 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20235 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20236 });
20237
20238 if let Err(mut ix) = ix {
20239 let index = post_inc(&mut self.highlight_order);
20240
20241 // If this range intersects with the preceding highlight, then merge it with
20242 // the preceding highlight. Otherwise insert a new highlight.
20243 let mut merged = false;
20244 if ix > 0 {
20245 let prev_highlight = &mut row_highlights[ix - 1];
20246 if prev_highlight
20247 .range
20248 .end
20249 .cmp(&range.start, &snapshot)
20250 .is_ge()
20251 {
20252 ix -= 1;
20253 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20254 prev_highlight.range.end = range.end;
20255 }
20256 merged = true;
20257 prev_highlight.index = index;
20258 prev_highlight.color = color;
20259 prev_highlight.options = options;
20260 }
20261 }
20262
20263 if !merged {
20264 row_highlights.insert(
20265 ix,
20266 RowHighlight {
20267 range,
20268 index,
20269 color,
20270 options,
20271 type_id: TypeId::of::<T>(),
20272 },
20273 );
20274 }
20275
20276 // If any of the following highlights intersect with this one, merge them.
20277 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20278 let highlight = &row_highlights[ix];
20279 if next_highlight
20280 .range
20281 .start
20282 .cmp(&highlight.range.end, &snapshot)
20283 .is_le()
20284 {
20285 if next_highlight
20286 .range
20287 .end
20288 .cmp(&highlight.range.end, &snapshot)
20289 .is_gt()
20290 {
20291 row_highlights[ix].range.end = next_highlight.range.end;
20292 }
20293 row_highlights.remove(ix + 1);
20294 } else {
20295 break;
20296 }
20297 }
20298 }
20299 }
20300
20301 /// Remove any highlighted row ranges of the given type that intersect the
20302 /// given ranges.
20303 pub fn remove_highlighted_rows<T: 'static>(
20304 &mut self,
20305 ranges_to_remove: Vec<Range<Anchor>>,
20306 cx: &mut Context<Self>,
20307 ) {
20308 let snapshot = self.buffer().read(cx).snapshot(cx);
20309 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20310 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20311 row_highlights.retain(|highlight| {
20312 while let Some(range_to_remove) = ranges_to_remove.peek() {
20313 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20314 Ordering::Less | Ordering::Equal => {
20315 ranges_to_remove.next();
20316 }
20317 Ordering::Greater => {
20318 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20319 Ordering::Less | Ordering::Equal => {
20320 return false;
20321 }
20322 Ordering::Greater => break,
20323 }
20324 }
20325 }
20326 }
20327
20328 true
20329 })
20330 }
20331
20332 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20333 pub fn clear_row_highlights<T: 'static>(&mut self) {
20334 self.highlighted_rows.remove(&TypeId::of::<T>());
20335 }
20336
20337 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20338 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20339 self.highlighted_rows
20340 .get(&TypeId::of::<T>())
20341 .map_or(&[] as &[_], |vec| vec.as_slice())
20342 .iter()
20343 .map(|highlight| (highlight.range.clone(), highlight.color))
20344 }
20345
20346 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20347 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20348 /// Allows to ignore certain kinds of highlights.
20349 pub fn highlighted_display_rows(
20350 &self,
20351 window: &mut Window,
20352 cx: &mut App,
20353 ) -> BTreeMap<DisplayRow, LineHighlight> {
20354 let snapshot = self.snapshot(window, cx);
20355 let mut used_highlight_orders = HashMap::default();
20356 self.highlighted_rows
20357 .iter()
20358 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20359 .fold(
20360 BTreeMap::<DisplayRow, LineHighlight>::new(),
20361 |mut unique_rows, highlight| {
20362 let start = highlight.range.start.to_display_point(&snapshot);
20363 let end = highlight.range.end.to_display_point(&snapshot);
20364 let start_row = start.row().0;
20365 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20366 && end.column() == 0
20367 {
20368 end.row().0.saturating_sub(1)
20369 } else {
20370 end.row().0
20371 };
20372 for row in start_row..=end_row {
20373 let used_index =
20374 used_highlight_orders.entry(row).or_insert(highlight.index);
20375 if highlight.index >= *used_index {
20376 *used_index = highlight.index;
20377 unique_rows.insert(
20378 DisplayRow(row),
20379 LineHighlight {
20380 include_gutter: highlight.options.include_gutter,
20381 border: None,
20382 background: highlight.color.into(),
20383 type_id: Some(highlight.type_id),
20384 },
20385 );
20386 }
20387 }
20388 unique_rows
20389 },
20390 )
20391 }
20392
20393 pub fn highlighted_display_row_for_autoscroll(
20394 &self,
20395 snapshot: &DisplaySnapshot,
20396 ) -> Option<DisplayRow> {
20397 self.highlighted_rows
20398 .values()
20399 .flat_map(|highlighted_rows| highlighted_rows.iter())
20400 .filter_map(|highlight| {
20401 if highlight.options.autoscroll {
20402 Some(highlight.range.start.to_display_point(snapshot).row())
20403 } else {
20404 None
20405 }
20406 })
20407 .min()
20408 }
20409
20410 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20411 self.highlight_background::<SearchWithinRange>(
20412 ranges,
20413 |colors| colors.colors().editor_document_highlight_read_background,
20414 cx,
20415 )
20416 }
20417
20418 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20419 self.breadcrumb_header = Some(new_header);
20420 }
20421
20422 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20423 self.clear_background_highlights::<SearchWithinRange>(cx);
20424 }
20425
20426 pub fn highlight_background<T: 'static>(
20427 &mut self,
20428 ranges: &[Range<Anchor>],
20429 color_fetcher: fn(&Theme) -> Hsla,
20430 cx: &mut Context<Self>,
20431 ) {
20432 self.background_highlights.insert(
20433 HighlightKey::Type(TypeId::of::<T>()),
20434 (color_fetcher, Arc::from(ranges)),
20435 );
20436 self.scrollbar_marker_state.dirty = true;
20437 cx.notify();
20438 }
20439
20440 pub fn highlight_background_key<T: 'static>(
20441 &mut self,
20442 key: usize,
20443 ranges: &[Range<Anchor>],
20444 color_fetcher: fn(&Theme) -> Hsla,
20445 cx: &mut Context<Self>,
20446 ) {
20447 self.background_highlights.insert(
20448 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20449 (color_fetcher, Arc::from(ranges)),
20450 );
20451 self.scrollbar_marker_state.dirty = true;
20452 cx.notify();
20453 }
20454
20455 pub fn clear_background_highlights<T: 'static>(
20456 &mut self,
20457 cx: &mut Context<Self>,
20458 ) -> Option<BackgroundHighlight> {
20459 let text_highlights = self
20460 .background_highlights
20461 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20462 if !text_highlights.1.is_empty() {
20463 self.scrollbar_marker_state.dirty = true;
20464 cx.notify();
20465 }
20466 Some(text_highlights)
20467 }
20468
20469 pub fn highlight_gutter<T: 'static>(
20470 &mut self,
20471 ranges: impl Into<Vec<Range<Anchor>>>,
20472 color_fetcher: fn(&App) -> Hsla,
20473 cx: &mut Context<Self>,
20474 ) {
20475 self.gutter_highlights
20476 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20477 cx.notify();
20478 }
20479
20480 pub fn clear_gutter_highlights<T: 'static>(
20481 &mut self,
20482 cx: &mut Context<Self>,
20483 ) -> Option<GutterHighlight> {
20484 cx.notify();
20485 self.gutter_highlights.remove(&TypeId::of::<T>())
20486 }
20487
20488 pub fn insert_gutter_highlight<T: 'static>(
20489 &mut self,
20490 range: Range<Anchor>,
20491 color_fetcher: fn(&App) -> Hsla,
20492 cx: &mut Context<Self>,
20493 ) {
20494 let snapshot = self.buffer().read(cx).snapshot(cx);
20495 let mut highlights = self
20496 .gutter_highlights
20497 .remove(&TypeId::of::<T>())
20498 .map(|(_, highlights)| highlights)
20499 .unwrap_or_default();
20500 let ix = highlights.binary_search_by(|highlight| {
20501 Ordering::Equal
20502 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20503 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20504 });
20505 if let Err(ix) = ix {
20506 highlights.insert(ix, range);
20507 }
20508 self.gutter_highlights
20509 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20510 }
20511
20512 pub fn remove_gutter_highlights<T: 'static>(
20513 &mut self,
20514 ranges_to_remove: Vec<Range<Anchor>>,
20515 cx: &mut Context<Self>,
20516 ) {
20517 let snapshot = self.buffer().read(cx).snapshot(cx);
20518 let Some((color_fetcher, mut gutter_highlights)) =
20519 self.gutter_highlights.remove(&TypeId::of::<T>())
20520 else {
20521 return;
20522 };
20523 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20524 gutter_highlights.retain(|highlight| {
20525 while let Some(range_to_remove) = ranges_to_remove.peek() {
20526 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20527 Ordering::Less | Ordering::Equal => {
20528 ranges_to_remove.next();
20529 }
20530 Ordering::Greater => {
20531 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20532 Ordering::Less | Ordering::Equal => {
20533 return false;
20534 }
20535 Ordering::Greater => break,
20536 }
20537 }
20538 }
20539 }
20540
20541 true
20542 });
20543 self.gutter_highlights
20544 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20545 }
20546
20547 #[cfg(feature = "test-support")]
20548 pub fn all_text_highlights(
20549 &self,
20550 window: &mut Window,
20551 cx: &mut Context<Self>,
20552 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20553 let snapshot = self.snapshot(window, cx);
20554 self.display_map.update(cx, |display_map, _| {
20555 display_map
20556 .all_text_highlights()
20557 .map(|highlight| {
20558 let (style, ranges) = highlight.as_ref();
20559 (
20560 *style,
20561 ranges
20562 .iter()
20563 .map(|range| range.clone().to_display_points(&snapshot))
20564 .collect(),
20565 )
20566 })
20567 .collect()
20568 })
20569 }
20570
20571 #[cfg(feature = "test-support")]
20572 pub fn all_text_background_highlights(
20573 &self,
20574 window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20577 let snapshot = self.snapshot(window, cx);
20578 let buffer = &snapshot.buffer_snapshot();
20579 let start = buffer.anchor_before(0);
20580 let end = buffer.anchor_after(buffer.len());
20581 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20582 }
20583
20584 #[cfg(any(test, feature = "test-support"))]
20585 pub fn sorted_background_highlights_in_range(
20586 &self,
20587 search_range: Range<Anchor>,
20588 display_snapshot: &DisplaySnapshot,
20589 theme: &Theme,
20590 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20591 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20592 res.sort_by(|a, b| {
20593 a.0.start
20594 .cmp(&b.0.start)
20595 .then_with(|| a.0.end.cmp(&b.0.end))
20596 .then_with(|| a.1.cmp(&b.1))
20597 });
20598 res
20599 }
20600
20601 #[cfg(feature = "test-support")]
20602 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20603 let snapshot = self.buffer().read(cx).snapshot(cx);
20604
20605 let highlights = self
20606 .background_highlights
20607 .get(&HighlightKey::Type(TypeId::of::<
20608 items::BufferSearchHighlights,
20609 >()));
20610
20611 if let Some((_color, ranges)) = highlights {
20612 ranges
20613 .iter()
20614 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20615 .collect_vec()
20616 } else {
20617 vec![]
20618 }
20619 }
20620
20621 fn document_highlights_for_position<'a>(
20622 &'a self,
20623 position: Anchor,
20624 buffer: &'a MultiBufferSnapshot,
20625 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20626 let read_highlights = self
20627 .background_highlights
20628 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20629 .map(|h| &h.1);
20630 let write_highlights = self
20631 .background_highlights
20632 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20633 .map(|h| &h.1);
20634 let left_position = position.bias_left(buffer);
20635 let right_position = position.bias_right(buffer);
20636 read_highlights
20637 .into_iter()
20638 .chain(write_highlights)
20639 .flat_map(move |ranges| {
20640 let start_ix = match ranges.binary_search_by(|probe| {
20641 let cmp = probe.end.cmp(&left_position, buffer);
20642 if cmp.is_ge() {
20643 Ordering::Greater
20644 } else {
20645 Ordering::Less
20646 }
20647 }) {
20648 Ok(i) | Err(i) => i,
20649 };
20650
20651 ranges[start_ix..]
20652 .iter()
20653 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20654 })
20655 }
20656
20657 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20658 self.background_highlights
20659 .get(&HighlightKey::Type(TypeId::of::<T>()))
20660 .is_some_and(|(_, highlights)| !highlights.is_empty())
20661 }
20662
20663 /// Returns all background highlights for a given range.
20664 ///
20665 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20666 pub fn background_highlights_in_range(
20667 &self,
20668 search_range: Range<Anchor>,
20669 display_snapshot: &DisplaySnapshot,
20670 theme: &Theme,
20671 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20672 let mut results = Vec::new();
20673 for (color_fetcher, ranges) in self.background_highlights.values() {
20674 let color = color_fetcher(theme);
20675 let start_ix = match ranges.binary_search_by(|probe| {
20676 let cmp = probe
20677 .end
20678 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20679 if cmp.is_gt() {
20680 Ordering::Greater
20681 } else {
20682 Ordering::Less
20683 }
20684 }) {
20685 Ok(i) | Err(i) => i,
20686 };
20687 for range in &ranges[start_ix..] {
20688 if range
20689 .start
20690 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20691 .is_ge()
20692 {
20693 break;
20694 }
20695
20696 let start = range.start.to_display_point(display_snapshot);
20697 let end = range.end.to_display_point(display_snapshot);
20698 results.push((start..end, color))
20699 }
20700 }
20701 results
20702 }
20703
20704 pub fn gutter_highlights_in_range(
20705 &self,
20706 search_range: Range<Anchor>,
20707 display_snapshot: &DisplaySnapshot,
20708 cx: &App,
20709 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20710 let mut results = Vec::new();
20711 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20712 let color = color_fetcher(cx);
20713 let start_ix = match ranges.binary_search_by(|probe| {
20714 let cmp = probe
20715 .end
20716 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20717 if cmp.is_gt() {
20718 Ordering::Greater
20719 } else {
20720 Ordering::Less
20721 }
20722 }) {
20723 Ok(i) | Err(i) => i,
20724 };
20725 for range in &ranges[start_ix..] {
20726 if range
20727 .start
20728 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20729 .is_ge()
20730 {
20731 break;
20732 }
20733
20734 let start = range.start.to_display_point(display_snapshot);
20735 let end = range.end.to_display_point(display_snapshot);
20736 results.push((start..end, color))
20737 }
20738 }
20739 results
20740 }
20741
20742 /// Get the text ranges corresponding to the redaction query
20743 pub fn redacted_ranges(
20744 &self,
20745 search_range: Range<Anchor>,
20746 display_snapshot: &DisplaySnapshot,
20747 cx: &App,
20748 ) -> Vec<Range<DisplayPoint>> {
20749 display_snapshot
20750 .buffer_snapshot()
20751 .redacted_ranges(search_range, |file| {
20752 if let Some(file) = file {
20753 file.is_private()
20754 && EditorSettings::get(
20755 Some(SettingsLocation {
20756 worktree_id: file.worktree_id(cx),
20757 path: file.path().as_ref(),
20758 }),
20759 cx,
20760 )
20761 .redact_private_values
20762 } else {
20763 false
20764 }
20765 })
20766 .map(|range| {
20767 range.start.to_display_point(display_snapshot)
20768 ..range.end.to_display_point(display_snapshot)
20769 })
20770 .collect()
20771 }
20772
20773 pub fn highlight_text_key<T: 'static>(
20774 &mut self,
20775 key: usize,
20776 ranges: Vec<Range<Anchor>>,
20777 style: HighlightStyle,
20778 cx: &mut Context<Self>,
20779 ) {
20780 self.display_map.update(cx, |map, _| {
20781 map.highlight_text(
20782 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20783 ranges,
20784 style,
20785 );
20786 });
20787 cx.notify();
20788 }
20789
20790 pub fn highlight_text<T: 'static>(
20791 &mut self,
20792 ranges: Vec<Range<Anchor>>,
20793 style: HighlightStyle,
20794 cx: &mut Context<Self>,
20795 ) {
20796 self.display_map.update(cx, |map, _| {
20797 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20798 });
20799 cx.notify();
20800 }
20801
20802 pub fn text_highlights<'a, T: 'static>(
20803 &'a self,
20804 cx: &'a App,
20805 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20806 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20807 }
20808
20809 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20810 let cleared = self
20811 .display_map
20812 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20813 if cleared {
20814 cx.notify();
20815 }
20816 }
20817
20818 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20819 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20820 && self.focus_handle.is_focused(window)
20821 }
20822
20823 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20824 self.show_cursor_when_unfocused = is_enabled;
20825 cx.notify();
20826 }
20827
20828 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20829 cx.notify();
20830 }
20831
20832 fn on_debug_session_event(
20833 &mut self,
20834 _session: Entity<Session>,
20835 event: &SessionEvent,
20836 cx: &mut Context<Self>,
20837 ) {
20838 if let SessionEvent::InvalidateInlineValue = event {
20839 self.refresh_inline_values(cx);
20840 }
20841 }
20842
20843 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20844 let Some(project) = self.project.clone() else {
20845 return;
20846 };
20847
20848 if !self.inline_value_cache.enabled {
20849 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20850 self.splice_inlays(&inlays, Vec::new(), cx);
20851 return;
20852 }
20853
20854 let current_execution_position = self
20855 .highlighted_rows
20856 .get(&TypeId::of::<ActiveDebugLine>())
20857 .and_then(|lines| lines.last().map(|line| line.range.end));
20858
20859 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20860 let inline_values = editor
20861 .update(cx, |editor, cx| {
20862 let Some(current_execution_position) = current_execution_position else {
20863 return Some(Task::ready(Ok(Vec::new())));
20864 };
20865
20866 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20867 let snapshot = buffer.snapshot(cx);
20868
20869 let excerpt = snapshot.excerpt_containing(
20870 current_execution_position..current_execution_position,
20871 )?;
20872
20873 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20874 })?;
20875
20876 let range =
20877 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20878
20879 project.inline_values(buffer, range, cx)
20880 })
20881 .ok()
20882 .flatten()?
20883 .await
20884 .context("refreshing debugger inlays")
20885 .log_err()?;
20886
20887 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20888
20889 for (buffer_id, inline_value) in inline_values
20890 .into_iter()
20891 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20892 {
20893 buffer_inline_values
20894 .entry(buffer_id)
20895 .or_default()
20896 .push(inline_value);
20897 }
20898
20899 editor
20900 .update(cx, |editor, cx| {
20901 let snapshot = editor.buffer.read(cx).snapshot(cx);
20902 let mut new_inlays = Vec::default();
20903
20904 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20905 let buffer_id = buffer_snapshot.remote_id();
20906 buffer_inline_values
20907 .get(&buffer_id)
20908 .into_iter()
20909 .flatten()
20910 .for_each(|hint| {
20911 let inlay = Inlay::debugger(
20912 post_inc(&mut editor.next_inlay_id),
20913 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20914 hint.text(),
20915 );
20916 if !inlay.text().chars().contains(&'\n') {
20917 new_inlays.push(inlay);
20918 }
20919 });
20920 }
20921
20922 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20923 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20924
20925 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20926 })
20927 .ok()?;
20928 Some(())
20929 });
20930 }
20931
20932 fn on_buffer_event(
20933 &mut self,
20934 multibuffer: &Entity<MultiBuffer>,
20935 event: &multi_buffer::Event,
20936 window: &mut Window,
20937 cx: &mut Context<Self>,
20938 ) {
20939 match event {
20940 multi_buffer::Event::Edited { edited_buffer } => {
20941 self.scrollbar_marker_state.dirty = true;
20942 self.active_indent_guides_state.dirty = true;
20943 self.refresh_active_diagnostics(cx);
20944 self.refresh_code_actions(window, cx);
20945 self.refresh_selected_text_highlights(true, window, cx);
20946 self.refresh_single_line_folds(window, cx);
20947 self.refresh_matching_bracket_highlights(window, cx);
20948 if self.has_active_edit_prediction() {
20949 self.update_visible_edit_prediction(window, cx);
20950 }
20951
20952 if let Some(buffer) = edited_buffer {
20953 if buffer.read(cx).file().is_none() {
20954 cx.emit(EditorEvent::TitleChanged);
20955 }
20956
20957 if self.project.is_some() {
20958 let buffer_id = buffer.read(cx).remote_id();
20959 self.register_buffer(buffer_id, cx);
20960 self.update_lsp_data(Some(buffer_id), window, cx);
20961 self.refresh_inlay_hints(
20962 InlayHintRefreshReason::BufferEdited(buffer_id),
20963 cx,
20964 );
20965 }
20966 }
20967
20968 cx.emit(EditorEvent::BufferEdited);
20969 cx.emit(SearchEvent::MatchesInvalidated);
20970
20971 let Some(project) = &self.project else { return };
20972 let (telemetry, is_via_ssh) = {
20973 let project = project.read(cx);
20974 let telemetry = project.client().telemetry().clone();
20975 let is_via_ssh = project.is_via_remote_server();
20976 (telemetry, is_via_ssh)
20977 };
20978 telemetry.log_edit_event("editor", is_via_ssh);
20979 }
20980 multi_buffer::Event::ExcerptsAdded {
20981 buffer,
20982 predecessor,
20983 excerpts,
20984 } => {
20985 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20986 let buffer_id = buffer.read(cx).remote_id();
20987 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20988 && let Some(project) = &self.project
20989 {
20990 update_uncommitted_diff_for_buffer(
20991 cx.entity(),
20992 project,
20993 [buffer.clone()],
20994 self.buffer.clone(),
20995 cx,
20996 )
20997 .detach();
20998 }
20999 self.update_lsp_data(Some(buffer_id), window, cx);
21000 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21001 cx.emit(EditorEvent::ExcerptsAdded {
21002 buffer: buffer.clone(),
21003 predecessor: *predecessor,
21004 excerpts: excerpts.clone(),
21005 });
21006 }
21007 multi_buffer::Event::ExcerptsRemoved {
21008 ids,
21009 removed_buffer_ids,
21010 } => {
21011 if let Some(inlay_hints) = &mut self.inlay_hints {
21012 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21013 }
21014 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21015 for buffer_id in removed_buffer_ids {
21016 self.registered_buffers.remove(buffer_id);
21017 }
21018 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21019 cx.emit(EditorEvent::ExcerptsRemoved {
21020 ids: ids.clone(),
21021 removed_buffer_ids: removed_buffer_ids.clone(),
21022 });
21023 }
21024 multi_buffer::Event::ExcerptsEdited {
21025 excerpt_ids,
21026 buffer_ids,
21027 } => {
21028 self.display_map.update(cx, |map, cx| {
21029 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21030 });
21031 cx.emit(EditorEvent::ExcerptsEdited {
21032 ids: excerpt_ids.clone(),
21033 });
21034 }
21035 multi_buffer::Event::ExcerptsExpanded { ids } => {
21036 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21037 self.refresh_document_highlights(cx);
21038 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21039 }
21040 multi_buffer::Event::Reparsed(buffer_id) => {
21041 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21042 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21043
21044 cx.emit(EditorEvent::Reparsed(*buffer_id));
21045 }
21046 multi_buffer::Event::DiffHunksToggled => {
21047 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21048 }
21049 multi_buffer::Event::LanguageChanged(buffer_id) => {
21050 self.registered_buffers.remove(&buffer_id);
21051 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21052 cx.emit(EditorEvent::Reparsed(*buffer_id));
21053 cx.notify();
21054 }
21055 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21056 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21057 multi_buffer::Event::FileHandleChanged
21058 | multi_buffer::Event::Reloaded
21059 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21060 multi_buffer::Event::DiagnosticsUpdated => {
21061 self.update_diagnostics_state(window, cx);
21062 }
21063 _ => {}
21064 };
21065 }
21066
21067 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21068 if !self.diagnostics_enabled() {
21069 return;
21070 }
21071 self.refresh_active_diagnostics(cx);
21072 self.refresh_inline_diagnostics(true, window, cx);
21073 self.scrollbar_marker_state.dirty = true;
21074 cx.notify();
21075 }
21076
21077 pub fn start_temporary_diff_override(&mut self) {
21078 self.load_diff_task.take();
21079 self.temporary_diff_override = true;
21080 }
21081
21082 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21083 self.temporary_diff_override = false;
21084 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21085 self.buffer.update(cx, |buffer, cx| {
21086 buffer.set_all_diff_hunks_collapsed(cx);
21087 });
21088
21089 if let Some(project) = self.project.clone() {
21090 self.load_diff_task = Some(
21091 update_uncommitted_diff_for_buffer(
21092 cx.entity(),
21093 &project,
21094 self.buffer.read(cx).all_buffers(),
21095 self.buffer.clone(),
21096 cx,
21097 )
21098 .shared(),
21099 );
21100 }
21101 }
21102
21103 fn on_display_map_changed(
21104 &mut self,
21105 _: Entity<DisplayMap>,
21106 _: &mut Window,
21107 cx: &mut Context<Self>,
21108 ) {
21109 cx.notify();
21110 }
21111
21112 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21113 if self.diagnostics_enabled() {
21114 let new_severity = EditorSettings::get_global(cx)
21115 .diagnostics_max_severity
21116 .unwrap_or(DiagnosticSeverity::Hint);
21117 self.set_max_diagnostics_severity(new_severity, cx);
21118 }
21119 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21120 self.update_edit_prediction_settings(cx);
21121 self.refresh_edit_prediction(true, false, window, cx);
21122 self.refresh_inline_values(cx);
21123 self.refresh_inlay_hints(
21124 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21125 self.selections.newest_anchor().head(),
21126 &self.buffer.read(cx).snapshot(cx),
21127 cx,
21128 )),
21129 cx,
21130 );
21131
21132 let old_cursor_shape = self.cursor_shape;
21133 let old_show_breadcrumbs = self.show_breadcrumbs;
21134
21135 {
21136 let editor_settings = EditorSettings::get_global(cx);
21137 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21138 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21139 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21140 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21141 }
21142
21143 if old_cursor_shape != self.cursor_shape {
21144 cx.emit(EditorEvent::CursorShapeChanged);
21145 }
21146
21147 if old_show_breadcrumbs != self.show_breadcrumbs {
21148 cx.emit(EditorEvent::BreadcrumbsChanged);
21149 }
21150
21151 let project_settings = ProjectSettings::get_global(cx);
21152 self.serialize_dirty_buffers =
21153 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21154
21155 if self.mode.is_full() {
21156 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21157 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21158 if self.show_inline_diagnostics != show_inline_diagnostics {
21159 self.show_inline_diagnostics = show_inline_diagnostics;
21160 self.refresh_inline_diagnostics(false, window, cx);
21161 }
21162
21163 if self.git_blame_inline_enabled != inline_blame_enabled {
21164 self.toggle_git_blame_inline_internal(false, window, cx);
21165 }
21166
21167 let minimap_settings = EditorSettings::get_global(cx).minimap;
21168 if self.minimap_visibility != MinimapVisibility::Disabled {
21169 if self.minimap_visibility.settings_visibility()
21170 != minimap_settings.minimap_enabled()
21171 {
21172 self.set_minimap_visibility(
21173 MinimapVisibility::for_mode(self.mode(), cx),
21174 window,
21175 cx,
21176 );
21177 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21178 minimap_entity.update(cx, |minimap_editor, cx| {
21179 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21180 })
21181 }
21182 }
21183 }
21184
21185 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21186 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21187 }) {
21188 if !inlay_splice.is_empty() {
21189 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21190 }
21191 self.refresh_colors_for_visible_range(None, window, cx);
21192 }
21193
21194 cx.notify();
21195 }
21196
21197 pub fn set_searchable(&mut self, searchable: bool) {
21198 self.searchable = searchable;
21199 }
21200
21201 pub fn searchable(&self) -> bool {
21202 self.searchable
21203 }
21204
21205 pub fn open_excerpts_in_split(
21206 &mut self,
21207 _: &OpenExcerptsSplit,
21208 window: &mut Window,
21209 cx: &mut Context<Self>,
21210 ) {
21211 self.open_excerpts_common(None, true, window, cx)
21212 }
21213
21214 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21215 self.open_excerpts_common(None, false, window, cx)
21216 }
21217
21218 fn open_excerpts_common(
21219 &mut self,
21220 jump_data: Option<JumpData>,
21221 split: bool,
21222 window: &mut Window,
21223 cx: &mut Context<Self>,
21224 ) {
21225 let Some(workspace) = self.workspace() else {
21226 cx.propagate();
21227 return;
21228 };
21229
21230 if self.buffer.read(cx).is_singleton() {
21231 cx.propagate();
21232 return;
21233 }
21234
21235 let mut new_selections_by_buffer = HashMap::default();
21236 match &jump_data {
21237 Some(JumpData::MultiBufferPoint {
21238 excerpt_id,
21239 position,
21240 anchor,
21241 line_offset_from_top,
21242 }) => {
21243 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21244 if let Some(buffer) = multi_buffer_snapshot
21245 .buffer_id_for_excerpt(*excerpt_id)
21246 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21247 {
21248 let buffer_snapshot = buffer.read(cx).snapshot();
21249 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21250 language::ToPoint::to_point(anchor, &buffer_snapshot)
21251 } else {
21252 buffer_snapshot.clip_point(*position, Bias::Left)
21253 };
21254 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21255 new_selections_by_buffer.insert(
21256 buffer,
21257 (
21258 vec![jump_to_offset..jump_to_offset],
21259 Some(*line_offset_from_top),
21260 ),
21261 );
21262 }
21263 }
21264 Some(JumpData::MultiBufferRow {
21265 row,
21266 line_offset_from_top,
21267 }) => {
21268 let point = MultiBufferPoint::new(row.0, 0);
21269 if let Some((buffer, buffer_point, _)) =
21270 self.buffer.read(cx).point_to_buffer_point(point, cx)
21271 {
21272 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21273 new_selections_by_buffer
21274 .entry(buffer)
21275 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21276 .0
21277 .push(buffer_offset..buffer_offset)
21278 }
21279 }
21280 None => {
21281 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21282 let multi_buffer = self.buffer.read(cx);
21283 for selection in selections {
21284 for (snapshot, range, _, anchor) in multi_buffer
21285 .snapshot(cx)
21286 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21287 {
21288 if let Some(anchor) = anchor {
21289 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21290 else {
21291 continue;
21292 };
21293 let offset = text::ToOffset::to_offset(
21294 &anchor.text_anchor,
21295 &buffer_handle.read(cx).snapshot(),
21296 );
21297 let range = offset..offset;
21298 new_selections_by_buffer
21299 .entry(buffer_handle)
21300 .or_insert((Vec::new(), None))
21301 .0
21302 .push(range)
21303 } else {
21304 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21305 else {
21306 continue;
21307 };
21308 new_selections_by_buffer
21309 .entry(buffer_handle)
21310 .or_insert((Vec::new(), None))
21311 .0
21312 .push(range)
21313 }
21314 }
21315 }
21316 }
21317 }
21318
21319 new_selections_by_buffer
21320 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21321
21322 if new_selections_by_buffer.is_empty() {
21323 return;
21324 }
21325
21326 // We defer the pane interaction because we ourselves are a workspace item
21327 // and activating a new item causes the pane to call a method on us reentrantly,
21328 // which panics if we're on the stack.
21329 window.defer(cx, move |window, cx| {
21330 workspace.update(cx, |workspace, cx| {
21331 let pane = if split {
21332 workspace.adjacent_pane(window, cx)
21333 } else {
21334 workspace.active_pane().clone()
21335 };
21336
21337 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21338 let editor = buffer
21339 .read(cx)
21340 .file()
21341 .is_none()
21342 .then(|| {
21343 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21344 // so `workspace.open_project_item` will never find them, always opening a new editor.
21345 // Instead, we try to activate the existing editor in the pane first.
21346 let (editor, pane_item_index) =
21347 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21348 let editor = item.downcast::<Editor>()?;
21349 let singleton_buffer =
21350 editor.read(cx).buffer().read(cx).as_singleton()?;
21351 if singleton_buffer == buffer {
21352 Some((editor, i))
21353 } else {
21354 None
21355 }
21356 })?;
21357 pane.update(cx, |pane, cx| {
21358 pane.activate_item(pane_item_index, true, true, window, cx)
21359 });
21360 Some(editor)
21361 })
21362 .flatten()
21363 .unwrap_or_else(|| {
21364 workspace.open_project_item::<Self>(
21365 pane.clone(),
21366 buffer,
21367 true,
21368 true,
21369 window,
21370 cx,
21371 )
21372 });
21373
21374 editor.update(cx, |editor, cx| {
21375 let autoscroll = match scroll_offset {
21376 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21377 None => Autoscroll::newest(),
21378 };
21379 let nav_history = editor.nav_history.take();
21380 editor.change_selections(
21381 SelectionEffects::scroll(autoscroll),
21382 window,
21383 cx,
21384 |s| {
21385 s.select_ranges(ranges);
21386 },
21387 );
21388 editor.nav_history = nav_history;
21389 });
21390 }
21391 })
21392 });
21393 }
21394
21395 // For now, don't allow opening excerpts in buffers that aren't backed by
21396 // regular project files.
21397 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21398 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21399 }
21400
21401 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21402 let snapshot = self.buffer.read(cx).read(cx);
21403 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21404 Some(
21405 ranges
21406 .iter()
21407 .map(move |range| {
21408 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21409 })
21410 .collect(),
21411 )
21412 }
21413
21414 fn selection_replacement_ranges(
21415 &self,
21416 range: Range<OffsetUtf16>,
21417 cx: &mut App,
21418 ) -> Vec<Range<OffsetUtf16>> {
21419 let selections = self
21420 .selections
21421 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21422 let newest_selection = selections
21423 .iter()
21424 .max_by_key(|selection| selection.id)
21425 .unwrap();
21426 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21427 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21428 let snapshot = self.buffer.read(cx).read(cx);
21429 selections
21430 .into_iter()
21431 .map(|mut selection| {
21432 selection.start.0 =
21433 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21434 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21435 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21436 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21437 })
21438 .collect()
21439 }
21440
21441 fn report_editor_event(
21442 &self,
21443 reported_event: ReportEditorEvent,
21444 file_extension: Option<String>,
21445 cx: &App,
21446 ) {
21447 if cfg!(any(test, feature = "test-support")) {
21448 return;
21449 }
21450
21451 let Some(project) = &self.project else { return };
21452
21453 // If None, we are in a file without an extension
21454 let file = self
21455 .buffer
21456 .read(cx)
21457 .as_singleton()
21458 .and_then(|b| b.read(cx).file());
21459 let file_extension = file_extension.or(file
21460 .as_ref()
21461 .and_then(|file| Path::new(file.file_name(cx)).extension())
21462 .and_then(|e| e.to_str())
21463 .map(|a| a.to_string()));
21464
21465 let vim_mode = vim_flavor(cx).is_some();
21466
21467 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21468 let copilot_enabled = edit_predictions_provider
21469 == language::language_settings::EditPredictionProvider::Copilot;
21470 let copilot_enabled_for_language = self
21471 .buffer
21472 .read(cx)
21473 .language_settings(cx)
21474 .show_edit_predictions;
21475
21476 let project = project.read(cx);
21477 let event_type = reported_event.event_type();
21478
21479 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21480 telemetry::event!(
21481 event_type,
21482 type = if auto_saved {"autosave"} else {"manual"},
21483 file_extension,
21484 vim_mode,
21485 copilot_enabled,
21486 copilot_enabled_for_language,
21487 edit_predictions_provider,
21488 is_via_ssh = project.is_via_remote_server(),
21489 );
21490 } else {
21491 telemetry::event!(
21492 event_type,
21493 file_extension,
21494 vim_mode,
21495 copilot_enabled,
21496 copilot_enabled_for_language,
21497 edit_predictions_provider,
21498 is_via_ssh = project.is_via_remote_server(),
21499 );
21500 };
21501 }
21502
21503 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21504 /// with each line being an array of {text, highlight} objects.
21505 fn copy_highlight_json(
21506 &mut self,
21507 _: &CopyHighlightJson,
21508 window: &mut Window,
21509 cx: &mut Context<Self>,
21510 ) {
21511 #[derive(Serialize)]
21512 struct Chunk<'a> {
21513 text: String,
21514 highlight: Option<&'a str>,
21515 }
21516
21517 let snapshot = self.buffer.read(cx).snapshot(cx);
21518 let range = self
21519 .selected_text_range(false, window, cx)
21520 .and_then(|selection| {
21521 if selection.range.is_empty() {
21522 None
21523 } else {
21524 Some(
21525 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21526 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21527 )
21528 }
21529 })
21530 .unwrap_or_else(|| 0..snapshot.len());
21531
21532 let chunks = snapshot.chunks(range, true);
21533 let mut lines = Vec::new();
21534 let mut line: VecDeque<Chunk> = VecDeque::new();
21535
21536 let Some(style) = self.style.as_ref() else {
21537 return;
21538 };
21539
21540 for chunk in chunks {
21541 let highlight = chunk
21542 .syntax_highlight_id
21543 .and_then(|id| id.name(&style.syntax));
21544 let mut chunk_lines = chunk.text.split('\n').peekable();
21545 while let Some(text) = chunk_lines.next() {
21546 let mut merged_with_last_token = false;
21547 if let Some(last_token) = line.back_mut()
21548 && last_token.highlight == highlight
21549 {
21550 last_token.text.push_str(text);
21551 merged_with_last_token = true;
21552 }
21553
21554 if !merged_with_last_token {
21555 line.push_back(Chunk {
21556 text: text.into(),
21557 highlight,
21558 });
21559 }
21560
21561 if chunk_lines.peek().is_some() {
21562 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21563 line.pop_front();
21564 }
21565 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21566 line.pop_back();
21567 }
21568
21569 lines.push(mem::take(&mut line));
21570 }
21571 }
21572 }
21573
21574 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21575 return;
21576 };
21577 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21578 }
21579
21580 pub fn open_context_menu(
21581 &mut self,
21582 _: &OpenContextMenu,
21583 window: &mut Window,
21584 cx: &mut Context<Self>,
21585 ) {
21586 self.request_autoscroll(Autoscroll::newest(), cx);
21587 let position = self
21588 .selections
21589 .newest_display(&self.display_snapshot(cx))
21590 .start;
21591 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21592 }
21593
21594 pub fn replay_insert_event(
21595 &mut self,
21596 text: &str,
21597 relative_utf16_range: Option<Range<isize>>,
21598 window: &mut Window,
21599 cx: &mut Context<Self>,
21600 ) {
21601 if !self.input_enabled {
21602 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21603 return;
21604 }
21605 if let Some(relative_utf16_range) = relative_utf16_range {
21606 let selections = self
21607 .selections
21608 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21609 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21610 let new_ranges = selections.into_iter().map(|range| {
21611 let start = OffsetUtf16(
21612 range
21613 .head()
21614 .0
21615 .saturating_add_signed(relative_utf16_range.start),
21616 );
21617 let end = OffsetUtf16(
21618 range
21619 .head()
21620 .0
21621 .saturating_add_signed(relative_utf16_range.end),
21622 );
21623 start..end
21624 });
21625 s.select_ranges(new_ranges);
21626 });
21627 }
21628
21629 self.handle_input(text, window, cx);
21630 }
21631
21632 pub fn is_focused(&self, window: &Window) -> bool {
21633 self.focus_handle.is_focused(window)
21634 }
21635
21636 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21637 cx.emit(EditorEvent::Focused);
21638
21639 if let Some(descendant) = self
21640 .last_focused_descendant
21641 .take()
21642 .and_then(|descendant| descendant.upgrade())
21643 {
21644 window.focus(&descendant);
21645 } else {
21646 if let Some(blame) = self.blame.as_ref() {
21647 blame.update(cx, GitBlame::focus)
21648 }
21649
21650 self.blink_manager.update(cx, BlinkManager::enable);
21651 self.show_cursor_names(window, cx);
21652 self.buffer.update(cx, |buffer, cx| {
21653 buffer.finalize_last_transaction(cx);
21654 if self.leader_id.is_none() {
21655 buffer.set_active_selections(
21656 &self.selections.disjoint_anchors_arc(),
21657 self.selections.line_mode(),
21658 self.cursor_shape,
21659 cx,
21660 );
21661 }
21662 });
21663 }
21664 }
21665
21666 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21667 cx.emit(EditorEvent::FocusedIn)
21668 }
21669
21670 fn handle_focus_out(
21671 &mut self,
21672 event: FocusOutEvent,
21673 _window: &mut Window,
21674 cx: &mut Context<Self>,
21675 ) {
21676 if event.blurred != self.focus_handle {
21677 self.last_focused_descendant = Some(event.blurred);
21678 }
21679 self.selection_drag_state = SelectionDragState::None;
21680 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21681 }
21682
21683 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21684 self.blink_manager.update(cx, BlinkManager::disable);
21685 self.buffer
21686 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21687
21688 if let Some(blame) = self.blame.as_ref() {
21689 blame.update(cx, GitBlame::blur)
21690 }
21691 if !self.hover_state.focused(window, cx) {
21692 hide_hover(self, cx);
21693 }
21694 if !self
21695 .context_menu
21696 .borrow()
21697 .as_ref()
21698 .is_some_and(|context_menu| context_menu.focused(window, cx))
21699 {
21700 self.hide_context_menu(window, cx);
21701 }
21702 self.take_active_edit_prediction(cx);
21703 cx.emit(EditorEvent::Blurred);
21704 cx.notify();
21705 }
21706
21707 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21708 let mut pending: String = window
21709 .pending_input_keystrokes()
21710 .into_iter()
21711 .flatten()
21712 .filter_map(|keystroke| {
21713 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21714 keystroke.key_char.clone()
21715 } else {
21716 None
21717 }
21718 })
21719 .collect();
21720
21721 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21722 pending = "".to_string();
21723 }
21724
21725 let existing_pending = self
21726 .text_highlights::<PendingInput>(cx)
21727 .map(|(_, ranges)| ranges.to_vec());
21728 if existing_pending.is_none() && pending.is_empty() {
21729 return;
21730 }
21731 let transaction =
21732 self.transact(window, cx, |this, window, cx| {
21733 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21734 let edits = selections
21735 .iter()
21736 .map(|selection| (selection.end..selection.end, pending.clone()));
21737 this.edit(edits, cx);
21738 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21739 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21740 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21741 }));
21742 });
21743 if let Some(existing_ranges) = existing_pending {
21744 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21745 this.edit(edits, cx);
21746 }
21747 });
21748
21749 let snapshot = self.snapshot(window, cx);
21750 let ranges = self
21751 .selections
21752 .all::<usize>(&snapshot.display_snapshot)
21753 .into_iter()
21754 .map(|selection| {
21755 snapshot.buffer_snapshot().anchor_after(selection.end)
21756 ..snapshot
21757 .buffer_snapshot()
21758 .anchor_before(selection.end + pending.len())
21759 })
21760 .collect();
21761
21762 if pending.is_empty() {
21763 self.clear_highlights::<PendingInput>(cx);
21764 } else {
21765 self.highlight_text::<PendingInput>(
21766 ranges,
21767 HighlightStyle {
21768 underline: Some(UnderlineStyle {
21769 thickness: px(1.),
21770 color: None,
21771 wavy: false,
21772 }),
21773 ..Default::default()
21774 },
21775 cx,
21776 );
21777 }
21778
21779 self.ime_transaction = self.ime_transaction.or(transaction);
21780 if let Some(transaction) = self.ime_transaction {
21781 self.buffer.update(cx, |buffer, cx| {
21782 buffer.group_until_transaction(transaction, cx);
21783 });
21784 }
21785
21786 if self.text_highlights::<PendingInput>(cx).is_none() {
21787 self.ime_transaction.take();
21788 }
21789 }
21790
21791 pub fn register_action_renderer(
21792 &mut self,
21793 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21794 ) -> Subscription {
21795 let id = self.next_editor_action_id.post_inc();
21796 self.editor_actions
21797 .borrow_mut()
21798 .insert(id, Box::new(listener));
21799
21800 let editor_actions = self.editor_actions.clone();
21801 Subscription::new(move || {
21802 editor_actions.borrow_mut().remove(&id);
21803 })
21804 }
21805
21806 pub fn register_action<A: Action>(
21807 &mut self,
21808 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21809 ) -> Subscription {
21810 let id = self.next_editor_action_id.post_inc();
21811 let listener = Arc::new(listener);
21812 self.editor_actions.borrow_mut().insert(
21813 id,
21814 Box::new(move |_, window, _| {
21815 let listener = listener.clone();
21816 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21817 let action = action.downcast_ref().unwrap();
21818 if phase == DispatchPhase::Bubble {
21819 listener(action, window, cx)
21820 }
21821 })
21822 }),
21823 );
21824
21825 let editor_actions = self.editor_actions.clone();
21826 Subscription::new(move || {
21827 editor_actions.borrow_mut().remove(&id);
21828 })
21829 }
21830
21831 pub fn file_header_size(&self) -> u32 {
21832 FILE_HEADER_HEIGHT
21833 }
21834
21835 pub fn restore(
21836 &mut self,
21837 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21838 window: &mut Window,
21839 cx: &mut Context<Self>,
21840 ) {
21841 let workspace = self.workspace();
21842 let project = self.project();
21843 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21844 let mut tasks = Vec::new();
21845 for (buffer_id, changes) in revert_changes {
21846 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21847 buffer.update(cx, |buffer, cx| {
21848 buffer.edit(
21849 changes
21850 .into_iter()
21851 .map(|(range, text)| (range, text.to_string())),
21852 None,
21853 cx,
21854 );
21855 });
21856
21857 if let Some(project) =
21858 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21859 {
21860 project.update(cx, |project, cx| {
21861 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21862 })
21863 }
21864 }
21865 }
21866 tasks
21867 });
21868 cx.spawn_in(window, async move |_, cx| {
21869 for (buffer, task) in save_tasks {
21870 let result = task.await;
21871 if result.is_err() {
21872 let Some(path) = buffer
21873 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21874 .ok()
21875 else {
21876 continue;
21877 };
21878 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21879 let Some(task) = cx
21880 .update_window_entity(workspace, |workspace, window, cx| {
21881 workspace
21882 .open_path_preview(path, None, false, false, false, window, cx)
21883 })
21884 .ok()
21885 else {
21886 continue;
21887 };
21888 task.await.log_err();
21889 }
21890 }
21891 }
21892 })
21893 .detach();
21894 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21895 selections.refresh()
21896 });
21897 }
21898
21899 pub fn to_pixel_point(
21900 &self,
21901 source: multi_buffer::Anchor,
21902 editor_snapshot: &EditorSnapshot,
21903 window: &mut Window,
21904 ) -> Option<gpui::Point<Pixels>> {
21905 let source_point = source.to_display_point(editor_snapshot);
21906 self.display_to_pixel_point(source_point, editor_snapshot, window)
21907 }
21908
21909 pub fn display_to_pixel_point(
21910 &self,
21911 source: DisplayPoint,
21912 editor_snapshot: &EditorSnapshot,
21913 window: &mut Window,
21914 ) -> Option<gpui::Point<Pixels>> {
21915 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21916 let text_layout_details = self.text_layout_details(window);
21917 let scroll_top = text_layout_details
21918 .scroll_anchor
21919 .scroll_position(editor_snapshot)
21920 .y;
21921
21922 if source.row().as_f64() < scroll_top.floor() {
21923 return None;
21924 }
21925 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21926 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21927 Some(gpui::Point::new(source_x, source_y))
21928 }
21929
21930 pub fn has_visible_completions_menu(&self) -> bool {
21931 !self.edit_prediction_preview_is_active()
21932 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21933 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21934 })
21935 }
21936
21937 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21938 if self.mode.is_minimap() {
21939 return;
21940 }
21941 self.addons
21942 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21943 }
21944
21945 pub fn unregister_addon<T: Addon>(&mut self) {
21946 self.addons.remove(&std::any::TypeId::of::<T>());
21947 }
21948
21949 pub fn addon<T: Addon>(&self) -> Option<&T> {
21950 let type_id = std::any::TypeId::of::<T>();
21951 self.addons
21952 .get(&type_id)
21953 .and_then(|item| item.to_any().downcast_ref::<T>())
21954 }
21955
21956 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21957 let type_id = std::any::TypeId::of::<T>();
21958 self.addons
21959 .get_mut(&type_id)
21960 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21961 }
21962
21963 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21964 let text_layout_details = self.text_layout_details(window);
21965 let style = &text_layout_details.editor_style;
21966 let font_id = window.text_system().resolve_font(&style.text.font());
21967 let font_size = style.text.font_size.to_pixels(window.rem_size());
21968 let line_height = style.text.line_height_in_pixels(window.rem_size());
21969 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21970 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21971
21972 CharacterDimensions {
21973 em_width,
21974 em_advance,
21975 line_height,
21976 }
21977 }
21978
21979 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21980 self.load_diff_task.clone()
21981 }
21982
21983 fn read_metadata_from_db(
21984 &mut self,
21985 item_id: u64,
21986 workspace_id: WorkspaceId,
21987 window: &mut Window,
21988 cx: &mut Context<Editor>,
21989 ) {
21990 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21991 && !self.mode.is_minimap()
21992 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21993 {
21994 let buffer_snapshot = OnceCell::new();
21995
21996 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21997 && !folds.is_empty()
21998 {
21999 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22000 self.fold_ranges(
22001 folds
22002 .into_iter()
22003 .map(|(start, end)| {
22004 snapshot.clip_offset(start, Bias::Left)
22005 ..snapshot.clip_offset(end, Bias::Right)
22006 })
22007 .collect(),
22008 false,
22009 window,
22010 cx,
22011 );
22012 }
22013
22014 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22015 && !selections.is_empty()
22016 {
22017 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22018 // skip adding the initial selection to selection history
22019 self.selection_history.mode = SelectionHistoryMode::Skipping;
22020 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22021 s.select_ranges(selections.into_iter().map(|(start, end)| {
22022 snapshot.clip_offset(start, Bias::Left)
22023 ..snapshot.clip_offset(end, Bias::Right)
22024 }));
22025 });
22026 self.selection_history.mode = SelectionHistoryMode::Normal;
22027 };
22028 }
22029
22030 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22031 }
22032
22033 fn update_lsp_data(
22034 &mut self,
22035 for_buffer: Option<BufferId>,
22036 window: &mut Window,
22037 cx: &mut Context<'_, Self>,
22038 ) {
22039 self.pull_diagnostics(for_buffer, window, cx);
22040 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22041 }
22042
22043 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22044 if self.ignore_lsp_data() {
22045 return;
22046 }
22047 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22048 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22049 }
22050 }
22051
22052 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22053 if !self.registered_buffers.contains_key(&buffer_id)
22054 && let Some(project) = self.project.as_ref()
22055 {
22056 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22057 project.update(cx, |project, cx| {
22058 self.registered_buffers.insert(
22059 buffer_id,
22060 project.register_buffer_with_language_servers(&buffer, cx),
22061 );
22062 });
22063 } else {
22064 self.registered_buffers.remove(&buffer_id);
22065 }
22066 }
22067 }
22068
22069 fn ignore_lsp_data(&self) -> bool {
22070 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22071 // skip any LSP updates for it.
22072 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22073 }
22074}
22075
22076fn edit_for_markdown_paste<'a>(
22077 buffer: &MultiBufferSnapshot,
22078 range: Range<usize>,
22079 to_insert: &'a str,
22080 url: Option<url::Url>,
22081) -> (Range<usize>, Cow<'a, str>) {
22082 if url.is_none() {
22083 return (range, Cow::Borrowed(to_insert));
22084 };
22085
22086 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22087
22088 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22089 Cow::Borrowed(to_insert)
22090 } else {
22091 Cow::Owned(format!("[{old_text}]({to_insert})"))
22092 };
22093 (range, new_text)
22094}
22095
22096#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22097pub enum VimFlavor {
22098 Vim,
22099 Helix,
22100}
22101
22102pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22103 if vim_mode_setting::HelixModeSetting::try_get(cx)
22104 .map(|helix_mode| helix_mode.0)
22105 .unwrap_or(false)
22106 {
22107 Some(VimFlavor::Helix)
22108 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22109 .map(|vim_mode| vim_mode.0)
22110 .unwrap_or(false)
22111 {
22112 Some(VimFlavor::Vim)
22113 } else {
22114 None // neither vim nor helix mode
22115 }
22116}
22117
22118fn process_completion_for_edit(
22119 completion: &Completion,
22120 intent: CompletionIntent,
22121 buffer: &Entity<Buffer>,
22122 cursor_position: &text::Anchor,
22123 cx: &mut Context<Editor>,
22124) -> CompletionEdit {
22125 let buffer = buffer.read(cx);
22126 let buffer_snapshot = buffer.snapshot();
22127 let (snippet, new_text) = if completion.is_snippet() {
22128 let mut snippet_source = completion.new_text.clone();
22129 // Workaround for typescript language server issues so that methods don't expand within
22130 // strings and functions with type expressions. The previous point is used because the query
22131 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22132 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22133 let previous_point = if previous_point.column > 0 {
22134 cursor_position.to_previous_offset(&buffer_snapshot)
22135 } else {
22136 cursor_position.to_offset(&buffer_snapshot)
22137 };
22138 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22139 && scope.prefers_label_for_snippet_in_completion()
22140 && let Some(label) = completion.label()
22141 && matches!(
22142 completion.kind(),
22143 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22144 )
22145 {
22146 snippet_source = label;
22147 }
22148 match Snippet::parse(&snippet_source).log_err() {
22149 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22150 None => (None, completion.new_text.clone()),
22151 }
22152 } else {
22153 (None, completion.new_text.clone())
22154 };
22155
22156 let mut range_to_replace = {
22157 let replace_range = &completion.replace_range;
22158 if let CompletionSource::Lsp {
22159 insert_range: Some(insert_range),
22160 ..
22161 } = &completion.source
22162 {
22163 debug_assert_eq!(
22164 insert_range.start, replace_range.start,
22165 "insert_range and replace_range should start at the same position"
22166 );
22167 debug_assert!(
22168 insert_range
22169 .start
22170 .cmp(cursor_position, &buffer_snapshot)
22171 .is_le(),
22172 "insert_range should start before or at cursor position"
22173 );
22174 debug_assert!(
22175 replace_range
22176 .start
22177 .cmp(cursor_position, &buffer_snapshot)
22178 .is_le(),
22179 "replace_range should start before or at cursor position"
22180 );
22181
22182 let should_replace = match intent {
22183 CompletionIntent::CompleteWithInsert => false,
22184 CompletionIntent::CompleteWithReplace => true,
22185 CompletionIntent::Complete | CompletionIntent::Compose => {
22186 let insert_mode =
22187 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22188 .completions
22189 .lsp_insert_mode;
22190 match insert_mode {
22191 LspInsertMode::Insert => false,
22192 LspInsertMode::Replace => true,
22193 LspInsertMode::ReplaceSubsequence => {
22194 let mut text_to_replace = buffer.chars_for_range(
22195 buffer.anchor_before(replace_range.start)
22196 ..buffer.anchor_after(replace_range.end),
22197 );
22198 let mut current_needle = text_to_replace.next();
22199 for haystack_ch in completion.label.text.chars() {
22200 if let Some(needle_ch) = current_needle
22201 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22202 {
22203 current_needle = text_to_replace.next();
22204 }
22205 }
22206 current_needle.is_none()
22207 }
22208 LspInsertMode::ReplaceSuffix => {
22209 if replace_range
22210 .end
22211 .cmp(cursor_position, &buffer_snapshot)
22212 .is_gt()
22213 {
22214 let range_after_cursor = *cursor_position..replace_range.end;
22215 let text_after_cursor = buffer
22216 .text_for_range(
22217 buffer.anchor_before(range_after_cursor.start)
22218 ..buffer.anchor_after(range_after_cursor.end),
22219 )
22220 .collect::<String>()
22221 .to_ascii_lowercase();
22222 completion
22223 .label
22224 .text
22225 .to_ascii_lowercase()
22226 .ends_with(&text_after_cursor)
22227 } else {
22228 true
22229 }
22230 }
22231 }
22232 }
22233 };
22234
22235 if should_replace {
22236 replace_range.clone()
22237 } else {
22238 insert_range.clone()
22239 }
22240 } else {
22241 replace_range.clone()
22242 }
22243 };
22244
22245 if range_to_replace
22246 .end
22247 .cmp(cursor_position, &buffer_snapshot)
22248 .is_lt()
22249 {
22250 range_to_replace.end = *cursor_position;
22251 }
22252
22253 CompletionEdit {
22254 new_text,
22255 replace_range: range_to_replace.to_offset(buffer),
22256 snippet,
22257 }
22258}
22259
22260struct CompletionEdit {
22261 new_text: String,
22262 replace_range: Range<usize>,
22263 snippet: Option<Snippet>,
22264}
22265
22266fn insert_extra_newline_brackets(
22267 buffer: &MultiBufferSnapshot,
22268 range: Range<usize>,
22269 language: &language::LanguageScope,
22270) -> bool {
22271 let leading_whitespace_len = buffer
22272 .reversed_chars_at(range.start)
22273 .take_while(|c| c.is_whitespace() && *c != '\n')
22274 .map(|c| c.len_utf8())
22275 .sum::<usize>();
22276 let trailing_whitespace_len = buffer
22277 .chars_at(range.end)
22278 .take_while(|c| c.is_whitespace() && *c != '\n')
22279 .map(|c| c.len_utf8())
22280 .sum::<usize>();
22281 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22282
22283 language.brackets().any(|(pair, enabled)| {
22284 let pair_start = pair.start.trim_end();
22285 let pair_end = pair.end.trim_start();
22286
22287 enabled
22288 && pair.newline
22289 && buffer.contains_str_at(range.end, pair_end)
22290 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22291 })
22292}
22293
22294fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22295 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22296 [(buffer, range, _)] => (*buffer, range.clone()),
22297 _ => return false,
22298 };
22299 let pair = {
22300 let mut result: Option<BracketMatch> = None;
22301
22302 for pair in buffer
22303 .all_bracket_ranges(range.clone())
22304 .filter(move |pair| {
22305 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22306 })
22307 {
22308 let len = pair.close_range.end - pair.open_range.start;
22309
22310 if let Some(existing) = &result {
22311 let existing_len = existing.close_range.end - existing.open_range.start;
22312 if len > existing_len {
22313 continue;
22314 }
22315 }
22316
22317 result = Some(pair);
22318 }
22319
22320 result
22321 };
22322 let Some(pair) = pair else {
22323 return false;
22324 };
22325 pair.newline_only
22326 && buffer
22327 .chars_for_range(pair.open_range.end..range.start)
22328 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22329 .all(|c| c.is_whitespace() && c != '\n')
22330}
22331
22332fn update_uncommitted_diff_for_buffer(
22333 editor: Entity<Editor>,
22334 project: &Entity<Project>,
22335 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22336 buffer: Entity<MultiBuffer>,
22337 cx: &mut App,
22338) -> Task<()> {
22339 let mut tasks = Vec::new();
22340 project.update(cx, |project, cx| {
22341 for buffer in buffers {
22342 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22343 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22344 }
22345 }
22346 });
22347 cx.spawn(async move |cx| {
22348 let diffs = future::join_all(tasks).await;
22349 if editor
22350 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22351 .unwrap_or(false)
22352 {
22353 return;
22354 }
22355
22356 buffer
22357 .update(cx, |buffer, cx| {
22358 for diff in diffs.into_iter().flatten() {
22359 buffer.add_diff(diff, cx);
22360 }
22361 })
22362 .ok();
22363 })
22364}
22365
22366fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22367 let tab_size = tab_size.get() as usize;
22368 let mut width = offset;
22369
22370 for ch in text.chars() {
22371 width += if ch == '\t' {
22372 tab_size - (width % tab_size)
22373 } else {
22374 1
22375 };
22376 }
22377
22378 width - offset
22379}
22380
22381#[cfg(test)]
22382mod tests {
22383 use super::*;
22384
22385 #[test]
22386 fn test_string_size_with_expanded_tabs() {
22387 let nz = |val| NonZeroU32::new(val).unwrap();
22388 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22389 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22390 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22391 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22392 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22393 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22394 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22395 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22396 }
22397}
22398
22399/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22400struct WordBreakingTokenizer<'a> {
22401 input: &'a str,
22402}
22403
22404impl<'a> WordBreakingTokenizer<'a> {
22405 fn new(input: &'a str) -> Self {
22406 Self { input }
22407 }
22408}
22409
22410fn is_char_ideographic(ch: char) -> bool {
22411 use unicode_script::Script::*;
22412 use unicode_script::UnicodeScript;
22413 matches!(ch.script(), Han | Tangut | Yi)
22414}
22415
22416fn is_grapheme_ideographic(text: &str) -> bool {
22417 text.chars().any(is_char_ideographic)
22418}
22419
22420fn is_grapheme_whitespace(text: &str) -> bool {
22421 text.chars().any(|x| x.is_whitespace())
22422}
22423
22424fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22425 text.chars()
22426 .next()
22427 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22428}
22429
22430#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22431enum WordBreakToken<'a> {
22432 Word { token: &'a str, grapheme_len: usize },
22433 InlineWhitespace { token: &'a str, grapheme_len: usize },
22434 Newline,
22435}
22436
22437impl<'a> Iterator for WordBreakingTokenizer<'a> {
22438 /// Yields a span, the count of graphemes in the token, and whether it was
22439 /// whitespace. Note that it also breaks at word boundaries.
22440 type Item = WordBreakToken<'a>;
22441
22442 fn next(&mut self) -> Option<Self::Item> {
22443 use unicode_segmentation::UnicodeSegmentation;
22444 if self.input.is_empty() {
22445 return None;
22446 }
22447
22448 let mut iter = self.input.graphemes(true).peekable();
22449 let mut offset = 0;
22450 let mut grapheme_len = 0;
22451 if let Some(first_grapheme) = iter.next() {
22452 let is_newline = first_grapheme == "\n";
22453 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22454 offset += first_grapheme.len();
22455 grapheme_len += 1;
22456 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22457 if let Some(grapheme) = iter.peek().copied()
22458 && should_stay_with_preceding_ideograph(grapheme)
22459 {
22460 offset += grapheme.len();
22461 grapheme_len += 1;
22462 }
22463 } else {
22464 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22465 let mut next_word_bound = words.peek().copied();
22466 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22467 next_word_bound = words.next();
22468 }
22469 while let Some(grapheme) = iter.peek().copied() {
22470 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22471 break;
22472 };
22473 if is_grapheme_whitespace(grapheme) != is_whitespace
22474 || (grapheme == "\n") != is_newline
22475 {
22476 break;
22477 };
22478 offset += grapheme.len();
22479 grapheme_len += 1;
22480 iter.next();
22481 }
22482 }
22483 let token = &self.input[..offset];
22484 self.input = &self.input[offset..];
22485 if token == "\n" {
22486 Some(WordBreakToken::Newline)
22487 } else if is_whitespace {
22488 Some(WordBreakToken::InlineWhitespace {
22489 token,
22490 grapheme_len,
22491 })
22492 } else {
22493 Some(WordBreakToken::Word {
22494 token,
22495 grapheme_len,
22496 })
22497 }
22498 } else {
22499 None
22500 }
22501 }
22502}
22503
22504#[test]
22505fn test_word_breaking_tokenizer() {
22506 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22507 ("", &[]),
22508 (" ", &[whitespace(" ", 2)]),
22509 ("Ʒ", &[word("Ʒ", 1)]),
22510 ("Ǽ", &[word("Ǽ", 1)]),
22511 ("⋑", &[word("⋑", 1)]),
22512 ("⋑⋑", &[word("⋑⋑", 2)]),
22513 (
22514 "原理,进而",
22515 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22516 ),
22517 (
22518 "hello world",
22519 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22520 ),
22521 (
22522 "hello, world",
22523 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22524 ),
22525 (
22526 " hello world",
22527 &[
22528 whitespace(" ", 2),
22529 word("hello", 5),
22530 whitespace(" ", 1),
22531 word("world", 5),
22532 ],
22533 ),
22534 (
22535 "这是什么 \n 钢笔",
22536 &[
22537 word("这", 1),
22538 word("是", 1),
22539 word("什", 1),
22540 word("么", 1),
22541 whitespace(" ", 1),
22542 newline(),
22543 whitespace(" ", 1),
22544 word("钢", 1),
22545 word("笔", 1),
22546 ],
22547 ),
22548 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22549 ];
22550
22551 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22552 WordBreakToken::Word {
22553 token,
22554 grapheme_len,
22555 }
22556 }
22557
22558 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22559 WordBreakToken::InlineWhitespace {
22560 token,
22561 grapheme_len,
22562 }
22563 }
22564
22565 fn newline() -> WordBreakToken<'static> {
22566 WordBreakToken::Newline
22567 }
22568
22569 for (input, result) in tests {
22570 assert_eq!(
22571 WordBreakingTokenizer::new(input)
22572 .collect::<Vec<_>>()
22573 .as_slice(),
22574 *result,
22575 );
22576 }
22577}
22578
22579fn wrap_with_prefix(
22580 first_line_prefix: String,
22581 subsequent_lines_prefix: String,
22582 unwrapped_text: String,
22583 wrap_column: usize,
22584 tab_size: NonZeroU32,
22585 preserve_existing_whitespace: bool,
22586) -> String {
22587 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22588 let subsequent_lines_prefix_len =
22589 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22590 let mut wrapped_text = String::new();
22591 let mut current_line = first_line_prefix;
22592 let mut is_first_line = true;
22593
22594 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22595 let mut current_line_len = first_line_prefix_len;
22596 let mut in_whitespace = false;
22597 for token in tokenizer {
22598 let have_preceding_whitespace = in_whitespace;
22599 match token {
22600 WordBreakToken::Word {
22601 token,
22602 grapheme_len,
22603 } => {
22604 in_whitespace = false;
22605 let current_prefix_len = if is_first_line {
22606 first_line_prefix_len
22607 } else {
22608 subsequent_lines_prefix_len
22609 };
22610 if current_line_len + grapheme_len > wrap_column
22611 && current_line_len != current_prefix_len
22612 {
22613 wrapped_text.push_str(current_line.trim_end());
22614 wrapped_text.push('\n');
22615 is_first_line = false;
22616 current_line = subsequent_lines_prefix.clone();
22617 current_line_len = subsequent_lines_prefix_len;
22618 }
22619 current_line.push_str(token);
22620 current_line_len += grapheme_len;
22621 }
22622 WordBreakToken::InlineWhitespace {
22623 mut token,
22624 mut grapheme_len,
22625 } => {
22626 in_whitespace = true;
22627 if have_preceding_whitespace && !preserve_existing_whitespace {
22628 continue;
22629 }
22630 if !preserve_existing_whitespace {
22631 // Keep a single whitespace grapheme as-is
22632 if let Some(first) =
22633 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22634 {
22635 token = first;
22636 } else {
22637 token = " ";
22638 }
22639 grapheme_len = 1;
22640 }
22641 let current_prefix_len = if is_first_line {
22642 first_line_prefix_len
22643 } else {
22644 subsequent_lines_prefix_len
22645 };
22646 if current_line_len + grapheme_len > wrap_column {
22647 wrapped_text.push_str(current_line.trim_end());
22648 wrapped_text.push('\n');
22649 is_first_line = false;
22650 current_line = subsequent_lines_prefix.clone();
22651 current_line_len = subsequent_lines_prefix_len;
22652 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22653 current_line.push_str(token);
22654 current_line_len += grapheme_len;
22655 }
22656 }
22657 WordBreakToken::Newline => {
22658 in_whitespace = true;
22659 let current_prefix_len = if is_first_line {
22660 first_line_prefix_len
22661 } else {
22662 subsequent_lines_prefix_len
22663 };
22664 if preserve_existing_whitespace {
22665 wrapped_text.push_str(current_line.trim_end());
22666 wrapped_text.push('\n');
22667 is_first_line = false;
22668 current_line = subsequent_lines_prefix.clone();
22669 current_line_len = subsequent_lines_prefix_len;
22670 } else if have_preceding_whitespace {
22671 continue;
22672 } else if current_line_len + 1 > wrap_column
22673 && current_line_len != current_prefix_len
22674 {
22675 wrapped_text.push_str(current_line.trim_end());
22676 wrapped_text.push('\n');
22677 is_first_line = false;
22678 current_line = subsequent_lines_prefix.clone();
22679 current_line_len = subsequent_lines_prefix_len;
22680 } else if current_line_len != current_prefix_len {
22681 current_line.push(' ');
22682 current_line_len += 1;
22683 }
22684 }
22685 }
22686 }
22687
22688 if !current_line.is_empty() {
22689 wrapped_text.push_str(¤t_line);
22690 }
22691 wrapped_text
22692}
22693
22694#[test]
22695fn test_wrap_with_prefix() {
22696 assert_eq!(
22697 wrap_with_prefix(
22698 "# ".to_string(),
22699 "# ".to_string(),
22700 "abcdefg".to_string(),
22701 4,
22702 NonZeroU32::new(4).unwrap(),
22703 false,
22704 ),
22705 "# abcdefg"
22706 );
22707 assert_eq!(
22708 wrap_with_prefix(
22709 "".to_string(),
22710 "".to_string(),
22711 "\thello world".to_string(),
22712 8,
22713 NonZeroU32::new(4).unwrap(),
22714 false,
22715 ),
22716 "hello\nworld"
22717 );
22718 assert_eq!(
22719 wrap_with_prefix(
22720 "// ".to_string(),
22721 "// ".to_string(),
22722 "xx \nyy zz aa bb cc".to_string(),
22723 12,
22724 NonZeroU32::new(4).unwrap(),
22725 false,
22726 ),
22727 "// xx yy zz\n// aa bb cc"
22728 );
22729 assert_eq!(
22730 wrap_with_prefix(
22731 String::new(),
22732 String::new(),
22733 "这是什么 \n 钢笔".to_string(),
22734 3,
22735 NonZeroU32::new(4).unwrap(),
22736 false,
22737 ),
22738 "这是什\n么 钢\n笔"
22739 );
22740 assert_eq!(
22741 wrap_with_prefix(
22742 String::new(),
22743 String::new(),
22744 format!("foo{}bar", '\u{2009}'), // thin space
22745 80,
22746 NonZeroU32::new(4).unwrap(),
22747 false,
22748 ),
22749 format!("foo{}bar", '\u{2009}')
22750 );
22751}
22752
22753pub trait CollaborationHub {
22754 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22755 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22756 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22757}
22758
22759impl CollaborationHub for Entity<Project> {
22760 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22761 self.read(cx).collaborators()
22762 }
22763
22764 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22765 self.read(cx).user_store().read(cx).participant_indices()
22766 }
22767
22768 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22769 let this = self.read(cx);
22770 let user_ids = this.collaborators().values().map(|c| c.user_id);
22771 this.user_store().read(cx).participant_names(user_ids, cx)
22772 }
22773}
22774
22775pub trait SemanticsProvider {
22776 fn hover(
22777 &self,
22778 buffer: &Entity<Buffer>,
22779 position: text::Anchor,
22780 cx: &mut App,
22781 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22782
22783 fn inline_values(
22784 &self,
22785 buffer_handle: Entity<Buffer>,
22786 range: Range<text::Anchor>,
22787 cx: &mut App,
22788 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22789
22790 fn applicable_inlay_chunks(
22791 &self,
22792 buffer: &Entity<Buffer>,
22793 ranges: &[Range<text::Anchor>],
22794 cx: &mut App,
22795 ) -> Vec<Range<BufferRow>>;
22796
22797 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22798
22799 fn inlay_hints(
22800 &self,
22801 invalidate: InvalidationStrategy,
22802 buffer: Entity<Buffer>,
22803 ranges: Vec<Range<text::Anchor>>,
22804 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22805 cx: &mut App,
22806 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22807
22808 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22809
22810 fn document_highlights(
22811 &self,
22812 buffer: &Entity<Buffer>,
22813 position: text::Anchor,
22814 cx: &mut App,
22815 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22816
22817 fn definitions(
22818 &self,
22819 buffer: &Entity<Buffer>,
22820 position: text::Anchor,
22821 kind: GotoDefinitionKind,
22822 cx: &mut App,
22823 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22824
22825 fn range_for_rename(
22826 &self,
22827 buffer: &Entity<Buffer>,
22828 position: text::Anchor,
22829 cx: &mut App,
22830 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22831
22832 fn perform_rename(
22833 &self,
22834 buffer: &Entity<Buffer>,
22835 position: text::Anchor,
22836 new_name: String,
22837 cx: &mut App,
22838 ) -> Option<Task<Result<ProjectTransaction>>>;
22839}
22840
22841pub trait CompletionProvider {
22842 fn completions(
22843 &self,
22844 excerpt_id: ExcerptId,
22845 buffer: &Entity<Buffer>,
22846 buffer_position: text::Anchor,
22847 trigger: CompletionContext,
22848 window: &mut Window,
22849 cx: &mut Context<Editor>,
22850 ) -> Task<Result<Vec<CompletionResponse>>>;
22851
22852 fn resolve_completions(
22853 &self,
22854 _buffer: Entity<Buffer>,
22855 _completion_indices: Vec<usize>,
22856 _completions: Rc<RefCell<Box<[Completion]>>>,
22857 _cx: &mut Context<Editor>,
22858 ) -> Task<Result<bool>> {
22859 Task::ready(Ok(false))
22860 }
22861
22862 fn apply_additional_edits_for_completion(
22863 &self,
22864 _buffer: Entity<Buffer>,
22865 _completions: Rc<RefCell<Box<[Completion]>>>,
22866 _completion_index: usize,
22867 _push_to_history: bool,
22868 _cx: &mut Context<Editor>,
22869 ) -> Task<Result<Option<language::Transaction>>> {
22870 Task::ready(Ok(None))
22871 }
22872
22873 fn is_completion_trigger(
22874 &self,
22875 buffer: &Entity<Buffer>,
22876 position: language::Anchor,
22877 text: &str,
22878 trigger_in_words: bool,
22879 menu_is_open: bool,
22880 cx: &mut Context<Editor>,
22881 ) -> bool;
22882
22883 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22884
22885 fn sort_completions(&self) -> bool {
22886 true
22887 }
22888
22889 fn filter_completions(&self) -> bool {
22890 true
22891 }
22892}
22893
22894pub trait CodeActionProvider {
22895 fn id(&self) -> Arc<str>;
22896
22897 fn code_actions(
22898 &self,
22899 buffer: &Entity<Buffer>,
22900 range: Range<text::Anchor>,
22901 window: &mut Window,
22902 cx: &mut App,
22903 ) -> Task<Result<Vec<CodeAction>>>;
22904
22905 fn apply_code_action(
22906 &self,
22907 buffer_handle: Entity<Buffer>,
22908 action: CodeAction,
22909 excerpt_id: ExcerptId,
22910 push_to_history: bool,
22911 window: &mut Window,
22912 cx: &mut App,
22913 ) -> Task<Result<ProjectTransaction>>;
22914}
22915
22916impl CodeActionProvider for Entity<Project> {
22917 fn id(&self) -> Arc<str> {
22918 "project".into()
22919 }
22920
22921 fn code_actions(
22922 &self,
22923 buffer: &Entity<Buffer>,
22924 range: Range<text::Anchor>,
22925 _window: &mut Window,
22926 cx: &mut App,
22927 ) -> Task<Result<Vec<CodeAction>>> {
22928 self.update(cx, |project, cx| {
22929 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22930 let code_actions = project.code_actions(buffer, range, None, cx);
22931 cx.background_spawn(async move {
22932 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22933 Ok(code_lens_actions
22934 .context("code lens fetch")?
22935 .into_iter()
22936 .flatten()
22937 .chain(
22938 code_actions
22939 .context("code action fetch")?
22940 .into_iter()
22941 .flatten(),
22942 )
22943 .collect())
22944 })
22945 })
22946 }
22947
22948 fn apply_code_action(
22949 &self,
22950 buffer_handle: Entity<Buffer>,
22951 action: CodeAction,
22952 _excerpt_id: ExcerptId,
22953 push_to_history: bool,
22954 _window: &mut Window,
22955 cx: &mut App,
22956 ) -> Task<Result<ProjectTransaction>> {
22957 self.update(cx, |project, cx| {
22958 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22959 })
22960 }
22961}
22962
22963fn snippet_completions(
22964 project: &Project,
22965 buffer: &Entity<Buffer>,
22966 buffer_position: text::Anchor,
22967 cx: &mut App,
22968) -> Task<Result<CompletionResponse>> {
22969 let languages = buffer.read(cx).languages_at(buffer_position);
22970 let snippet_store = project.snippets().read(cx);
22971
22972 let scopes: Vec<_> = languages
22973 .iter()
22974 .filter_map(|language| {
22975 let language_name = language.lsp_id();
22976 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22977
22978 if snippets.is_empty() {
22979 None
22980 } else {
22981 Some((language.default_scope(), snippets))
22982 }
22983 })
22984 .collect();
22985
22986 if scopes.is_empty() {
22987 return Task::ready(Ok(CompletionResponse {
22988 completions: vec![],
22989 display_options: CompletionDisplayOptions::default(),
22990 is_incomplete: false,
22991 }));
22992 }
22993
22994 let snapshot = buffer.read(cx).text_snapshot();
22995 let executor = cx.background_executor().clone();
22996
22997 cx.background_spawn(async move {
22998 let mut is_incomplete = false;
22999 let mut completions: Vec<Completion> = Vec::new();
23000 for (scope, snippets) in scopes.into_iter() {
23001 let classifier =
23002 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23003
23004 const MAX_WORD_PREFIX_LEN: usize = 128;
23005 let last_word: String = snapshot
23006 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23007 .take(MAX_WORD_PREFIX_LEN)
23008 .take_while(|c| classifier.is_word(*c))
23009 .collect::<String>()
23010 .chars()
23011 .rev()
23012 .collect();
23013
23014 if last_word.is_empty() {
23015 return Ok(CompletionResponse {
23016 completions: vec![],
23017 display_options: CompletionDisplayOptions::default(),
23018 is_incomplete: true,
23019 });
23020 }
23021
23022 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23023 let to_lsp = |point: &text::Anchor| {
23024 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23025 point_to_lsp(end)
23026 };
23027 let lsp_end = to_lsp(&buffer_position);
23028
23029 let candidates = snippets
23030 .iter()
23031 .enumerate()
23032 .flat_map(|(ix, snippet)| {
23033 snippet
23034 .prefix
23035 .iter()
23036 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23037 })
23038 .collect::<Vec<StringMatchCandidate>>();
23039
23040 const MAX_RESULTS: usize = 100;
23041 let mut matches = fuzzy::match_strings(
23042 &candidates,
23043 &last_word,
23044 last_word.chars().any(|c| c.is_uppercase()),
23045 true,
23046 MAX_RESULTS,
23047 &Default::default(),
23048 executor.clone(),
23049 )
23050 .await;
23051
23052 if matches.len() >= MAX_RESULTS {
23053 is_incomplete = true;
23054 }
23055
23056 // Remove all candidates where the query's start does not match the start of any word in the candidate
23057 if let Some(query_start) = last_word.chars().next() {
23058 matches.retain(|string_match| {
23059 split_words(&string_match.string).any(|word| {
23060 // Check that the first codepoint of the word as lowercase matches the first
23061 // codepoint of the query as lowercase
23062 word.chars()
23063 .flat_map(|codepoint| codepoint.to_lowercase())
23064 .zip(query_start.to_lowercase())
23065 .all(|(word_cp, query_cp)| word_cp == query_cp)
23066 })
23067 });
23068 }
23069
23070 let matched_strings = matches
23071 .into_iter()
23072 .map(|m| m.string)
23073 .collect::<HashSet<_>>();
23074
23075 completions.extend(snippets.iter().filter_map(|snippet| {
23076 let matching_prefix = snippet
23077 .prefix
23078 .iter()
23079 .find(|prefix| matched_strings.contains(*prefix))?;
23080 let start = as_offset - last_word.len();
23081 let start = snapshot.anchor_before(start);
23082 let range = start..buffer_position;
23083 let lsp_start = to_lsp(&start);
23084 let lsp_range = lsp::Range {
23085 start: lsp_start,
23086 end: lsp_end,
23087 };
23088 Some(Completion {
23089 replace_range: range,
23090 new_text: snippet.body.clone(),
23091 source: CompletionSource::Lsp {
23092 insert_range: None,
23093 server_id: LanguageServerId(usize::MAX),
23094 resolved: true,
23095 lsp_completion: Box::new(lsp::CompletionItem {
23096 label: snippet.prefix.first().unwrap().clone(),
23097 kind: Some(CompletionItemKind::SNIPPET),
23098 label_details: snippet.description.as_ref().map(|description| {
23099 lsp::CompletionItemLabelDetails {
23100 detail: Some(description.clone()),
23101 description: None,
23102 }
23103 }),
23104 insert_text_format: Some(InsertTextFormat::SNIPPET),
23105 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23106 lsp::InsertReplaceEdit {
23107 new_text: snippet.body.clone(),
23108 insert: lsp_range,
23109 replace: lsp_range,
23110 },
23111 )),
23112 filter_text: Some(snippet.body.clone()),
23113 sort_text: Some(char::MAX.to_string()),
23114 ..lsp::CompletionItem::default()
23115 }),
23116 lsp_defaults: None,
23117 },
23118 label: CodeLabel::plain(matching_prefix.clone(), None),
23119 icon_path: None,
23120 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23121 single_line: snippet.name.clone().into(),
23122 plain_text: snippet
23123 .description
23124 .clone()
23125 .map(|description| description.into()),
23126 }),
23127 insert_text_mode: None,
23128 confirm: None,
23129 })
23130 }))
23131 }
23132
23133 Ok(CompletionResponse {
23134 completions,
23135 display_options: CompletionDisplayOptions::default(),
23136 is_incomplete,
23137 })
23138 })
23139}
23140
23141impl CompletionProvider for Entity<Project> {
23142 fn completions(
23143 &self,
23144 _excerpt_id: ExcerptId,
23145 buffer: &Entity<Buffer>,
23146 buffer_position: text::Anchor,
23147 options: CompletionContext,
23148 _window: &mut Window,
23149 cx: &mut Context<Editor>,
23150 ) -> Task<Result<Vec<CompletionResponse>>> {
23151 self.update(cx, |project, cx| {
23152 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23153 let project_completions = project.completions(buffer, buffer_position, options, cx);
23154 cx.background_spawn(async move {
23155 let mut responses = project_completions.await?;
23156 let snippets = snippets.await?;
23157 if !snippets.completions.is_empty() {
23158 responses.push(snippets);
23159 }
23160 Ok(responses)
23161 })
23162 })
23163 }
23164
23165 fn resolve_completions(
23166 &self,
23167 buffer: Entity<Buffer>,
23168 completion_indices: Vec<usize>,
23169 completions: Rc<RefCell<Box<[Completion]>>>,
23170 cx: &mut Context<Editor>,
23171 ) -> Task<Result<bool>> {
23172 self.update(cx, |project, cx| {
23173 project.lsp_store().update(cx, |lsp_store, cx| {
23174 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23175 })
23176 })
23177 }
23178
23179 fn apply_additional_edits_for_completion(
23180 &self,
23181 buffer: Entity<Buffer>,
23182 completions: Rc<RefCell<Box<[Completion]>>>,
23183 completion_index: usize,
23184 push_to_history: bool,
23185 cx: &mut Context<Editor>,
23186 ) -> Task<Result<Option<language::Transaction>>> {
23187 self.update(cx, |project, cx| {
23188 project.lsp_store().update(cx, |lsp_store, cx| {
23189 lsp_store.apply_additional_edits_for_completion(
23190 buffer,
23191 completions,
23192 completion_index,
23193 push_to_history,
23194 cx,
23195 )
23196 })
23197 })
23198 }
23199
23200 fn is_completion_trigger(
23201 &self,
23202 buffer: &Entity<Buffer>,
23203 position: language::Anchor,
23204 text: &str,
23205 trigger_in_words: bool,
23206 menu_is_open: bool,
23207 cx: &mut Context<Editor>,
23208 ) -> bool {
23209 let mut chars = text.chars();
23210 let char = if let Some(char) = chars.next() {
23211 char
23212 } else {
23213 return false;
23214 };
23215 if chars.next().is_some() {
23216 return false;
23217 }
23218
23219 let buffer = buffer.read(cx);
23220 let snapshot = buffer.snapshot();
23221 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23222 return false;
23223 }
23224 let classifier = snapshot
23225 .char_classifier_at(position)
23226 .scope_context(Some(CharScopeContext::Completion));
23227 if trigger_in_words && classifier.is_word(char) {
23228 return true;
23229 }
23230
23231 buffer.completion_triggers().contains(text)
23232 }
23233}
23234
23235impl SemanticsProvider for Entity<Project> {
23236 fn hover(
23237 &self,
23238 buffer: &Entity<Buffer>,
23239 position: text::Anchor,
23240 cx: &mut App,
23241 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23242 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23243 }
23244
23245 fn document_highlights(
23246 &self,
23247 buffer: &Entity<Buffer>,
23248 position: text::Anchor,
23249 cx: &mut App,
23250 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23251 Some(self.update(cx, |project, cx| {
23252 project.document_highlights(buffer, position, cx)
23253 }))
23254 }
23255
23256 fn definitions(
23257 &self,
23258 buffer: &Entity<Buffer>,
23259 position: text::Anchor,
23260 kind: GotoDefinitionKind,
23261 cx: &mut App,
23262 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23263 Some(self.update(cx, |project, cx| match kind {
23264 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23265 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23266 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23267 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23268 }))
23269 }
23270
23271 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23272 self.update(cx, |project, cx| {
23273 if project
23274 .active_debug_session(cx)
23275 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23276 {
23277 return true;
23278 }
23279
23280 buffer.update(cx, |buffer, cx| {
23281 project.any_language_server_supports_inlay_hints(buffer, cx)
23282 })
23283 })
23284 }
23285
23286 fn inline_values(
23287 &self,
23288 buffer_handle: Entity<Buffer>,
23289 range: Range<text::Anchor>,
23290 cx: &mut App,
23291 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23292 self.update(cx, |project, cx| {
23293 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23294
23295 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23296 })
23297 }
23298
23299 fn applicable_inlay_chunks(
23300 &self,
23301 buffer: &Entity<Buffer>,
23302 ranges: &[Range<text::Anchor>],
23303 cx: &mut App,
23304 ) -> Vec<Range<BufferRow>> {
23305 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23306 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23307 })
23308 }
23309
23310 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23311 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23312 lsp_store.invalidate_inlay_hints(for_buffers)
23313 });
23314 }
23315
23316 fn inlay_hints(
23317 &self,
23318 invalidate: InvalidationStrategy,
23319 buffer: Entity<Buffer>,
23320 ranges: Vec<Range<text::Anchor>>,
23321 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23322 cx: &mut App,
23323 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23324 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23325 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23326 }))
23327 }
23328
23329 fn range_for_rename(
23330 &self,
23331 buffer: &Entity<Buffer>,
23332 position: text::Anchor,
23333 cx: &mut App,
23334 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23335 Some(self.update(cx, |project, cx| {
23336 let buffer = buffer.clone();
23337 let task = project.prepare_rename(buffer.clone(), position, cx);
23338 cx.spawn(async move |_, cx| {
23339 Ok(match task.await? {
23340 PrepareRenameResponse::Success(range) => Some(range),
23341 PrepareRenameResponse::InvalidPosition => None,
23342 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23343 // Fallback on using TreeSitter info to determine identifier range
23344 buffer.read_with(cx, |buffer, _| {
23345 let snapshot = buffer.snapshot();
23346 let (range, kind) = snapshot.surrounding_word(position, None);
23347 if kind != Some(CharKind::Word) {
23348 return None;
23349 }
23350 Some(
23351 snapshot.anchor_before(range.start)
23352 ..snapshot.anchor_after(range.end),
23353 )
23354 })?
23355 }
23356 })
23357 })
23358 }))
23359 }
23360
23361 fn perform_rename(
23362 &self,
23363 buffer: &Entity<Buffer>,
23364 position: text::Anchor,
23365 new_name: String,
23366 cx: &mut App,
23367 ) -> Option<Task<Result<ProjectTransaction>>> {
23368 Some(self.update(cx, |project, cx| {
23369 project.perform_rename(buffer.clone(), position, new_name, cx)
23370 }))
23371 }
23372}
23373
23374fn consume_contiguous_rows(
23375 contiguous_row_selections: &mut Vec<Selection<Point>>,
23376 selection: &Selection<Point>,
23377 display_map: &DisplaySnapshot,
23378 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23379) -> (MultiBufferRow, MultiBufferRow) {
23380 contiguous_row_selections.push(selection.clone());
23381 let start_row = starting_row(selection, display_map);
23382 let mut end_row = ending_row(selection, display_map);
23383
23384 while let Some(next_selection) = selections.peek() {
23385 if next_selection.start.row <= end_row.0 {
23386 end_row = ending_row(next_selection, display_map);
23387 contiguous_row_selections.push(selections.next().unwrap().clone());
23388 } else {
23389 break;
23390 }
23391 }
23392 (start_row, end_row)
23393}
23394
23395fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23396 if selection.start.column > 0 {
23397 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23398 } else {
23399 MultiBufferRow(selection.start.row)
23400 }
23401}
23402
23403fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23404 if next_selection.end.column > 0 || next_selection.is_empty() {
23405 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23406 } else {
23407 MultiBufferRow(next_selection.end.row)
23408 }
23409}
23410
23411impl EditorSnapshot {
23412 pub fn remote_selections_in_range<'a>(
23413 &'a self,
23414 range: &'a Range<Anchor>,
23415 collaboration_hub: &dyn CollaborationHub,
23416 cx: &'a App,
23417 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23418 let participant_names = collaboration_hub.user_names(cx);
23419 let participant_indices = collaboration_hub.user_participant_indices(cx);
23420 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23421 let collaborators_by_replica_id = collaborators_by_peer_id
23422 .values()
23423 .map(|collaborator| (collaborator.replica_id, collaborator))
23424 .collect::<HashMap<_, _>>();
23425 self.buffer_snapshot()
23426 .selections_in_range(range, false)
23427 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23428 if replica_id == ReplicaId::AGENT {
23429 Some(RemoteSelection {
23430 replica_id,
23431 selection,
23432 cursor_shape,
23433 line_mode,
23434 collaborator_id: CollaboratorId::Agent,
23435 user_name: Some("Agent".into()),
23436 color: cx.theme().players().agent(),
23437 })
23438 } else {
23439 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23440 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23441 let user_name = participant_names.get(&collaborator.user_id).cloned();
23442 Some(RemoteSelection {
23443 replica_id,
23444 selection,
23445 cursor_shape,
23446 line_mode,
23447 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23448 user_name,
23449 color: if let Some(index) = participant_index {
23450 cx.theme().players().color_for_participant(index.0)
23451 } else {
23452 cx.theme().players().absent()
23453 },
23454 })
23455 }
23456 })
23457 }
23458
23459 pub fn hunks_for_ranges(
23460 &self,
23461 ranges: impl IntoIterator<Item = Range<Point>>,
23462 ) -> Vec<MultiBufferDiffHunk> {
23463 let mut hunks = Vec::new();
23464 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23465 HashMap::default();
23466 for query_range in ranges {
23467 let query_rows =
23468 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23469 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23470 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23471 ) {
23472 // Include deleted hunks that are adjacent to the query range, because
23473 // otherwise they would be missed.
23474 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23475 if hunk.status().is_deleted() {
23476 intersects_range |= hunk.row_range.start == query_rows.end;
23477 intersects_range |= hunk.row_range.end == query_rows.start;
23478 }
23479 if intersects_range {
23480 if !processed_buffer_rows
23481 .entry(hunk.buffer_id)
23482 .or_default()
23483 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23484 {
23485 continue;
23486 }
23487 hunks.push(hunk);
23488 }
23489 }
23490 }
23491
23492 hunks
23493 }
23494
23495 fn display_diff_hunks_for_rows<'a>(
23496 &'a self,
23497 display_rows: Range<DisplayRow>,
23498 folded_buffers: &'a HashSet<BufferId>,
23499 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23500 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23501 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23502
23503 self.buffer_snapshot()
23504 .diff_hunks_in_range(buffer_start..buffer_end)
23505 .filter_map(|hunk| {
23506 if folded_buffers.contains(&hunk.buffer_id) {
23507 return None;
23508 }
23509
23510 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23511 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23512
23513 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23514 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23515
23516 let display_hunk = if hunk_display_start.column() != 0 {
23517 DisplayDiffHunk::Folded {
23518 display_row: hunk_display_start.row(),
23519 }
23520 } else {
23521 let mut end_row = hunk_display_end.row();
23522 if hunk_display_end.column() > 0 {
23523 end_row.0 += 1;
23524 }
23525 let is_created_file = hunk.is_created_file();
23526 DisplayDiffHunk::Unfolded {
23527 status: hunk.status(),
23528 diff_base_byte_range: hunk.diff_base_byte_range,
23529 display_row_range: hunk_display_start.row()..end_row,
23530 multi_buffer_range: Anchor::range_in_buffer(
23531 hunk.excerpt_id,
23532 hunk.buffer_id,
23533 hunk.buffer_range,
23534 ),
23535 is_created_file,
23536 }
23537 };
23538
23539 Some(display_hunk)
23540 })
23541 }
23542
23543 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23544 self.display_snapshot
23545 .buffer_snapshot()
23546 .language_at(position)
23547 }
23548
23549 pub fn is_focused(&self) -> bool {
23550 self.is_focused
23551 }
23552
23553 pub fn placeholder_text(&self) -> Option<String> {
23554 self.placeholder_display_snapshot
23555 .as_ref()
23556 .map(|display_map| display_map.text())
23557 }
23558
23559 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23560 self.scroll_anchor.scroll_position(&self.display_snapshot)
23561 }
23562
23563 fn gutter_dimensions(
23564 &self,
23565 font_id: FontId,
23566 font_size: Pixels,
23567 max_line_number_width: Pixels,
23568 cx: &App,
23569 ) -> Option<GutterDimensions> {
23570 if !self.show_gutter {
23571 return None;
23572 }
23573
23574 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23575 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23576
23577 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23578 matches!(
23579 ProjectSettings::get_global(cx).git.git_gutter,
23580 GitGutterSetting::TrackedFiles
23581 )
23582 });
23583 let gutter_settings = EditorSettings::get_global(cx).gutter;
23584 let show_line_numbers = self
23585 .show_line_numbers
23586 .unwrap_or(gutter_settings.line_numbers);
23587 let line_gutter_width = if show_line_numbers {
23588 // Avoid flicker-like gutter resizes when the line number gains another digit by
23589 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23590 let min_width_for_number_on_gutter =
23591 ch_advance * gutter_settings.min_line_number_digits as f32;
23592 max_line_number_width.max(min_width_for_number_on_gutter)
23593 } else {
23594 0.0.into()
23595 };
23596
23597 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23598 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23599
23600 let git_blame_entries_width =
23601 self.git_blame_gutter_max_author_length
23602 .map(|max_author_length| {
23603 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23604 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23605
23606 /// The number of characters to dedicate to gaps and margins.
23607 const SPACING_WIDTH: usize = 4;
23608
23609 let max_char_count = max_author_length.min(renderer.max_author_length())
23610 + ::git::SHORT_SHA_LENGTH
23611 + MAX_RELATIVE_TIMESTAMP.len()
23612 + SPACING_WIDTH;
23613
23614 ch_advance * max_char_count
23615 });
23616
23617 let is_singleton = self.buffer_snapshot().is_singleton();
23618
23619 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23620 left_padding += if !is_singleton {
23621 ch_width * 4.0
23622 } else if show_runnables || show_breakpoints {
23623 ch_width * 3.0
23624 } else if show_git_gutter && show_line_numbers {
23625 ch_width * 2.0
23626 } else if show_git_gutter || show_line_numbers {
23627 ch_width
23628 } else {
23629 px(0.)
23630 };
23631
23632 let shows_folds = is_singleton && gutter_settings.folds;
23633
23634 let right_padding = if shows_folds && show_line_numbers {
23635 ch_width * 4.0
23636 } else if shows_folds || (!is_singleton && show_line_numbers) {
23637 ch_width * 3.0
23638 } else if show_line_numbers {
23639 ch_width
23640 } else {
23641 px(0.)
23642 };
23643
23644 Some(GutterDimensions {
23645 left_padding,
23646 right_padding,
23647 width: line_gutter_width + left_padding + right_padding,
23648 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23649 git_blame_entries_width,
23650 })
23651 }
23652
23653 pub fn render_crease_toggle(
23654 &self,
23655 buffer_row: MultiBufferRow,
23656 row_contains_cursor: bool,
23657 editor: Entity<Editor>,
23658 window: &mut Window,
23659 cx: &mut App,
23660 ) -> Option<AnyElement> {
23661 let folded = self.is_line_folded(buffer_row);
23662 let mut is_foldable = false;
23663
23664 if let Some(crease) = self
23665 .crease_snapshot
23666 .query_row(buffer_row, self.buffer_snapshot())
23667 {
23668 is_foldable = true;
23669 match crease {
23670 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23671 if let Some(render_toggle) = render_toggle {
23672 let toggle_callback =
23673 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23674 if folded {
23675 editor.update(cx, |editor, cx| {
23676 editor.fold_at(buffer_row, window, cx)
23677 });
23678 } else {
23679 editor.update(cx, |editor, cx| {
23680 editor.unfold_at(buffer_row, window, cx)
23681 });
23682 }
23683 });
23684 return Some((render_toggle)(
23685 buffer_row,
23686 folded,
23687 toggle_callback,
23688 window,
23689 cx,
23690 ));
23691 }
23692 }
23693 }
23694 }
23695
23696 is_foldable |= self.starts_indent(buffer_row);
23697
23698 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23699 Some(
23700 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23701 .toggle_state(folded)
23702 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23703 if folded {
23704 this.unfold_at(buffer_row, window, cx);
23705 } else {
23706 this.fold_at(buffer_row, window, cx);
23707 }
23708 }))
23709 .into_any_element(),
23710 )
23711 } else {
23712 None
23713 }
23714 }
23715
23716 pub fn render_crease_trailer(
23717 &self,
23718 buffer_row: MultiBufferRow,
23719 window: &mut Window,
23720 cx: &mut App,
23721 ) -> Option<AnyElement> {
23722 let folded = self.is_line_folded(buffer_row);
23723 if let Crease::Inline { render_trailer, .. } = self
23724 .crease_snapshot
23725 .query_row(buffer_row, self.buffer_snapshot())?
23726 {
23727 let render_trailer = render_trailer.as_ref()?;
23728 Some(render_trailer(buffer_row, folded, window, cx))
23729 } else {
23730 None
23731 }
23732 }
23733}
23734
23735impl Deref for EditorSnapshot {
23736 type Target = DisplaySnapshot;
23737
23738 fn deref(&self) -> &Self::Target {
23739 &self.display_snapshot
23740 }
23741}
23742
23743#[derive(Clone, Debug, PartialEq, Eq)]
23744pub enum EditorEvent {
23745 InputIgnored {
23746 text: Arc<str>,
23747 },
23748 InputHandled {
23749 utf16_range_to_replace: Option<Range<isize>>,
23750 text: Arc<str>,
23751 },
23752 ExcerptsAdded {
23753 buffer: Entity<Buffer>,
23754 predecessor: ExcerptId,
23755 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23756 },
23757 ExcerptsRemoved {
23758 ids: Vec<ExcerptId>,
23759 removed_buffer_ids: Vec<BufferId>,
23760 },
23761 BufferFoldToggled {
23762 ids: Vec<ExcerptId>,
23763 folded: bool,
23764 },
23765 ExcerptsEdited {
23766 ids: Vec<ExcerptId>,
23767 },
23768 ExcerptsExpanded {
23769 ids: Vec<ExcerptId>,
23770 },
23771 BufferEdited,
23772 Edited {
23773 transaction_id: clock::Lamport,
23774 },
23775 Reparsed(BufferId),
23776 Focused,
23777 FocusedIn,
23778 Blurred,
23779 DirtyChanged,
23780 Saved,
23781 TitleChanged,
23782 SelectionsChanged {
23783 local: bool,
23784 },
23785 ScrollPositionChanged {
23786 local: bool,
23787 autoscroll: bool,
23788 },
23789 TransactionUndone {
23790 transaction_id: clock::Lamport,
23791 },
23792 TransactionBegun {
23793 transaction_id: clock::Lamport,
23794 },
23795 CursorShapeChanged,
23796 BreadcrumbsChanged,
23797 PushedToNavHistory {
23798 anchor: Anchor,
23799 is_deactivate: bool,
23800 },
23801}
23802
23803impl EventEmitter<EditorEvent> for Editor {}
23804
23805impl Focusable for Editor {
23806 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23807 self.focus_handle.clone()
23808 }
23809}
23810
23811impl Render for Editor {
23812 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23813 let settings = ThemeSettings::get_global(cx);
23814
23815 let mut text_style = match self.mode {
23816 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23817 color: cx.theme().colors().editor_foreground,
23818 font_family: settings.ui_font.family.clone(),
23819 font_features: settings.ui_font.features.clone(),
23820 font_fallbacks: settings.ui_font.fallbacks.clone(),
23821 font_size: rems(0.875).into(),
23822 font_weight: settings.ui_font.weight,
23823 line_height: relative(settings.buffer_line_height.value()),
23824 ..Default::default()
23825 },
23826 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23827 color: cx.theme().colors().editor_foreground,
23828 font_family: settings.buffer_font.family.clone(),
23829 font_features: settings.buffer_font.features.clone(),
23830 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23831 font_size: settings.buffer_font_size(cx).into(),
23832 font_weight: settings.buffer_font.weight,
23833 line_height: relative(settings.buffer_line_height.value()),
23834 ..Default::default()
23835 },
23836 };
23837 if let Some(text_style_refinement) = &self.text_style_refinement {
23838 text_style.refine(text_style_refinement)
23839 }
23840
23841 let background = match self.mode {
23842 EditorMode::SingleLine => cx.theme().system().transparent,
23843 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23844 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23845 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23846 };
23847
23848 EditorElement::new(
23849 &cx.entity(),
23850 EditorStyle {
23851 background,
23852 border: cx.theme().colors().border,
23853 local_player: cx.theme().players().local(),
23854 text: text_style,
23855 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23856 syntax: cx.theme().syntax().clone(),
23857 status: cx.theme().status().clone(),
23858 inlay_hints_style: make_inlay_hints_style(cx),
23859 edit_prediction_styles: make_suggestion_styles(cx),
23860 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23861 show_underlines: self.diagnostics_enabled(),
23862 },
23863 )
23864 }
23865}
23866
23867impl EntityInputHandler for Editor {
23868 fn text_for_range(
23869 &mut self,
23870 range_utf16: Range<usize>,
23871 adjusted_range: &mut Option<Range<usize>>,
23872 _: &mut Window,
23873 cx: &mut Context<Self>,
23874 ) -> Option<String> {
23875 let snapshot = self.buffer.read(cx).read(cx);
23876 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23877 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23878 if (start.0..end.0) != range_utf16 {
23879 adjusted_range.replace(start.0..end.0);
23880 }
23881 Some(snapshot.text_for_range(start..end).collect())
23882 }
23883
23884 fn selected_text_range(
23885 &mut self,
23886 ignore_disabled_input: bool,
23887 _: &mut Window,
23888 cx: &mut Context<Self>,
23889 ) -> Option<UTF16Selection> {
23890 // Prevent the IME menu from appearing when holding down an alphabetic key
23891 // while input is disabled.
23892 if !ignore_disabled_input && !self.input_enabled {
23893 return None;
23894 }
23895
23896 let selection = self
23897 .selections
23898 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23899 let range = selection.range();
23900
23901 Some(UTF16Selection {
23902 range: range.start.0..range.end.0,
23903 reversed: selection.reversed,
23904 })
23905 }
23906
23907 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23908 let snapshot = self.buffer.read(cx).read(cx);
23909 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23910 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23911 }
23912
23913 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23914 self.clear_highlights::<InputComposition>(cx);
23915 self.ime_transaction.take();
23916 }
23917
23918 fn replace_text_in_range(
23919 &mut self,
23920 range_utf16: Option<Range<usize>>,
23921 text: &str,
23922 window: &mut Window,
23923 cx: &mut Context<Self>,
23924 ) {
23925 if !self.input_enabled {
23926 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23927 return;
23928 }
23929
23930 self.transact(window, cx, |this, window, cx| {
23931 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23932 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23933 Some(this.selection_replacement_ranges(range_utf16, cx))
23934 } else {
23935 this.marked_text_ranges(cx)
23936 };
23937
23938 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23939 let newest_selection_id = this.selections.newest_anchor().id;
23940 this.selections
23941 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23942 .iter()
23943 .zip(ranges_to_replace.iter())
23944 .find_map(|(selection, range)| {
23945 if selection.id == newest_selection_id {
23946 Some(
23947 (range.start.0 as isize - selection.head().0 as isize)
23948 ..(range.end.0 as isize - selection.head().0 as isize),
23949 )
23950 } else {
23951 None
23952 }
23953 })
23954 });
23955
23956 cx.emit(EditorEvent::InputHandled {
23957 utf16_range_to_replace: range_to_replace,
23958 text: text.into(),
23959 });
23960
23961 if let Some(new_selected_ranges) = new_selected_ranges {
23962 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23963 selections.select_ranges(new_selected_ranges)
23964 });
23965 this.backspace(&Default::default(), window, cx);
23966 }
23967
23968 this.handle_input(text, window, cx);
23969 });
23970
23971 if let Some(transaction) = self.ime_transaction {
23972 self.buffer.update(cx, |buffer, cx| {
23973 buffer.group_until_transaction(transaction, cx);
23974 });
23975 }
23976
23977 self.unmark_text(window, cx);
23978 }
23979
23980 fn replace_and_mark_text_in_range(
23981 &mut self,
23982 range_utf16: Option<Range<usize>>,
23983 text: &str,
23984 new_selected_range_utf16: Option<Range<usize>>,
23985 window: &mut Window,
23986 cx: &mut Context<Self>,
23987 ) {
23988 if !self.input_enabled {
23989 return;
23990 }
23991
23992 let transaction = self.transact(window, cx, |this, window, cx| {
23993 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23994 let snapshot = this.buffer.read(cx).read(cx);
23995 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23996 for marked_range in &mut marked_ranges {
23997 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23998 marked_range.start.0 += relative_range_utf16.start;
23999 marked_range.start =
24000 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24001 marked_range.end =
24002 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24003 }
24004 }
24005 Some(marked_ranges)
24006 } else if let Some(range_utf16) = range_utf16 {
24007 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24008 Some(this.selection_replacement_ranges(range_utf16, cx))
24009 } else {
24010 None
24011 };
24012
24013 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24014 let newest_selection_id = this.selections.newest_anchor().id;
24015 this.selections
24016 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24017 .iter()
24018 .zip(ranges_to_replace.iter())
24019 .find_map(|(selection, range)| {
24020 if selection.id == newest_selection_id {
24021 Some(
24022 (range.start.0 as isize - selection.head().0 as isize)
24023 ..(range.end.0 as isize - selection.head().0 as isize),
24024 )
24025 } else {
24026 None
24027 }
24028 })
24029 });
24030
24031 cx.emit(EditorEvent::InputHandled {
24032 utf16_range_to_replace: range_to_replace,
24033 text: text.into(),
24034 });
24035
24036 if let Some(ranges) = ranges_to_replace {
24037 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24038 s.select_ranges(ranges)
24039 });
24040 }
24041
24042 let marked_ranges = {
24043 let snapshot = this.buffer.read(cx).read(cx);
24044 this.selections
24045 .disjoint_anchors_arc()
24046 .iter()
24047 .map(|selection| {
24048 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24049 })
24050 .collect::<Vec<_>>()
24051 };
24052
24053 if text.is_empty() {
24054 this.unmark_text(window, cx);
24055 } else {
24056 this.highlight_text::<InputComposition>(
24057 marked_ranges.clone(),
24058 HighlightStyle {
24059 underline: Some(UnderlineStyle {
24060 thickness: px(1.),
24061 color: None,
24062 wavy: false,
24063 }),
24064 ..Default::default()
24065 },
24066 cx,
24067 );
24068 }
24069
24070 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24071 let use_autoclose = this.use_autoclose;
24072 let use_auto_surround = this.use_auto_surround;
24073 this.set_use_autoclose(false);
24074 this.set_use_auto_surround(false);
24075 this.handle_input(text, window, cx);
24076 this.set_use_autoclose(use_autoclose);
24077 this.set_use_auto_surround(use_auto_surround);
24078
24079 if let Some(new_selected_range) = new_selected_range_utf16 {
24080 let snapshot = this.buffer.read(cx).read(cx);
24081 let new_selected_ranges = marked_ranges
24082 .into_iter()
24083 .map(|marked_range| {
24084 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24085 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24086 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24087 snapshot.clip_offset_utf16(new_start, Bias::Left)
24088 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24089 })
24090 .collect::<Vec<_>>();
24091
24092 drop(snapshot);
24093 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24094 selections.select_ranges(new_selected_ranges)
24095 });
24096 }
24097 });
24098
24099 self.ime_transaction = self.ime_transaction.or(transaction);
24100 if let Some(transaction) = self.ime_transaction {
24101 self.buffer.update(cx, |buffer, cx| {
24102 buffer.group_until_transaction(transaction, cx);
24103 });
24104 }
24105
24106 if self.text_highlights::<InputComposition>(cx).is_none() {
24107 self.ime_transaction.take();
24108 }
24109 }
24110
24111 fn bounds_for_range(
24112 &mut self,
24113 range_utf16: Range<usize>,
24114 element_bounds: gpui::Bounds<Pixels>,
24115 window: &mut Window,
24116 cx: &mut Context<Self>,
24117 ) -> Option<gpui::Bounds<Pixels>> {
24118 let text_layout_details = self.text_layout_details(window);
24119 let CharacterDimensions {
24120 em_width,
24121 em_advance,
24122 line_height,
24123 } = self.character_dimensions(window);
24124
24125 let snapshot = self.snapshot(window, cx);
24126 let scroll_position = snapshot.scroll_position();
24127 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24128
24129 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24130 let x = Pixels::from(
24131 ScrollOffset::from(
24132 snapshot.x_for_display_point(start, &text_layout_details)
24133 + self.gutter_dimensions.full_width(),
24134 ) - scroll_left,
24135 );
24136 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24137
24138 Some(Bounds {
24139 origin: element_bounds.origin + point(x, y),
24140 size: size(em_width, line_height),
24141 })
24142 }
24143
24144 fn character_index_for_point(
24145 &mut self,
24146 point: gpui::Point<Pixels>,
24147 _window: &mut Window,
24148 _cx: &mut Context<Self>,
24149 ) -> Option<usize> {
24150 let position_map = self.last_position_map.as_ref()?;
24151 if !position_map.text_hitbox.contains(&point) {
24152 return None;
24153 }
24154 let display_point = position_map.point_for_position(point).previous_valid;
24155 let anchor = position_map
24156 .snapshot
24157 .display_point_to_anchor(display_point, Bias::Left);
24158 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24159 Some(utf16_offset.0)
24160 }
24161}
24162
24163trait SelectionExt {
24164 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24165 fn spanned_rows(
24166 &self,
24167 include_end_if_at_line_start: bool,
24168 map: &DisplaySnapshot,
24169 ) -> Range<MultiBufferRow>;
24170}
24171
24172impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24173 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24174 let start = self
24175 .start
24176 .to_point(map.buffer_snapshot())
24177 .to_display_point(map);
24178 let end = self
24179 .end
24180 .to_point(map.buffer_snapshot())
24181 .to_display_point(map);
24182 if self.reversed {
24183 end..start
24184 } else {
24185 start..end
24186 }
24187 }
24188
24189 fn spanned_rows(
24190 &self,
24191 include_end_if_at_line_start: bool,
24192 map: &DisplaySnapshot,
24193 ) -> Range<MultiBufferRow> {
24194 let start = self.start.to_point(map.buffer_snapshot());
24195 let mut end = self.end.to_point(map.buffer_snapshot());
24196 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24197 end.row -= 1;
24198 }
24199
24200 let buffer_start = map.prev_line_boundary(start).0;
24201 let buffer_end = map.next_line_boundary(end).0;
24202 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24203 }
24204}
24205
24206impl<T: InvalidationRegion> InvalidationStack<T> {
24207 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24208 where
24209 S: Clone + ToOffset,
24210 {
24211 while let Some(region) = self.last() {
24212 let all_selections_inside_invalidation_ranges =
24213 if selections.len() == region.ranges().len() {
24214 selections
24215 .iter()
24216 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24217 .all(|(selection, invalidation_range)| {
24218 let head = selection.head().to_offset(buffer);
24219 invalidation_range.start <= head && invalidation_range.end >= head
24220 })
24221 } else {
24222 false
24223 };
24224
24225 if all_selections_inside_invalidation_ranges {
24226 break;
24227 } else {
24228 self.pop();
24229 }
24230 }
24231 }
24232}
24233
24234impl<T> Default for InvalidationStack<T> {
24235 fn default() -> Self {
24236 Self(Default::default())
24237 }
24238}
24239
24240impl<T> Deref for InvalidationStack<T> {
24241 type Target = Vec<T>;
24242
24243 fn deref(&self) -> &Self::Target {
24244 &self.0
24245 }
24246}
24247
24248impl<T> DerefMut for InvalidationStack<T> {
24249 fn deref_mut(&mut self) -> &mut Self::Target {
24250 &mut self.0
24251 }
24252}
24253
24254impl InvalidationRegion for SnippetState {
24255 fn ranges(&self) -> &[Range<Anchor>] {
24256 &self.ranges[self.active_index]
24257 }
24258}
24259
24260fn edit_prediction_edit_text(
24261 current_snapshot: &BufferSnapshot,
24262 edits: &[(Range<Anchor>, String)],
24263 edit_preview: &EditPreview,
24264 include_deletions: bool,
24265 cx: &App,
24266) -> HighlightedText {
24267 let edits = edits
24268 .iter()
24269 .map(|(anchor, text)| {
24270 (
24271 anchor.start.text_anchor..anchor.end.text_anchor,
24272 text.clone(),
24273 )
24274 })
24275 .collect::<Vec<_>>();
24276
24277 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24278}
24279
24280fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24281 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24282 // Just show the raw edit text with basic styling
24283 let mut text = String::new();
24284 let mut highlights = Vec::new();
24285
24286 let insertion_highlight_style = HighlightStyle {
24287 color: Some(cx.theme().colors().text),
24288 ..Default::default()
24289 };
24290
24291 for (_, edit_text) in edits {
24292 let start_offset = text.len();
24293 text.push_str(edit_text);
24294 let end_offset = text.len();
24295
24296 if start_offset < end_offset {
24297 highlights.push((start_offset..end_offset, insertion_highlight_style));
24298 }
24299 }
24300
24301 HighlightedText {
24302 text: text.into(),
24303 highlights,
24304 }
24305}
24306
24307pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24308 match severity {
24309 lsp::DiagnosticSeverity::ERROR => colors.error,
24310 lsp::DiagnosticSeverity::WARNING => colors.warning,
24311 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24312 lsp::DiagnosticSeverity::HINT => colors.info,
24313 _ => colors.ignored,
24314 }
24315}
24316
24317pub fn styled_runs_for_code_label<'a>(
24318 label: &'a CodeLabel,
24319 syntax_theme: &'a theme::SyntaxTheme,
24320) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24321 let fade_out = HighlightStyle {
24322 fade_out: Some(0.35),
24323 ..Default::default()
24324 };
24325
24326 let mut prev_end = label.filter_range.end;
24327 label
24328 .runs
24329 .iter()
24330 .enumerate()
24331 .flat_map(move |(ix, (range, highlight_id))| {
24332 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24333 style
24334 } else {
24335 return Default::default();
24336 };
24337 let muted_style = style.highlight(fade_out);
24338
24339 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24340 if range.start >= label.filter_range.end {
24341 if range.start > prev_end {
24342 runs.push((prev_end..range.start, fade_out));
24343 }
24344 runs.push((range.clone(), muted_style));
24345 } else if range.end <= label.filter_range.end {
24346 runs.push((range.clone(), style));
24347 } else {
24348 runs.push((range.start..label.filter_range.end, style));
24349 runs.push((label.filter_range.end..range.end, muted_style));
24350 }
24351 prev_end = cmp::max(prev_end, range.end);
24352
24353 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24354 runs.push((prev_end..label.text.len(), fade_out));
24355 }
24356
24357 runs
24358 })
24359}
24360
24361pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24362 let mut prev_index = 0;
24363 let mut prev_codepoint: Option<char> = None;
24364 text.char_indices()
24365 .chain([(text.len(), '\0')])
24366 .filter_map(move |(index, codepoint)| {
24367 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24368 let is_boundary = index == text.len()
24369 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24370 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24371 if is_boundary {
24372 let chunk = &text[prev_index..index];
24373 prev_index = index;
24374 Some(chunk)
24375 } else {
24376 None
24377 }
24378 })
24379}
24380
24381pub trait RangeToAnchorExt: Sized {
24382 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24383
24384 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24385 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24386 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24387 }
24388}
24389
24390impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24391 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24392 let start_offset = self.start.to_offset(snapshot);
24393 let end_offset = self.end.to_offset(snapshot);
24394 if start_offset == end_offset {
24395 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24396 } else {
24397 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24398 }
24399 }
24400}
24401
24402pub trait RowExt {
24403 fn as_f64(&self) -> f64;
24404
24405 fn next_row(&self) -> Self;
24406
24407 fn previous_row(&self) -> Self;
24408
24409 fn minus(&self, other: Self) -> u32;
24410}
24411
24412impl RowExt for DisplayRow {
24413 fn as_f64(&self) -> f64 {
24414 self.0 as _
24415 }
24416
24417 fn next_row(&self) -> Self {
24418 Self(self.0 + 1)
24419 }
24420
24421 fn previous_row(&self) -> Self {
24422 Self(self.0.saturating_sub(1))
24423 }
24424
24425 fn minus(&self, other: Self) -> u32 {
24426 self.0 - other.0
24427 }
24428}
24429
24430impl RowExt for MultiBufferRow {
24431 fn as_f64(&self) -> f64 {
24432 self.0 as _
24433 }
24434
24435 fn next_row(&self) -> Self {
24436 Self(self.0 + 1)
24437 }
24438
24439 fn previous_row(&self) -> Self {
24440 Self(self.0.saturating_sub(1))
24441 }
24442
24443 fn minus(&self, other: Self) -> u32 {
24444 self.0 - other.0
24445 }
24446}
24447
24448trait RowRangeExt {
24449 type Row;
24450
24451 fn len(&self) -> usize;
24452
24453 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24454}
24455
24456impl RowRangeExt for Range<MultiBufferRow> {
24457 type Row = MultiBufferRow;
24458
24459 fn len(&self) -> usize {
24460 (self.end.0 - self.start.0) as usize
24461 }
24462
24463 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24464 (self.start.0..self.end.0).map(MultiBufferRow)
24465 }
24466}
24467
24468impl RowRangeExt for Range<DisplayRow> {
24469 type Row = DisplayRow;
24470
24471 fn len(&self) -> usize {
24472 (self.end.0 - self.start.0) as usize
24473 }
24474
24475 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24476 (self.start.0..self.end.0).map(DisplayRow)
24477 }
24478}
24479
24480/// If select range has more than one line, we
24481/// just point the cursor to range.start.
24482fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24483 if range.start.row == range.end.row {
24484 range
24485 } else {
24486 range.start..range.start
24487 }
24488}
24489pub struct KillRing(ClipboardItem);
24490impl Global for KillRing {}
24491
24492const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24493
24494enum BreakpointPromptEditAction {
24495 Log,
24496 Condition,
24497 HitCondition,
24498}
24499
24500struct BreakpointPromptEditor {
24501 pub(crate) prompt: Entity<Editor>,
24502 editor: WeakEntity<Editor>,
24503 breakpoint_anchor: Anchor,
24504 breakpoint: Breakpoint,
24505 edit_action: BreakpointPromptEditAction,
24506 block_ids: HashSet<CustomBlockId>,
24507 editor_margins: Arc<Mutex<EditorMargins>>,
24508 _subscriptions: Vec<Subscription>,
24509}
24510
24511impl BreakpointPromptEditor {
24512 const MAX_LINES: u8 = 4;
24513
24514 fn new(
24515 editor: WeakEntity<Editor>,
24516 breakpoint_anchor: Anchor,
24517 breakpoint: Breakpoint,
24518 edit_action: BreakpointPromptEditAction,
24519 window: &mut Window,
24520 cx: &mut Context<Self>,
24521 ) -> Self {
24522 let base_text = match edit_action {
24523 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24524 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24525 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24526 }
24527 .map(|msg| msg.to_string())
24528 .unwrap_or_default();
24529
24530 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24531 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24532
24533 let prompt = cx.new(|cx| {
24534 let mut prompt = Editor::new(
24535 EditorMode::AutoHeight {
24536 min_lines: 1,
24537 max_lines: Some(Self::MAX_LINES as usize),
24538 },
24539 buffer,
24540 None,
24541 window,
24542 cx,
24543 );
24544 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24545 prompt.set_show_cursor_when_unfocused(false, cx);
24546 prompt.set_placeholder_text(
24547 match edit_action {
24548 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24549 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24550 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24551 },
24552 window,
24553 cx,
24554 );
24555
24556 prompt
24557 });
24558
24559 Self {
24560 prompt,
24561 editor,
24562 breakpoint_anchor,
24563 breakpoint,
24564 edit_action,
24565 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24566 block_ids: Default::default(),
24567 _subscriptions: vec![],
24568 }
24569 }
24570
24571 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24572 self.block_ids.extend(block_ids)
24573 }
24574
24575 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24576 if let Some(editor) = self.editor.upgrade() {
24577 let message = self
24578 .prompt
24579 .read(cx)
24580 .buffer
24581 .read(cx)
24582 .as_singleton()
24583 .expect("A multi buffer in breakpoint prompt isn't possible")
24584 .read(cx)
24585 .as_rope()
24586 .to_string();
24587
24588 editor.update(cx, |editor, cx| {
24589 editor.edit_breakpoint_at_anchor(
24590 self.breakpoint_anchor,
24591 self.breakpoint.clone(),
24592 match self.edit_action {
24593 BreakpointPromptEditAction::Log => {
24594 BreakpointEditAction::EditLogMessage(message.into())
24595 }
24596 BreakpointPromptEditAction::Condition => {
24597 BreakpointEditAction::EditCondition(message.into())
24598 }
24599 BreakpointPromptEditAction::HitCondition => {
24600 BreakpointEditAction::EditHitCondition(message.into())
24601 }
24602 },
24603 cx,
24604 );
24605
24606 editor.remove_blocks(self.block_ids.clone(), None, cx);
24607 cx.focus_self(window);
24608 });
24609 }
24610 }
24611
24612 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24613 self.editor
24614 .update(cx, |editor, cx| {
24615 editor.remove_blocks(self.block_ids.clone(), None, cx);
24616 window.focus(&editor.focus_handle);
24617 })
24618 .log_err();
24619 }
24620
24621 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24622 let settings = ThemeSettings::get_global(cx);
24623 let text_style = TextStyle {
24624 color: if self.prompt.read(cx).read_only(cx) {
24625 cx.theme().colors().text_disabled
24626 } else {
24627 cx.theme().colors().text
24628 },
24629 font_family: settings.buffer_font.family.clone(),
24630 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24631 font_size: settings.buffer_font_size(cx).into(),
24632 font_weight: settings.buffer_font.weight,
24633 line_height: relative(settings.buffer_line_height.value()),
24634 ..Default::default()
24635 };
24636 EditorElement::new(
24637 &self.prompt,
24638 EditorStyle {
24639 background: cx.theme().colors().editor_background,
24640 local_player: cx.theme().players().local(),
24641 text: text_style,
24642 ..Default::default()
24643 },
24644 )
24645 }
24646}
24647
24648impl Render for BreakpointPromptEditor {
24649 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24650 let editor_margins = *self.editor_margins.lock();
24651 let gutter_dimensions = editor_margins.gutter;
24652 h_flex()
24653 .key_context("Editor")
24654 .bg(cx.theme().colors().editor_background)
24655 .border_y_1()
24656 .border_color(cx.theme().status().info_border)
24657 .size_full()
24658 .py(window.line_height() / 2.5)
24659 .on_action(cx.listener(Self::confirm))
24660 .on_action(cx.listener(Self::cancel))
24661 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24662 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24663 }
24664}
24665
24666impl Focusable for BreakpointPromptEditor {
24667 fn focus_handle(&self, cx: &App) -> FocusHandle {
24668 self.prompt.focus_handle(cx)
24669 }
24670}
24671
24672fn all_edits_insertions_or_deletions(
24673 edits: &Vec<(Range<Anchor>, String)>,
24674 snapshot: &MultiBufferSnapshot,
24675) -> bool {
24676 let mut all_insertions = true;
24677 let mut all_deletions = true;
24678
24679 for (range, new_text) in edits.iter() {
24680 let range_is_empty = range.to_offset(snapshot).is_empty();
24681 let text_is_empty = new_text.is_empty();
24682
24683 if range_is_empty != text_is_empty {
24684 if range_is_empty {
24685 all_deletions = false;
24686 } else {
24687 all_insertions = false;
24688 }
24689 } else {
24690 return false;
24691 }
24692
24693 if !all_insertions && !all_deletions {
24694 return false;
24695 }
24696 }
24697 all_insertions || all_deletions
24698}
24699
24700struct MissingEditPredictionKeybindingTooltip;
24701
24702impl Render for MissingEditPredictionKeybindingTooltip {
24703 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24704 ui::tooltip_container(cx, |container, cx| {
24705 container
24706 .flex_shrink_0()
24707 .max_w_80()
24708 .min_h(rems_from_px(124.))
24709 .justify_between()
24710 .child(
24711 v_flex()
24712 .flex_1()
24713 .text_ui_sm(cx)
24714 .child(Label::new("Conflict with Accept Keybinding"))
24715 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24716 )
24717 .child(
24718 h_flex()
24719 .pb_1()
24720 .gap_1()
24721 .items_end()
24722 .w_full()
24723 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24724 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24725 }))
24726 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24727 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24728 })),
24729 )
24730 })
24731 }
24732}
24733
24734#[derive(Debug, Clone, Copy, PartialEq)]
24735pub struct LineHighlight {
24736 pub background: Background,
24737 pub border: Option<gpui::Hsla>,
24738 pub include_gutter: bool,
24739 pub type_id: Option<TypeId>,
24740}
24741
24742struct LineManipulationResult {
24743 pub new_text: String,
24744 pub line_count_before: usize,
24745 pub line_count_after: usize,
24746}
24747
24748fn render_diff_hunk_controls(
24749 row: u32,
24750 status: &DiffHunkStatus,
24751 hunk_range: Range<Anchor>,
24752 is_created_file: bool,
24753 line_height: Pixels,
24754 editor: &Entity<Editor>,
24755 _window: &mut Window,
24756 cx: &mut App,
24757) -> AnyElement {
24758 h_flex()
24759 .h(line_height)
24760 .mr_1()
24761 .gap_1()
24762 .px_0p5()
24763 .pb_1()
24764 .border_x_1()
24765 .border_b_1()
24766 .border_color(cx.theme().colors().border_variant)
24767 .rounded_b_lg()
24768 .bg(cx.theme().colors().editor_background)
24769 .gap_1()
24770 .block_mouse_except_scroll()
24771 .shadow_md()
24772 .child(if status.has_secondary_hunk() {
24773 Button::new(("stage", row as u64), "Stage")
24774 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24775 .tooltip({
24776 let focus_handle = editor.focus_handle(cx);
24777 move |_window, cx| {
24778 Tooltip::for_action_in(
24779 "Stage Hunk",
24780 &::git::ToggleStaged,
24781 &focus_handle,
24782 cx,
24783 )
24784 }
24785 })
24786 .on_click({
24787 let editor = editor.clone();
24788 move |_event, _window, cx| {
24789 editor.update(cx, |editor, cx| {
24790 editor.stage_or_unstage_diff_hunks(
24791 true,
24792 vec![hunk_range.start..hunk_range.start],
24793 cx,
24794 );
24795 });
24796 }
24797 })
24798 } else {
24799 Button::new(("unstage", row as u64), "Unstage")
24800 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24801 .tooltip({
24802 let focus_handle = editor.focus_handle(cx);
24803 move |_window, cx| {
24804 Tooltip::for_action_in(
24805 "Unstage Hunk",
24806 &::git::ToggleStaged,
24807 &focus_handle,
24808 cx,
24809 )
24810 }
24811 })
24812 .on_click({
24813 let editor = editor.clone();
24814 move |_event, _window, cx| {
24815 editor.update(cx, |editor, cx| {
24816 editor.stage_or_unstage_diff_hunks(
24817 false,
24818 vec![hunk_range.start..hunk_range.start],
24819 cx,
24820 );
24821 });
24822 }
24823 })
24824 })
24825 .child(
24826 Button::new(("restore", row as u64), "Restore")
24827 .tooltip({
24828 let focus_handle = editor.focus_handle(cx);
24829 move |_window, cx| {
24830 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24831 }
24832 })
24833 .on_click({
24834 let editor = editor.clone();
24835 move |_event, window, cx| {
24836 editor.update(cx, |editor, cx| {
24837 let snapshot = editor.snapshot(window, cx);
24838 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24839 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24840 });
24841 }
24842 })
24843 .disabled(is_created_file),
24844 )
24845 .when(
24846 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24847 |el| {
24848 el.child(
24849 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24850 .shape(IconButtonShape::Square)
24851 .icon_size(IconSize::Small)
24852 // .disabled(!has_multiple_hunks)
24853 .tooltip({
24854 let focus_handle = editor.focus_handle(cx);
24855 move |_window, cx| {
24856 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24857 }
24858 })
24859 .on_click({
24860 let editor = editor.clone();
24861 move |_event, window, cx| {
24862 editor.update(cx, |editor, cx| {
24863 let snapshot = editor.snapshot(window, cx);
24864 let position =
24865 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24866 editor.go_to_hunk_before_or_after_position(
24867 &snapshot,
24868 position,
24869 Direction::Next,
24870 window,
24871 cx,
24872 );
24873 editor.expand_selected_diff_hunks(cx);
24874 });
24875 }
24876 }),
24877 )
24878 .child(
24879 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24880 .shape(IconButtonShape::Square)
24881 .icon_size(IconSize::Small)
24882 // .disabled(!has_multiple_hunks)
24883 .tooltip({
24884 let focus_handle = editor.focus_handle(cx);
24885 move |_window, cx| {
24886 Tooltip::for_action_in(
24887 "Previous Hunk",
24888 &GoToPreviousHunk,
24889 &focus_handle,
24890 cx,
24891 )
24892 }
24893 })
24894 .on_click({
24895 let editor = editor.clone();
24896 move |_event, window, cx| {
24897 editor.update(cx, |editor, cx| {
24898 let snapshot = editor.snapshot(window, cx);
24899 let point =
24900 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24901 editor.go_to_hunk_before_or_after_position(
24902 &snapshot,
24903 point,
24904 Direction::Prev,
24905 window,
24906 cx,
24907 );
24908 editor.expand_selected_diff_hunks(cx);
24909 });
24910 }
24911 }),
24912 )
24913 },
24914 )
24915 .into_any_element()
24916}
24917
24918pub fn multibuffer_context_lines(cx: &App) -> u32 {
24919 EditorSettings::try_get(cx)
24920 .map(|settings| settings.excerpt_context_lines)
24921 .unwrap_or(2)
24922 .min(32)
24923}