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::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init_settings(cx: &mut App) {
314 EditorSettings::register(cx);
315}
316
317pub fn init(cx: &mut App) {
318 init_settings(cx);
319
320 cx.set_global(GlobalBlameRenderer(Arc::new(())));
321
322 workspace::register_project_item::<Editor>(cx);
323 workspace::FollowableViewRegistry::register::<Editor>(cx);
324 workspace::register_serializable_item::<Editor>(cx);
325
326 cx.observe_new(
327 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
328 workspace.register_action(Editor::new_file);
329 workspace.register_action(Editor::new_file_split);
330 workspace.register_action(Editor::new_file_vertical);
331 workspace.register_action(Editor::new_file_horizontal);
332 workspace.register_action(Editor::cancel_language_server_work);
333 workspace.register_action(Editor::toggle_focus);
334 },
335 )
336 .detach();
337
338 cx.on_action(move |_: &workspace::NewFile, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352 cx.on_action(move |_: &workspace::NewWindow, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 cx.activate(true);
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367}
368
369pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
370 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
371}
372
373pub trait DiagnosticRenderer {
374 fn render_group(
375 &self,
376 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
377 buffer_id: BufferId,
378 snapshot: EditorSnapshot,
379 editor: WeakEntity<Editor>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 cx: &mut App,
389 ) -> Option<Entity<markdown::Markdown>>;
390
391 fn open_link(
392 &self,
393 editor: &mut Editor,
394 link: SharedString,
395 window: &mut Window,
396 cx: &mut Context<Editor>,
397 );
398}
399
400pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
401
402impl GlobalDiagnosticRenderer {
403 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
404 cx.try_global::<Self>().map(|g| g.0.clone())
405 }
406}
407
408impl gpui::Global for GlobalDiagnosticRenderer {}
409pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
410 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
411}
412
413pub struct SearchWithinRange;
414
415trait InvalidationRegion {
416 fn ranges(&self) -> &[Range<Anchor>];
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub enum SelectPhase {
421 Begin {
422 position: DisplayPoint,
423 add: bool,
424 click_count: usize,
425 },
426 BeginColumnar {
427 position: DisplayPoint,
428 reset: bool,
429 mode: ColumnarMode,
430 goal_column: u32,
431 },
432 Extend {
433 position: DisplayPoint,
434 click_count: usize,
435 },
436 Update {
437 position: DisplayPoint,
438 goal_column: u32,
439 scroll_delta: gpui::Point<f32>,
440 },
441 End,
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum ColumnarMode {
446 FromMouse,
447 FromSelection,
448}
449
450#[derive(Clone, Debug)]
451pub enum SelectMode {
452 Character,
453 Word(Range<Anchor>),
454 Line(Range<Anchor>),
455 All,
456}
457
458#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
459pub enum SizingBehavior {
460 /// The editor will layout itself using `size_full` and will include the vertical
461 /// scroll margin as requested by user settings.
462 #[default]
463 Default,
464 /// The editor will layout itself using `size_full`, but will not have any
465 /// vertical overscroll.
466 ExcludeOverscrollMargin,
467 /// The editor will request a vertical size according to its content and will be
468 /// layouted without a vertical scroll margin.
469 SizeByContent,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine,
475 AutoHeight {
476 min_lines: usize,
477 max_lines: Option<usize>,
478 },
479 Full {
480 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
481 scale_ui_elements_with_buffer_font_size: bool,
482 /// When set to `true`, the editor will render a background for the active line.
483 show_active_line_background: bool,
484 /// Determines the sizing behavior for this editor
485 sizing_behavior: SizingBehavior,
486 },
487 Minimap {
488 parent: WeakEntity<Editor>,
489 },
490}
491
492impl EditorMode {
493 pub fn full() -> Self {
494 Self::Full {
495 scale_ui_elements_with_buffer_font_size: true,
496 show_active_line_background: true,
497 sizing_behavior: SizingBehavior::Default,
498 }
499 }
500
501 #[inline]
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 #[inline]
507 pub fn is_single_line(&self) -> bool {
508 matches!(self, Self::SingleLine { .. })
509 }
510
511 #[inline]
512 fn is_minimap(&self) -> bool {
513 matches!(self, Self::Minimap { .. })
514 }
515}
516
517#[derive(Copy, Clone, Debug)]
518pub enum SoftWrap {
519 /// Prefer not to wrap at all.
520 ///
521 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
522 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
523 GitDiff,
524 /// Prefer a single line generally, unless an overly long line is encountered.
525 None,
526 /// Soft wrap lines that exceed the editor width.
527 EditorWidth,
528 /// Soft wrap lines at the preferred line length.
529 Column(u32),
530 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
531 Bounded(u32),
532}
533
534#[derive(Clone)]
535pub struct EditorStyle {
536 pub background: Hsla,
537 pub border: Hsla,
538 pub local_player: PlayerColor,
539 pub text: TextStyle,
540 pub scrollbar_width: Pixels,
541 pub syntax: Arc<SyntaxTheme>,
542 pub status: StatusColors,
543 pub inlay_hints_style: HighlightStyle,
544 pub edit_prediction_styles: EditPredictionStyles,
545 pub unnecessary_code_fade: f32,
546 pub show_underlines: bool,
547}
548
549impl Default for EditorStyle {
550 fn default() -> Self {
551 Self {
552 background: Hsla::default(),
553 border: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 edit_prediction_styles: EditPredictionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 let mut style = cx.theme().syntax().get("hint");
579
580 if style.color.is_none() {
581 style.color = Some(cx.theme().status().hint);
582 }
583
584 if !show_background {
585 style.background_color = None;
586 return style;
587 }
588
589 if style.background_color.is_none() {
590 style.background_color = Some(cx.theme().status().hint_background);
591 }
592
593 style
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
597 EditPredictionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum EditPrediction {
618 Edit {
619 edits: Vec<(Range<Anchor>, String)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 /// Move to a specific location in the active editor
625 MoveWithin {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in a different editor (not the active one)
630 MoveOutside {
631 target: language::Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Option<Range<Anchor>>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Clone, Debug)]
816struct RunnableTasks {
817 templates: Vec<(TaskSourceKind, TaskTemplate)>,
818 offset: multi_buffer::Anchor,
819 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
820 column: u32,
821 // Values of all named captures, including those starting with '_'
822 extra_variables: HashMap<String, String>,
823 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
824 context_range: Range<BufferOffset>,
825}
826
827impl RunnableTasks {
828 fn resolve<'a>(
829 &'a self,
830 cx: &'a task::TaskContext,
831 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
832 self.templates.iter().filter_map(|(kind, template)| {
833 template
834 .resolve_task(&kind.to_id_base(), cx)
835 .map(|task| (kind.clone(), task))
836 })
837 }
838}
839
840#[derive(Clone)]
841pub struct ResolvedTasks {
842 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
843 position: Anchor,
844}
845
846#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
847struct BufferOffset(usize);
848
849/// Addons allow storing per-editor state in other crates (e.g. Vim)
850pub trait Addon: 'static {
851 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
852
853 fn render_buffer_header_controls(
854 &self,
855 _: &ExcerptInfo,
856 _: &Window,
857 _: &App,
858 ) -> Option<AnyElement> {
859 None
860 }
861
862 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 placeholder_display_map: Option<Entity<DisplayMap>>,
1012 pub selections: SelectionsCollection,
1013 pub scroll_manager: ScrollManager,
1014 /// When inline assist editors are linked, they all render cursors because
1015 /// typing enters text into each of them, even the ones that aren't focused.
1016 pub(crate) show_cursor_when_unfocused: bool,
1017 columnar_selection_state: Option<ColumnarSelectionState>,
1018 add_selections_state: Option<AddSelectionsState>,
1019 select_next_state: Option<SelectNextState>,
1020 select_prev_state: Option<SelectNextState>,
1021 selection_history: SelectionHistory,
1022 defer_selection_effects: bool,
1023 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1024 autoclose_regions: Vec<AutocloseRegion>,
1025 snippet_stack: InvalidationStack<SnippetState>,
1026 select_syntax_node_history: SelectSyntaxNodeHistory,
1027 ime_transaction: Option<TransactionId>,
1028 pub diagnostics_max_severity: DiagnosticSeverity,
1029 active_diagnostics: ActiveDiagnostic,
1030 show_inline_diagnostics: bool,
1031 inline_diagnostics_update: Task<()>,
1032 inline_diagnostics_enabled: bool,
1033 diagnostics_enabled: bool,
1034 word_completions_enabled: bool,
1035 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1036 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1037 hard_wrap: Option<usize>,
1038 project: Option<Entity<Project>>,
1039 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1040 completion_provider: Option<Rc<dyn CompletionProvider>>,
1041 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1042 blink_manager: Entity<BlinkManager>,
1043 show_cursor_names: bool,
1044 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1045 pub show_local_selections: bool,
1046 mode: EditorMode,
1047 show_breadcrumbs: bool,
1048 show_gutter: bool,
1049 show_scrollbars: ScrollbarAxes,
1050 minimap_visibility: MinimapVisibility,
1051 offset_content: bool,
1052 disable_expand_excerpt_buttons: bool,
1053 show_line_numbers: Option<bool>,
1054 use_relative_line_numbers: Option<bool>,
1055 show_git_diff_gutter: Option<bool>,
1056 show_code_actions: Option<bool>,
1057 show_runnables: Option<bool>,
1058 show_breakpoints: Option<bool>,
1059 show_wrap_guides: Option<bool>,
1060 show_indent_guides: Option<bool>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 next_inlay_id: usize,
1113 next_color_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 colors: Option<LspColorData>,
1178 post_scroll_update: Task<()>,
1179 refresh_colors_task: Task<()>,
1180 inlay_hints: Option<LspInlayHintData>,
1181 folding_newlines: Task<()>,
1182 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1183}
1184
1185fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1186 if debounce_ms > 0 {
1187 Some(Duration::from_millis(debounce_ms))
1188 } else {
1189 None
1190 }
1191}
1192
1193#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1194enum NextScrollCursorCenterTopBottom {
1195 #[default]
1196 Center,
1197 Top,
1198 Bottom,
1199}
1200
1201impl NextScrollCursorCenterTopBottom {
1202 fn next(&self) -> Self {
1203 match self {
1204 Self::Center => Self::Top,
1205 Self::Top => Self::Bottom,
1206 Self::Bottom => Self::Center,
1207 }
1208 }
1209}
1210
1211#[derive(Clone)]
1212pub struct EditorSnapshot {
1213 pub mode: EditorMode,
1214 show_gutter: bool,
1215 show_line_numbers: Option<bool>,
1216 show_git_diff_gutter: Option<bool>,
1217 show_code_actions: Option<bool>,
1218 show_runnables: Option<bool>,
1219 show_breakpoints: Option<bool>,
1220 git_blame_gutter_max_author_length: Option<usize>,
1221 pub display_snapshot: DisplaySnapshot,
1222 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1223 is_focused: bool,
1224 scroll_anchor: ScrollAnchor,
1225 ongoing_scroll: OngoingScroll,
1226 current_line_highlight: CurrentLineHighlight,
1227 gutter_hovered: bool,
1228}
1229
1230#[derive(Default, Debug, Clone, Copy)]
1231pub struct GutterDimensions {
1232 pub left_padding: Pixels,
1233 pub right_padding: Pixels,
1234 pub width: Pixels,
1235 pub margin: Pixels,
1236 pub git_blame_entries_width: Option<Pixels>,
1237}
1238
1239impl GutterDimensions {
1240 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1241 Self {
1242 margin: Self::default_gutter_margin(font_id, font_size, cx),
1243 ..Default::default()
1244 }
1245 }
1246
1247 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1248 -cx.text_system().descent(font_id, font_size)
1249 }
1250 /// The full width of the space taken up by the gutter.
1251 pub fn full_width(&self) -> Pixels {
1252 self.margin + self.width
1253 }
1254
1255 /// The width of the space reserved for the fold indicators,
1256 /// use alongside 'justify_end' and `gutter_width` to
1257 /// right align content with the line numbers
1258 pub fn fold_area_width(&self) -> Pixels {
1259 self.margin + self.right_padding
1260 }
1261}
1262
1263struct CharacterDimensions {
1264 em_width: Pixels,
1265 em_advance: Pixels,
1266 line_height: Pixels,
1267}
1268
1269#[derive(Debug)]
1270pub struct RemoteSelection {
1271 pub replica_id: ReplicaId,
1272 pub selection: Selection<Anchor>,
1273 pub cursor_shape: CursorShape,
1274 pub collaborator_id: CollaboratorId,
1275 pub line_mode: bool,
1276 pub user_name: Option<SharedString>,
1277 pub color: PlayerColor,
1278}
1279
1280#[derive(Clone, Debug)]
1281struct SelectionHistoryEntry {
1282 selections: Arc<[Selection<Anchor>]>,
1283 select_next_state: Option<SelectNextState>,
1284 select_prev_state: Option<SelectNextState>,
1285 add_selections_state: Option<AddSelectionsState>,
1286}
1287
1288#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1289enum SelectionHistoryMode {
1290 Normal,
1291 Undoing,
1292 Redoing,
1293 Skipping,
1294}
1295
1296#[derive(Clone, PartialEq, Eq, Hash)]
1297struct HoveredCursor {
1298 replica_id: ReplicaId,
1299 selection_id: usize,
1300}
1301
1302impl Default for SelectionHistoryMode {
1303 fn default() -> Self {
1304 Self::Normal
1305 }
1306}
1307
1308#[derive(Debug)]
1309/// SelectionEffects controls the side-effects of updating the selection.
1310///
1311/// The default behaviour does "what you mostly want":
1312/// - it pushes to the nav history if the cursor moved by >10 lines
1313/// - it re-triggers completion requests
1314/// - it scrolls to fit
1315///
1316/// You might want to modify these behaviours. For example when doing a "jump"
1317/// like go to definition, we always want to add to nav history; but when scrolling
1318/// in vim mode we never do.
1319///
1320/// Similarly, you might want to disable scrolling if you don't want the viewport to
1321/// move.
1322#[derive(Clone)]
1323pub struct SelectionEffects {
1324 nav_history: Option<bool>,
1325 completions: bool,
1326 scroll: Option<Autoscroll>,
1327}
1328
1329impl Default for SelectionEffects {
1330 fn default() -> Self {
1331 Self {
1332 nav_history: None,
1333 completions: true,
1334 scroll: Some(Autoscroll::fit()),
1335 }
1336 }
1337}
1338impl SelectionEffects {
1339 pub fn scroll(scroll: Autoscroll) -> Self {
1340 Self {
1341 scroll: Some(scroll),
1342 ..Default::default()
1343 }
1344 }
1345
1346 pub fn no_scroll() -> Self {
1347 Self {
1348 scroll: None,
1349 ..Default::default()
1350 }
1351 }
1352
1353 pub fn completions(self, completions: bool) -> Self {
1354 Self {
1355 completions,
1356 ..self
1357 }
1358 }
1359
1360 pub fn nav_history(self, nav_history: bool) -> Self {
1361 Self {
1362 nav_history: Some(nav_history),
1363 ..self
1364 }
1365 }
1366}
1367
1368struct DeferredSelectionEffectsState {
1369 changed: bool,
1370 effects: SelectionEffects,
1371 old_cursor_position: Anchor,
1372 history_entry: SelectionHistoryEntry,
1373}
1374
1375#[derive(Default)]
1376struct SelectionHistory {
1377 #[allow(clippy::type_complexity)]
1378 selections_by_transaction:
1379 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1380 mode: SelectionHistoryMode,
1381 undo_stack: VecDeque<SelectionHistoryEntry>,
1382 redo_stack: VecDeque<SelectionHistoryEntry>,
1383}
1384
1385impl SelectionHistory {
1386 #[track_caller]
1387 fn insert_transaction(
1388 &mut self,
1389 transaction_id: TransactionId,
1390 selections: Arc<[Selection<Anchor>]>,
1391 ) {
1392 if selections.is_empty() {
1393 log::error!(
1394 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1395 std::panic::Location::caller()
1396 );
1397 return;
1398 }
1399 self.selections_by_transaction
1400 .insert(transaction_id, (selections, None));
1401 }
1402
1403 #[allow(clippy::type_complexity)]
1404 fn transaction(
1405 &self,
1406 transaction_id: TransactionId,
1407 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1408 self.selections_by_transaction.get(&transaction_id)
1409 }
1410
1411 #[allow(clippy::type_complexity)]
1412 fn transaction_mut(
1413 &mut self,
1414 transaction_id: TransactionId,
1415 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1416 self.selections_by_transaction.get_mut(&transaction_id)
1417 }
1418
1419 fn push(&mut self, entry: SelectionHistoryEntry) {
1420 if !entry.selections.is_empty() {
1421 match self.mode {
1422 SelectionHistoryMode::Normal => {
1423 self.push_undo(entry);
1424 self.redo_stack.clear();
1425 }
1426 SelectionHistoryMode::Undoing => self.push_redo(entry),
1427 SelectionHistoryMode::Redoing => self.push_undo(entry),
1428 SelectionHistoryMode::Skipping => {}
1429 }
1430 }
1431 }
1432
1433 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .undo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.undo_stack.push_back(entry);
1440 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.undo_stack.pop_front();
1442 }
1443 }
1444 }
1445
1446 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1447 if self
1448 .redo_stack
1449 .back()
1450 .is_none_or(|e| e.selections != entry.selections)
1451 {
1452 self.redo_stack.push_back(entry);
1453 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1454 self.redo_stack.pop_front();
1455 }
1456 }
1457 }
1458}
1459
1460#[derive(Clone, Copy)]
1461pub struct RowHighlightOptions {
1462 pub autoscroll: bool,
1463 pub include_gutter: bool,
1464}
1465
1466impl Default for RowHighlightOptions {
1467 fn default() -> Self {
1468 Self {
1469 autoscroll: Default::default(),
1470 include_gutter: true,
1471 }
1472 }
1473}
1474
1475struct RowHighlight {
1476 index: usize,
1477 range: Range<Anchor>,
1478 color: Hsla,
1479 options: RowHighlightOptions,
1480 type_id: TypeId,
1481}
1482
1483#[derive(Clone, Debug)]
1484struct AddSelectionsState {
1485 groups: Vec<AddSelectionsGroup>,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsGroup {
1490 above: bool,
1491 stack: Vec<usize>,
1492}
1493
1494#[derive(Clone)]
1495struct SelectNextState {
1496 query: AhoCorasick,
1497 wordwise: bool,
1498 done: bool,
1499}
1500
1501impl std::fmt::Debug for SelectNextState {
1502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1503 f.debug_struct(std::any::type_name::<Self>())
1504 .field("wordwise", &self.wordwise)
1505 .field("done", &self.done)
1506 .finish()
1507 }
1508}
1509
1510#[derive(Debug)]
1511struct AutocloseRegion {
1512 selection_id: usize,
1513 range: Range<Anchor>,
1514 pair: BracketPair,
1515}
1516
1517#[derive(Debug)]
1518struct SnippetState {
1519 ranges: Vec<Vec<Range<Anchor>>>,
1520 active_index: usize,
1521 choices: Vec<Option<Vec<String>>>,
1522}
1523
1524#[doc(hidden)]
1525pub struct RenameState {
1526 pub range: Range<Anchor>,
1527 pub old_name: Arc<str>,
1528 pub editor: Entity<Editor>,
1529 block_id: CustomBlockId,
1530}
1531
1532struct InvalidationStack<T>(Vec<T>);
1533
1534struct RegisteredEditPredictionProvider {
1535 provider: Arc<dyn EditPredictionProviderHandle>,
1536 _subscription: Subscription,
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540pub struct ActiveDiagnosticGroup {
1541 pub active_range: Range<Anchor>,
1542 pub active_message: String,
1543 pub group_id: usize,
1544 pub blocks: HashSet<CustomBlockId>,
1545}
1546
1547#[derive(Debug, PartialEq, Eq)]
1548
1549pub(crate) enum ActiveDiagnostic {
1550 None,
1551 All,
1552 Group(ActiveDiagnosticGroup),
1553}
1554
1555#[derive(Serialize, Deserialize, Clone, Debug)]
1556pub struct ClipboardSelection {
1557 /// The number of bytes in this selection.
1558 pub len: usize,
1559 /// Whether this was a full-line selection.
1560 pub is_entire_line: bool,
1561 /// The indentation of the first line when this content was originally copied.
1562 pub first_line_indent: u32,
1563}
1564
1565// selections, scroll behavior, was newest selection reversed
1566type SelectSyntaxNodeHistoryState = (
1567 Box<[Selection<usize>]>,
1568 SelectSyntaxNodeScrollBehavior,
1569 bool,
1570);
1571
1572#[derive(Default)]
1573struct SelectSyntaxNodeHistory {
1574 stack: Vec<SelectSyntaxNodeHistoryState>,
1575 // disable temporarily to allow changing selections without losing the stack
1576 pub disable_clearing: bool,
1577}
1578
1579impl SelectSyntaxNodeHistory {
1580 pub fn try_clear(&mut self) {
1581 if !self.disable_clearing {
1582 self.stack.clear();
1583 }
1584 }
1585
1586 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1587 self.stack.push(selection);
1588 }
1589
1590 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1591 self.stack.pop()
1592 }
1593}
1594
1595enum SelectSyntaxNodeScrollBehavior {
1596 CursorTop,
1597 FitSelection,
1598 CursorBottom,
1599}
1600
1601#[derive(Debug)]
1602pub(crate) struct NavigationData {
1603 cursor_anchor: Anchor,
1604 cursor_position: Point,
1605 scroll_anchor: ScrollAnchor,
1606 scroll_top_row: u32,
1607}
1608
1609#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1610pub enum GotoDefinitionKind {
1611 Symbol,
1612 Declaration,
1613 Type,
1614 Implementation,
1615}
1616
1617pub enum FormatTarget {
1618 Buffers(HashSet<Entity<Buffer>>),
1619 Ranges(Vec<Range<MultiBufferPoint>>),
1620}
1621
1622pub(crate) struct FocusedBlock {
1623 id: BlockId,
1624 focus_handle: WeakFocusHandle,
1625}
1626
1627#[derive(Clone)]
1628enum JumpData {
1629 MultiBufferRow {
1630 row: MultiBufferRow,
1631 line_offset_from_top: u32,
1632 },
1633 MultiBufferPoint {
1634 excerpt_id: ExcerptId,
1635 position: Point,
1636 anchor: text::Anchor,
1637 line_offset_from_top: u32,
1638 },
1639}
1640
1641pub enum MultibufferSelectionMode {
1642 First,
1643 All,
1644}
1645
1646#[derive(Clone, Copy, Debug, Default)]
1647pub struct RewrapOptions {
1648 pub override_language_settings: bool,
1649 pub preserve_existing_whitespace: bool,
1650}
1651
1652impl Editor {
1653 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1657 }
1658
1659 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1660 let buffer = cx.new(|cx| Buffer::local("", cx));
1661 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1662 Self::new(EditorMode::full(), buffer, None, window, cx)
1663 }
1664
1665 pub fn auto_height(
1666 min_lines: usize,
1667 max_lines: usize,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(
1674 EditorMode::AutoHeight {
1675 min_lines,
1676 max_lines: Some(max_lines),
1677 },
1678 buffer,
1679 None,
1680 window,
1681 cx,
1682 )
1683 }
1684
1685 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1686 /// The editor grows as tall as needed to fit its content.
1687 pub fn auto_height_unbounded(
1688 min_lines: usize,
1689 window: &mut Window,
1690 cx: &mut Context<Self>,
1691 ) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(
1695 EditorMode::AutoHeight {
1696 min_lines,
1697 max_lines: None,
1698 },
1699 buffer,
1700 None,
1701 window,
1702 cx,
1703 )
1704 }
1705
1706 pub fn for_buffer(
1707 buffer: Entity<Buffer>,
1708 project: Option<Entity<Project>>,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1713 Self::new(EditorMode::full(), buffer, project, window, cx)
1714 }
1715
1716 pub fn for_multibuffer(
1717 buffer: Entity<MultiBuffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 Self::new(EditorMode::full(), buffer, project, window, cx)
1723 }
1724
1725 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let mut clone = Self::new(
1727 self.mode.clone(),
1728 self.buffer.clone(),
1729 self.project.clone(),
1730 window,
1731 cx,
1732 );
1733 self.display_map.update(cx, |display_map, cx| {
1734 let snapshot = display_map.snapshot(cx);
1735 clone.display_map.update(cx, |display_map, cx| {
1736 display_map.set_state(&snapshot, cx);
1737 });
1738 });
1739 clone.folds_did_change(cx);
1740 clone.selections.clone_state(&self.selections);
1741 clone.scroll_manager.clone_state(&self.scroll_manager);
1742 clone.searchable = self.searchable;
1743 clone.read_only = self.read_only;
1744 clone
1745 }
1746
1747 pub fn new(
1748 mode: EditorMode,
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Editor::new_internal(mode, buffer, project, None, window, cx)
1755 }
1756
1757 fn new_internal(
1758 mode: EditorMode,
1759 multi_buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 display_map: Option<Entity<DisplayMap>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 debug_assert!(
1766 display_map.is_none() || mode.is_minimap(),
1767 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1768 );
1769
1770 let full_mode = mode.is_full();
1771 let is_minimap = mode.is_minimap();
1772 let diagnostics_max_severity = if full_mode {
1773 EditorSettings::get_global(cx)
1774 .diagnostics_max_severity
1775 .unwrap_or(DiagnosticSeverity::Hint)
1776 } else {
1777 DiagnosticSeverity::Off
1778 };
1779 let style = window.text_style();
1780 let font_size = style.font_size.to_pixels(window.rem_size());
1781 let editor = cx.entity().downgrade();
1782 let fold_placeholder = FoldPlaceholder {
1783 constrain_width: false,
1784 render: Arc::new(move |fold_id, fold_range, cx| {
1785 let editor = editor.clone();
1786 div()
1787 .id(fold_id)
1788 .bg(cx.theme().colors().ghost_element_background)
1789 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1790 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1791 .rounded_xs()
1792 .size_full()
1793 .cursor_pointer()
1794 .child("⋯")
1795 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1796 .on_click(move |_, _window, cx| {
1797 editor
1798 .update(cx, |editor, cx| {
1799 editor.unfold_ranges(
1800 &[fold_range.start..fold_range.end],
1801 true,
1802 false,
1803 cx,
1804 );
1805 cx.stop_propagation();
1806 })
1807 .ok();
1808 })
1809 .into_any()
1810 }),
1811 merge_adjacent: true,
1812 ..FoldPlaceholder::default()
1813 };
1814 let display_map = display_map.unwrap_or_else(|| {
1815 cx.new(|cx| {
1816 DisplayMap::new(
1817 multi_buffer.clone(),
1818 style.font(),
1819 font_size,
1820 None,
1821 FILE_HEADER_HEIGHT,
1822 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1823 fold_placeholder,
1824 diagnostics_max_severity,
1825 cx,
1826 )
1827 })
1828 });
1829
1830 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1831
1832 let blink_manager = cx.new(|cx| {
1833 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1834 if is_minimap {
1835 blink_manager.disable(cx);
1836 }
1837 blink_manager
1838 });
1839
1840 let soft_wrap_mode_override =
1841 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1842
1843 let mut project_subscriptions = Vec::new();
1844 if full_mode && let Some(project) = project.as_ref() {
1845 project_subscriptions.push(cx.subscribe_in(
1846 project,
1847 window,
1848 |editor, _, event, window, cx| match event {
1849 project::Event::RefreshCodeLens => {
1850 // we always query lens with actions, without storing them, always refreshing them
1851 }
1852 project::Event::RefreshInlayHints {
1853 server_id,
1854 request_id,
1855 } => {
1856 editor.refresh_inlay_hints(
1857 InlayHintRefreshReason::RefreshRequested {
1858 server_id: *server_id,
1859 request_id: *request_id,
1860 },
1861 cx,
1862 );
1863 }
1864 project::Event::LanguageServerRemoved(..) => {
1865 if editor.tasks_update_task.is_none() {
1866 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1867 }
1868 editor.registered_buffers.clear();
1869 editor.register_visible_buffers(cx);
1870 }
1871 project::Event::LanguageServerAdded(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 let buffer_id = *buffer_id;
1898 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1899 editor.register_buffer(buffer_id, cx);
1900 editor.update_lsp_data(Some(buffer_id), window, cx);
1901 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1902 refresh_linked_ranges(editor, window, cx);
1903 editor.refresh_code_actions(window, cx);
1904 editor.refresh_document_highlights(cx);
1905 }
1906 }
1907
1908 project::Event::EntryRenamed(transaction) => {
1909 let Some(workspace) = editor.workspace() else {
1910 return;
1911 };
1912 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1913 else {
1914 return;
1915 };
1916 if active_editor.entity_id() == cx.entity_id() {
1917 let edited_buffers_already_open = {
1918 let other_editors: Vec<Entity<Editor>> = workspace
1919 .read(cx)
1920 .panes()
1921 .iter()
1922 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1923 .filter(|editor| editor.entity_id() != cx.entity_id())
1924 .collect();
1925
1926 transaction.0.keys().all(|buffer| {
1927 other_editors.iter().any(|editor| {
1928 let multi_buffer = editor.read(cx).buffer();
1929 multi_buffer.read(cx).is_singleton()
1930 && multi_buffer.read(cx).as_singleton().map_or(
1931 false,
1932 |singleton| {
1933 singleton.entity_id() == buffer.entity_id()
1934 },
1935 )
1936 })
1937 })
1938 };
1939
1940 if !edited_buffers_already_open {
1941 let workspace = workspace.downgrade();
1942 let transaction = transaction.clone();
1943 cx.defer_in(window, move |_, window, cx| {
1944 cx.spawn_in(window, async move |editor, cx| {
1945 Self::open_project_transaction(
1946 &editor,
1947 workspace,
1948 transaction,
1949 "Rename".to_string(),
1950 cx,
1951 )
1952 .await
1953 .ok()
1954 })
1955 .detach();
1956 });
1957 }
1958 }
1959 }
1960
1961 _ => {}
1962 },
1963 ));
1964 if let Some(task_inventory) = project
1965 .read(cx)
1966 .task_store()
1967 .read(cx)
1968 .task_inventory()
1969 .cloned()
1970 {
1971 project_subscriptions.push(cx.observe_in(
1972 &task_inventory,
1973 window,
1974 |editor, _, window, cx| {
1975 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1976 },
1977 ));
1978 };
1979
1980 project_subscriptions.push(cx.subscribe_in(
1981 &project.read(cx).breakpoint_store(),
1982 window,
1983 |editor, _, event, window, cx| match event {
1984 BreakpointStoreEvent::ClearDebugLines => {
1985 editor.clear_row_highlights::<ActiveDebugLine>();
1986 editor.refresh_inline_values(cx);
1987 }
1988 BreakpointStoreEvent::SetDebugLine => {
1989 if editor.go_to_active_debug_line(window, cx) {
1990 cx.stop_propagation();
1991 }
1992
1993 editor.refresh_inline_values(cx);
1994 }
1995 _ => {}
1996 },
1997 ));
1998 let git_store = project.read(cx).git_store().clone();
1999 let project = project.clone();
2000 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2001 if let GitStoreEvent::RepositoryAdded = event {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 multi_buffer.read(cx).all_buffers(),
2054 multi_buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: multi_buffer.clone(),
2067 display_map: display_map.clone(),
2068 placeholder_display_map: None,
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: HashMap::default(),
2115 gutter_highlights: HashMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 word_completions_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 gutter_hovered: false,
2169 pixel_position_of_newest_cursor: None,
2170 last_bounds: None,
2171 last_position_map: None,
2172 expect_bounds_change: None,
2173 gutter_dimensions: GutterDimensions::default(),
2174 style: None,
2175 show_cursor_names: false,
2176 hovered_cursors: HashMap::default(),
2177 next_editor_action_id: EditorActionId::default(),
2178 editor_actions: Rc::default(),
2179 edit_predictions_hidden_for_vim_mode: false,
2180 show_edit_predictions_override: None,
2181 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2182 edit_prediction_settings: EditPredictionSettings::Disabled,
2183 edit_prediction_indent_conflict: false,
2184 edit_prediction_requires_modifier_in_indent_conflict: true,
2185 custom_context_menu: None,
2186 show_git_blame_gutter: false,
2187 show_git_blame_inline: false,
2188 show_selection_menu: None,
2189 show_git_blame_inline_delay_task: None,
2190 git_blame_inline_enabled: full_mode
2191 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2192 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2193 serialize_dirty_buffers: !is_minimap
2194 && ProjectSettings::get_global(cx)
2195 .session
2196 .restore_unsaved_buffers,
2197 blame: None,
2198 blame_subscription: None,
2199 tasks: BTreeMap::default(),
2200
2201 breakpoint_store,
2202 gutter_breakpoint_indicator: (None, None),
2203 hovered_diff_hunk_row: None,
2204 _subscriptions: (!is_minimap)
2205 .then(|| {
2206 vec![
2207 cx.observe(&multi_buffer, Self::on_buffer_changed),
2208 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2209 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2210 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2211 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2212 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2213 cx.observe_window_activation(window, |editor, window, cx| {
2214 let active = window.is_window_active();
2215 editor.blink_manager.update(cx, |blink_manager, cx| {
2216 if active {
2217 blink_manager.enable(cx);
2218 } else {
2219 blink_manager.disable(cx);
2220 }
2221 });
2222 if active {
2223 editor.show_mouse_cursor(cx);
2224 }
2225 }),
2226 ]
2227 })
2228 .unwrap_or_default(),
2229 tasks_update_task: None,
2230 pull_diagnostics_task: Task::ready(()),
2231 colors: None,
2232 refresh_colors_task: Task::ready(()),
2233 inlay_hints: None,
2234 next_color_inlay_id: 0,
2235 post_scroll_update: Task::ready(()),
2236 linked_edit_ranges: Default::default(),
2237 in_project_search: false,
2238 previous_search_ranges: None,
2239 breadcrumb_header: None,
2240 focused_block: None,
2241 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2242 addons: HashMap::default(),
2243 registered_buffers: HashMap::default(),
2244 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2245 selection_mark_mode: false,
2246 toggle_fold_multiple_buffers: Task::ready(()),
2247 serialize_selections: Task::ready(()),
2248 serialize_folds: Task::ready(()),
2249 text_style_refinement: None,
2250 load_diff_task: load_uncommitted_diff,
2251 temporary_diff_override: false,
2252 mouse_cursor_hidden: false,
2253 minimap: None,
2254 hide_mouse_mode: EditorSettings::get_global(cx)
2255 .hide_mouse
2256 .unwrap_or_default(),
2257 change_list: ChangeList::new(),
2258 mode,
2259 selection_drag_state: SelectionDragState::None,
2260 folding_newlines: Task::ready(()),
2261 lookup_key: None,
2262 };
2263
2264 if is_minimap {
2265 return editor;
2266 }
2267
2268 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2269 editor
2270 ._subscriptions
2271 .push(cx.observe(breakpoints, |_, _, cx| {
2272 cx.notify();
2273 }));
2274 }
2275 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2276 editor._subscriptions.extend(project_subscriptions);
2277
2278 editor._subscriptions.push(cx.subscribe_in(
2279 &cx.entity(),
2280 window,
2281 |editor, _, e: &EditorEvent, window, cx| match e {
2282 EditorEvent::ScrollPositionChanged { local, .. } => {
2283 if *local {
2284 let new_anchor = editor.scroll_manager.anchor();
2285 let snapshot = editor.snapshot(window, cx);
2286 editor.update_restoration_data(cx, move |data| {
2287 data.scroll_position = (
2288 new_anchor.top_row(snapshot.buffer_snapshot()),
2289 new_anchor.offset,
2290 );
2291 });
2292 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2293 editor.inline_blame_popover.take();
2294 }
2295 }
2296 EditorEvent::Edited { .. } => {
2297 if vim_flavor(cx).is_none() {
2298 let display_map = editor.display_snapshot(cx);
2299 let selections = editor.selections.all_adjusted_display(&display_map);
2300 let pop_state = editor
2301 .change_list
2302 .last()
2303 .map(|previous| {
2304 previous.len() == selections.len()
2305 && previous.iter().enumerate().all(|(ix, p)| {
2306 p.to_display_point(&display_map).row()
2307 == selections[ix].head().row()
2308 })
2309 })
2310 .unwrap_or(false);
2311 let new_positions = selections
2312 .into_iter()
2313 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2314 .collect();
2315 editor
2316 .change_list
2317 .push_to_change_list(pop_state, new_positions);
2318 }
2319 }
2320 _ => (),
2321 },
2322 ));
2323
2324 if let Some(dap_store) = editor
2325 .project
2326 .as_ref()
2327 .map(|project| project.read(cx).dap_store())
2328 {
2329 let weak_editor = cx.weak_entity();
2330
2331 editor
2332 ._subscriptions
2333 .push(
2334 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2335 let session_entity = cx.entity();
2336 weak_editor
2337 .update(cx, |editor, cx| {
2338 editor._subscriptions.push(
2339 cx.subscribe(&session_entity, Self::on_debug_session_event),
2340 );
2341 })
2342 .ok();
2343 }),
2344 );
2345
2346 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2347 editor
2348 ._subscriptions
2349 .push(cx.subscribe(&session, Self::on_debug_session_event));
2350 }
2351 }
2352
2353 // skip adding the initial selection to selection history
2354 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2355 editor.end_selection(window, cx);
2356 editor.selection_history.mode = SelectionHistoryMode::Normal;
2357
2358 editor.scroll_manager.show_scrollbars(window, cx);
2359 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2360
2361 if full_mode {
2362 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2363 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2364
2365 if editor.git_blame_inline_enabled {
2366 editor.start_git_blame_inline(false, window, cx);
2367 }
2368
2369 editor.go_to_active_debug_line(window, cx);
2370
2371 editor.minimap =
2372 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2373 editor.colors = Some(LspColorData::new(cx));
2374 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2375
2376 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2377 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2378 }
2379 editor.update_lsp_data(None, window, cx);
2380 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2381 }
2382
2383 editor
2384 }
2385
2386 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2387 self.selections.display_map(cx)
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending_anchor()
2416 .is_some_and(|pending_selection| {
2417 let snapshot = self.buffer().read(cx).snapshot(cx);
2418 pending_selection.range().includes(range, &snapshot)
2419 })
2420 {
2421 return true;
2422 }
2423
2424 self.selections
2425 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2426 .into_iter()
2427 .any(|selection| {
2428 // This is needed to cover a corner case, if we just check for an existing
2429 // selection in the fold range, having a cursor at the start of the fold
2430 // marks it as selected. Non-empty selections don't cause this.
2431 let length = selection.end - selection.start;
2432 length > 0
2433 })
2434 }
2435
2436 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2437 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2438 }
2439
2440 fn key_context_internal(
2441 &self,
2442 has_active_edit_prediction: bool,
2443 window: &mut Window,
2444 cx: &mut App,
2445 ) -> KeyContext {
2446 let mut key_context = KeyContext::new_with_defaults();
2447 key_context.add("Editor");
2448 let mode = match self.mode {
2449 EditorMode::SingleLine => "single_line",
2450 EditorMode::AutoHeight { .. } => "auto_height",
2451 EditorMode::Minimap { .. } => "minimap",
2452 EditorMode::Full { .. } => "full",
2453 };
2454
2455 if EditorSettings::jupyter_enabled(cx) {
2456 key_context.add("jupyter");
2457 }
2458
2459 key_context.set("mode", mode);
2460 if self.pending_rename.is_some() {
2461 key_context.add("renaming");
2462 }
2463
2464 match self.context_menu.borrow().as_ref() {
2465 Some(CodeContextMenu::Completions(menu)) => {
2466 if menu.visible() {
2467 key_context.add("menu");
2468 key_context.add("showing_completions");
2469 }
2470 }
2471 Some(CodeContextMenu::CodeActions(menu)) => {
2472 if menu.visible() {
2473 key_context.add("menu");
2474 key_context.add("showing_code_actions")
2475 }
2476 }
2477 None => {}
2478 }
2479
2480 if self.signature_help_state.has_multiple_signatures() {
2481 key_context.add("showing_signature_help");
2482 }
2483
2484 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2485 if !self.focus_handle(cx).contains_focused(window, cx)
2486 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2487 {
2488 for addon in self.addons.values() {
2489 addon.extend_key_context(&mut key_context, cx)
2490 }
2491 }
2492
2493 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2494 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2495 Some(
2496 file.full_path(cx)
2497 .extension()?
2498 .to_string_lossy()
2499 .into_owned(),
2500 )
2501 }) {
2502 key_context.set("extension", extension);
2503 }
2504 } else {
2505 key_context.add("multibuffer");
2506 }
2507
2508 if has_active_edit_prediction {
2509 if self.edit_prediction_in_conflict() {
2510 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2511 } else {
2512 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2513 key_context.add("copilot_suggestion");
2514 }
2515 }
2516
2517 if self.selection_mark_mode {
2518 key_context.add("selection_mode");
2519 }
2520
2521 let disjoint = self.selections.disjoint_anchors();
2522 let snapshot = self.snapshot(window, cx);
2523 let snapshot = snapshot.buffer_snapshot();
2524 if self.mode == EditorMode::SingleLine
2525 && let [selection] = disjoint
2526 && selection.start == selection.end
2527 && selection.end.to_offset(snapshot) == snapshot.len()
2528 {
2529 key_context.add("end_of_input");
2530 }
2531
2532 key_context
2533 }
2534
2535 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2536 self.last_bounds.as_ref()
2537 }
2538
2539 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2540 if self.mouse_cursor_hidden {
2541 self.mouse_cursor_hidden = false;
2542 cx.notify();
2543 }
2544 }
2545
2546 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2547 let hide_mouse_cursor = match origin {
2548 HideMouseCursorOrigin::TypingAction => {
2549 matches!(
2550 self.hide_mouse_mode,
2551 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2552 )
2553 }
2554 HideMouseCursorOrigin::MovementAction => {
2555 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2556 }
2557 };
2558 if self.mouse_cursor_hidden != hide_mouse_cursor {
2559 self.mouse_cursor_hidden = hide_mouse_cursor;
2560 cx.notify();
2561 }
2562 }
2563
2564 pub fn edit_prediction_in_conflict(&self) -> bool {
2565 if !self.show_edit_predictions_in_menu() {
2566 return false;
2567 }
2568
2569 let showing_completions = self
2570 .context_menu
2571 .borrow()
2572 .as_ref()
2573 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2574
2575 showing_completions
2576 || self.edit_prediction_requires_modifier()
2577 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2578 // bindings to insert tab characters.
2579 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2580 }
2581
2582 pub fn accept_edit_prediction_keybind(
2583 &self,
2584 accept_partial: bool,
2585 window: &mut Window,
2586 cx: &mut App,
2587 ) -> AcceptEditPredictionBinding {
2588 let key_context = self.key_context_internal(true, window, cx);
2589 let in_conflict = self.edit_prediction_in_conflict();
2590
2591 let bindings = if accept_partial {
2592 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2593 } else {
2594 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2595 };
2596
2597 // TODO: if the binding contains multiple keystrokes, display all of them, not
2598 // just the first one.
2599 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2600 !in_conflict
2601 || binding
2602 .keystrokes()
2603 .first()
2604 .is_some_and(|keystroke| keystroke.modifiers().modified())
2605 }))
2606 }
2607
2608 pub fn new_file(
2609 workspace: &mut Workspace,
2610 _: &workspace::NewFile,
2611 window: &mut Window,
2612 cx: &mut Context<Workspace>,
2613 ) {
2614 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2615 "Failed to create buffer",
2616 window,
2617 cx,
2618 |e, _, _| match e.error_code() {
2619 ErrorCode::RemoteUpgradeRequired => Some(format!(
2620 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2621 e.error_tag("required").unwrap_or("the latest version")
2622 )),
2623 _ => None,
2624 },
2625 );
2626 }
2627
2628 pub fn new_in_workspace(
2629 workspace: &mut Workspace,
2630 window: &mut Window,
2631 cx: &mut Context<Workspace>,
2632 ) -> Task<Result<Entity<Editor>>> {
2633 let project = workspace.project().clone();
2634 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2635
2636 cx.spawn_in(window, async move |workspace, cx| {
2637 let buffer = create.await?;
2638 workspace.update_in(cx, |workspace, window, cx| {
2639 let editor =
2640 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2641 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2642 editor
2643 })
2644 })
2645 }
2646
2647 fn new_file_vertical(
2648 workspace: &mut Workspace,
2649 _: &workspace::NewFileSplitVertical,
2650 window: &mut Window,
2651 cx: &mut Context<Workspace>,
2652 ) {
2653 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2654 }
2655
2656 fn new_file_horizontal(
2657 workspace: &mut Workspace,
2658 _: &workspace::NewFileSplitHorizontal,
2659 window: &mut Window,
2660 cx: &mut Context<Workspace>,
2661 ) {
2662 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2663 }
2664
2665 fn new_file_split(
2666 workspace: &mut Workspace,
2667 action: &workspace::NewFileSplit,
2668 window: &mut Window,
2669 cx: &mut Context<Workspace>,
2670 ) {
2671 Self::new_file_in_direction(workspace, action.0, window, cx)
2672 }
2673
2674 fn new_file_in_direction(
2675 workspace: &mut Workspace,
2676 direction: SplitDirection,
2677 window: &mut Window,
2678 cx: &mut Context<Workspace>,
2679 ) {
2680 let project = workspace.project().clone();
2681 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2682
2683 cx.spawn_in(window, async move |workspace, cx| {
2684 let buffer = create.await?;
2685 workspace.update_in(cx, move |workspace, window, cx| {
2686 workspace.split_item(
2687 direction,
2688 Box::new(
2689 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2690 ),
2691 window,
2692 cx,
2693 )
2694 })?;
2695 anyhow::Ok(())
2696 })
2697 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2698 match e.error_code() {
2699 ErrorCode::RemoteUpgradeRequired => Some(format!(
2700 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2701 e.error_tag("required").unwrap_or("the latest version")
2702 )),
2703 _ => None,
2704 }
2705 });
2706 }
2707
2708 pub fn leader_id(&self) -> Option<CollaboratorId> {
2709 self.leader_id
2710 }
2711
2712 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2713 &self.buffer
2714 }
2715
2716 pub fn project(&self) -> Option<&Entity<Project>> {
2717 self.project.as_ref()
2718 }
2719
2720 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2721 self.workspace.as_ref()?.0.upgrade()
2722 }
2723
2724 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2725 self.buffer().read(cx).title(cx)
2726 }
2727
2728 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2729 let git_blame_gutter_max_author_length = self
2730 .render_git_blame_gutter(cx)
2731 .then(|| {
2732 if let Some(blame) = self.blame.as_ref() {
2733 let max_author_length =
2734 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2735 Some(max_author_length)
2736 } else {
2737 None
2738 }
2739 })
2740 .flatten();
2741
2742 EditorSnapshot {
2743 mode: self.mode.clone(),
2744 show_gutter: self.show_gutter,
2745 show_line_numbers: self.show_line_numbers,
2746 show_git_diff_gutter: self.show_git_diff_gutter,
2747 show_code_actions: self.show_code_actions,
2748 show_runnables: self.show_runnables,
2749 show_breakpoints: self.show_breakpoints,
2750 git_blame_gutter_max_author_length,
2751 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2752 placeholder_display_snapshot: self
2753 .placeholder_display_map
2754 .as_ref()
2755 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2756 scroll_anchor: self.scroll_manager.anchor(),
2757 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2758 is_focused: self.focus_handle.is_focused(window),
2759 current_line_highlight: self
2760 .current_line_highlight
2761 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2762 gutter_hovered: self.gutter_hovered,
2763 }
2764 }
2765
2766 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2767 self.buffer.read(cx).language_at(point, cx)
2768 }
2769
2770 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2771 self.buffer.read(cx).read(cx).file_at(point).cloned()
2772 }
2773
2774 pub fn active_excerpt(
2775 &self,
2776 cx: &App,
2777 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2778 self.buffer
2779 .read(cx)
2780 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2781 }
2782
2783 pub fn mode(&self) -> &EditorMode {
2784 &self.mode
2785 }
2786
2787 pub fn set_mode(&mut self, mode: EditorMode) {
2788 self.mode = mode;
2789 }
2790
2791 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2792 self.collaboration_hub.as_deref()
2793 }
2794
2795 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2796 self.collaboration_hub = Some(hub);
2797 }
2798
2799 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2800 self.in_project_search = in_project_search;
2801 }
2802
2803 pub fn set_custom_context_menu(
2804 &mut self,
2805 f: impl 'static
2806 + Fn(
2807 &mut Self,
2808 DisplayPoint,
2809 &mut Window,
2810 &mut Context<Self>,
2811 ) -> Option<Entity<ui::ContextMenu>>,
2812 ) {
2813 self.custom_context_menu = Some(Box::new(f))
2814 }
2815
2816 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2817 self.completion_provider = provider;
2818 }
2819
2820 #[cfg(any(test, feature = "test-support"))]
2821 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2822 self.completion_provider.clone()
2823 }
2824
2825 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2826 self.semantics_provider.clone()
2827 }
2828
2829 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2830 self.semantics_provider = provider;
2831 }
2832
2833 pub fn set_edit_prediction_provider<T>(
2834 &mut self,
2835 provider: Option<Entity<T>>,
2836 window: &mut Window,
2837 cx: &mut Context<Self>,
2838 ) where
2839 T: EditPredictionProvider,
2840 {
2841 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2842 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2843 if this.focus_handle.is_focused(window) {
2844 this.update_visible_edit_prediction(window, cx);
2845 }
2846 }),
2847 provider: Arc::new(provider),
2848 });
2849 self.update_edit_prediction_settings(cx);
2850 self.refresh_edit_prediction(false, false, window, cx);
2851 }
2852
2853 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2854 self.placeholder_display_map
2855 .as_ref()
2856 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2857 }
2858
2859 pub fn set_placeholder_text(
2860 &mut self,
2861 placeholder_text: &str,
2862 window: &mut Window,
2863 cx: &mut Context<Self>,
2864 ) {
2865 let multibuffer = cx
2866 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2867
2868 let style = window.text_style();
2869
2870 self.placeholder_display_map = Some(cx.new(|cx| {
2871 DisplayMap::new(
2872 multibuffer,
2873 style.font(),
2874 style.font_size.to_pixels(window.rem_size()),
2875 None,
2876 FILE_HEADER_HEIGHT,
2877 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2878 Default::default(),
2879 DiagnosticSeverity::Off,
2880 cx,
2881 )
2882 }));
2883 cx.notify();
2884 }
2885
2886 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2887 self.cursor_shape = cursor_shape;
2888
2889 // Disrupt blink for immediate user feedback that the cursor shape has changed
2890 self.blink_manager.update(cx, BlinkManager::show_cursor);
2891
2892 cx.notify();
2893 }
2894
2895 pub fn set_current_line_highlight(
2896 &mut self,
2897 current_line_highlight: Option<CurrentLineHighlight>,
2898 ) {
2899 self.current_line_highlight = current_line_highlight;
2900 }
2901
2902 pub fn range_for_match<T: std::marker::Copy>(
2903 &self,
2904 range: &Range<T>,
2905 collapse: bool,
2906 ) -> Range<T> {
2907 if collapse {
2908 return range.start..range.start;
2909 }
2910 range.clone()
2911 }
2912
2913 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2914 if self.display_map.read(cx).clip_at_line_ends != clip {
2915 self.display_map
2916 .update(cx, |map, _| map.clip_at_line_ends = clip);
2917 }
2918 }
2919
2920 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2921 self.input_enabled = input_enabled;
2922 }
2923
2924 pub fn set_edit_predictions_hidden_for_vim_mode(
2925 &mut self,
2926 hidden: bool,
2927 window: &mut Window,
2928 cx: &mut Context<Self>,
2929 ) {
2930 if hidden != self.edit_predictions_hidden_for_vim_mode {
2931 self.edit_predictions_hidden_for_vim_mode = hidden;
2932 if hidden {
2933 self.update_visible_edit_prediction(window, cx);
2934 } else {
2935 self.refresh_edit_prediction(true, false, window, cx);
2936 }
2937 }
2938 }
2939
2940 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2941 self.menu_edit_predictions_policy = value;
2942 }
2943
2944 pub fn set_autoindent(&mut self, autoindent: bool) {
2945 if autoindent {
2946 self.autoindent_mode = Some(AutoindentMode::EachLine);
2947 } else {
2948 self.autoindent_mode = None;
2949 }
2950 }
2951
2952 pub fn read_only(&self, cx: &App) -> bool {
2953 self.read_only || self.buffer.read(cx).read_only()
2954 }
2955
2956 pub fn set_read_only(&mut self, read_only: bool) {
2957 self.read_only = read_only;
2958 }
2959
2960 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2961 self.use_autoclose = autoclose;
2962 }
2963
2964 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2965 self.use_auto_surround = auto_surround;
2966 }
2967
2968 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2969 self.auto_replace_emoji_shortcode = auto_replace;
2970 }
2971
2972 pub fn toggle_edit_predictions(
2973 &mut self,
2974 _: &ToggleEditPrediction,
2975 window: &mut Window,
2976 cx: &mut Context<Self>,
2977 ) {
2978 if self.show_edit_predictions_override.is_some() {
2979 self.set_show_edit_predictions(None, window, cx);
2980 } else {
2981 let show_edit_predictions = !self.edit_predictions_enabled();
2982 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2983 }
2984 }
2985
2986 pub fn set_show_edit_predictions(
2987 &mut self,
2988 show_edit_predictions: Option<bool>,
2989 window: &mut Window,
2990 cx: &mut Context<Self>,
2991 ) {
2992 self.show_edit_predictions_override = show_edit_predictions;
2993 self.update_edit_prediction_settings(cx);
2994
2995 if let Some(false) = show_edit_predictions {
2996 self.discard_edit_prediction(false, cx);
2997 } else {
2998 self.refresh_edit_prediction(false, true, window, cx);
2999 }
3000 }
3001
3002 fn edit_predictions_disabled_in_scope(
3003 &self,
3004 buffer: &Entity<Buffer>,
3005 buffer_position: language::Anchor,
3006 cx: &App,
3007 ) -> bool {
3008 let snapshot = buffer.read(cx).snapshot();
3009 let settings = snapshot.settings_at(buffer_position, cx);
3010
3011 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3012 return false;
3013 };
3014
3015 scope.override_name().is_some_and(|scope_name| {
3016 settings
3017 .edit_predictions_disabled_in
3018 .iter()
3019 .any(|s| s == scope_name)
3020 })
3021 }
3022
3023 pub fn set_use_modal_editing(&mut self, to: bool) {
3024 self.use_modal_editing = to;
3025 }
3026
3027 pub fn use_modal_editing(&self) -> bool {
3028 self.use_modal_editing
3029 }
3030
3031 fn selections_did_change(
3032 &mut self,
3033 local: bool,
3034 old_cursor_position: &Anchor,
3035 effects: SelectionEffects,
3036 window: &mut Window,
3037 cx: &mut Context<Self>,
3038 ) {
3039 window.invalidate_character_coordinates();
3040
3041 // Copy selections to primary selection buffer
3042 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3043 if local {
3044 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3045 let buffer_handle = self.buffer.read(cx).read(cx);
3046
3047 let mut text = String::new();
3048 for (index, selection) in selections.iter().enumerate() {
3049 let text_for_selection = buffer_handle
3050 .text_for_range(selection.start..selection.end)
3051 .collect::<String>();
3052
3053 text.push_str(&text_for_selection);
3054 if index != selections.len() - 1 {
3055 text.push('\n');
3056 }
3057 }
3058
3059 if !text.is_empty() {
3060 cx.write_to_primary(ClipboardItem::new_string(text));
3061 }
3062 }
3063
3064 let selection_anchors = self.selections.disjoint_anchors_arc();
3065
3066 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3067 self.buffer.update(cx, |buffer, cx| {
3068 buffer.set_active_selections(
3069 &selection_anchors,
3070 self.selections.line_mode(),
3071 self.cursor_shape,
3072 cx,
3073 )
3074 });
3075 }
3076 let display_map = self
3077 .display_map
3078 .update(cx, |display_map, cx| display_map.snapshot(cx));
3079 let buffer = display_map.buffer_snapshot();
3080 if self.selections.count() == 1 {
3081 self.add_selections_state = None;
3082 }
3083 self.select_next_state = None;
3084 self.select_prev_state = None;
3085 self.select_syntax_node_history.try_clear();
3086 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3087 self.snippet_stack.invalidate(&selection_anchors, buffer);
3088 self.take_rename(false, window, cx);
3089
3090 let newest_selection = self.selections.newest_anchor();
3091 let new_cursor_position = newest_selection.head();
3092 let selection_start = newest_selection.start;
3093
3094 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3095 self.push_to_nav_history(
3096 *old_cursor_position,
3097 Some(new_cursor_position.to_point(buffer)),
3098 false,
3099 effects.nav_history == Some(true),
3100 cx,
3101 );
3102 }
3103
3104 if local {
3105 if let Some(buffer_id) = new_cursor_position.buffer_id {
3106 self.register_buffer(buffer_id, cx);
3107 }
3108
3109 let mut context_menu = self.context_menu.borrow_mut();
3110 let completion_menu = match context_menu.as_ref() {
3111 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3112 Some(CodeContextMenu::CodeActions(_)) => {
3113 *context_menu = None;
3114 None
3115 }
3116 None => None,
3117 };
3118 let completion_position = completion_menu.map(|menu| menu.initial_position);
3119 drop(context_menu);
3120
3121 if effects.completions
3122 && let Some(completion_position) = completion_position
3123 {
3124 let start_offset = selection_start.to_offset(buffer);
3125 let position_matches = start_offset == completion_position.to_offset(buffer);
3126 let continue_showing = if position_matches {
3127 if self.snippet_stack.is_empty() {
3128 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3129 == Some(CharKind::Word)
3130 } else {
3131 // Snippet choices can be shown even when the cursor is in whitespace.
3132 // Dismissing the menu with actions like backspace is handled by
3133 // invalidation regions.
3134 true
3135 }
3136 } else {
3137 false
3138 };
3139
3140 if continue_showing {
3141 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3142 } else {
3143 self.hide_context_menu(window, cx);
3144 }
3145 }
3146
3147 hide_hover(self, cx);
3148
3149 if old_cursor_position.to_display_point(&display_map).row()
3150 != new_cursor_position.to_display_point(&display_map).row()
3151 {
3152 self.available_code_actions.take();
3153 }
3154 self.refresh_code_actions(window, cx);
3155 self.refresh_document_highlights(cx);
3156 refresh_linked_ranges(self, window, cx);
3157
3158 self.refresh_selected_text_highlights(false, window, cx);
3159 self.refresh_matching_bracket_highlights(window, cx);
3160 self.update_visible_edit_prediction(window, cx);
3161 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3162 self.inline_blame_popover.take();
3163 if self.git_blame_inline_enabled {
3164 self.start_inline_blame_timer(window, cx);
3165 }
3166 }
3167
3168 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3169 cx.emit(EditorEvent::SelectionsChanged { local });
3170
3171 let selections = &self.selections.disjoint_anchors_arc();
3172 if selections.len() == 1 {
3173 cx.emit(SearchEvent::ActiveMatchChanged)
3174 }
3175 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3176 let inmemory_selections = selections
3177 .iter()
3178 .map(|s| {
3179 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3180 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3181 })
3182 .collect();
3183 self.update_restoration_data(cx, |data| {
3184 data.selections = inmemory_selections;
3185 });
3186
3187 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3188 && let Some(workspace_id) =
3189 self.workspace.as_ref().and_then(|workspace| workspace.1)
3190 {
3191 let snapshot = self.buffer().read(cx).snapshot(cx);
3192 let selections = selections.clone();
3193 let background_executor = cx.background_executor().clone();
3194 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3195 self.serialize_selections = cx.background_spawn(async move {
3196 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3197 let db_selections = selections
3198 .iter()
3199 .map(|selection| {
3200 (
3201 selection.start.to_offset(&snapshot),
3202 selection.end.to_offset(&snapshot),
3203 )
3204 })
3205 .collect();
3206
3207 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3208 .await
3209 .with_context(|| {
3210 format!(
3211 "persisting editor selections for editor {editor_id}, \
3212 workspace {workspace_id:?}"
3213 )
3214 })
3215 .log_err();
3216 });
3217 }
3218 }
3219
3220 cx.notify();
3221 }
3222
3223 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3224 use text::ToOffset as _;
3225 use text::ToPoint as _;
3226
3227 if self.mode.is_minimap()
3228 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3229 {
3230 return;
3231 }
3232
3233 if !self.buffer().read(cx).is_singleton() {
3234 return;
3235 }
3236
3237 let display_snapshot = self
3238 .display_map
3239 .update(cx, |display_map, cx| display_map.snapshot(cx));
3240 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3241 return;
3242 };
3243 let inmemory_folds = display_snapshot
3244 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3245 .map(|fold| {
3246 fold.range.start.text_anchor.to_point(&snapshot)
3247 ..fold.range.end.text_anchor.to_point(&snapshot)
3248 })
3249 .collect();
3250 self.update_restoration_data(cx, |data| {
3251 data.folds = inmemory_folds;
3252 });
3253
3254 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3255 return;
3256 };
3257 let background_executor = cx.background_executor().clone();
3258 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3259 let db_folds = display_snapshot
3260 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3261 .map(|fold| {
3262 (
3263 fold.range.start.text_anchor.to_offset(&snapshot),
3264 fold.range.end.text_anchor.to_offset(&snapshot),
3265 )
3266 })
3267 .collect();
3268 self.serialize_folds = cx.background_spawn(async move {
3269 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3270 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3271 .await
3272 .with_context(|| {
3273 format!(
3274 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3275 )
3276 })
3277 .log_err();
3278 });
3279 }
3280
3281 pub fn sync_selections(
3282 &mut self,
3283 other: Entity<Editor>,
3284 cx: &mut Context<Self>,
3285 ) -> gpui::Subscription {
3286 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3287 if !other_selections.is_empty() {
3288 self.selections.change_with(cx, |selections| {
3289 selections.select_anchors(other_selections);
3290 });
3291 }
3292
3293 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3294 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3295 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3296 if other_selections.is_empty() {
3297 return;
3298 }
3299 this.selections.change_with(cx, |selections| {
3300 selections.select_anchors(other_selections);
3301 });
3302 }
3303 });
3304
3305 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3306 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3307 let these_selections = this.selections.disjoint_anchors().to_vec();
3308 if these_selections.is_empty() {
3309 return;
3310 }
3311 other.update(cx, |other_editor, cx| {
3312 other_editor.selections.change_with(cx, |selections| {
3313 selections.select_anchors(these_selections);
3314 })
3315 });
3316 }
3317 });
3318
3319 Subscription::join(other_subscription, this_subscription)
3320 }
3321
3322 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3323 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3324 /// effects of selection change occur at the end of the transaction.
3325 pub fn change_selections<R>(
3326 &mut self,
3327 effects: SelectionEffects,
3328 window: &mut Window,
3329 cx: &mut Context<Self>,
3330 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3331 ) -> R {
3332 if let Some(state) = &mut self.deferred_selection_effects_state {
3333 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3334 state.effects.completions = effects.completions;
3335 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3336 let (changed, result) = self.selections.change_with(cx, change);
3337 state.changed |= changed;
3338 return result;
3339 }
3340 let mut state = DeferredSelectionEffectsState {
3341 changed: false,
3342 effects,
3343 old_cursor_position: self.selections.newest_anchor().head(),
3344 history_entry: SelectionHistoryEntry {
3345 selections: self.selections.disjoint_anchors_arc(),
3346 select_next_state: self.select_next_state.clone(),
3347 select_prev_state: self.select_prev_state.clone(),
3348 add_selections_state: self.add_selections_state.clone(),
3349 },
3350 };
3351 let (changed, result) = self.selections.change_with(cx, change);
3352 state.changed = state.changed || changed;
3353 if self.defer_selection_effects {
3354 self.deferred_selection_effects_state = Some(state);
3355 } else {
3356 self.apply_selection_effects(state, window, cx);
3357 }
3358 result
3359 }
3360
3361 /// Defers the effects of selection change, so that the effects of multiple calls to
3362 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3363 /// to selection history and the state of popovers based on selection position aren't
3364 /// erroneously updated.
3365 pub fn with_selection_effects_deferred<R>(
3366 &mut self,
3367 window: &mut Window,
3368 cx: &mut Context<Self>,
3369 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3370 ) -> R {
3371 let already_deferred = self.defer_selection_effects;
3372 self.defer_selection_effects = true;
3373 let result = update(self, window, cx);
3374 if !already_deferred {
3375 self.defer_selection_effects = false;
3376 if let Some(state) = self.deferred_selection_effects_state.take() {
3377 self.apply_selection_effects(state, window, cx);
3378 }
3379 }
3380 result
3381 }
3382
3383 fn apply_selection_effects(
3384 &mut self,
3385 state: DeferredSelectionEffectsState,
3386 window: &mut Window,
3387 cx: &mut Context<Self>,
3388 ) {
3389 if state.changed {
3390 self.selection_history.push(state.history_entry);
3391
3392 if let Some(autoscroll) = state.effects.scroll {
3393 self.request_autoscroll(autoscroll, cx);
3394 }
3395
3396 let old_cursor_position = &state.old_cursor_position;
3397
3398 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3399
3400 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3401 self.show_signature_help(&ShowSignatureHelp, window, cx);
3402 }
3403 }
3404 }
3405
3406 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3407 where
3408 I: IntoIterator<Item = (Range<S>, T)>,
3409 S: ToOffset,
3410 T: Into<Arc<str>>,
3411 {
3412 if self.read_only(cx) {
3413 return;
3414 }
3415
3416 self.buffer
3417 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3418 }
3419
3420 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3421 where
3422 I: IntoIterator<Item = (Range<S>, T)>,
3423 S: ToOffset,
3424 T: Into<Arc<str>>,
3425 {
3426 if self.read_only(cx) {
3427 return;
3428 }
3429
3430 self.buffer.update(cx, |buffer, cx| {
3431 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3432 });
3433 }
3434
3435 pub fn edit_with_block_indent<I, S, T>(
3436 &mut self,
3437 edits: I,
3438 original_indent_columns: Vec<Option<u32>>,
3439 cx: &mut Context<Self>,
3440 ) where
3441 I: IntoIterator<Item = (Range<S>, T)>,
3442 S: ToOffset,
3443 T: Into<Arc<str>>,
3444 {
3445 if self.read_only(cx) {
3446 return;
3447 }
3448
3449 self.buffer.update(cx, |buffer, cx| {
3450 buffer.edit(
3451 edits,
3452 Some(AutoindentMode::Block {
3453 original_indent_columns,
3454 }),
3455 cx,
3456 )
3457 });
3458 }
3459
3460 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3461 self.hide_context_menu(window, cx);
3462
3463 match phase {
3464 SelectPhase::Begin {
3465 position,
3466 add,
3467 click_count,
3468 } => self.begin_selection(position, add, click_count, window, cx),
3469 SelectPhase::BeginColumnar {
3470 position,
3471 goal_column,
3472 reset,
3473 mode,
3474 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3475 SelectPhase::Extend {
3476 position,
3477 click_count,
3478 } => self.extend_selection(position, click_count, window, cx),
3479 SelectPhase::Update {
3480 position,
3481 goal_column,
3482 scroll_delta,
3483 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3484 SelectPhase::End => self.end_selection(window, cx),
3485 }
3486 }
3487
3488 fn extend_selection(
3489 &mut self,
3490 position: DisplayPoint,
3491 click_count: usize,
3492 window: &mut Window,
3493 cx: &mut Context<Self>,
3494 ) {
3495 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3496 let tail = self.selections.newest::<usize>(&display_map).tail();
3497 let click_count = click_count.max(match self.selections.select_mode() {
3498 SelectMode::Character => 1,
3499 SelectMode::Word(_) => 2,
3500 SelectMode::Line(_) => 3,
3501 SelectMode::All => 4,
3502 });
3503 self.begin_selection(position, false, click_count, window, cx);
3504
3505 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3506
3507 let current_selection = match self.selections.select_mode() {
3508 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3509 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3510 };
3511
3512 let mut pending_selection = self
3513 .selections
3514 .pending_anchor()
3515 .cloned()
3516 .expect("extend_selection not called with pending selection");
3517
3518 if pending_selection
3519 .start
3520 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3521 == Ordering::Greater
3522 {
3523 pending_selection.start = current_selection.start;
3524 }
3525 if pending_selection
3526 .end
3527 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3528 == Ordering::Less
3529 {
3530 pending_selection.end = current_selection.end;
3531 pending_selection.reversed = true;
3532 }
3533
3534 let mut pending_mode = self.selections.pending_mode().unwrap();
3535 match &mut pending_mode {
3536 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3537 _ => {}
3538 }
3539
3540 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3541 SelectionEffects::scroll(Autoscroll::fit())
3542 } else {
3543 SelectionEffects::no_scroll()
3544 };
3545
3546 self.change_selections(effects, window, cx, |s| {
3547 s.set_pending(pending_selection.clone(), pending_mode);
3548 s.set_is_extending(true);
3549 });
3550 }
3551
3552 fn begin_selection(
3553 &mut self,
3554 position: DisplayPoint,
3555 add: bool,
3556 click_count: usize,
3557 window: &mut Window,
3558 cx: &mut Context<Self>,
3559 ) {
3560 if !self.focus_handle.is_focused(window) {
3561 self.last_focused_descendant = None;
3562 window.focus(&self.focus_handle);
3563 }
3564
3565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3566 let buffer = display_map.buffer_snapshot();
3567 let position = display_map.clip_point(position, Bias::Left);
3568
3569 let start;
3570 let end;
3571 let mode;
3572 let mut auto_scroll;
3573 match click_count {
3574 1 => {
3575 start = buffer.anchor_before(position.to_point(&display_map));
3576 end = start;
3577 mode = SelectMode::Character;
3578 auto_scroll = true;
3579 }
3580 2 => {
3581 let position = display_map
3582 .clip_point(position, Bias::Left)
3583 .to_offset(&display_map, Bias::Left);
3584 let (range, _) = buffer.surrounding_word(position, None);
3585 start = buffer.anchor_before(range.start);
3586 end = buffer.anchor_before(range.end);
3587 mode = SelectMode::Word(start..end);
3588 auto_scroll = true;
3589 }
3590 3 => {
3591 let position = display_map
3592 .clip_point(position, Bias::Left)
3593 .to_point(&display_map);
3594 let line_start = display_map.prev_line_boundary(position).0;
3595 let next_line_start = buffer.clip_point(
3596 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3597 Bias::Left,
3598 );
3599 start = buffer.anchor_before(line_start);
3600 end = buffer.anchor_before(next_line_start);
3601 mode = SelectMode::Line(start..end);
3602 auto_scroll = true;
3603 }
3604 _ => {
3605 start = buffer.anchor_before(0);
3606 end = buffer.anchor_before(buffer.len());
3607 mode = SelectMode::All;
3608 auto_scroll = false;
3609 }
3610 }
3611 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3612
3613 let point_to_delete: Option<usize> = {
3614 let selected_points: Vec<Selection<Point>> =
3615 self.selections.disjoint_in_range(start..end, &display_map);
3616
3617 if !add || click_count > 1 {
3618 None
3619 } else if !selected_points.is_empty() {
3620 Some(selected_points[0].id)
3621 } else {
3622 let clicked_point_already_selected =
3623 self.selections.disjoint_anchors().iter().find(|selection| {
3624 selection.start.to_point(buffer) == start.to_point(buffer)
3625 || selection.end.to_point(buffer) == end.to_point(buffer)
3626 });
3627
3628 clicked_point_already_selected.map(|selection| selection.id)
3629 }
3630 };
3631
3632 let selections_count = self.selections.count();
3633 let effects = if auto_scroll {
3634 SelectionEffects::default()
3635 } else {
3636 SelectionEffects::no_scroll()
3637 };
3638
3639 self.change_selections(effects, window, cx, |s| {
3640 if let Some(point_to_delete) = point_to_delete {
3641 s.delete(point_to_delete);
3642
3643 if selections_count == 1 {
3644 s.set_pending_anchor_range(start..end, mode);
3645 }
3646 } else {
3647 if !add {
3648 s.clear_disjoint();
3649 }
3650
3651 s.set_pending_anchor_range(start..end, mode);
3652 }
3653 });
3654 }
3655
3656 fn begin_columnar_selection(
3657 &mut self,
3658 position: DisplayPoint,
3659 goal_column: u32,
3660 reset: bool,
3661 mode: ColumnarMode,
3662 window: &mut Window,
3663 cx: &mut Context<Self>,
3664 ) {
3665 if !self.focus_handle.is_focused(window) {
3666 self.last_focused_descendant = None;
3667 window.focus(&self.focus_handle);
3668 }
3669
3670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3671
3672 if reset {
3673 let pointer_position = display_map
3674 .buffer_snapshot()
3675 .anchor_before(position.to_point(&display_map));
3676
3677 self.change_selections(
3678 SelectionEffects::scroll(Autoscroll::newest()),
3679 window,
3680 cx,
3681 |s| {
3682 s.clear_disjoint();
3683 s.set_pending_anchor_range(
3684 pointer_position..pointer_position,
3685 SelectMode::Character,
3686 );
3687 },
3688 );
3689 };
3690
3691 let tail = self.selections.newest::<Point>(&display_map).tail();
3692 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3693 self.columnar_selection_state = match mode {
3694 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3695 selection_tail: selection_anchor,
3696 display_point: if reset {
3697 if position.column() != goal_column {
3698 Some(DisplayPoint::new(position.row(), goal_column))
3699 } else {
3700 None
3701 }
3702 } else {
3703 None
3704 },
3705 }),
3706 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3707 selection_tail: selection_anchor,
3708 }),
3709 };
3710
3711 if !reset {
3712 self.select_columns(position, goal_column, &display_map, window, cx);
3713 }
3714 }
3715
3716 fn update_selection(
3717 &mut self,
3718 position: DisplayPoint,
3719 goal_column: u32,
3720 scroll_delta: gpui::Point<f32>,
3721 window: &mut Window,
3722 cx: &mut Context<Self>,
3723 ) {
3724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3725
3726 if self.columnar_selection_state.is_some() {
3727 self.select_columns(position, goal_column, &display_map, window, cx);
3728 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3729 let buffer = display_map.buffer_snapshot();
3730 let head;
3731 let tail;
3732 let mode = self.selections.pending_mode().unwrap();
3733 match &mode {
3734 SelectMode::Character => {
3735 head = position.to_point(&display_map);
3736 tail = pending.tail().to_point(buffer);
3737 }
3738 SelectMode::Word(original_range) => {
3739 let offset = display_map
3740 .clip_point(position, Bias::Left)
3741 .to_offset(&display_map, Bias::Left);
3742 let original_range = original_range.to_offset(buffer);
3743
3744 let head_offset = if buffer.is_inside_word(offset, None)
3745 || original_range.contains(&offset)
3746 {
3747 let (word_range, _) = buffer.surrounding_word(offset, None);
3748 if word_range.start < original_range.start {
3749 word_range.start
3750 } else {
3751 word_range.end
3752 }
3753 } else {
3754 offset
3755 };
3756
3757 head = head_offset.to_point(buffer);
3758 if head_offset <= original_range.start {
3759 tail = original_range.end.to_point(buffer);
3760 } else {
3761 tail = original_range.start.to_point(buffer);
3762 }
3763 }
3764 SelectMode::Line(original_range) => {
3765 let original_range = original_range.to_point(display_map.buffer_snapshot());
3766
3767 let position = display_map
3768 .clip_point(position, Bias::Left)
3769 .to_point(&display_map);
3770 let line_start = display_map.prev_line_boundary(position).0;
3771 let next_line_start = buffer.clip_point(
3772 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3773 Bias::Left,
3774 );
3775
3776 if line_start < original_range.start {
3777 head = line_start
3778 } else {
3779 head = next_line_start
3780 }
3781
3782 if head <= original_range.start {
3783 tail = original_range.end;
3784 } else {
3785 tail = original_range.start;
3786 }
3787 }
3788 SelectMode::All => {
3789 return;
3790 }
3791 };
3792
3793 if head < tail {
3794 pending.start = buffer.anchor_before(head);
3795 pending.end = buffer.anchor_before(tail);
3796 pending.reversed = true;
3797 } else {
3798 pending.start = buffer.anchor_before(tail);
3799 pending.end = buffer.anchor_before(head);
3800 pending.reversed = false;
3801 }
3802
3803 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3804 s.set_pending(pending.clone(), mode);
3805 });
3806 } else {
3807 log::error!("update_selection dispatched with no pending selection");
3808 return;
3809 }
3810
3811 self.apply_scroll_delta(scroll_delta, window, cx);
3812 cx.notify();
3813 }
3814
3815 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3816 self.columnar_selection_state.take();
3817 if let Some(pending_mode) = self.selections.pending_mode() {
3818 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3819 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3820 s.select(selections);
3821 s.clear_pending();
3822 if s.is_extending() {
3823 s.set_is_extending(false);
3824 } else {
3825 s.set_select_mode(pending_mode);
3826 }
3827 });
3828 }
3829 }
3830
3831 fn select_columns(
3832 &mut self,
3833 head: DisplayPoint,
3834 goal_column: u32,
3835 display_map: &DisplaySnapshot,
3836 window: &mut Window,
3837 cx: &mut Context<Self>,
3838 ) {
3839 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3840 return;
3841 };
3842
3843 let tail = match columnar_state {
3844 ColumnarSelectionState::FromMouse {
3845 selection_tail,
3846 display_point,
3847 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3848 ColumnarSelectionState::FromSelection { selection_tail } => {
3849 selection_tail.to_display_point(display_map)
3850 }
3851 };
3852
3853 let start_row = cmp::min(tail.row(), head.row());
3854 let end_row = cmp::max(tail.row(), head.row());
3855 let start_column = cmp::min(tail.column(), goal_column);
3856 let end_column = cmp::max(tail.column(), goal_column);
3857 let reversed = start_column < tail.column();
3858
3859 let selection_ranges = (start_row.0..=end_row.0)
3860 .map(DisplayRow)
3861 .filter_map(|row| {
3862 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3863 || start_column <= display_map.line_len(row))
3864 && !display_map.is_block_line(row)
3865 {
3866 let start = display_map
3867 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3868 .to_point(display_map);
3869 let end = display_map
3870 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3871 .to_point(display_map);
3872 if reversed {
3873 Some(end..start)
3874 } else {
3875 Some(start..end)
3876 }
3877 } else {
3878 None
3879 }
3880 })
3881 .collect::<Vec<_>>();
3882 if selection_ranges.is_empty() {
3883 return;
3884 }
3885
3886 let ranges = match columnar_state {
3887 ColumnarSelectionState::FromMouse { .. } => {
3888 let mut non_empty_ranges = selection_ranges
3889 .iter()
3890 .filter(|selection_range| selection_range.start != selection_range.end)
3891 .peekable();
3892 if non_empty_ranges.peek().is_some() {
3893 non_empty_ranges.cloned().collect()
3894 } else {
3895 selection_ranges
3896 }
3897 }
3898 _ => selection_ranges,
3899 };
3900
3901 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3902 s.select_ranges(ranges);
3903 });
3904 cx.notify();
3905 }
3906
3907 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3908 self.selections
3909 .all_adjusted(snapshot)
3910 .iter()
3911 .any(|selection| !selection.is_empty())
3912 }
3913
3914 pub fn has_pending_nonempty_selection(&self) -> bool {
3915 let pending_nonempty_selection = match self.selections.pending_anchor() {
3916 Some(Selection { start, end, .. }) => start != end,
3917 None => false,
3918 };
3919
3920 pending_nonempty_selection
3921 || (self.columnar_selection_state.is_some()
3922 && self.selections.disjoint_anchors().len() > 1)
3923 }
3924
3925 pub fn has_pending_selection(&self) -> bool {
3926 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3927 }
3928
3929 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3930 self.selection_mark_mode = false;
3931 self.selection_drag_state = SelectionDragState::None;
3932
3933 if self.clear_expanded_diff_hunks(cx) {
3934 cx.notify();
3935 return;
3936 }
3937 if self.dismiss_menus_and_popups(true, window, cx) {
3938 return;
3939 }
3940
3941 if self.mode.is_full()
3942 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3943 {
3944 return;
3945 }
3946
3947 cx.propagate();
3948 }
3949
3950 pub fn dismiss_menus_and_popups(
3951 &mut self,
3952 is_user_requested: bool,
3953 window: &mut Window,
3954 cx: &mut Context<Self>,
3955 ) -> bool {
3956 if self.take_rename(false, window, cx).is_some() {
3957 return true;
3958 }
3959
3960 if self.hide_blame_popover(true, cx) {
3961 return true;
3962 }
3963
3964 if hide_hover(self, cx) {
3965 return true;
3966 }
3967
3968 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3969 return true;
3970 }
3971
3972 if self.hide_context_menu(window, cx).is_some() {
3973 return true;
3974 }
3975
3976 if self.mouse_context_menu.take().is_some() {
3977 return true;
3978 }
3979
3980 if is_user_requested && self.discard_edit_prediction(true, cx) {
3981 return true;
3982 }
3983
3984 if self.snippet_stack.pop().is_some() {
3985 return true;
3986 }
3987
3988 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3989 self.dismiss_diagnostics(cx);
3990 return true;
3991 }
3992
3993 false
3994 }
3995
3996 fn linked_editing_ranges_for(
3997 &self,
3998 selection: Range<text::Anchor>,
3999 cx: &App,
4000 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4001 if self.linked_edit_ranges.is_empty() {
4002 return None;
4003 }
4004 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4005 selection.end.buffer_id.and_then(|end_buffer_id| {
4006 if selection.start.buffer_id != Some(end_buffer_id) {
4007 return None;
4008 }
4009 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4010 let snapshot = buffer.read(cx).snapshot();
4011 self.linked_edit_ranges
4012 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4013 .map(|ranges| (ranges, snapshot, buffer))
4014 })?;
4015 use text::ToOffset as TO;
4016 // find offset from the start of current range to current cursor position
4017 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4018
4019 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4020 let start_difference = start_offset - start_byte_offset;
4021 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4022 let end_difference = end_offset - start_byte_offset;
4023 // Current range has associated linked ranges.
4024 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4025 for range in linked_ranges.iter() {
4026 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4027 let end_offset = start_offset + end_difference;
4028 let start_offset = start_offset + start_difference;
4029 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4030 continue;
4031 }
4032 if self.selections.disjoint_anchor_ranges().any(|s| {
4033 if s.start.buffer_id != selection.start.buffer_id
4034 || s.end.buffer_id != selection.end.buffer_id
4035 {
4036 return false;
4037 }
4038 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4039 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4040 }) {
4041 continue;
4042 }
4043 let start = buffer_snapshot.anchor_after(start_offset);
4044 let end = buffer_snapshot.anchor_after(end_offset);
4045 linked_edits
4046 .entry(buffer.clone())
4047 .or_default()
4048 .push(start..end);
4049 }
4050 Some(linked_edits)
4051 }
4052
4053 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4054 let text: Arc<str> = text.into();
4055
4056 if self.read_only(cx) {
4057 return;
4058 }
4059
4060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4061
4062 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4063 let mut bracket_inserted = false;
4064 let mut edits = Vec::new();
4065 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4066 let mut new_selections = Vec::with_capacity(selections.len());
4067 let mut new_autoclose_regions = Vec::new();
4068 let snapshot = self.buffer.read(cx).read(cx);
4069 let mut clear_linked_edit_ranges = false;
4070
4071 for (selection, autoclose_region) in
4072 self.selections_with_autoclose_regions(selections, &snapshot)
4073 {
4074 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4075 // Determine if the inserted text matches the opening or closing
4076 // bracket of any of this language's bracket pairs.
4077 let mut bracket_pair = None;
4078 let mut is_bracket_pair_start = false;
4079 let mut is_bracket_pair_end = false;
4080 if !text.is_empty() {
4081 let mut bracket_pair_matching_end = None;
4082 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4083 // and they are removing the character that triggered IME popup.
4084 for (pair, enabled) in scope.brackets() {
4085 if !pair.close && !pair.surround {
4086 continue;
4087 }
4088
4089 if enabled && pair.start.ends_with(text.as_ref()) {
4090 let prefix_len = pair.start.len() - text.len();
4091 let preceding_text_matches_prefix = prefix_len == 0
4092 || (selection.start.column >= (prefix_len as u32)
4093 && snapshot.contains_str_at(
4094 Point::new(
4095 selection.start.row,
4096 selection.start.column - (prefix_len as u32),
4097 ),
4098 &pair.start[..prefix_len],
4099 ));
4100 if preceding_text_matches_prefix {
4101 bracket_pair = Some(pair.clone());
4102 is_bracket_pair_start = true;
4103 break;
4104 }
4105 }
4106 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4107 {
4108 // take first bracket pair matching end, but don't break in case a later bracket
4109 // pair matches start
4110 bracket_pair_matching_end = Some(pair.clone());
4111 }
4112 }
4113 if let Some(end) = bracket_pair_matching_end
4114 && bracket_pair.is_none()
4115 {
4116 bracket_pair = Some(end);
4117 is_bracket_pair_end = true;
4118 }
4119 }
4120
4121 if let Some(bracket_pair) = bracket_pair {
4122 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4123 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4124 let auto_surround =
4125 self.use_auto_surround && snapshot_settings.use_auto_surround;
4126 if selection.is_empty() {
4127 if is_bracket_pair_start {
4128 // If the inserted text is a suffix of an opening bracket and the
4129 // selection is preceded by the rest of the opening bracket, then
4130 // insert the closing bracket.
4131 let following_text_allows_autoclose = snapshot
4132 .chars_at(selection.start)
4133 .next()
4134 .is_none_or(|c| scope.should_autoclose_before(c));
4135
4136 let preceding_text_allows_autoclose = selection.start.column == 0
4137 || snapshot
4138 .reversed_chars_at(selection.start)
4139 .next()
4140 .is_none_or(|c| {
4141 bracket_pair.start != bracket_pair.end
4142 || !snapshot
4143 .char_classifier_at(selection.start)
4144 .is_word(c)
4145 });
4146
4147 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4148 && bracket_pair.start.len() == 1
4149 {
4150 let target = bracket_pair.start.chars().next().unwrap();
4151 let current_line_count = snapshot
4152 .reversed_chars_at(selection.start)
4153 .take_while(|&c| c != '\n')
4154 .filter(|&c| c == target)
4155 .count();
4156 current_line_count % 2 == 1
4157 } else {
4158 false
4159 };
4160
4161 if autoclose
4162 && bracket_pair.close
4163 && following_text_allows_autoclose
4164 && preceding_text_allows_autoclose
4165 && !is_closing_quote
4166 {
4167 let anchor = snapshot.anchor_before(selection.end);
4168 new_selections.push((selection.map(|_| anchor), text.len()));
4169 new_autoclose_regions.push((
4170 anchor,
4171 text.len(),
4172 selection.id,
4173 bracket_pair.clone(),
4174 ));
4175 edits.push((
4176 selection.range(),
4177 format!("{}{}", text, bracket_pair.end).into(),
4178 ));
4179 bracket_inserted = true;
4180 continue;
4181 }
4182 }
4183
4184 if let Some(region) = autoclose_region {
4185 // If the selection is followed by an auto-inserted closing bracket,
4186 // then don't insert that closing bracket again; just move the selection
4187 // past the closing bracket.
4188 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4189 && text.as_ref() == region.pair.end.as_str()
4190 && snapshot.contains_str_at(region.range.end, text.as_ref());
4191 if should_skip {
4192 let anchor = snapshot.anchor_after(selection.end);
4193 new_selections
4194 .push((selection.map(|_| anchor), region.pair.end.len()));
4195 continue;
4196 }
4197 }
4198
4199 let always_treat_brackets_as_autoclosed = snapshot
4200 .language_settings_at(selection.start, cx)
4201 .always_treat_brackets_as_autoclosed;
4202 if always_treat_brackets_as_autoclosed
4203 && is_bracket_pair_end
4204 && snapshot.contains_str_at(selection.end, text.as_ref())
4205 {
4206 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4207 // and the inserted text is a closing bracket and the selection is followed
4208 // by the closing bracket then move the selection past the closing bracket.
4209 let anchor = snapshot.anchor_after(selection.end);
4210 new_selections.push((selection.map(|_| anchor), text.len()));
4211 continue;
4212 }
4213 }
4214 // If an opening bracket is 1 character long and is typed while
4215 // text is selected, then surround that text with the bracket pair.
4216 else if auto_surround
4217 && bracket_pair.surround
4218 && is_bracket_pair_start
4219 && bracket_pair.start.chars().count() == 1
4220 {
4221 edits.push((selection.start..selection.start, text.clone()));
4222 edits.push((
4223 selection.end..selection.end,
4224 bracket_pair.end.as_str().into(),
4225 ));
4226 bracket_inserted = true;
4227 new_selections.push((
4228 Selection {
4229 id: selection.id,
4230 start: snapshot.anchor_after(selection.start),
4231 end: snapshot.anchor_before(selection.end),
4232 reversed: selection.reversed,
4233 goal: selection.goal,
4234 },
4235 0,
4236 ));
4237 continue;
4238 }
4239 }
4240 }
4241
4242 if self.auto_replace_emoji_shortcode
4243 && selection.is_empty()
4244 && text.as_ref().ends_with(':')
4245 && let Some(possible_emoji_short_code) =
4246 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4247 && !possible_emoji_short_code.is_empty()
4248 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4249 {
4250 let emoji_shortcode_start = Point::new(
4251 selection.start.row,
4252 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4253 );
4254
4255 // Remove shortcode from buffer
4256 edits.push((
4257 emoji_shortcode_start..selection.start,
4258 "".to_string().into(),
4259 ));
4260 new_selections.push((
4261 Selection {
4262 id: selection.id,
4263 start: snapshot.anchor_after(emoji_shortcode_start),
4264 end: snapshot.anchor_before(selection.start),
4265 reversed: selection.reversed,
4266 goal: selection.goal,
4267 },
4268 0,
4269 ));
4270
4271 // Insert emoji
4272 let selection_start_anchor = snapshot.anchor_after(selection.start);
4273 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4274 edits.push((selection.start..selection.end, emoji.to_string().into()));
4275
4276 continue;
4277 }
4278
4279 // If not handling any auto-close operation, then just replace the selected
4280 // text with the given input and move the selection to the end of the
4281 // newly inserted text.
4282 let anchor = snapshot.anchor_after(selection.end);
4283 if !self.linked_edit_ranges.is_empty() {
4284 let start_anchor = snapshot.anchor_before(selection.start);
4285
4286 let is_word_char = text.chars().next().is_none_or(|char| {
4287 let classifier = snapshot
4288 .char_classifier_at(start_anchor.to_offset(&snapshot))
4289 .scope_context(Some(CharScopeContext::LinkedEdit));
4290 classifier.is_word(char)
4291 });
4292
4293 if is_word_char {
4294 if let Some(ranges) = self
4295 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4296 {
4297 for (buffer, edits) in ranges {
4298 linked_edits
4299 .entry(buffer.clone())
4300 .or_default()
4301 .extend(edits.into_iter().map(|range| (range, text.clone())));
4302 }
4303 }
4304 } else {
4305 clear_linked_edit_ranges = true;
4306 }
4307 }
4308
4309 new_selections.push((selection.map(|_| anchor), 0));
4310 edits.push((selection.start..selection.end, text.clone()));
4311 }
4312
4313 drop(snapshot);
4314
4315 self.transact(window, cx, |this, window, cx| {
4316 if clear_linked_edit_ranges {
4317 this.linked_edit_ranges.clear();
4318 }
4319 let initial_buffer_versions =
4320 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4321
4322 this.buffer.update(cx, |buffer, cx| {
4323 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4324 });
4325 for (buffer, edits) in linked_edits {
4326 buffer.update(cx, |buffer, cx| {
4327 let snapshot = buffer.snapshot();
4328 let edits = edits
4329 .into_iter()
4330 .map(|(range, text)| {
4331 use text::ToPoint as TP;
4332 let end_point = TP::to_point(&range.end, &snapshot);
4333 let start_point = TP::to_point(&range.start, &snapshot);
4334 (start_point..end_point, text)
4335 })
4336 .sorted_by_key(|(range, _)| range.start);
4337 buffer.edit(edits, None, cx);
4338 })
4339 }
4340 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4341 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4342 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4343 let new_selections =
4344 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4345 .zip(new_selection_deltas)
4346 .map(|(selection, delta)| Selection {
4347 id: selection.id,
4348 start: selection.start + delta,
4349 end: selection.end + delta,
4350 reversed: selection.reversed,
4351 goal: SelectionGoal::None,
4352 })
4353 .collect::<Vec<_>>();
4354
4355 let mut i = 0;
4356 for (position, delta, selection_id, pair) in new_autoclose_regions {
4357 let position = position.to_offset(map.buffer_snapshot()) + delta;
4358 let start = map.buffer_snapshot().anchor_before(position);
4359 let end = map.buffer_snapshot().anchor_after(position);
4360 while let Some(existing_state) = this.autoclose_regions.get(i) {
4361 match existing_state
4362 .range
4363 .start
4364 .cmp(&start, map.buffer_snapshot())
4365 {
4366 Ordering::Less => i += 1,
4367 Ordering::Greater => break,
4368 Ordering::Equal => {
4369 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4370 Ordering::Less => i += 1,
4371 Ordering::Equal => break,
4372 Ordering::Greater => break,
4373 }
4374 }
4375 }
4376 }
4377 this.autoclose_regions.insert(
4378 i,
4379 AutocloseRegion {
4380 selection_id,
4381 range: start..end,
4382 pair,
4383 },
4384 );
4385 }
4386
4387 let had_active_edit_prediction = this.has_active_edit_prediction();
4388 this.change_selections(
4389 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4390 window,
4391 cx,
4392 |s| s.select(new_selections),
4393 );
4394
4395 if !bracket_inserted
4396 && let Some(on_type_format_task) =
4397 this.trigger_on_type_formatting(text.to_string(), window, cx)
4398 {
4399 on_type_format_task.detach_and_log_err(cx);
4400 }
4401
4402 let editor_settings = EditorSettings::get_global(cx);
4403 if bracket_inserted
4404 && (editor_settings.auto_signature_help
4405 || editor_settings.show_signature_help_after_edits)
4406 {
4407 this.show_signature_help(&ShowSignatureHelp, window, cx);
4408 }
4409
4410 let trigger_in_words =
4411 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4412 if this.hard_wrap.is_some() {
4413 let latest: Range<Point> = this.selections.newest(&map).range();
4414 if latest.is_empty()
4415 && this
4416 .buffer()
4417 .read(cx)
4418 .snapshot(cx)
4419 .line_len(MultiBufferRow(latest.start.row))
4420 == latest.start.column
4421 {
4422 this.rewrap_impl(
4423 RewrapOptions {
4424 override_language_settings: true,
4425 preserve_existing_whitespace: true,
4426 },
4427 cx,
4428 )
4429 }
4430 }
4431 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4432 refresh_linked_ranges(this, window, cx);
4433 this.refresh_edit_prediction(true, false, window, cx);
4434 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4435 });
4436 }
4437
4438 fn find_possible_emoji_shortcode_at_position(
4439 snapshot: &MultiBufferSnapshot,
4440 position: Point,
4441 ) -> Option<String> {
4442 let mut chars = Vec::new();
4443 let mut found_colon = false;
4444 for char in snapshot.reversed_chars_at(position).take(100) {
4445 // Found a possible emoji shortcode in the middle of the buffer
4446 if found_colon {
4447 if char.is_whitespace() {
4448 chars.reverse();
4449 return Some(chars.iter().collect());
4450 }
4451 // If the previous character is not a whitespace, we are in the middle of a word
4452 // and we only want to complete the shortcode if the word is made up of other emojis
4453 let mut containing_word = String::new();
4454 for ch in snapshot
4455 .reversed_chars_at(position)
4456 .skip(chars.len() + 1)
4457 .take(100)
4458 {
4459 if ch.is_whitespace() {
4460 break;
4461 }
4462 containing_word.push(ch);
4463 }
4464 let containing_word = containing_word.chars().rev().collect::<String>();
4465 if util::word_consists_of_emojis(containing_word.as_str()) {
4466 chars.reverse();
4467 return Some(chars.iter().collect());
4468 }
4469 }
4470
4471 if char.is_whitespace() || !char.is_ascii() {
4472 return None;
4473 }
4474 if char == ':' {
4475 found_colon = true;
4476 } else {
4477 chars.push(char);
4478 }
4479 }
4480 // Found a possible emoji shortcode at the beginning of the buffer
4481 chars.reverse();
4482 Some(chars.iter().collect())
4483 }
4484
4485 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4487 self.transact(window, cx, |this, window, cx| {
4488 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4489 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4490 let multi_buffer = this.buffer.read(cx);
4491 let buffer = multi_buffer.snapshot(cx);
4492 selections
4493 .iter()
4494 .map(|selection| {
4495 let start_point = selection.start.to_point(&buffer);
4496 let mut existing_indent =
4497 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4498 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4499 let start = selection.start;
4500 let end = selection.end;
4501 let selection_is_empty = start == end;
4502 let language_scope = buffer.language_scope_at(start);
4503 let (
4504 comment_delimiter,
4505 doc_delimiter,
4506 insert_extra_newline,
4507 indent_on_newline,
4508 indent_on_extra_newline,
4509 ) = if let Some(language) = &language_scope {
4510 let mut insert_extra_newline =
4511 insert_extra_newline_brackets(&buffer, start..end, language)
4512 || insert_extra_newline_tree_sitter(&buffer, start..end);
4513
4514 // Comment extension on newline is allowed only for cursor selections
4515 let comment_delimiter = maybe!({
4516 if !selection_is_empty {
4517 return None;
4518 }
4519
4520 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4521 return None;
4522 }
4523
4524 let delimiters = language.line_comment_prefixes();
4525 let max_len_of_delimiter =
4526 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4527 let (snapshot, range) =
4528 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4529
4530 let num_of_whitespaces = snapshot
4531 .chars_for_range(range.clone())
4532 .take_while(|c| c.is_whitespace())
4533 .count();
4534 let comment_candidate = snapshot
4535 .chars_for_range(range.clone())
4536 .skip(num_of_whitespaces)
4537 .take(max_len_of_delimiter)
4538 .collect::<String>();
4539 let (delimiter, trimmed_len) = delimiters
4540 .iter()
4541 .filter_map(|delimiter| {
4542 let prefix = delimiter.trim_end();
4543 if comment_candidate.starts_with(prefix) {
4544 Some((delimiter, prefix.len()))
4545 } else {
4546 None
4547 }
4548 })
4549 .max_by_key(|(_, len)| *len)?;
4550
4551 if let Some(BlockCommentConfig {
4552 start: block_start, ..
4553 }) = language.block_comment()
4554 {
4555 let block_start_trimmed = block_start.trim_end();
4556 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4557 let line_content = snapshot
4558 .chars_for_range(range)
4559 .skip(num_of_whitespaces)
4560 .take(block_start_trimmed.len())
4561 .collect::<String>();
4562
4563 if line_content.starts_with(block_start_trimmed) {
4564 return None;
4565 }
4566 }
4567 }
4568
4569 let cursor_is_placed_after_comment_marker =
4570 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4571 if cursor_is_placed_after_comment_marker {
4572 Some(delimiter.clone())
4573 } else {
4574 None
4575 }
4576 });
4577
4578 let mut indent_on_newline = IndentSize::spaces(0);
4579 let mut indent_on_extra_newline = IndentSize::spaces(0);
4580
4581 let doc_delimiter = maybe!({
4582 if !selection_is_empty {
4583 return None;
4584 }
4585
4586 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4587 return None;
4588 }
4589
4590 let BlockCommentConfig {
4591 start: start_tag,
4592 end: end_tag,
4593 prefix: delimiter,
4594 tab_size: len,
4595 } = language.documentation_comment()?;
4596 let is_within_block_comment = buffer
4597 .language_scope_at(start_point)
4598 .is_some_and(|scope| scope.override_name() == Some("comment"));
4599 if !is_within_block_comment {
4600 return None;
4601 }
4602
4603 let (snapshot, range) =
4604 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4605
4606 let num_of_whitespaces = snapshot
4607 .chars_for_range(range.clone())
4608 .take_while(|c| c.is_whitespace())
4609 .count();
4610
4611 // 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.
4612 let column = start_point.column;
4613 let cursor_is_after_start_tag = {
4614 let start_tag_len = start_tag.len();
4615 let start_tag_line = snapshot
4616 .chars_for_range(range.clone())
4617 .skip(num_of_whitespaces)
4618 .take(start_tag_len)
4619 .collect::<String>();
4620 if start_tag_line.starts_with(start_tag.as_ref()) {
4621 num_of_whitespaces + start_tag_len <= column as usize
4622 } else {
4623 false
4624 }
4625 };
4626
4627 let cursor_is_after_delimiter = {
4628 let delimiter_trim = delimiter.trim_end();
4629 let delimiter_line = snapshot
4630 .chars_for_range(range.clone())
4631 .skip(num_of_whitespaces)
4632 .take(delimiter_trim.len())
4633 .collect::<String>();
4634 if delimiter_line.starts_with(delimiter_trim) {
4635 num_of_whitespaces + delimiter_trim.len() <= column as usize
4636 } else {
4637 false
4638 }
4639 };
4640
4641 let cursor_is_before_end_tag_if_exists = {
4642 let mut char_position = 0u32;
4643 let mut end_tag_offset = None;
4644
4645 'outer: for chunk in snapshot.text_for_range(range) {
4646 if let Some(byte_pos) = chunk.find(&**end_tag) {
4647 let chars_before_match =
4648 chunk[..byte_pos].chars().count() as u32;
4649 end_tag_offset =
4650 Some(char_position + chars_before_match);
4651 break 'outer;
4652 }
4653 char_position += chunk.chars().count() as u32;
4654 }
4655
4656 if let Some(end_tag_offset) = end_tag_offset {
4657 let cursor_is_before_end_tag = column <= end_tag_offset;
4658 if cursor_is_after_start_tag {
4659 if cursor_is_before_end_tag {
4660 insert_extra_newline = true;
4661 }
4662 let cursor_is_at_start_of_end_tag =
4663 column == end_tag_offset;
4664 if cursor_is_at_start_of_end_tag {
4665 indent_on_extra_newline.len = *len;
4666 }
4667 }
4668 cursor_is_before_end_tag
4669 } else {
4670 true
4671 }
4672 };
4673
4674 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4675 && cursor_is_before_end_tag_if_exists
4676 {
4677 if cursor_is_after_start_tag {
4678 indent_on_newline.len = *len;
4679 }
4680 Some(delimiter.clone())
4681 } else {
4682 None
4683 }
4684 });
4685
4686 (
4687 comment_delimiter,
4688 doc_delimiter,
4689 insert_extra_newline,
4690 indent_on_newline,
4691 indent_on_extra_newline,
4692 )
4693 } else {
4694 (
4695 None,
4696 None,
4697 false,
4698 IndentSize::default(),
4699 IndentSize::default(),
4700 )
4701 };
4702
4703 let prevent_auto_indent = doc_delimiter.is_some();
4704 let delimiter = comment_delimiter.or(doc_delimiter);
4705
4706 let capacity_for_delimiter =
4707 delimiter.as_deref().map(str::len).unwrap_or_default();
4708 let mut new_text = String::with_capacity(
4709 1 + capacity_for_delimiter
4710 + existing_indent.len as usize
4711 + indent_on_newline.len as usize
4712 + indent_on_extra_newline.len as usize,
4713 );
4714 new_text.push('\n');
4715 new_text.extend(existing_indent.chars());
4716 new_text.extend(indent_on_newline.chars());
4717
4718 if let Some(delimiter) = &delimiter {
4719 new_text.push_str(delimiter);
4720 }
4721
4722 if insert_extra_newline {
4723 new_text.push('\n');
4724 new_text.extend(existing_indent.chars());
4725 new_text.extend(indent_on_extra_newline.chars());
4726 }
4727
4728 let anchor = buffer.anchor_after(end);
4729 let new_selection = selection.map(|_| anchor);
4730 (
4731 ((start..end, new_text), prevent_auto_indent),
4732 (insert_extra_newline, new_selection),
4733 )
4734 })
4735 .unzip()
4736 };
4737
4738 let mut auto_indent_edits = Vec::new();
4739 let mut edits = Vec::new();
4740 for (edit, prevent_auto_indent) in edits_with_flags {
4741 if prevent_auto_indent {
4742 edits.push(edit);
4743 } else {
4744 auto_indent_edits.push(edit);
4745 }
4746 }
4747 if !edits.is_empty() {
4748 this.edit(edits, cx);
4749 }
4750 if !auto_indent_edits.is_empty() {
4751 this.edit_with_autoindent(auto_indent_edits, cx);
4752 }
4753
4754 let buffer = this.buffer.read(cx).snapshot(cx);
4755 let new_selections = selection_info
4756 .into_iter()
4757 .map(|(extra_newline_inserted, new_selection)| {
4758 let mut cursor = new_selection.end.to_point(&buffer);
4759 if extra_newline_inserted {
4760 cursor.row -= 1;
4761 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4762 }
4763 new_selection.map(|_| cursor)
4764 })
4765 .collect();
4766
4767 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4768 this.refresh_edit_prediction(true, false, window, cx);
4769 });
4770 }
4771
4772 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4774
4775 let buffer = self.buffer.read(cx);
4776 let snapshot = buffer.snapshot(cx);
4777
4778 let mut edits = Vec::new();
4779 let mut rows = Vec::new();
4780
4781 for (rows_inserted, selection) in self
4782 .selections
4783 .all_adjusted(&self.display_snapshot(cx))
4784 .into_iter()
4785 .enumerate()
4786 {
4787 let cursor = selection.head();
4788 let row = cursor.row;
4789
4790 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4791
4792 let newline = "\n".to_string();
4793 edits.push((start_of_line..start_of_line, newline));
4794
4795 rows.push(row + rows_inserted as u32);
4796 }
4797
4798 self.transact(window, cx, |editor, window, cx| {
4799 editor.edit(edits, cx);
4800
4801 editor.change_selections(Default::default(), window, cx, |s| {
4802 let mut index = 0;
4803 s.move_cursors_with(|map, _, _| {
4804 let row = rows[index];
4805 index += 1;
4806
4807 let point = Point::new(row, 0);
4808 let boundary = map.next_line_boundary(point).1;
4809 let clipped = map.clip_point(boundary, Bias::Left);
4810
4811 (clipped, SelectionGoal::None)
4812 });
4813 });
4814
4815 let mut indent_edits = Vec::new();
4816 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4817 for row in rows {
4818 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4819 for (row, indent) in indents {
4820 if indent.len == 0 {
4821 continue;
4822 }
4823
4824 let text = match indent.kind {
4825 IndentKind::Space => " ".repeat(indent.len as usize),
4826 IndentKind::Tab => "\t".repeat(indent.len as usize),
4827 };
4828 let point = Point::new(row.0, 0);
4829 indent_edits.push((point..point, text));
4830 }
4831 }
4832 editor.edit(indent_edits, cx);
4833 });
4834 }
4835
4836 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4838
4839 let buffer = self.buffer.read(cx);
4840 let snapshot = buffer.snapshot(cx);
4841
4842 let mut edits = Vec::new();
4843 let mut rows = Vec::new();
4844 let mut rows_inserted = 0;
4845
4846 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4847 let cursor = selection.head();
4848 let row = cursor.row;
4849
4850 let point = Point::new(row + 1, 0);
4851 let start_of_line = snapshot.clip_point(point, Bias::Left);
4852
4853 let newline = "\n".to_string();
4854 edits.push((start_of_line..start_of_line, newline));
4855
4856 rows_inserted += 1;
4857 rows.push(row + rows_inserted);
4858 }
4859
4860 self.transact(window, cx, |editor, window, cx| {
4861 editor.edit(edits, cx);
4862
4863 editor.change_selections(Default::default(), window, cx, |s| {
4864 let mut index = 0;
4865 s.move_cursors_with(|map, _, _| {
4866 let row = rows[index];
4867 index += 1;
4868
4869 let point = Point::new(row, 0);
4870 let boundary = map.next_line_boundary(point).1;
4871 let clipped = map.clip_point(boundary, Bias::Left);
4872
4873 (clipped, SelectionGoal::None)
4874 });
4875 });
4876
4877 let mut indent_edits = Vec::new();
4878 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4879 for row in rows {
4880 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4881 for (row, indent) in indents {
4882 if indent.len == 0 {
4883 continue;
4884 }
4885
4886 let text = match indent.kind {
4887 IndentKind::Space => " ".repeat(indent.len as usize),
4888 IndentKind::Tab => "\t".repeat(indent.len as usize),
4889 };
4890 let point = Point::new(row.0, 0);
4891 indent_edits.push((point..point, text));
4892 }
4893 }
4894 editor.edit(indent_edits, cx);
4895 });
4896 }
4897
4898 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4899 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4900 original_indent_columns: Vec::new(),
4901 });
4902 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4903 }
4904
4905 fn insert_with_autoindent_mode(
4906 &mut self,
4907 text: &str,
4908 autoindent_mode: Option<AutoindentMode>,
4909 window: &mut Window,
4910 cx: &mut Context<Self>,
4911 ) {
4912 if self.read_only(cx) {
4913 return;
4914 }
4915
4916 let text: Arc<str> = text.into();
4917 self.transact(window, cx, |this, window, cx| {
4918 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4919 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4920 let anchors = {
4921 let snapshot = buffer.read(cx);
4922 old_selections
4923 .iter()
4924 .map(|s| {
4925 let anchor = snapshot.anchor_after(s.head());
4926 s.map(|_| anchor)
4927 })
4928 .collect::<Vec<_>>()
4929 };
4930 buffer.edit(
4931 old_selections
4932 .iter()
4933 .map(|s| (s.start..s.end, text.clone())),
4934 autoindent_mode,
4935 cx,
4936 );
4937 anchors
4938 });
4939
4940 this.change_selections(Default::default(), window, cx, |s| {
4941 s.select_anchors(selection_anchors);
4942 });
4943
4944 cx.notify();
4945 });
4946 }
4947
4948 fn trigger_completion_on_input(
4949 &mut self,
4950 text: &str,
4951 trigger_in_words: bool,
4952 window: &mut Window,
4953 cx: &mut Context<Self>,
4954 ) {
4955 let completions_source = self
4956 .context_menu
4957 .borrow()
4958 .as_ref()
4959 .and_then(|menu| match menu {
4960 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4961 CodeContextMenu::CodeActions(_) => None,
4962 });
4963
4964 match completions_source {
4965 Some(CompletionsMenuSource::Words { .. }) => {
4966 self.open_or_update_completions_menu(
4967 Some(CompletionsMenuSource::Words {
4968 ignore_threshold: false,
4969 }),
4970 None,
4971 window,
4972 cx,
4973 );
4974 }
4975 Some(CompletionsMenuSource::Normal)
4976 | Some(CompletionsMenuSource::SnippetChoices)
4977 | None
4978 if self.is_completion_trigger(
4979 text,
4980 trigger_in_words,
4981 completions_source.is_some(),
4982 cx,
4983 ) =>
4984 {
4985 self.show_completions(
4986 &ShowCompletions {
4987 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4988 },
4989 window,
4990 cx,
4991 )
4992 }
4993 _ => {
4994 self.hide_context_menu(window, cx);
4995 }
4996 }
4997 }
4998
4999 fn is_completion_trigger(
5000 &self,
5001 text: &str,
5002 trigger_in_words: bool,
5003 menu_is_open: bool,
5004 cx: &mut Context<Self>,
5005 ) -> bool {
5006 let position = self.selections.newest_anchor().head();
5007 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5008 return false;
5009 };
5010
5011 if let Some(completion_provider) = &self.completion_provider {
5012 completion_provider.is_completion_trigger(
5013 &buffer,
5014 position.text_anchor,
5015 text,
5016 trigger_in_words,
5017 menu_is_open,
5018 cx,
5019 )
5020 } else {
5021 false
5022 }
5023 }
5024
5025 /// If any empty selections is touching the start of its innermost containing autoclose
5026 /// region, expand it to select the brackets.
5027 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5028 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5029 let buffer = self.buffer.read(cx).read(cx);
5030 let new_selections = self
5031 .selections_with_autoclose_regions(selections, &buffer)
5032 .map(|(mut selection, region)| {
5033 if !selection.is_empty() {
5034 return selection;
5035 }
5036
5037 if let Some(region) = region {
5038 let mut range = region.range.to_offset(&buffer);
5039 if selection.start == range.start && range.start >= region.pair.start.len() {
5040 range.start -= region.pair.start.len();
5041 if buffer.contains_str_at(range.start, ®ion.pair.start)
5042 && buffer.contains_str_at(range.end, ®ion.pair.end)
5043 {
5044 range.end += region.pair.end.len();
5045 selection.start = range.start;
5046 selection.end = range.end;
5047
5048 return selection;
5049 }
5050 }
5051 }
5052
5053 let always_treat_brackets_as_autoclosed = buffer
5054 .language_settings_at(selection.start, cx)
5055 .always_treat_brackets_as_autoclosed;
5056
5057 if !always_treat_brackets_as_autoclosed {
5058 return selection;
5059 }
5060
5061 if let Some(scope) = buffer.language_scope_at(selection.start) {
5062 for (pair, enabled) in scope.brackets() {
5063 if !enabled || !pair.close {
5064 continue;
5065 }
5066
5067 if buffer.contains_str_at(selection.start, &pair.end) {
5068 let pair_start_len = pair.start.len();
5069 if buffer.contains_str_at(
5070 selection.start.saturating_sub(pair_start_len),
5071 &pair.start,
5072 ) {
5073 selection.start -= pair_start_len;
5074 selection.end += pair.end.len();
5075
5076 return selection;
5077 }
5078 }
5079 }
5080 }
5081
5082 selection
5083 })
5084 .collect();
5085
5086 drop(buffer);
5087 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5088 selections.select(new_selections)
5089 });
5090 }
5091
5092 /// Iterate the given selections, and for each one, find the smallest surrounding
5093 /// autoclose region. This uses the ordering of the selections and the autoclose
5094 /// regions to avoid repeated comparisons.
5095 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5096 &'a self,
5097 selections: impl IntoIterator<Item = Selection<D>>,
5098 buffer: &'a MultiBufferSnapshot,
5099 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5100 let mut i = 0;
5101 let mut regions = self.autoclose_regions.as_slice();
5102 selections.into_iter().map(move |selection| {
5103 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5104
5105 let mut enclosing = None;
5106 while let Some(pair_state) = regions.get(i) {
5107 if pair_state.range.end.to_offset(buffer) < range.start {
5108 regions = ®ions[i + 1..];
5109 i = 0;
5110 } else if pair_state.range.start.to_offset(buffer) > range.end {
5111 break;
5112 } else {
5113 if pair_state.selection_id == selection.id {
5114 enclosing = Some(pair_state);
5115 }
5116 i += 1;
5117 }
5118 }
5119
5120 (selection, enclosing)
5121 })
5122 }
5123
5124 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5125 fn invalidate_autoclose_regions(
5126 &mut self,
5127 mut selections: &[Selection<Anchor>],
5128 buffer: &MultiBufferSnapshot,
5129 ) {
5130 self.autoclose_regions.retain(|state| {
5131 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5132 return false;
5133 }
5134
5135 let mut i = 0;
5136 while let Some(selection) = selections.get(i) {
5137 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5138 selections = &selections[1..];
5139 continue;
5140 }
5141 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5142 break;
5143 }
5144 if selection.id == state.selection_id {
5145 return true;
5146 } else {
5147 i += 1;
5148 }
5149 }
5150 false
5151 });
5152 }
5153
5154 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5155 let offset = position.to_offset(buffer);
5156 let (word_range, kind) =
5157 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5158 if offset > word_range.start && kind == Some(CharKind::Word) {
5159 Some(
5160 buffer
5161 .text_for_range(word_range.start..offset)
5162 .collect::<String>(),
5163 )
5164 } else {
5165 None
5166 }
5167 }
5168
5169 pub fn visible_excerpts(
5170 &self,
5171 cx: &mut Context<Editor>,
5172 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5173 let Some(project) = self.project() else {
5174 return HashMap::default();
5175 };
5176 let project = project.read(cx);
5177 let multi_buffer = self.buffer().read(cx);
5178 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5179 let multi_buffer_visible_start = self
5180 .scroll_manager
5181 .anchor()
5182 .anchor
5183 .to_point(&multi_buffer_snapshot);
5184 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5185 multi_buffer_visible_start
5186 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5187 Bias::Left,
5188 );
5189 multi_buffer_snapshot
5190 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5191 .into_iter()
5192 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5193 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5194 let buffer_file = project::File::from_dyn(buffer.file())?;
5195 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5196 let worktree_entry = buffer_worktree
5197 .read(cx)
5198 .entry_for_id(buffer_file.project_entry_id()?)?;
5199 if worktree_entry.is_ignored {
5200 None
5201 } else {
5202 Some((
5203 excerpt_id,
5204 (
5205 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5206 buffer.version().clone(),
5207 excerpt_visible_range,
5208 ),
5209 ))
5210 }
5211 })
5212 .collect()
5213 }
5214
5215 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5216 TextLayoutDetails {
5217 text_system: window.text_system().clone(),
5218 editor_style: self.style.clone().unwrap(),
5219 rem_size: window.rem_size(),
5220 scroll_anchor: self.scroll_manager.anchor(),
5221 visible_rows: self.visible_line_count(),
5222 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5223 }
5224 }
5225
5226 fn trigger_on_type_formatting(
5227 &self,
5228 input: String,
5229 window: &mut Window,
5230 cx: &mut Context<Self>,
5231 ) -> Option<Task<Result<()>>> {
5232 if input.len() != 1 {
5233 return None;
5234 }
5235
5236 let project = self.project()?;
5237 let position = self.selections.newest_anchor().head();
5238 let (buffer, buffer_position) = self
5239 .buffer
5240 .read(cx)
5241 .text_anchor_for_position(position, cx)?;
5242
5243 let settings = language_settings::language_settings(
5244 buffer
5245 .read(cx)
5246 .language_at(buffer_position)
5247 .map(|l| l.name()),
5248 buffer.read(cx).file(),
5249 cx,
5250 );
5251 if !settings.use_on_type_format {
5252 return None;
5253 }
5254
5255 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5256 // hence we do LSP request & edit on host side only — add formats to host's history.
5257 let push_to_lsp_host_history = true;
5258 // If this is not the host, append its history with new edits.
5259 let push_to_client_history = project.read(cx).is_via_collab();
5260
5261 let on_type_formatting = project.update(cx, |project, cx| {
5262 project.on_type_format(
5263 buffer.clone(),
5264 buffer_position,
5265 input,
5266 push_to_lsp_host_history,
5267 cx,
5268 )
5269 });
5270 Some(cx.spawn_in(window, async move |editor, cx| {
5271 if let Some(transaction) = on_type_formatting.await? {
5272 if push_to_client_history {
5273 buffer
5274 .update(cx, |buffer, _| {
5275 buffer.push_transaction(transaction, Instant::now());
5276 buffer.finalize_last_transaction();
5277 })
5278 .ok();
5279 }
5280 editor.update(cx, |editor, cx| {
5281 editor.refresh_document_highlights(cx);
5282 })?;
5283 }
5284 Ok(())
5285 }))
5286 }
5287
5288 pub fn show_word_completions(
5289 &mut self,
5290 _: &ShowWordCompletions,
5291 window: &mut Window,
5292 cx: &mut Context<Self>,
5293 ) {
5294 self.open_or_update_completions_menu(
5295 Some(CompletionsMenuSource::Words {
5296 ignore_threshold: true,
5297 }),
5298 None,
5299 window,
5300 cx,
5301 );
5302 }
5303
5304 pub fn show_completions(
5305 &mut self,
5306 options: &ShowCompletions,
5307 window: &mut Window,
5308 cx: &mut Context<Self>,
5309 ) {
5310 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5311 }
5312
5313 fn open_or_update_completions_menu(
5314 &mut self,
5315 requested_source: Option<CompletionsMenuSource>,
5316 trigger: Option<&str>,
5317 window: &mut Window,
5318 cx: &mut Context<Self>,
5319 ) {
5320 if self.pending_rename.is_some() {
5321 return;
5322 }
5323
5324 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5325
5326 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5327 // inserted and selected. To handle that case, the start of the selection is used so that
5328 // the menu starts with all choices.
5329 let position = self
5330 .selections
5331 .newest_anchor()
5332 .start
5333 .bias_right(&multibuffer_snapshot);
5334 if position.diff_base_anchor.is_some() {
5335 return;
5336 }
5337 let buffer_position = multibuffer_snapshot.anchor_before(position);
5338 let Some(buffer) = buffer_position
5339 .buffer_id
5340 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5341 else {
5342 return;
5343 };
5344 let buffer_snapshot = buffer.read(cx).snapshot();
5345
5346 let query: Option<Arc<String>> =
5347 Self::completion_query(&multibuffer_snapshot, buffer_position)
5348 .map(|query| query.into());
5349
5350 drop(multibuffer_snapshot);
5351
5352 // Hide the current completions menu when query is empty. Without this, cached
5353 // completions from before the trigger char may be reused (#32774).
5354 if query.is_none() {
5355 let menu_is_open = matches!(
5356 self.context_menu.borrow().as_ref(),
5357 Some(CodeContextMenu::Completions(_))
5358 );
5359 if menu_is_open {
5360 self.hide_context_menu(window, cx);
5361 }
5362 }
5363
5364 let mut ignore_word_threshold = false;
5365 let provider = match requested_source {
5366 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5367 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5368 ignore_word_threshold = ignore_threshold;
5369 None
5370 }
5371 Some(CompletionsMenuSource::SnippetChoices) => {
5372 log::error!("bug: SnippetChoices requested_source is not handled");
5373 None
5374 }
5375 };
5376
5377 let sort_completions = provider
5378 .as_ref()
5379 .is_some_and(|provider| provider.sort_completions());
5380
5381 let filter_completions = provider
5382 .as_ref()
5383 .is_none_or(|provider| provider.filter_completions());
5384
5385 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5386 if filter_completions {
5387 menu.filter(query.clone(), provider.clone(), window, cx);
5388 }
5389 // When `is_incomplete` is false, no need to re-query completions when the current query
5390 // is a suffix of the initial query.
5391 if !menu.is_incomplete {
5392 // If the new query is a suffix of the old query (typing more characters) and
5393 // the previous result was complete, the existing completions can be filtered.
5394 //
5395 // Note that this is always true for snippet completions.
5396 let query_matches = match (&menu.initial_query, &query) {
5397 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5398 (None, _) => true,
5399 _ => false,
5400 };
5401 if query_matches {
5402 let position_matches = if menu.initial_position == position {
5403 true
5404 } else {
5405 let snapshot = self.buffer.read(cx).read(cx);
5406 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5407 };
5408 if position_matches {
5409 return;
5410 }
5411 }
5412 }
5413 };
5414
5415 let trigger_kind = match trigger {
5416 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5417 CompletionTriggerKind::TRIGGER_CHARACTER
5418 }
5419 _ => CompletionTriggerKind::INVOKED,
5420 };
5421 let completion_context = CompletionContext {
5422 trigger_character: trigger.and_then(|trigger| {
5423 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5424 Some(String::from(trigger))
5425 } else {
5426 None
5427 }
5428 }),
5429 trigger_kind,
5430 };
5431
5432 let Anchor {
5433 excerpt_id: buffer_excerpt_id,
5434 text_anchor: buffer_position,
5435 ..
5436 } = buffer_position;
5437
5438 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5439 buffer_snapshot.surrounding_word(buffer_position, None)
5440 {
5441 let word_to_exclude = buffer_snapshot
5442 .text_for_range(word_range.clone())
5443 .collect::<String>();
5444 (
5445 buffer_snapshot.anchor_before(word_range.start)
5446 ..buffer_snapshot.anchor_after(buffer_position),
5447 Some(word_to_exclude),
5448 )
5449 } else {
5450 (buffer_position..buffer_position, None)
5451 };
5452
5453 let language = buffer_snapshot
5454 .language_at(buffer_position)
5455 .map(|language| language.name());
5456
5457 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5458 .completions
5459 .clone();
5460
5461 let show_completion_documentation = buffer_snapshot
5462 .settings_at(buffer_position, cx)
5463 .show_completion_documentation;
5464
5465 // The document can be large, so stay in reasonable bounds when searching for words,
5466 // otherwise completion pop-up might be slow to appear.
5467 const WORD_LOOKUP_ROWS: u32 = 5_000;
5468 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5469 let min_word_search = buffer_snapshot.clip_point(
5470 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5471 Bias::Left,
5472 );
5473 let max_word_search = buffer_snapshot.clip_point(
5474 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5475 Bias::Right,
5476 );
5477 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5478 ..buffer_snapshot.point_to_offset(max_word_search);
5479
5480 let skip_digits = query
5481 .as_ref()
5482 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5483
5484 let omit_word_completions = !self.word_completions_enabled
5485 || (!ignore_word_threshold
5486 && match &query {
5487 Some(query) => query.chars().count() < completion_settings.words_min_length,
5488 None => completion_settings.words_min_length != 0,
5489 });
5490
5491 let (mut words, provider_responses) = match &provider {
5492 Some(provider) => {
5493 let provider_responses = provider.completions(
5494 buffer_excerpt_id,
5495 &buffer,
5496 buffer_position,
5497 completion_context,
5498 window,
5499 cx,
5500 );
5501
5502 let words = match (omit_word_completions, completion_settings.words) {
5503 (true, _) | (_, WordsCompletionMode::Disabled) => {
5504 Task::ready(BTreeMap::default())
5505 }
5506 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5507 .background_spawn(async move {
5508 buffer_snapshot.words_in_range(WordsQuery {
5509 fuzzy_contents: None,
5510 range: word_search_range,
5511 skip_digits,
5512 })
5513 }),
5514 };
5515
5516 (words, provider_responses)
5517 }
5518 None => {
5519 let words = if omit_word_completions {
5520 Task::ready(BTreeMap::default())
5521 } else {
5522 cx.background_spawn(async move {
5523 buffer_snapshot.words_in_range(WordsQuery {
5524 fuzzy_contents: None,
5525 range: word_search_range,
5526 skip_digits,
5527 })
5528 })
5529 };
5530 (words, Task::ready(Ok(Vec::new())))
5531 }
5532 };
5533
5534 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5535
5536 let id = post_inc(&mut self.next_completion_id);
5537 let task = cx.spawn_in(window, async move |editor, cx| {
5538 let Ok(()) = editor.update(cx, |this, _| {
5539 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5540 }) else {
5541 return;
5542 };
5543
5544 // TODO: Ideally completions from different sources would be selectively re-queried, so
5545 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5546 let mut completions = Vec::new();
5547 let mut is_incomplete = false;
5548 let mut display_options: Option<CompletionDisplayOptions> = None;
5549 if let Some(provider_responses) = provider_responses.await.log_err()
5550 && !provider_responses.is_empty()
5551 {
5552 for response in provider_responses {
5553 completions.extend(response.completions);
5554 is_incomplete = is_incomplete || response.is_incomplete;
5555 match display_options.as_mut() {
5556 None => {
5557 display_options = Some(response.display_options);
5558 }
5559 Some(options) => options.merge(&response.display_options),
5560 }
5561 }
5562 if completion_settings.words == WordsCompletionMode::Fallback {
5563 words = Task::ready(BTreeMap::default());
5564 }
5565 }
5566 let display_options = display_options.unwrap_or_default();
5567
5568 let mut words = words.await;
5569 if let Some(word_to_exclude) = &word_to_exclude {
5570 words.remove(word_to_exclude);
5571 }
5572 for lsp_completion in &completions {
5573 words.remove(&lsp_completion.new_text);
5574 }
5575 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5576 replace_range: word_replace_range.clone(),
5577 new_text: word.clone(),
5578 label: CodeLabel::plain(word, None),
5579 icon_path: None,
5580 documentation: None,
5581 source: CompletionSource::BufferWord {
5582 word_range,
5583 resolved: false,
5584 },
5585 insert_text_mode: Some(InsertTextMode::AS_IS),
5586 confirm: None,
5587 }));
5588
5589 let menu = if completions.is_empty() {
5590 None
5591 } else {
5592 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5593 let languages = editor
5594 .workspace
5595 .as_ref()
5596 .and_then(|(workspace, _)| workspace.upgrade())
5597 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5598 let menu = CompletionsMenu::new(
5599 id,
5600 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5601 sort_completions,
5602 show_completion_documentation,
5603 position,
5604 query.clone(),
5605 is_incomplete,
5606 buffer.clone(),
5607 completions.into(),
5608 display_options,
5609 snippet_sort_order,
5610 languages,
5611 language,
5612 cx,
5613 );
5614
5615 let query = if filter_completions { query } else { None };
5616 let matches_task = if let Some(query) = query {
5617 menu.do_async_filtering(query, cx)
5618 } else {
5619 Task::ready(menu.unfiltered_matches())
5620 };
5621 (menu, matches_task)
5622 }) else {
5623 return;
5624 };
5625
5626 let matches = matches_task.await;
5627
5628 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5629 // Newer menu already set, so exit.
5630 if let Some(CodeContextMenu::Completions(prev_menu)) =
5631 editor.context_menu.borrow().as_ref()
5632 && prev_menu.id > id
5633 {
5634 return;
5635 };
5636
5637 // Only valid to take prev_menu because it the new menu is immediately set
5638 // below, or the menu is hidden.
5639 if let Some(CodeContextMenu::Completions(prev_menu)) =
5640 editor.context_menu.borrow_mut().take()
5641 {
5642 let position_matches =
5643 if prev_menu.initial_position == menu.initial_position {
5644 true
5645 } else {
5646 let snapshot = editor.buffer.read(cx).read(cx);
5647 prev_menu.initial_position.to_offset(&snapshot)
5648 == menu.initial_position.to_offset(&snapshot)
5649 };
5650 if position_matches {
5651 // Preserve markdown cache before `set_filter_results` because it will
5652 // try to populate the documentation cache.
5653 menu.preserve_markdown_cache(prev_menu);
5654 }
5655 };
5656
5657 menu.set_filter_results(matches, provider, window, cx);
5658 }) else {
5659 return;
5660 };
5661
5662 menu.visible().then_some(menu)
5663 };
5664
5665 editor
5666 .update_in(cx, |editor, window, cx| {
5667 if editor.focus_handle.is_focused(window)
5668 && let Some(menu) = menu
5669 {
5670 *editor.context_menu.borrow_mut() =
5671 Some(CodeContextMenu::Completions(menu));
5672
5673 crate::hover_popover::hide_hover(editor, cx);
5674 if editor.show_edit_predictions_in_menu() {
5675 editor.update_visible_edit_prediction(window, cx);
5676 } else {
5677 editor.discard_edit_prediction(false, cx);
5678 }
5679
5680 cx.notify();
5681 return;
5682 }
5683
5684 if editor.completion_tasks.len() <= 1 {
5685 // If there are no more completion tasks and the last menu was empty, we should hide it.
5686 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5687 // If it was already hidden and we don't show edit predictions in the menu,
5688 // we should also show the edit prediction when available.
5689 if was_hidden && editor.show_edit_predictions_in_menu() {
5690 editor.update_visible_edit_prediction(window, cx);
5691 }
5692 }
5693 })
5694 .ok();
5695 });
5696
5697 self.completion_tasks.push((id, task));
5698 }
5699
5700 #[cfg(feature = "test-support")]
5701 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5702 let menu = self.context_menu.borrow();
5703 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5704 let completions = menu.completions.borrow();
5705 Some(completions.to_vec())
5706 } else {
5707 None
5708 }
5709 }
5710
5711 pub fn with_completions_menu_matching_id<R>(
5712 &self,
5713 id: CompletionId,
5714 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5715 ) -> R {
5716 let mut context_menu = self.context_menu.borrow_mut();
5717 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5718 return f(None);
5719 };
5720 if completions_menu.id != id {
5721 return f(None);
5722 }
5723 f(Some(completions_menu))
5724 }
5725
5726 pub fn confirm_completion(
5727 &mut self,
5728 action: &ConfirmCompletion,
5729 window: &mut Window,
5730 cx: &mut Context<Self>,
5731 ) -> Option<Task<Result<()>>> {
5732 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5733 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5734 }
5735
5736 pub fn confirm_completion_insert(
5737 &mut self,
5738 _: &ConfirmCompletionInsert,
5739 window: &mut Window,
5740 cx: &mut Context<Self>,
5741 ) -> Option<Task<Result<()>>> {
5742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5743 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5744 }
5745
5746 pub fn confirm_completion_replace(
5747 &mut self,
5748 _: &ConfirmCompletionReplace,
5749 window: &mut Window,
5750 cx: &mut Context<Self>,
5751 ) -> Option<Task<Result<()>>> {
5752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5753 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5754 }
5755
5756 pub fn compose_completion(
5757 &mut self,
5758 action: &ComposeCompletion,
5759 window: &mut Window,
5760 cx: &mut Context<Self>,
5761 ) -> Option<Task<Result<()>>> {
5762 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5763 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5764 }
5765
5766 fn do_completion(
5767 &mut self,
5768 item_ix: Option<usize>,
5769 intent: CompletionIntent,
5770 window: &mut Window,
5771 cx: &mut Context<Editor>,
5772 ) -> Option<Task<Result<()>>> {
5773 use language::ToOffset as _;
5774
5775 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5776 else {
5777 return None;
5778 };
5779
5780 let candidate_id = {
5781 let entries = completions_menu.entries.borrow();
5782 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5783 if self.show_edit_predictions_in_menu() {
5784 self.discard_edit_prediction(true, cx);
5785 }
5786 mat.candidate_id
5787 };
5788
5789 let completion = completions_menu
5790 .completions
5791 .borrow()
5792 .get(candidate_id)?
5793 .clone();
5794 cx.stop_propagation();
5795
5796 let buffer_handle = completions_menu.buffer.clone();
5797
5798 let CompletionEdit {
5799 new_text,
5800 snippet,
5801 replace_range,
5802 } = process_completion_for_edit(
5803 &completion,
5804 intent,
5805 &buffer_handle,
5806 &completions_menu.initial_position.text_anchor,
5807 cx,
5808 );
5809
5810 let buffer = buffer_handle.read(cx);
5811 let snapshot = self.buffer.read(cx).snapshot(cx);
5812 let newest_anchor = self.selections.newest_anchor();
5813 let replace_range_multibuffer = {
5814 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5815 excerpt.map_range_from_buffer(replace_range.clone())
5816 };
5817 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5818 return None;
5819 }
5820
5821 let old_text = buffer
5822 .text_for_range(replace_range.clone())
5823 .collect::<String>();
5824 let lookbehind = newest_anchor
5825 .start
5826 .text_anchor
5827 .to_offset(buffer)
5828 .saturating_sub(replace_range.start);
5829 let lookahead = replace_range
5830 .end
5831 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5832 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5833 let suffix = &old_text[lookbehind.min(old_text.len())..];
5834
5835 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5836 let mut ranges = Vec::new();
5837 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5838
5839 for selection in &selections {
5840 let range = if selection.id == newest_anchor.id {
5841 replace_range_multibuffer.clone()
5842 } else {
5843 let mut range = selection.range();
5844
5845 // if prefix is present, don't duplicate it
5846 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5847 range.start = range.start.saturating_sub(lookbehind);
5848
5849 // if suffix is also present, mimic the newest cursor and replace it
5850 if selection.id != newest_anchor.id
5851 && snapshot.contains_str_at(range.end, suffix)
5852 {
5853 range.end += lookahead;
5854 }
5855 }
5856 range
5857 };
5858
5859 ranges.push(range.clone());
5860
5861 if !self.linked_edit_ranges.is_empty() {
5862 let start_anchor = snapshot.anchor_before(range.start);
5863 let end_anchor = snapshot.anchor_after(range.end);
5864 if let Some(ranges) = self
5865 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5866 {
5867 for (buffer, edits) in ranges {
5868 linked_edits
5869 .entry(buffer.clone())
5870 .or_default()
5871 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5872 }
5873 }
5874 }
5875 }
5876
5877 let common_prefix_len = old_text
5878 .chars()
5879 .zip(new_text.chars())
5880 .take_while(|(a, b)| a == b)
5881 .map(|(a, _)| a.len_utf8())
5882 .sum::<usize>();
5883
5884 cx.emit(EditorEvent::InputHandled {
5885 utf16_range_to_replace: None,
5886 text: new_text[common_prefix_len..].into(),
5887 });
5888
5889 self.transact(window, cx, |editor, window, cx| {
5890 if let Some(mut snippet) = snippet {
5891 snippet.text = new_text.to_string();
5892 editor
5893 .insert_snippet(&ranges, snippet, window, cx)
5894 .log_err();
5895 } else {
5896 editor.buffer.update(cx, |multi_buffer, cx| {
5897 let auto_indent = match completion.insert_text_mode {
5898 Some(InsertTextMode::AS_IS) => None,
5899 _ => editor.autoindent_mode.clone(),
5900 };
5901 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5902 multi_buffer.edit(edits, auto_indent, cx);
5903 });
5904 }
5905 for (buffer, edits) in linked_edits {
5906 buffer.update(cx, |buffer, cx| {
5907 let snapshot = buffer.snapshot();
5908 let edits = edits
5909 .into_iter()
5910 .map(|(range, text)| {
5911 use text::ToPoint as TP;
5912 let end_point = TP::to_point(&range.end, &snapshot);
5913 let start_point = TP::to_point(&range.start, &snapshot);
5914 (start_point..end_point, text)
5915 })
5916 .sorted_by_key(|(range, _)| range.start);
5917 buffer.edit(edits, None, cx);
5918 })
5919 }
5920
5921 editor.refresh_edit_prediction(true, false, window, cx);
5922 });
5923 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5924
5925 let show_new_completions_on_confirm = completion
5926 .confirm
5927 .as_ref()
5928 .is_some_and(|confirm| confirm(intent, window, cx));
5929 if show_new_completions_on_confirm {
5930 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5931 }
5932
5933 let provider = self.completion_provider.as_ref()?;
5934 drop(completion);
5935 let apply_edits = provider.apply_additional_edits_for_completion(
5936 buffer_handle,
5937 completions_menu.completions.clone(),
5938 candidate_id,
5939 true,
5940 cx,
5941 );
5942
5943 let editor_settings = EditorSettings::get_global(cx);
5944 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5945 // After the code completion is finished, users often want to know what signatures are needed.
5946 // so we should automatically call signature_help
5947 self.show_signature_help(&ShowSignatureHelp, window, cx);
5948 }
5949
5950 Some(cx.foreground_executor().spawn(async move {
5951 apply_edits.await?;
5952 Ok(())
5953 }))
5954 }
5955
5956 pub fn toggle_code_actions(
5957 &mut self,
5958 action: &ToggleCodeActions,
5959 window: &mut Window,
5960 cx: &mut Context<Self>,
5961 ) {
5962 let quick_launch = action.quick_launch;
5963 let mut context_menu = self.context_menu.borrow_mut();
5964 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5965 if code_actions.deployed_from == action.deployed_from {
5966 // Toggle if we're selecting the same one
5967 *context_menu = None;
5968 cx.notify();
5969 return;
5970 } else {
5971 // Otherwise, clear it and start a new one
5972 *context_menu = None;
5973 cx.notify();
5974 }
5975 }
5976 drop(context_menu);
5977 let snapshot = self.snapshot(window, cx);
5978 let deployed_from = action.deployed_from.clone();
5979 let action = action.clone();
5980 self.completion_tasks.clear();
5981 self.discard_edit_prediction(false, cx);
5982
5983 let multibuffer_point = match &action.deployed_from {
5984 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5985 DisplayPoint::new(*row, 0).to_point(&snapshot)
5986 }
5987 _ => self
5988 .selections
5989 .newest::<Point>(&snapshot.display_snapshot)
5990 .head(),
5991 };
5992 let Some((buffer, buffer_row)) = snapshot
5993 .buffer_snapshot()
5994 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5995 .and_then(|(buffer_snapshot, range)| {
5996 self.buffer()
5997 .read(cx)
5998 .buffer(buffer_snapshot.remote_id())
5999 .map(|buffer| (buffer, range.start.row))
6000 })
6001 else {
6002 return;
6003 };
6004 let buffer_id = buffer.read(cx).remote_id();
6005 let tasks = self
6006 .tasks
6007 .get(&(buffer_id, buffer_row))
6008 .map(|t| Arc::new(t.to_owned()));
6009
6010 if !self.focus_handle.is_focused(window) {
6011 return;
6012 }
6013 let project = self.project.clone();
6014
6015 let code_actions_task = match deployed_from {
6016 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6017 _ => self.code_actions(buffer_row, window, cx),
6018 };
6019
6020 let runnable_task = match deployed_from {
6021 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6022 _ => {
6023 let mut task_context_task = Task::ready(None);
6024 if let Some(tasks) = &tasks
6025 && let Some(project) = project
6026 {
6027 task_context_task =
6028 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6029 }
6030
6031 cx.spawn_in(window, {
6032 let buffer = buffer.clone();
6033 async move |editor, cx| {
6034 let task_context = task_context_task.await;
6035
6036 let resolved_tasks =
6037 tasks
6038 .zip(task_context.clone())
6039 .map(|(tasks, task_context)| ResolvedTasks {
6040 templates: tasks.resolve(&task_context).collect(),
6041 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6042 multibuffer_point.row,
6043 tasks.column,
6044 )),
6045 });
6046 let debug_scenarios = editor
6047 .update(cx, |editor, cx| {
6048 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6049 })?
6050 .await;
6051 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6052 }
6053 })
6054 }
6055 };
6056
6057 cx.spawn_in(window, async move |editor, cx| {
6058 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6059 let code_actions = code_actions_task.await;
6060 let spawn_straight_away = quick_launch
6061 && resolved_tasks
6062 .as_ref()
6063 .is_some_and(|tasks| tasks.templates.len() == 1)
6064 && code_actions
6065 .as_ref()
6066 .is_none_or(|actions| actions.is_empty())
6067 && debug_scenarios.is_empty();
6068
6069 editor.update_in(cx, |editor, window, cx| {
6070 crate::hover_popover::hide_hover(editor, cx);
6071 let actions = CodeActionContents::new(
6072 resolved_tasks,
6073 code_actions,
6074 debug_scenarios,
6075 task_context.unwrap_or_default(),
6076 );
6077
6078 // Don't show the menu if there are no actions available
6079 if actions.is_empty() {
6080 cx.notify();
6081 return Task::ready(Ok(()));
6082 }
6083
6084 *editor.context_menu.borrow_mut() =
6085 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6086 buffer,
6087 actions,
6088 selected_item: Default::default(),
6089 scroll_handle: UniformListScrollHandle::default(),
6090 deployed_from,
6091 }));
6092 cx.notify();
6093 if spawn_straight_away
6094 && let Some(task) = editor.confirm_code_action(
6095 &ConfirmCodeAction { item_ix: Some(0) },
6096 window,
6097 cx,
6098 )
6099 {
6100 return task;
6101 }
6102
6103 Task::ready(Ok(()))
6104 })
6105 })
6106 .detach_and_log_err(cx);
6107 }
6108
6109 fn debug_scenarios(
6110 &mut self,
6111 resolved_tasks: &Option<ResolvedTasks>,
6112 buffer: &Entity<Buffer>,
6113 cx: &mut App,
6114 ) -> Task<Vec<task::DebugScenario>> {
6115 maybe!({
6116 let project = self.project()?;
6117 let dap_store = project.read(cx).dap_store();
6118 let mut scenarios = vec![];
6119 let resolved_tasks = resolved_tasks.as_ref()?;
6120 let buffer = buffer.read(cx);
6121 let language = buffer.language()?;
6122 let file = buffer.file();
6123 let debug_adapter = language_settings(language.name().into(), file, cx)
6124 .debuggers
6125 .first()
6126 .map(SharedString::from)
6127 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6128
6129 dap_store.update(cx, |dap_store, cx| {
6130 for (_, task) in &resolved_tasks.templates {
6131 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6132 task.original_task().clone(),
6133 debug_adapter.clone().into(),
6134 task.display_label().to_owned().into(),
6135 cx,
6136 );
6137 scenarios.push(maybe_scenario);
6138 }
6139 });
6140 Some(cx.background_spawn(async move {
6141 futures::future::join_all(scenarios)
6142 .await
6143 .into_iter()
6144 .flatten()
6145 .collect::<Vec<_>>()
6146 }))
6147 })
6148 .unwrap_or_else(|| Task::ready(vec![]))
6149 }
6150
6151 fn code_actions(
6152 &mut self,
6153 buffer_row: u32,
6154 window: &mut Window,
6155 cx: &mut Context<Self>,
6156 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6157 let mut task = self.code_actions_task.take();
6158 cx.spawn_in(window, async move |editor, cx| {
6159 while let Some(prev_task) = task {
6160 prev_task.await.log_err();
6161 task = editor
6162 .update(cx, |this, _| this.code_actions_task.take())
6163 .ok()?;
6164 }
6165
6166 editor
6167 .update(cx, |editor, cx| {
6168 editor
6169 .available_code_actions
6170 .clone()
6171 .and_then(|(location, code_actions)| {
6172 let snapshot = location.buffer.read(cx).snapshot();
6173 let point_range = location.range.to_point(&snapshot);
6174 let point_range = point_range.start.row..=point_range.end.row;
6175 if point_range.contains(&buffer_row) {
6176 Some(code_actions)
6177 } else {
6178 None
6179 }
6180 })
6181 })
6182 .ok()
6183 .flatten()
6184 })
6185 }
6186
6187 pub fn confirm_code_action(
6188 &mut self,
6189 action: &ConfirmCodeAction,
6190 window: &mut Window,
6191 cx: &mut Context<Self>,
6192 ) -> Option<Task<Result<()>>> {
6193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6194
6195 let actions_menu =
6196 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6197 menu
6198 } else {
6199 return None;
6200 };
6201
6202 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6203 let action = actions_menu.actions.get(action_ix)?;
6204 let title = action.label();
6205 let buffer = actions_menu.buffer;
6206 let workspace = self.workspace()?;
6207
6208 match action {
6209 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6210 workspace.update(cx, |workspace, cx| {
6211 workspace.schedule_resolved_task(
6212 task_source_kind,
6213 resolved_task,
6214 false,
6215 window,
6216 cx,
6217 );
6218
6219 Some(Task::ready(Ok(())))
6220 })
6221 }
6222 CodeActionsItem::CodeAction {
6223 excerpt_id,
6224 action,
6225 provider,
6226 } => {
6227 let apply_code_action =
6228 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6229 let workspace = workspace.downgrade();
6230 Some(cx.spawn_in(window, async move |editor, cx| {
6231 let project_transaction = apply_code_action.await?;
6232 Self::open_project_transaction(
6233 &editor,
6234 workspace,
6235 project_transaction,
6236 title,
6237 cx,
6238 )
6239 .await
6240 }))
6241 }
6242 CodeActionsItem::DebugScenario(scenario) => {
6243 let context = actions_menu.actions.context;
6244
6245 workspace.update(cx, |workspace, cx| {
6246 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6247 workspace.start_debug_session(
6248 scenario,
6249 context,
6250 Some(buffer),
6251 None,
6252 window,
6253 cx,
6254 );
6255 });
6256 Some(Task::ready(Ok(())))
6257 }
6258 }
6259 }
6260
6261 pub async fn open_project_transaction(
6262 editor: &WeakEntity<Editor>,
6263 workspace: WeakEntity<Workspace>,
6264 transaction: ProjectTransaction,
6265 title: String,
6266 cx: &mut AsyncWindowContext,
6267 ) -> Result<()> {
6268 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6269 cx.update(|_, cx| {
6270 entries.sort_unstable_by_key(|(buffer, _)| {
6271 buffer.read(cx).file().map(|f| f.path().clone())
6272 });
6273 })?;
6274 if entries.is_empty() {
6275 return Ok(());
6276 }
6277
6278 // If the project transaction's edits are all contained within this editor, then
6279 // avoid opening a new editor to display them.
6280
6281 if let [(buffer, transaction)] = &*entries {
6282 let excerpt = editor.update(cx, |editor, cx| {
6283 editor
6284 .buffer()
6285 .read(cx)
6286 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6287 })?;
6288 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6289 && excerpted_buffer == *buffer
6290 {
6291 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6292 let excerpt_range = excerpt_range.to_offset(buffer);
6293 buffer
6294 .edited_ranges_for_transaction::<usize>(transaction)
6295 .all(|range| {
6296 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6297 })
6298 })?;
6299
6300 if all_edits_within_excerpt {
6301 return Ok(());
6302 }
6303 }
6304 }
6305
6306 let mut ranges_to_highlight = Vec::new();
6307 let excerpt_buffer = cx.new(|cx| {
6308 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6309 for (buffer_handle, transaction) in &entries {
6310 let edited_ranges = buffer_handle
6311 .read(cx)
6312 .edited_ranges_for_transaction::<Point>(transaction)
6313 .collect::<Vec<_>>();
6314 let (ranges, _) = multibuffer.set_excerpts_for_path(
6315 PathKey::for_buffer(buffer_handle, cx),
6316 buffer_handle.clone(),
6317 edited_ranges,
6318 multibuffer_context_lines(cx),
6319 cx,
6320 );
6321
6322 ranges_to_highlight.extend(ranges);
6323 }
6324 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6325 multibuffer
6326 })?;
6327
6328 workspace.update_in(cx, |workspace, window, cx| {
6329 let project = workspace.project().clone();
6330 let editor =
6331 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6332 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6333 editor.update(cx, |editor, cx| {
6334 editor.highlight_background::<Self>(
6335 &ranges_to_highlight,
6336 |theme| theme.colors().editor_highlighted_line_background,
6337 cx,
6338 );
6339 });
6340 })?;
6341
6342 Ok(())
6343 }
6344
6345 pub fn clear_code_action_providers(&mut self) {
6346 self.code_action_providers.clear();
6347 self.available_code_actions.take();
6348 }
6349
6350 pub fn add_code_action_provider(
6351 &mut self,
6352 provider: Rc<dyn CodeActionProvider>,
6353 window: &mut Window,
6354 cx: &mut Context<Self>,
6355 ) {
6356 if self
6357 .code_action_providers
6358 .iter()
6359 .any(|existing_provider| existing_provider.id() == provider.id())
6360 {
6361 return;
6362 }
6363
6364 self.code_action_providers.push(provider);
6365 self.refresh_code_actions(window, cx);
6366 }
6367
6368 pub fn remove_code_action_provider(
6369 &mut self,
6370 id: Arc<str>,
6371 window: &mut Window,
6372 cx: &mut Context<Self>,
6373 ) {
6374 self.code_action_providers
6375 .retain(|provider| provider.id() != id);
6376 self.refresh_code_actions(window, cx);
6377 }
6378
6379 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6380 !self.code_action_providers.is_empty()
6381 && EditorSettings::get_global(cx).toolbar.code_actions
6382 }
6383
6384 pub fn has_available_code_actions(&self) -> bool {
6385 self.available_code_actions
6386 .as_ref()
6387 .is_some_and(|(_, actions)| !actions.is_empty())
6388 }
6389
6390 fn render_inline_code_actions(
6391 &self,
6392 icon_size: ui::IconSize,
6393 display_row: DisplayRow,
6394 is_active: bool,
6395 cx: &mut Context<Self>,
6396 ) -> AnyElement {
6397 let show_tooltip = !self.context_menu_visible();
6398 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6399 .icon_size(icon_size)
6400 .shape(ui::IconButtonShape::Square)
6401 .icon_color(ui::Color::Hidden)
6402 .toggle_state(is_active)
6403 .when(show_tooltip, |this| {
6404 this.tooltip({
6405 let focus_handle = self.focus_handle.clone();
6406 move |_window, cx| {
6407 Tooltip::for_action_in(
6408 "Toggle Code Actions",
6409 &ToggleCodeActions {
6410 deployed_from: None,
6411 quick_launch: false,
6412 },
6413 &focus_handle,
6414 cx,
6415 )
6416 }
6417 })
6418 })
6419 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6420 window.focus(&editor.focus_handle(cx));
6421 editor.toggle_code_actions(
6422 &crate::actions::ToggleCodeActions {
6423 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6424 display_row,
6425 )),
6426 quick_launch: false,
6427 },
6428 window,
6429 cx,
6430 );
6431 }))
6432 .into_any_element()
6433 }
6434
6435 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6436 &self.context_menu
6437 }
6438
6439 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6440 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6441 cx.background_executor()
6442 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6443 .await;
6444
6445 let (start_buffer, start, _, end, newest_selection) = this
6446 .update(cx, |this, cx| {
6447 let newest_selection = this.selections.newest_anchor().clone();
6448 if newest_selection.head().diff_base_anchor.is_some() {
6449 return None;
6450 }
6451 let display_snapshot = this.display_snapshot(cx);
6452 let newest_selection_adjusted =
6453 this.selections.newest_adjusted(&display_snapshot);
6454 let buffer = this.buffer.read(cx);
6455
6456 let (start_buffer, start) =
6457 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6458 let (end_buffer, end) =
6459 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6460
6461 Some((start_buffer, start, end_buffer, end, newest_selection))
6462 })?
6463 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6464 .context(
6465 "Expected selection to lie in a single buffer when refreshing code actions",
6466 )?;
6467 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6468 let providers = this.code_action_providers.clone();
6469 let tasks = this
6470 .code_action_providers
6471 .iter()
6472 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6473 .collect::<Vec<_>>();
6474 (providers, tasks)
6475 })?;
6476
6477 let mut actions = Vec::new();
6478 for (provider, provider_actions) in
6479 providers.into_iter().zip(future::join_all(tasks).await)
6480 {
6481 if let Some(provider_actions) = provider_actions.log_err() {
6482 actions.extend(provider_actions.into_iter().map(|action| {
6483 AvailableCodeAction {
6484 excerpt_id: newest_selection.start.excerpt_id,
6485 action,
6486 provider: provider.clone(),
6487 }
6488 }));
6489 }
6490 }
6491
6492 this.update(cx, |this, cx| {
6493 this.available_code_actions = if actions.is_empty() {
6494 None
6495 } else {
6496 Some((
6497 Location {
6498 buffer: start_buffer,
6499 range: start..end,
6500 },
6501 actions.into(),
6502 ))
6503 };
6504 cx.notify();
6505 })
6506 }));
6507 }
6508
6509 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6510 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6511 self.show_git_blame_inline = false;
6512
6513 self.show_git_blame_inline_delay_task =
6514 Some(cx.spawn_in(window, async move |this, cx| {
6515 cx.background_executor().timer(delay).await;
6516
6517 this.update(cx, |this, cx| {
6518 this.show_git_blame_inline = true;
6519 cx.notify();
6520 })
6521 .log_err();
6522 }));
6523 }
6524 }
6525
6526 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6527 let snapshot = self.snapshot(window, cx);
6528 let cursor = self
6529 .selections
6530 .newest::<Point>(&snapshot.display_snapshot)
6531 .head();
6532 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6533 else {
6534 return;
6535 };
6536
6537 let Some(blame) = self.blame.as_ref() else {
6538 return;
6539 };
6540
6541 let row_info = RowInfo {
6542 buffer_id: Some(buffer.remote_id()),
6543 buffer_row: Some(point.row),
6544 ..Default::default()
6545 };
6546 let Some((buffer, blame_entry)) = blame
6547 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6548 .flatten()
6549 else {
6550 return;
6551 };
6552
6553 let anchor = self.selections.newest_anchor().head();
6554 let position = self.to_pixel_point(anchor, &snapshot, window);
6555 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6556 self.show_blame_popover(
6557 buffer,
6558 &blame_entry,
6559 position + last_bounds.origin,
6560 true,
6561 cx,
6562 );
6563 };
6564 }
6565
6566 fn show_blame_popover(
6567 &mut self,
6568 buffer: BufferId,
6569 blame_entry: &BlameEntry,
6570 position: gpui::Point<Pixels>,
6571 ignore_timeout: bool,
6572 cx: &mut Context<Self>,
6573 ) {
6574 if let Some(state) = &mut self.inline_blame_popover {
6575 state.hide_task.take();
6576 } else {
6577 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6578 let blame_entry = blame_entry.clone();
6579 let show_task = cx.spawn(async move |editor, cx| {
6580 if !ignore_timeout {
6581 cx.background_executor()
6582 .timer(std::time::Duration::from_millis(blame_popover_delay))
6583 .await;
6584 }
6585 editor
6586 .update(cx, |editor, cx| {
6587 editor.inline_blame_popover_show_task.take();
6588 let Some(blame) = editor.blame.as_ref() else {
6589 return;
6590 };
6591 let blame = blame.read(cx);
6592 let details = blame.details_for_entry(buffer, &blame_entry);
6593 let markdown = cx.new(|cx| {
6594 Markdown::new(
6595 details
6596 .as_ref()
6597 .map(|message| message.message.clone())
6598 .unwrap_or_default(),
6599 None,
6600 None,
6601 cx,
6602 )
6603 });
6604 editor.inline_blame_popover = Some(InlineBlamePopover {
6605 position,
6606 hide_task: None,
6607 popover_bounds: None,
6608 popover_state: InlineBlamePopoverState {
6609 scroll_handle: ScrollHandle::new(),
6610 commit_message: details,
6611 markdown,
6612 },
6613 keyboard_grace: ignore_timeout,
6614 });
6615 cx.notify();
6616 })
6617 .ok();
6618 });
6619 self.inline_blame_popover_show_task = Some(show_task);
6620 }
6621 }
6622
6623 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6624 self.inline_blame_popover_show_task.take();
6625 if let Some(state) = &mut self.inline_blame_popover {
6626 let hide_task = cx.spawn(async move |editor, cx| {
6627 if !ignore_timeout {
6628 cx.background_executor()
6629 .timer(std::time::Duration::from_millis(100))
6630 .await;
6631 }
6632 editor
6633 .update(cx, |editor, cx| {
6634 editor.inline_blame_popover.take();
6635 cx.notify();
6636 })
6637 .ok();
6638 });
6639 state.hide_task = Some(hide_task);
6640 true
6641 } else {
6642 false
6643 }
6644 }
6645
6646 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6647 if self.pending_rename.is_some() {
6648 return None;
6649 }
6650
6651 let provider = self.semantics_provider.clone()?;
6652 let buffer = self.buffer.read(cx);
6653 let newest_selection = self.selections.newest_anchor().clone();
6654 let cursor_position = newest_selection.head();
6655 let (cursor_buffer, cursor_buffer_position) =
6656 buffer.text_anchor_for_position(cursor_position, cx)?;
6657 let (tail_buffer, tail_buffer_position) =
6658 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6659 if cursor_buffer != tail_buffer {
6660 return None;
6661 }
6662
6663 let snapshot = cursor_buffer.read(cx).snapshot();
6664 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6665 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6666 if start_word_range != end_word_range {
6667 self.document_highlights_task.take();
6668 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6669 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6670 return None;
6671 }
6672
6673 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6674 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6675 cx.background_executor()
6676 .timer(Duration::from_millis(debounce))
6677 .await;
6678
6679 let highlights = if let Some(highlights) = cx
6680 .update(|cx| {
6681 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6682 })
6683 .ok()
6684 .flatten()
6685 {
6686 highlights.await.log_err()
6687 } else {
6688 None
6689 };
6690
6691 if let Some(highlights) = highlights {
6692 this.update(cx, |this, cx| {
6693 if this.pending_rename.is_some() {
6694 return;
6695 }
6696
6697 let buffer = this.buffer.read(cx);
6698 if buffer
6699 .text_anchor_for_position(cursor_position, cx)
6700 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6701 {
6702 return;
6703 }
6704
6705 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6706 let mut write_ranges = Vec::new();
6707 let mut read_ranges = Vec::new();
6708 for highlight in highlights {
6709 let buffer_id = cursor_buffer.read(cx).remote_id();
6710 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6711 {
6712 let start = highlight
6713 .range
6714 .start
6715 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6716 let end = highlight
6717 .range
6718 .end
6719 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6720 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6721 continue;
6722 }
6723
6724 let range =
6725 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6726 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6727 write_ranges.push(range);
6728 } else {
6729 read_ranges.push(range);
6730 }
6731 }
6732 }
6733
6734 this.highlight_background::<DocumentHighlightRead>(
6735 &read_ranges,
6736 |theme| theme.colors().editor_document_highlight_read_background,
6737 cx,
6738 );
6739 this.highlight_background::<DocumentHighlightWrite>(
6740 &write_ranges,
6741 |theme| theme.colors().editor_document_highlight_write_background,
6742 cx,
6743 );
6744 cx.notify();
6745 })
6746 .log_err();
6747 }
6748 }));
6749 None
6750 }
6751
6752 fn prepare_highlight_query_from_selection(
6753 &mut self,
6754 window: &Window,
6755 cx: &mut Context<Editor>,
6756 ) -> Option<(String, Range<Anchor>)> {
6757 if matches!(self.mode, EditorMode::SingleLine) {
6758 return None;
6759 }
6760 if !EditorSettings::get_global(cx).selection_highlight {
6761 return None;
6762 }
6763 if self.selections.count() != 1 || self.selections.line_mode() {
6764 return None;
6765 }
6766 let snapshot = self.snapshot(window, cx);
6767 let selection = self.selections.newest::<Point>(&snapshot);
6768 // If the selection spans multiple rows OR it is empty
6769 if selection.start.row != selection.end.row
6770 || selection.start.column == selection.end.column
6771 {
6772 return None;
6773 }
6774 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6775 let query = snapshot
6776 .buffer_snapshot()
6777 .text_for_range(selection_anchor_range.clone())
6778 .collect::<String>();
6779 if query.trim().is_empty() {
6780 return None;
6781 }
6782 Some((query, selection_anchor_range))
6783 }
6784
6785 fn update_selection_occurrence_highlights(
6786 &mut self,
6787 query_text: String,
6788 query_range: Range<Anchor>,
6789 multi_buffer_range_to_query: Range<Point>,
6790 use_debounce: bool,
6791 window: &mut Window,
6792 cx: &mut Context<Editor>,
6793 ) -> Task<()> {
6794 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6795 cx.spawn_in(window, async move |editor, cx| {
6796 if use_debounce {
6797 cx.background_executor()
6798 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6799 .await;
6800 }
6801 let match_task = cx.background_spawn(async move {
6802 let buffer_ranges = multi_buffer_snapshot
6803 .range_to_buffer_ranges(multi_buffer_range_to_query)
6804 .into_iter()
6805 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6806 let mut match_ranges = Vec::new();
6807 let Ok(regex) = project::search::SearchQuery::text(
6808 query_text.clone(),
6809 false,
6810 false,
6811 false,
6812 Default::default(),
6813 Default::default(),
6814 false,
6815 None,
6816 ) else {
6817 return Vec::default();
6818 };
6819 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6820 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6821 match_ranges.extend(
6822 regex
6823 .search(buffer_snapshot, Some(search_range.clone()))
6824 .await
6825 .into_iter()
6826 .filter_map(|match_range| {
6827 let match_start = buffer_snapshot
6828 .anchor_after(search_range.start + match_range.start);
6829 let match_end = buffer_snapshot
6830 .anchor_before(search_range.start + match_range.end);
6831 let match_anchor_range = Anchor::range_in_buffer(
6832 excerpt_id,
6833 buffer_snapshot.remote_id(),
6834 match_start..match_end,
6835 );
6836 (match_anchor_range != query_range).then_some(match_anchor_range)
6837 }),
6838 );
6839 }
6840 match_ranges
6841 });
6842 let match_ranges = match_task.await;
6843 editor
6844 .update_in(cx, |editor, _, cx| {
6845 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6846 if !match_ranges.is_empty() {
6847 editor.highlight_background::<SelectedTextHighlight>(
6848 &match_ranges,
6849 |theme| theme.colors().editor_document_highlight_bracket_background,
6850 cx,
6851 )
6852 }
6853 })
6854 .log_err();
6855 })
6856 }
6857
6858 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6859 struct NewlineFold;
6860 let type_id = std::any::TypeId::of::<NewlineFold>();
6861 if !self.mode.is_single_line() {
6862 return;
6863 }
6864 let snapshot = self.snapshot(window, cx);
6865 if snapshot.buffer_snapshot().max_point().row == 0 {
6866 return;
6867 }
6868 let task = cx.background_spawn(async move {
6869 let new_newlines = snapshot
6870 .buffer_chars_at(0)
6871 .filter_map(|(c, i)| {
6872 if c == '\n' {
6873 Some(
6874 snapshot.buffer_snapshot().anchor_after(i)
6875 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6876 )
6877 } else {
6878 None
6879 }
6880 })
6881 .collect::<Vec<_>>();
6882 let existing_newlines = snapshot
6883 .folds_in_range(0..snapshot.buffer_snapshot().len())
6884 .filter_map(|fold| {
6885 if fold.placeholder.type_tag == Some(type_id) {
6886 Some(fold.range.start..fold.range.end)
6887 } else {
6888 None
6889 }
6890 })
6891 .collect::<Vec<_>>();
6892
6893 (new_newlines, existing_newlines)
6894 });
6895 self.folding_newlines = cx.spawn(async move |this, cx| {
6896 let (new_newlines, existing_newlines) = task.await;
6897 if new_newlines == existing_newlines {
6898 return;
6899 }
6900 let placeholder = FoldPlaceholder {
6901 render: Arc::new(move |_, _, cx| {
6902 div()
6903 .bg(cx.theme().status().hint_background)
6904 .border_b_1()
6905 .size_full()
6906 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6907 .border_color(cx.theme().status().hint)
6908 .child("\\n")
6909 .into_any()
6910 }),
6911 constrain_width: false,
6912 merge_adjacent: false,
6913 type_tag: Some(type_id),
6914 };
6915 let creases = new_newlines
6916 .into_iter()
6917 .map(|range| Crease::simple(range, placeholder.clone()))
6918 .collect();
6919 this.update(cx, |this, cx| {
6920 this.display_map.update(cx, |display_map, cx| {
6921 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6922 display_map.fold(creases, cx);
6923 });
6924 })
6925 .ok();
6926 });
6927 }
6928
6929 fn refresh_selected_text_highlights(
6930 &mut self,
6931 on_buffer_edit: bool,
6932 window: &mut Window,
6933 cx: &mut Context<Editor>,
6934 ) {
6935 let Some((query_text, query_range)) =
6936 self.prepare_highlight_query_from_selection(window, cx)
6937 else {
6938 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6939 self.quick_selection_highlight_task.take();
6940 self.debounced_selection_highlight_task.take();
6941 return;
6942 };
6943 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6944 if on_buffer_edit
6945 || self
6946 .quick_selection_highlight_task
6947 .as_ref()
6948 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6949 {
6950 let multi_buffer_visible_start = self
6951 .scroll_manager
6952 .anchor()
6953 .anchor
6954 .to_point(&multi_buffer_snapshot);
6955 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6956 multi_buffer_visible_start
6957 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6958 Bias::Left,
6959 );
6960 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6961 self.quick_selection_highlight_task = Some((
6962 query_range.clone(),
6963 self.update_selection_occurrence_highlights(
6964 query_text.clone(),
6965 query_range.clone(),
6966 multi_buffer_visible_range,
6967 false,
6968 window,
6969 cx,
6970 ),
6971 ));
6972 }
6973 if on_buffer_edit
6974 || self
6975 .debounced_selection_highlight_task
6976 .as_ref()
6977 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6978 {
6979 let multi_buffer_start = multi_buffer_snapshot
6980 .anchor_before(0)
6981 .to_point(&multi_buffer_snapshot);
6982 let multi_buffer_end = multi_buffer_snapshot
6983 .anchor_after(multi_buffer_snapshot.len())
6984 .to_point(&multi_buffer_snapshot);
6985 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6986 self.debounced_selection_highlight_task = Some((
6987 query_range.clone(),
6988 self.update_selection_occurrence_highlights(
6989 query_text,
6990 query_range,
6991 multi_buffer_full_range,
6992 true,
6993 window,
6994 cx,
6995 ),
6996 ));
6997 }
6998 }
6999
7000 pub fn refresh_edit_prediction(
7001 &mut self,
7002 debounce: bool,
7003 user_requested: bool,
7004 window: &mut Window,
7005 cx: &mut Context<Self>,
7006 ) -> Option<()> {
7007 if DisableAiSettings::get_global(cx).disable_ai {
7008 return None;
7009 }
7010
7011 let provider = self.edit_prediction_provider()?;
7012 let cursor = self.selections.newest_anchor().head();
7013 let (buffer, cursor_buffer_position) =
7014 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7015
7016 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7017 self.discard_edit_prediction(false, cx);
7018 return None;
7019 }
7020
7021 self.update_visible_edit_prediction(window, cx);
7022
7023 if !user_requested
7024 && (!self.should_show_edit_predictions()
7025 || !self.is_focused(window)
7026 || buffer.read(cx).is_empty())
7027 {
7028 self.discard_edit_prediction(false, cx);
7029 return None;
7030 }
7031
7032 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7033 Some(())
7034 }
7035
7036 fn show_edit_predictions_in_menu(&self) -> bool {
7037 match self.edit_prediction_settings {
7038 EditPredictionSettings::Disabled => false,
7039 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7040 }
7041 }
7042
7043 pub fn edit_predictions_enabled(&self) -> bool {
7044 match self.edit_prediction_settings {
7045 EditPredictionSettings::Disabled => false,
7046 EditPredictionSettings::Enabled { .. } => true,
7047 }
7048 }
7049
7050 fn edit_prediction_requires_modifier(&self) -> bool {
7051 match self.edit_prediction_settings {
7052 EditPredictionSettings::Disabled => false,
7053 EditPredictionSettings::Enabled {
7054 preview_requires_modifier,
7055 ..
7056 } => preview_requires_modifier,
7057 }
7058 }
7059
7060 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7061 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7062 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7063 self.discard_edit_prediction(false, cx);
7064 } else {
7065 let selection = self.selections.newest_anchor();
7066 let cursor = selection.head();
7067
7068 if let Some((buffer, cursor_buffer_position)) =
7069 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7070 {
7071 self.edit_prediction_settings =
7072 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7073 }
7074 }
7075 }
7076
7077 fn edit_prediction_settings_at_position(
7078 &self,
7079 buffer: &Entity<Buffer>,
7080 buffer_position: language::Anchor,
7081 cx: &App,
7082 ) -> EditPredictionSettings {
7083 if !self.mode.is_full()
7084 || !self.show_edit_predictions_override.unwrap_or(true)
7085 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7086 {
7087 return EditPredictionSettings::Disabled;
7088 }
7089
7090 let buffer = buffer.read(cx);
7091
7092 let file = buffer.file();
7093
7094 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7095 return EditPredictionSettings::Disabled;
7096 };
7097
7098 let by_provider = matches!(
7099 self.menu_edit_predictions_policy,
7100 MenuEditPredictionsPolicy::ByProvider
7101 );
7102
7103 let show_in_menu = by_provider
7104 && self
7105 .edit_prediction_provider
7106 .as_ref()
7107 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7108
7109 let preview_requires_modifier =
7110 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7111
7112 EditPredictionSettings::Enabled {
7113 show_in_menu,
7114 preview_requires_modifier,
7115 }
7116 }
7117
7118 fn should_show_edit_predictions(&self) -> bool {
7119 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7120 }
7121
7122 pub fn edit_prediction_preview_is_active(&self) -> bool {
7123 matches!(
7124 self.edit_prediction_preview,
7125 EditPredictionPreview::Active { .. }
7126 )
7127 }
7128
7129 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7130 let cursor = self.selections.newest_anchor().head();
7131 if let Some((buffer, cursor_position)) =
7132 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7133 {
7134 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7135 } else {
7136 false
7137 }
7138 }
7139
7140 pub fn supports_minimap(&self, cx: &App) -> bool {
7141 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7142 }
7143
7144 fn edit_predictions_enabled_in_buffer(
7145 &self,
7146 buffer: &Entity<Buffer>,
7147 buffer_position: language::Anchor,
7148 cx: &App,
7149 ) -> bool {
7150 maybe!({
7151 if self.read_only(cx) {
7152 return Some(false);
7153 }
7154 let provider = self.edit_prediction_provider()?;
7155 if !provider.is_enabled(buffer, buffer_position, cx) {
7156 return Some(false);
7157 }
7158 let buffer = buffer.read(cx);
7159 let Some(file) = buffer.file() else {
7160 return Some(true);
7161 };
7162 let settings = all_language_settings(Some(file), cx);
7163 Some(settings.edit_predictions_enabled_for_file(file, cx))
7164 })
7165 .unwrap_or(false)
7166 }
7167
7168 fn cycle_edit_prediction(
7169 &mut self,
7170 direction: Direction,
7171 window: &mut Window,
7172 cx: &mut Context<Self>,
7173 ) -> Option<()> {
7174 let provider = self.edit_prediction_provider()?;
7175 let cursor = self.selections.newest_anchor().head();
7176 let (buffer, cursor_buffer_position) =
7177 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7178 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7179 return None;
7180 }
7181
7182 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7183 self.update_visible_edit_prediction(window, cx);
7184
7185 Some(())
7186 }
7187
7188 pub fn show_edit_prediction(
7189 &mut self,
7190 _: &ShowEditPrediction,
7191 window: &mut Window,
7192 cx: &mut Context<Self>,
7193 ) {
7194 if !self.has_active_edit_prediction() {
7195 self.refresh_edit_prediction(false, true, window, cx);
7196 return;
7197 }
7198
7199 self.update_visible_edit_prediction(window, cx);
7200 }
7201
7202 pub fn display_cursor_names(
7203 &mut self,
7204 _: &DisplayCursorNames,
7205 window: &mut Window,
7206 cx: &mut Context<Self>,
7207 ) {
7208 self.show_cursor_names(window, cx);
7209 }
7210
7211 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7212 self.show_cursor_names = true;
7213 cx.notify();
7214 cx.spawn_in(window, async move |this, cx| {
7215 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7216 this.update(cx, |this, cx| {
7217 this.show_cursor_names = false;
7218 cx.notify()
7219 })
7220 .ok()
7221 })
7222 .detach();
7223 }
7224
7225 pub fn next_edit_prediction(
7226 &mut self,
7227 _: &NextEditPrediction,
7228 window: &mut Window,
7229 cx: &mut Context<Self>,
7230 ) {
7231 if self.has_active_edit_prediction() {
7232 self.cycle_edit_prediction(Direction::Next, window, cx);
7233 } else {
7234 let is_copilot_disabled = self
7235 .refresh_edit_prediction(false, true, window, cx)
7236 .is_none();
7237 if is_copilot_disabled {
7238 cx.propagate();
7239 }
7240 }
7241 }
7242
7243 pub fn previous_edit_prediction(
7244 &mut self,
7245 _: &PreviousEditPrediction,
7246 window: &mut Window,
7247 cx: &mut Context<Self>,
7248 ) {
7249 if self.has_active_edit_prediction() {
7250 self.cycle_edit_prediction(Direction::Prev, window, cx);
7251 } else {
7252 let is_copilot_disabled = self
7253 .refresh_edit_prediction(false, true, window, cx)
7254 .is_none();
7255 if is_copilot_disabled {
7256 cx.propagate();
7257 }
7258 }
7259 }
7260
7261 pub fn accept_edit_prediction(
7262 &mut self,
7263 _: &AcceptEditPrediction,
7264 window: &mut Window,
7265 cx: &mut Context<Self>,
7266 ) {
7267 if self.show_edit_predictions_in_menu() {
7268 self.hide_context_menu(window, cx);
7269 }
7270
7271 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7272 return;
7273 };
7274
7275 match &active_edit_prediction.completion {
7276 EditPrediction::MoveWithin { target, .. } => {
7277 let target = *target;
7278
7279 if let Some(position_map) = &self.last_position_map {
7280 if position_map
7281 .visible_row_range
7282 .contains(&target.to_display_point(&position_map.snapshot).row())
7283 || !self.edit_prediction_requires_modifier()
7284 {
7285 self.unfold_ranges(&[target..target], true, false, cx);
7286 // Note that this is also done in vim's handler of the Tab action.
7287 self.change_selections(
7288 SelectionEffects::scroll(Autoscroll::newest()),
7289 window,
7290 cx,
7291 |selections| {
7292 selections.select_anchor_ranges([target..target]);
7293 },
7294 );
7295 self.clear_row_highlights::<EditPredictionPreview>();
7296
7297 self.edit_prediction_preview
7298 .set_previous_scroll_position(None);
7299 } else {
7300 self.edit_prediction_preview
7301 .set_previous_scroll_position(Some(
7302 position_map.snapshot.scroll_anchor,
7303 ));
7304
7305 self.highlight_rows::<EditPredictionPreview>(
7306 target..target,
7307 cx.theme().colors().editor_highlighted_line_background,
7308 RowHighlightOptions {
7309 autoscroll: true,
7310 ..Default::default()
7311 },
7312 cx,
7313 );
7314 self.request_autoscroll(Autoscroll::fit(), cx);
7315 }
7316 }
7317 }
7318 EditPrediction::MoveOutside { snapshot, target } => {
7319 if let Some(workspace) = self.workspace() {
7320 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7321 .detach_and_log_err(cx);
7322 }
7323 }
7324 EditPrediction::Edit { edits, .. } => {
7325 self.report_edit_prediction_event(
7326 active_edit_prediction.completion_id.clone(),
7327 true,
7328 cx,
7329 );
7330
7331 if let Some(provider) = self.edit_prediction_provider() {
7332 provider.accept(cx);
7333 }
7334
7335 // Store the transaction ID and selections before applying the edit
7336 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7337
7338 let snapshot = self.buffer.read(cx).snapshot(cx);
7339 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7340
7341 self.buffer.update(cx, |buffer, cx| {
7342 buffer.edit(edits.iter().cloned(), None, cx)
7343 });
7344
7345 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7346 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7347 });
7348
7349 let selections = self.selections.disjoint_anchors_arc();
7350 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7351 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7352 if has_new_transaction {
7353 self.selection_history
7354 .insert_transaction(transaction_id_now, selections);
7355 }
7356 }
7357
7358 self.update_visible_edit_prediction(window, cx);
7359 if self.active_edit_prediction.is_none() {
7360 self.refresh_edit_prediction(true, true, window, cx);
7361 }
7362
7363 cx.notify();
7364 }
7365 }
7366
7367 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7368 }
7369
7370 pub fn accept_partial_edit_prediction(
7371 &mut self,
7372 _: &AcceptPartialEditPrediction,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) {
7376 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7377 return;
7378 };
7379 if self.selections.count() != 1 {
7380 return;
7381 }
7382
7383 match &active_edit_prediction.completion {
7384 EditPrediction::MoveWithin { target, .. } => {
7385 let target = *target;
7386 self.change_selections(
7387 SelectionEffects::scroll(Autoscroll::newest()),
7388 window,
7389 cx,
7390 |selections| {
7391 selections.select_anchor_ranges([target..target]);
7392 },
7393 );
7394 }
7395 EditPrediction::MoveOutside { snapshot, target } => {
7396 if let Some(workspace) = self.workspace() {
7397 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7398 .detach_and_log_err(cx);
7399 }
7400 }
7401 EditPrediction::Edit { edits, .. } => {
7402 self.report_edit_prediction_event(
7403 active_edit_prediction.completion_id.clone(),
7404 true,
7405 cx,
7406 );
7407
7408 // Find an insertion that starts at the cursor position.
7409 let snapshot = self.buffer.read(cx).snapshot(cx);
7410 let cursor_offset = self
7411 .selections
7412 .newest::<usize>(&self.display_snapshot(cx))
7413 .head();
7414 let insertion = edits.iter().find_map(|(range, text)| {
7415 let range = range.to_offset(&snapshot);
7416 if range.is_empty() && range.start == cursor_offset {
7417 Some(text)
7418 } else {
7419 None
7420 }
7421 });
7422
7423 if let Some(text) = insertion {
7424 let mut partial_completion = text
7425 .chars()
7426 .by_ref()
7427 .take_while(|c| c.is_alphabetic())
7428 .collect::<String>();
7429 if partial_completion.is_empty() {
7430 partial_completion = text
7431 .chars()
7432 .by_ref()
7433 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7434 .collect::<String>();
7435 }
7436
7437 cx.emit(EditorEvent::InputHandled {
7438 utf16_range_to_replace: None,
7439 text: partial_completion.clone().into(),
7440 });
7441
7442 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7443
7444 self.refresh_edit_prediction(true, true, window, cx);
7445 cx.notify();
7446 } else {
7447 self.accept_edit_prediction(&Default::default(), window, cx);
7448 }
7449 }
7450 }
7451 }
7452
7453 fn discard_edit_prediction(
7454 &mut self,
7455 should_report_edit_prediction_event: bool,
7456 cx: &mut Context<Self>,
7457 ) -> bool {
7458 if should_report_edit_prediction_event {
7459 let completion_id = self
7460 .active_edit_prediction
7461 .as_ref()
7462 .and_then(|active_completion| active_completion.completion_id.clone());
7463
7464 self.report_edit_prediction_event(completion_id, false, cx);
7465 }
7466
7467 if let Some(provider) = self.edit_prediction_provider() {
7468 provider.discard(cx);
7469 }
7470
7471 self.take_active_edit_prediction(cx)
7472 }
7473
7474 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7475 let Some(provider) = self.edit_prediction_provider() else {
7476 return;
7477 };
7478
7479 let Some((_, buffer, _)) = self
7480 .buffer
7481 .read(cx)
7482 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7483 else {
7484 return;
7485 };
7486
7487 let extension = buffer
7488 .read(cx)
7489 .file()
7490 .and_then(|file| Some(file.path().extension()?.to_string()));
7491
7492 let event_type = match accepted {
7493 true => "Edit Prediction Accepted",
7494 false => "Edit Prediction Discarded",
7495 };
7496 telemetry::event!(
7497 event_type,
7498 provider = provider.name(),
7499 prediction_id = id,
7500 suggestion_accepted = accepted,
7501 file_extension = extension,
7502 );
7503 }
7504
7505 fn open_editor_at_anchor(
7506 snapshot: &language::BufferSnapshot,
7507 target: language::Anchor,
7508 workspace: &Entity<Workspace>,
7509 window: &mut Window,
7510 cx: &mut App,
7511 ) -> Task<Result<()>> {
7512 workspace.update(cx, |workspace, cx| {
7513 let path = snapshot.file().map(|file| file.full_path(cx));
7514 let Some(path) =
7515 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7516 else {
7517 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7518 };
7519 let target = text::ToPoint::to_point(&target, snapshot);
7520 let item = workspace.open_path(path, None, true, window, cx);
7521 window.spawn(cx, async move |cx| {
7522 let Some(editor) = item.await?.downcast::<Editor>() else {
7523 return Ok(());
7524 };
7525 editor
7526 .update_in(cx, |editor, window, cx| {
7527 editor.go_to_singleton_buffer_point(target, window, cx);
7528 })
7529 .ok();
7530 anyhow::Ok(())
7531 })
7532 })
7533 }
7534
7535 pub fn has_active_edit_prediction(&self) -> bool {
7536 self.active_edit_prediction.is_some()
7537 }
7538
7539 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7540 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7541 return false;
7542 };
7543
7544 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7545 self.clear_highlights::<EditPredictionHighlight>(cx);
7546 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7547 true
7548 }
7549
7550 /// Returns true when we're displaying the edit prediction popover below the cursor
7551 /// like we are not previewing and the LSP autocomplete menu is visible
7552 /// or we are in `when_holding_modifier` mode.
7553 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7554 if self.edit_prediction_preview_is_active()
7555 || !self.show_edit_predictions_in_menu()
7556 || !self.edit_predictions_enabled()
7557 {
7558 return false;
7559 }
7560
7561 if self.has_visible_completions_menu() {
7562 return true;
7563 }
7564
7565 has_completion && self.edit_prediction_requires_modifier()
7566 }
7567
7568 fn handle_modifiers_changed(
7569 &mut self,
7570 modifiers: Modifiers,
7571 position_map: &PositionMap,
7572 window: &mut Window,
7573 cx: &mut Context<Self>,
7574 ) {
7575 // Ensure that the edit prediction preview is updated, even when not
7576 // enabled, if there's an active edit prediction preview.
7577 if self.show_edit_predictions_in_menu()
7578 || matches!(
7579 self.edit_prediction_preview,
7580 EditPredictionPreview::Active { .. }
7581 )
7582 {
7583 self.update_edit_prediction_preview(&modifiers, window, cx);
7584 }
7585
7586 self.update_selection_mode(&modifiers, position_map, window, cx);
7587
7588 let mouse_position = window.mouse_position();
7589 if !position_map.text_hitbox.is_hovered(window) {
7590 return;
7591 }
7592
7593 self.update_hovered_link(
7594 position_map.point_for_position(mouse_position),
7595 &position_map.snapshot,
7596 modifiers,
7597 window,
7598 cx,
7599 )
7600 }
7601
7602 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7603 match EditorSettings::get_global(cx).multi_cursor_modifier {
7604 MultiCursorModifier::Alt => modifiers.secondary(),
7605 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7606 }
7607 }
7608
7609 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7610 match EditorSettings::get_global(cx).multi_cursor_modifier {
7611 MultiCursorModifier::Alt => modifiers.alt,
7612 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7613 }
7614 }
7615
7616 fn columnar_selection_mode(
7617 modifiers: &Modifiers,
7618 cx: &mut Context<Self>,
7619 ) -> Option<ColumnarMode> {
7620 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7621 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7622 Some(ColumnarMode::FromMouse)
7623 } else if Self::is_alt_pressed(modifiers, cx) {
7624 Some(ColumnarMode::FromSelection)
7625 } else {
7626 None
7627 }
7628 } else {
7629 None
7630 }
7631 }
7632
7633 fn update_selection_mode(
7634 &mut self,
7635 modifiers: &Modifiers,
7636 position_map: &PositionMap,
7637 window: &mut Window,
7638 cx: &mut Context<Self>,
7639 ) {
7640 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7641 return;
7642 };
7643 if self.selections.pending_anchor().is_none() {
7644 return;
7645 }
7646
7647 let mouse_position = window.mouse_position();
7648 let point_for_position = position_map.point_for_position(mouse_position);
7649 let position = point_for_position.previous_valid;
7650
7651 self.select(
7652 SelectPhase::BeginColumnar {
7653 position,
7654 reset: false,
7655 mode,
7656 goal_column: point_for_position.exact_unclipped.column(),
7657 },
7658 window,
7659 cx,
7660 );
7661 }
7662
7663 fn update_edit_prediction_preview(
7664 &mut self,
7665 modifiers: &Modifiers,
7666 window: &mut Window,
7667 cx: &mut Context<Self>,
7668 ) {
7669 let mut modifiers_held = false;
7670 if let Some(accept_keystroke) = self
7671 .accept_edit_prediction_keybind(false, window, cx)
7672 .keystroke()
7673 {
7674 modifiers_held = modifiers_held
7675 || (accept_keystroke.modifiers() == modifiers
7676 && accept_keystroke.modifiers().modified());
7677 };
7678 if let Some(accept_partial_keystroke) = self
7679 .accept_edit_prediction_keybind(true, window, cx)
7680 .keystroke()
7681 {
7682 modifiers_held = modifiers_held
7683 || (accept_partial_keystroke.modifiers() == modifiers
7684 && accept_partial_keystroke.modifiers().modified());
7685 }
7686
7687 if modifiers_held {
7688 if matches!(
7689 self.edit_prediction_preview,
7690 EditPredictionPreview::Inactive { .. }
7691 ) {
7692 self.edit_prediction_preview = EditPredictionPreview::Active {
7693 previous_scroll_position: None,
7694 since: Instant::now(),
7695 };
7696
7697 self.update_visible_edit_prediction(window, cx);
7698 cx.notify();
7699 }
7700 } else if let EditPredictionPreview::Active {
7701 previous_scroll_position,
7702 since,
7703 } = self.edit_prediction_preview
7704 {
7705 if let (Some(previous_scroll_position), Some(position_map)) =
7706 (previous_scroll_position, self.last_position_map.as_ref())
7707 {
7708 self.set_scroll_position(
7709 previous_scroll_position
7710 .scroll_position(&position_map.snapshot.display_snapshot),
7711 window,
7712 cx,
7713 );
7714 }
7715
7716 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7717 released_too_fast: since.elapsed() < Duration::from_millis(200),
7718 };
7719 self.clear_row_highlights::<EditPredictionPreview>();
7720 self.update_visible_edit_prediction(window, cx);
7721 cx.notify();
7722 }
7723 }
7724
7725 fn update_visible_edit_prediction(
7726 &mut self,
7727 _window: &mut Window,
7728 cx: &mut Context<Self>,
7729 ) -> Option<()> {
7730 if DisableAiSettings::get_global(cx).disable_ai {
7731 return None;
7732 }
7733
7734 if self.ime_transaction.is_some() {
7735 self.discard_edit_prediction(false, cx);
7736 return None;
7737 }
7738
7739 let selection = self.selections.newest_anchor();
7740 let cursor = selection.head();
7741 let multibuffer = self.buffer.read(cx).snapshot(cx);
7742 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7743 let excerpt_id = cursor.excerpt_id;
7744
7745 let show_in_menu = self.show_edit_predictions_in_menu();
7746 let completions_menu_has_precedence = !show_in_menu
7747 && (self.context_menu.borrow().is_some()
7748 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7749
7750 if completions_menu_has_precedence
7751 || !offset_selection.is_empty()
7752 || self
7753 .active_edit_prediction
7754 .as_ref()
7755 .is_some_and(|completion| {
7756 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7757 return false;
7758 };
7759 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7760 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7761 !invalidation_range.contains(&offset_selection.head())
7762 })
7763 {
7764 self.discard_edit_prediction(false, cx);
7765 return None;
7766 }
7767
7768 self.take_active_edit_prediction(cx);
7769 let Some(provider) = self.edit_prediction_provider() else {
7770 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7771 return None;
7772 };
7773
7774 let (buffer, cursor_buffer_position) =
7775 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7776
7777 self.edit_prediction_settings =
7778 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7779
7780 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7781
7782 if self.edit_prediction_indent_conflict {
7783 let cursor_point = cursor.to_point(&multibuffer);
7784
7785 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7786
7787 if let Some((_, indent)) = indents.iter().next()
7788 && indent.len == cursor_point.column
7789 {
7790 self.edit_prediction_indent_conflict = false;
7791 }
7792 }
7793
7794 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7795
7796 let (completion_id, edits, edit_preview) = match edit_prediction {
7797 edit_prediction::EditPrediction::Local {
7798 id,
7799 edits,
7800 edit_preview,
7801 } => (id, edits, edit_preview),
7802 edit_prediction::EditPrediction::Jump {
7803 id,
7804 snapshot,
7805 target,
7806 } => {
7807 self.stale_edit_prediction_in_menu = None;
7808 self.active_edit_prediction = Some(EditPredictionState {
7809 inlay_ids: vec![],
7810 completion: EditPrediction::MoveOutside { snapshot, target },
7811 completion_id: id,
7812 invalidation_range: None,
7813 });
7814 cx.notify();
7815 return Some(());
7816 }
7817 };
7818
7819 let edits = edits
7820 .into_iter()
7821 .flat_map(|(range, new_text)| {
7822 Some((
7823 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7824 new_text,
7825 ))
7826 })
7827 .collect::<Vec<_>>();
7828 if edits.is_empty() {
7829 return None;
7830 }
7831
7832 let first_edit_start = edits.first().unwrap().0.start;
7833 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7834 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7835
7836 let last_edit_end = edits.last().unwrap().0.end;
7837 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7838 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7839
7840 let cursor_row = cursor.to_point(&multibuffer).row;
7841
7842 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7843
7844 let mut inlay_ids = Vec::new();
7845 let invalidation_row_range;
7846 let move_invalidation_row_range = if cursor_row < edit_start_row {
7847 Some(cursor_row..edit_end_row)
7848 } else if cursor_row > edit_end_row {
7849 Some(edit_start_row..cursor_row)
7850 } else {
7851 None
7852 };
7853 let supports_jump = self
7854 .edit_prediction_provider
7855 .as_ref()
7856 .map(|provider| provider.provider.supports_jump_to_edit())
7857 .unwrap_or(true);
7858
7859 let is_move = supports_jump
7860 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7861 let completion = if is_move {
7862 invalidation_row_range =
7863 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7864 let target = first_edit_start;
7865 EditPrediction::MoveWithin { target, snapshot }
7866 } else {
7867 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7868 && !self.edit_predictions_hidden_for_vim_mode;
7869
7870 if show_completions_in_buffer {
7871 if edits
7872 .iter()
7873 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7874 {
7875 let mut inlays = Vec::new();
7876 for (range, new_text) in &edits {
7877 let inlay = Inlay::edit_prediction(
7878 post_inc(&mut self.next_inlay_id),
7879 range.start,
7880 new_text.as_str(),
7881 );
7882 inlay_ids.push(inlay.id);
7883 inlays.push(inlay);
7884 }
7885
7886 self.splice_inlays(&[], inlays, cx);
7887 } else {
7888 let background_color = cx.theme().status().deleted_background;
7889 self.highlight_text::<EditPredictionHighlight>(
7890 edits.iter().map(|(range, _)| range.clone()).collect(),
7891 HighlightStyle {
7892 background_color: Some(background_color),
7893 ..Default::default()
7894 },
7895 cx,
7896 );
7897 }
7898 }
7899
7900 invalidation_row_range = edit_start_row..edit_end_row;
7901
7902 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7903 if provider.show_tab_accept_marker() {
7904 EditDisplayMode::TabAccept
7905 } else {
7906 EditDisplayMode::Inline
7907 }
7908 } else {
7909 EditDisplayMode::DiffPopover
7910 };
7911
7912 EditPrediction::Edit {
7913 edits,
7914 edit_preview,
7915 display_mode,
7916 snapshot,
7917 }
7918 };
7919
7920 let invalidation_range = multibuffer
7921 .anchor_before(Point::new(invalidation_row_range.start, 0))
7922 ..multibuffer.anchor_after(Point::new(
7923 invalidation_row_range.end,
7924 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7925 ));
7926
7927 self.stale_edit_prediction_in_menu = None;
7928 self.active_edit_prediction = Some(EditPredictionState {
7929 inlay_ids,
7930 completion,
7931 completion_id,
7932 invalidation_range: Some(invalidation_range),
7933 });
7934
7935 cx.notify();
7936
7937 Some(())
7938 }
7939
7940 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7941 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7942 }
7943
7944 fn clear_tasks(&mut self) {
7945 self.tasks.clear()
7946 }
7947
7948 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7949 if self.tasks.insert(key, value).is_some() {
7950 // This case should hopefully be rare, but just in case...
7951 log::error!(
7952 "multiple different run targets found on a single line, only the last target will be rendered"
7953 )
7954 }
7955 }
7956
7957 /// Get all display points of breakpoints that will be rendered within editor
7958 ///
7959 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7960 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7961 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7962 fn active_breakpoints(
7963 &self,
7964 range: Range<DisplayRow>,
7965 window: &mut Window,
7966 cx: &mut Context<Self>,
7967 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7968 let mut breakpoint_display_points = HashMap::default();
7969
7970 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7971 return breakpoint_display_points;
7972 };
7973
7974 let snapshot = self.snapshot(window, cx);
7975
7976 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7977 let Some(project) = self.project() else {
7978 return breakpoint_display_points;
7979 };
7980
7981 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7982 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7983
7984 for (buffer_snapshot, range, excerpt_id) in
7985 multi_buffer_snapshot.range_to_buffer_ranges(range)
7986 {
7987 let Some(buffer) = project
7988 .read(cx)
7989 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7990 else {
7991 continue;
7992 };
7993 let breakpoints = breakpoint_store.read(cx).breakpoints(
7994 &buffer,
7995 Some(
7996 buffer_snapshot.anchor_before(range.start)
7997 ..buffer_snapshot.anchor_after(range.end),
7998 ),
7999 buffer_snapshot,
8000 cx,
8001 );
8002 for (breakpoint, state) in breakpoints {
8003 let multi_buffer_anchor =
8004 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8005 let position = multi_buffer_anchor
8006 .to_point(&multi_buffer_snapshot)
8007 .to_display_point(&snapshot);
8008
8009 breakpoint_display_points.insert(
8010 position.row(),
8011 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8012 );
8013 }
8014 }
8015
8016 breakpoint_display_points
8017 }
8018
8019 fn breakpoint_context_menu(
8020 &self,
8021 anchor: Anchor,
8022 window: &mut Window,
8023 cx: &mut Context<Self>,
8024 ) -> Entity<ui::ContextMenu> {
8025 let weak_editor = cx.weak_entity();
8026 let focus_handle = self.focus_handle(cx);
8027
8028 let row = self
8029 .buffer
8030 .read(cx)
8031 .snapshot(cx)
8032 .summary_for_anchor::<Point>(&anchor)
8033 .row;
8034
8035 let breakpoint = self
8036 .breakpoint_at_row(row, window, cx)
8037 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8038
8039 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8040 "Edit Log Breakpoint"
8041 } else {
8042 "Set Log Breakpoint"
8043 };
8044
8045 let condition_breakpoint_msg = if breakpoint
8046 .as_ref()
8047 .is_some_and(|bp| bp.1.condition.is_some())
8048 {
8049 "Edit Condition Breakpoint"
8050 } else {
8051 "Set Condition Breakpoint"
8052 };
8053
8054 let hit_condition_breakpoint_msg = if breakpoint
8055 .as_ref()
8056 .is_some_and(|bp| bp.1.hit_condition.is_some())
8057 {
8058 "Edit Hit Condition Breakpoint"
8059 } else {
8060 "Set Hit Condition Breakpoint"
8061 };
8062
8063 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8064 "Unset Breakpoint"
8065 } else {
8066 "Set Breakpoint"
8067 };
8068
8069 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8070
8071 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8072 BreakpointState::Enabled => Some("Disable"),
8073 BreakpointState::Disabled => Some("Enable"),
8074 });
8075
8076 let (anchor, breakpoint) =
8077 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8078
8079 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8080 menu.on_blur_subscription(Subscription::new(|| {}))
8081 .context(focus_handle)
8082 .when(run_to_cursor, |this| {
8083 let weak_editor = weak_editor.clone();
8084 this.entry("Run to cursor", None, move |window, cx| {
8085 weak_editor
8086 .update(cx, |editor, cx| {
8087 editor.change_selections(
8088 SelectionEffects::no_scroll(),
8089 window,
8090 cx,
8091 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8092 );
8093 })
8094 .ok();
8095
8096 window.dispatch_action(Box::new(RunToCursor), cx);
8097 })
8098 .separator()
8099 })
8100 .when_some(toggle_state_msg, |this, msg| {
8101 this.entry(msg, None, {
8102 let weak_editor = weak_editor.clone();
8103 let breakpoint = breakpoint.clone();
8104 move |_window, cx| {
8105 weak_editor
8106 .update(cx, |this, cx| {
8107 this.edit_breakpoint_at_anchor(
8108 anchor,
8109 breakpoint.as_ref().clone(),
8110 BreakpointEditAction::InvertState,
8111 cx,
8112 );
8113 })
8114 .log_err();
8115 }
8116 })
8117 })
8118 .entry(set_breakpoint_msg, None, {
8119 let weak_editor = weak_editor.clone();
8120 let breakpoint = breakpoint.clone();
8121 move |_window, cx| {
8122 weak_editor
8123 .update(cx, |this, cx| {
8124 this.edit_breakpoint_at_anchor(
8125 anchor,
8126 breakpoint.as_ref().clone(),
8127 BreakpointEditAction::Toggle,
8128 cx,
8129 );
8130 })
8131 .log_err();
8132 }
8133 })
8134 .entry(log_breakpoint_msg, None, {
8135 let breakpoint = breakpoint.clone();
8136 let weak_editor = weak_editor.clone();
8137 move |window, cx| {
8138 weak_editor
8139 .update(cx, |this, cx| {
8140 this.add_edit_breakpoint_block(
8141 anchor,
8142 breakpoint.as_ref(),
8143 BreakpointPromptEditAction::Log,
8144 window,
8145 cx,
8146 );
8147 })
8148 .log_err();
8149 }
8150 })
8151 .entry(condition_breakpoint_msg, None, {
8152 let breakpoint = breakpoint.clone();
8153 let weak_editor = weak_editor.clone();
8154 move |window, cx| {
8155 weak_editor
8156 .update(cx, |this, cx| {
8157 this.add_edit_breakpoint_block(
8158 anchor,
8159 breakpoint.as_ref(),
8160 BreakpointPromptEditAction::Condition,
8161 window,
8162 cx,
8163 );
8164 })
8165 .log_err();
8166 }
8167 })
8168 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8169 weak_editor
8170 .update(cx, |this, cx| {
8171 this.add_edit_breakpoint_block(
8172 anchor,
8173 breakpoint.as_ref(),
8174 BreakpointPromptEditAction::HitCondition,
8175 window,
8176 cx,
8177 );
8178 })
8179 .log_err();
8180 })
8181 })
8182 }
8183
8184 fn render_breakpoint(
8185 &self,
8186 position: Anchor,
8187 row: DisplayRow,
8188 breakpoint: &Breakpoint,
8189 state: Option<BreakpointSessionState>,
8190 cx: &mut Context<Self>,
8191 ) -> IconButton {
8192 let is_rejected = state.is_some_and(|s| !s.verified);
8193 // Is it a breakpoint that shows up when hovering over gutter?
8194 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8195 (false, false),
8196 |PhantomBreakpointIndicator {
8197 is_active,
8198 display_row,
8199 collides_with_existing_breakpoint,
8200 }| {
8201 (
8202 is_active && display_row == row,
8203 collides_with_existing_breakpoint,
8204 )
8205 },
8206 );
8207
8208 let (color, icon) = {
8209 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8210 (false, false) => ui::IconName::DebugBreakpoint,
8211 (true, false) => ui::IconName::DebugLogBreakpoint,
8212 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8213 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8214 };
8215
8216 let color = if is_phantom {
8217 Color::Hint
8218 } else if is_rejected {
8219 Color::Disabled
8220 } else {
8221 Color::Debugger
8222 };
8223
8224 (color, icon)
8225 };
8226
8227 let breakpoint = Arc::from(breakpoint.clone());
8228
8229 let alt_as_text = gpui::Keystroke {
8230 modifiers: Modifiers::secondary_key(),
8231 ..Default::default()
8232 };
8233 let primary_action_text = if breakpoint.is_disabled() {
8234 "Enable breakpoint"
8235 } else if is_phantom && !collides_with_existing {
8236 "Set breakpoint"
8237 } else {
8238 "Unset breakpoint"
8239 };
8240 let focus_handle = self.focus_handle.clone();
8241
8242 let meta = if is_rejected {
8243 SharedString::from("No executable code is associated with this line.")
8244 } else if collides_with_existing && !breakpoint.is_disabled() {
8245 SharedString::from(format!(
8246 "{alt_as_text}-click to disable,\nright-click for more options."
8247 ))
8248 } else {
8249 SharedString::from("Right-click for more options.")
8250 };
8251 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8252 .icon_size(IconSize::XSmall)
8253 .size(ui::ButtonSize::None)
8254 .when(is_rejected, |this| {
8255 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8256 })
8257 .icon_color(color)
8258 .style(ButtonStyle::Transparent)
8259 .on_click(cx.listener({
8260 move |editor, event: &ClickEvent, window, cx| {
8261 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8262 BreakpointEditAction::InvertState
8263 } else {
8264 BreakpointEditAction::Toggle
8265 };
8266
8267 window.focus(&editor.focus_handle(cx));
8268 editor.edit_breakpoint_at_anchor(
8269 position,
8270 breakpoint.as_ref().clone(),
8271 edit_action,
8272 cx,
8273 );
8274 }
8275 }))
8276 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8277 editor.set_breakpoint_context_menu(
8278 row,
8279 Some(position),
8280 event.position(),
8281 window,
8282 cx,
8283 );
8284 }))
8285 .tooltip(move |_window, cx| {
8286 Tooltip::with_meta_in(
8287 primary_action_text,
8288 Some(&ToggleBreakpoint),
8289 meta.clone(),
8290 &focus_handle,
8291 cx,
8292 )
8293 })
8294 }
8295
8296 fn build_tasks_context(
8297 project: &Entity<Project>,
8298 buffer: &Entity<Buffer>,
8299 buffer_row: u32,
8300 tasks: &Arc<RunnableTasks>,
8301 cx: &mut Context<Self>,
8302 ) -> Task<Option<task::TaskContext>> {
8303 let position = Point::new(buffer_row, tasks.column);
8304 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8305 let location = Location {
8306 buffer: buffer.clone(),
8307 range: range_start..range_start,
8308 };
8309 // Fill in the environmental variables from the tree-sitter captures
8310 let mut captured_task_variables = TaskVariables::default();
8311 for (capture_name, value) in tasks.extra_variables.clone() {
8312 captured_task_variables.insert(
8313 task::VariableName::Custom(capture_name.into()),
8314 value.clone(),
8315 );
8316 }
8317 project.update(cx, |project, cx| {
8318 project.task_store().update(cx, |task_store, cx| {
8319 task_store.task_context_for_location(captured_task_variables, location, cx)
8320 })
8321 })
8322 }
8323
8324 pub fn spawn_nearest_task(
8325 &mut self,
8326 action: &SpawnNearestTask,
8327 window: &mut Window,
8328 cx: &mut Context<Self>,
8329 ) {
8330 let Some((workspace, _)) = self.workspace.clone() else {
8331 return;
8332 };
8333 let Some(project) = self.project.clone() else {
8334 return;
8335 };
8336
8337 // Try to find a closest, enclosing node using tree-sitter that has a task
8338 let Some((buffer, buffer_row, tasks)) = self
8339 .find_enclosing_node_task(cx)
8340 // Or find the task that's closest in row-distance.
8341 .or_else(|| self.find_closest_task(cx))
8342 else {
8343 return;
8344 };
8345
8346 let reveal_strategy = action.reveal;
8347 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8348 cx.spawn_in(window, async move |_, cx| {
8349 let context = task_context.await?;
8350 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8351
8352 let resolved = &mut resolved_task.resolved;
8353 resolved.reveal = reveal_strategy;
8354
8355 workspace
8356 .update_in(cx, |workspace, window, cx| {
8357 workspace.schedule_resolved_task(
8358 task_source_kind,
8359 resolved_task,
8360 false,
8361 window,
8362 cx,
8363 );
8364 })
8365 .ok()
8366 })
8367 .detach();
8368 }
8369
8370 fn find_closest_task(
8371 &mut self,
8372 cx: &mut Context<Self>,
8373 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8374 let cursor_row = self
8375 .selections
8376 .newest_adjusted(&self.display_snapshot(cx))
8377 .head()
8378 .row;
8379
8380 let ((buffer_id, row), tasks) = self
8381 .tasks
8382 .iter()
8383 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8384
8385 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8386 let tasks = Arc::new(tasks.to_owned());
8387 Some((buffer, *row, tasks))
8388 }
8389
8390 fn find_enclosing_node_task(
8391 &mut self,
8392 cx: &mut Context<Self>,
8393 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8394 let snapshot = self.buffer.read(cx).snapshot(cx);
8395 let offset = self
8396 .selections
8397 .newest::<usize>(&self.display_snapshot(cx))
8398 .head();
8399 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8400 let buffer_id = excerpt.buffer().remote_id();
8401
8402 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8403 let mut cursor = layer.node().walk();
8404
8405 while cursor.goto_first_child_for_byte(offset).is_some() {
8406 if cursor.node().end_byte() == offset {
8407 cursor.goto_next_sibling();
8408 }
8409 }
8410
8411 // Ascend to the smallest ancestor that contains the range and has a task.
8412 loop {
8413 let node = cursor.node();
8414 let node_range = node.byte_range();
8415 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8416
8417 // Check if this node contains our offset
8418 if node_range.start <= offset && node_range.end >= offset {
8419 // If it contains offset, check for task
8420 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8421 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8422 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8423 }
8424 }
8425
8426 if !cursor.goto_parent() {
8427 break;
8428 }
8429 }
8430 None
8431 }
8432
8433 fn render_run_indicator(
8434 &self,
8435 _style: &EditorStyle,
8436 is_active: bool,
8437 row: DisplayRow,
8438 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8439 cx: &mut Context<Self>,
8440 ) -> IconButton {
8441 let color = Color::Muted;
8442 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8443
8444 IconButton::new(
8445 ("run_indicator", row.0 as usize),
8446 ui::IconName::PlayOutlined,
8447 )
8448 .shape(ui::IconButtonShape::Square)
8449 .icon_size(IconSize::XSmall)
8450 .icon_color(color)
8451 .toggle_state(is_active)
8452 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8453 let quick_launch = match e {
8454 ClickEvent::Keyboard(_) => true,
8455 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8456 };
8457
8458 window.focus(&editor.focus_handle(cx));
8459 editor.toggle_code_actions(
8460 &ToggleCodeActions {
8461 deployed_from: Some(CodeActionSource::RunMenu(row)),
8462 quick_launch,
8463 },
8464 window,
8465 cx,
8466 );
8467 }))
8468 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8469 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8470 }))
8471 }
8472
8473 pub fn context_menu_visible(&self) -> bool {
8474 !self.edit_prediction_preview_is_active()
8475 && self
8476 .context_menu
8477 .borrow()
8478 .as_ref()
8479 .is_some_and(|menu| menu.visible())
8480 }
8481
8482 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8483 self.context_menu
8484 .borrow()
8485 .as_ref()
8486 .map(|menu| menu.origin())
8487 }
8488
8489 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8490 self.context_menu_options = Some(options);
8491 }
8492
8493 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8494 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8495
8496 fn render_edit_prediction_popover(
8497 &mut self,
8498 text_bounds: &Bounds<Pixels>,
8499 content_origin: gpui::Point<Pixels>,
8500 right_margin: Pixels,
8501 editor_snapshot: &EditorSnapshot,
8502 visible_row_range: Range<DisplayRow>,
8503 scroll_top: ScrollOffset,
8504 scroll_bottom: ScrollOffset,
8505 line_layouts: &[LineWithInvisibles],
8506 line_height: Pixels,
8507 scroll_position: gpui::Point<ScrollOffset>,
8508 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8509 newest_selection_head: Option<DisplayPoint>,
8510 editor_width: Pixels,
8511 style: &EditorStyle,
8512 window: &mut Window,
8513 cx: &mut App,
8514 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8515 if self.mode().is_minimap() {
8516 return None;
8517 }
8518 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8519
8520 if self.edit_prediction_visible_in_cursor_popover(true) {
8521 return None;
8522 }
8523
8524 match &active_edit_prediction.completion {
8525 EditPrediction::MoveWithin { target, .. } => {
8526 let target_display_point = target.to_display_point(editor_snapshot);
8527
8528 if self.edit_prediction_requires_modifier() {
8529 if !self.edit_prediction_preview_is_active() {
8530 return None;
8531 }
8532
8533 self.render_edit_prediction_modifier_jump_popover(
8534 text_bounds,
8535 content_origin,
8536 visible_row_range,
8537 line_layouts,
8538 line_height,
8539 scroll_pixel_position,
8540 newest_selection_head,
8541 target_display_point,
8542 window,
8543 cx,
8544 )
8545 } else {
8546 self.render_edit_prediction_eager_jump_popover(
8547 text_bounds,
8548 content_origin,
8549 editor_snapshot,
8550 visible_row_range,
8551 scroll_top,
8552 scroll_bottom,
8553 line_height,
8554 scroll_pixel_position,
8555 target_display_point,
8556 editor_width,
8557 window,
8558 cx,
8559 )
8560 }
8561 }
8562 EditPrediction::Edit {
8563 display_mode: EditDisplayMode::Inline,
8564 ..
8565 } => None,
8566 EditPrediction::Edit {
8567 display_mode: EditDisplayMode::TabAccept,
8568 edits,
8569 ..
8570 } => {
8571 let range = &edits.first()?.0;
8572 let target_display_point = range.end.to_display_point(editor_snapshot);
8573
8574 self.render_edit_prediction_end_of_line_popover(
8575 "Accept",
8576 editor_snapshot,
8577 visible_row_range,
8578 target_display_point,
8579 line_height,
8580 scroll_pixel_position,
8581 content_origin,
8582 editor_width,
8583 window,
8584 cx,
8585 )
8586 }
8587 EditPrediction::Edit {
8588 edits,
8589 edit_preview,
8590 display_mode: EditDisplayMode::DiffPopover,
8591 snapshot,
8592 } => self.render_edit_prediction_diff_popover(
8593 text_bounds,
8594 content_origin,
8595 right_margin,
8596 editor_snapshot,
8597 visible_row_range,
8598 line_layouts,
8599 line_height,
8600 scroll_position,
8601 scroll_pixel_position,
8602 newest_selection_head,
8603 editor_width,
8604 style,
8605 edits,
8606 edit_preview,
8607 snapshot,
8608 window,
8609 cx,
8610 ),
8611 EditPrediction::MoveOutside { snapshot, .. } => {
8612 let file_name = snapshot
8613 .file()
8614 .map(|file| file.file_name(cx))
8615 .unwrap_or("untitled");
8616 let mut element = self
8617 .render_edit_prediction_line_popover(
8618 format!("Jump to {file_name}"),
8619 Some(IconName::ZedPredict),
8620 window,
8621 cx,
8622 )
8623 .into_any();
8624
8625 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8626 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8627 let origin_y = text_bounds.size.height - size.height - px(30.);
8628 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8629 element.prepaint_at(origin, window, cx);
8630
8631 Some((element, origin))
8632 }
8633 }
8634 }
8635
8636 fn render_edit_prediction_modifier_jump_popover(
8637 &mut self,
8638 text_bounds: &Bounds<Pixels>,
8639 content_origin: gpui::Point<Pixels>,
8640 visible_row_range: Range<DisplayRow>,
8641 line_layouts: &[LineWithInvisibles],
8642 line_height: Pixels,
8643 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8644 newest_selection_head: Option<DisplayPoint>,
8645 target_display_point: DisplayPoint,
8646 window: &mut Window,
8647 cx: &mut App,
8648 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8649 let scrolled_content_origin =
8650 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8651
8652 const SCROLL_PADDING_Y: Pixels = px(12.);
8653
8654 if target_display_point.row() < visible_row_range.start {
8655 return self.render_edit_prediction_scroll_popover(
8656 |_| SCROLL_PADDING_Y,
8657 IconName::ArrowUp,
8658 visible_row_range,
8659 line_layouts,
8660 newest_selection_head,
8661 scrolled_content_origin,
8662 window,
8663 cx,
8664 );
8665 } else if target_display_point.row() >= visible_row_range.end {
8666 return self.render_edit_prediction_scroll_popover(
8667 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8668 IconName::ArrowDown,
8669 visible_row_range,
8670 line_layouts,
8671 newest_selection_head,
8672 scrolled_content_origin,
8673 window,
8674 cx,
8675 );
8676 }
8677
8678 const POLE_WIDTH: Pixels = px(2.);
8679
8680 let line_layout =
8681 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8682 let target_column = target_display_point.column() as usize;
8683
8684 let target_x = line_layout.x_for_index(target_column);
8685 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8686 - scroll_pixel_position.y;
8687
8688 let flag_on_right = target_x < text_bounds.size.width / 2.;
8689
8690 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8691 border_color.l += 0.001;
8692
8693 let mut element = v_flex()
8694 .items_end()
8695 .when(flag_on_right, |el| el.items_start())
8696 .child(if flag_on_right {
8697 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8698 .rounded_bl(px(0.))
8699 .rounded_tl(px(0.))
8700 .border_l_2()
8701 .border_color(border_color)
8702 } else {
8703 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8704 .rounded_br(px(0.))
8705 .rounded_tr(px(0.))
8706 .border_r_2()
8707 .border_color(border_color)
8708 })
8709 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8710 .into_any();
8711
8712 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8713
8714 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8715 - point(
8716 if flag_on_right {
8717 POLE_WIDTH
8718 } else {
8719 size.width - POLE_WIDTH
8720 },
8721 size.height - line_height,
8722 );
8723
8724 origin.x = origin.x.max(content_origin.x);
8725
8726 element.prepaint_at(origin, window, cx);
8727
8728 Some((element, origin))
8729 }
8730
8731 fn render_edit_prediction_scroll_popover(
8732 &mut self,
8733 to_y: impl Fn(Size<Pixels>) -> Pixels,
8734 scroll_icon: IconName,
8735 visible_row_range: Range<DisplayRow>,
8736 line_layouts: &[LineWithInvisibles],
8737 newest_selection_head: Option<DisplayPoint>,
8738 scrolled_content_origin: gpui::Point<Pixels>,
8739 window: &mut Window,
8740 cx: &mut App,
8741 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8742 let mut element = self
8743 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8744 .into_any();
8745
8746 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8747
8748 let cursor = newest_selection_head?;
8749 let cursor_row_layout =
8750 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8751 let cursor_column = cursor.column() as usize;
8752
8753 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8754
8755 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8756
8757 element.prepaint_at(origin, window, cx);
8758 Some((element, origin))
8759 }
8760
8761 fn render_edit_prediction_eager_jump_popover(
8762 &mut self,
8763 text_bounds: &Bounds<Pixels>,
8764 content_origin: gpui::Point<Pixels>,
8765 editor_snapshot: &EditorSnapshot,
8766 visible_row_range: Range<DisplayRow>,
8767 scroll_top: ScrollOffset,
8768 scroll_bottom: ScrollOffset,
8769 line_height: Pixels,
8770 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8771 target_display_point: DisplayPoint,
8772 editor_width: Pixels,
8773 window: &mut Window,
8774 cx: &mut App,
8775 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8776 if target_display_point.row().as_f64() < scroll_top {
8777 let mut element = self
8778 .render_edit_prediction_line_popover(
8779 "Jump to Edit",
8780 Some(IconName::ArrowUp),
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 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 if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8796 let mut element = self
8797 .render_edit_prediction_line_popover(
8798 "Jump to Edit",
8799 Some(IconName::ArrowDown),
8800 window,
8801 cx,
8802 )
8803 .into_any();
8804
8805 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8806 let offset = point(
8807 (text_bounds.size.width - size.width) / 2.,
8808 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8809 );
8810
8811 let origin = text_bounds.origin + offset;
8812 element.prepaint_at(origin, window, cx);
8813 Some((element, origin))
8814 } else {
8815 self.render_edit_prediction_end_of_line_popover(
8816 "Jump to Edit",
8817 editor_snapshot,
8818 visible_row_range,
8819 target_display_point,
8820 line_height,
8821 scroll_pixel_position,
8822 content_origin,
8823 editor_width,
8824 window,
8825 cx,
8826 )
8827 }
8828 }
8829
8830 fn render_edit_prediction_end_of_line_popover(
8831 self: &mut Editor,
8832 label: &'static str,
8833 editor_snapshot: &EditorSnapshot,
8834 visible_row_range: Range<DisplayRow>,
8835 target_display_point: DisplayPoint,
8836 line_height: Pixels,
8837 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8838 content_origin: gpui::Point<Pixels>,
8839 editor_width: Pixels,
8840 window: &mut Window,
8841 cx: &mut App,
8842 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8843 let target_line_end = DisplayPoint::new(
8844 target_display_point.row(),
8845 editor_snapshot.line_len(target_display_point.row()),
8846 );
8847
8848 let mut element = self
8849 .render_edit_prediction_line_popover(label, None, window, cx)
8850 .into_any();
8851
8852 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8853
8854 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8855
8856 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8857 let mut origin = start_point
8858 + line_origin
8859 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8860 origin.x = origin.x.max(content_origin.x);
8861
8862 let max_x = content_origin.x + editor_width - size.width;
8863
8864 if origin.x > max_x {
8865 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8866
8867 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8868 origin.y += offset;
8869 IconName::ArrowUp
8870 } else {
8871 origin.y -= offset;
8872 IconName::ArrowDown
8873 };
8874
8875 element = self
8876 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8877 .into_any();
8878
8879 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8880
8881 origin.x = content_origin.x + editor_width - size.width - px(2.);
8882 }
8883
8884 element.prepaint_at(origin, window, cx);
8885 Some((element, origin))
8886 }
8887
8888 fn render_edit_prediction_diff_popover(
8889 self: &Editor,
8890 text_bounds: &Bounds<Pixels>,
8891 content_origin: gpui::Point<Pixels>,
8892 right_margin: Pixels,
8893 editor_snapshot: &EditorSnapshot,
8894 visible_row_range: Range<DisplayRow>,
8895 line_layouts: &[LineWithInvisibles],
8896 line_height: Pixels,
8897 scroll_position: gpui::Point<ScrollOffset>,
8898 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8899 newest_selection_head: Option<DisplayPoint>,
8900 editor_width: Pixels,
8901 style: &EditorStyle,
8902 edits: &Vec<(Range<Anchor>, String)>,
8903 edit_preview: &Option<language::EditPreview>,
8904 snapshot: &language::BufferSnapshot,
8905 window: &mut Window,
8906 cx: &mut App,
8907 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8908 let edit_start = edits
8909 .first()
8910 .unwrap()
8911 .0
8912 .start
8913 .to_display_point(editor_snapshot);
8914 let edit_end = edits
8915 .last()
8916 .unwrap()
8917 .0
8918 .end
8919 .to_display_point(editor_snapshot);
8920
8921 let is_visible = visible_row_range.contains(&edit_start.row())
8922 || visible_row_range.contains(&edit_end.row());
8923 if !is_visible {
8924 return None;
8925 }
8926
8927 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8928 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8929 } else {
8930 // Fallback for providers without edit_preview
8931 crate::edit_prediction_fallback_text(edits, cx)
8932 };
8933
8934 let styled_text = highlighted_edits.to_styled_text(&style.text);
8935 let line_count = highlighted_edits.text.lines().count();
8936
8937 const BORDER_WIDTH: Pixels = px(1.);
8938
8939 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8940 let has_keybind = keybind.is_some();
8941
8942 let mut element = h_flex()
8943 .items_start()
8944 .child(
8945 h_flex()
8946 .bg(cx.theme().colors().editor_background)
8947 .border(BORDER_WIDTH)
8948 .shadow_xs()
8949 .border_color(cx.theme().colors().border)
8950 .rounded_l_lg()
8951 .when(line_count > 1, |el| el.rounded_br_lg())
8952 .pr_1()
8953 .child(styled_text),
8954 )
8955 .child(
8956 h_flex()
8957 .h(line_height + BORDER_WIDTH * 2.)
8958 .px_1p5()
8959 .gap_1()
8960 // Workaround: For some reason, there's a gap if we don't do this
8961 .ml(-BORDER_WIDTH)
8962 .shadow(vec![gpui::BoxShadow {
8963 color: gpui::black().opacity(0.05),
8964 offset: point(px(1.), px(1.)),
8965 blur_radius: px(2.),
8966 spread_radius: px(0.),
8967 }])
8968 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8969 .border(BORDER_WIDTH)
8970 .border_color(cx.theme().colors().border)
8971 .rounded_r_lg()
8972 .id("edit_prediction_diff_popover_keybind")
8973 .when(!has_keybind, |el| {
8974 let status_colors = cx.theme().status();
8975
8976 el.bg(status_colors.error_background)
8977 .border_color(status_colors.error.opacity(0.6))
8978 .child(Icon::new(IconName::Info).color(Color::Error))
8979 .cursor_default()
8980 .hoverable_tooltip(move |_window, cx| {
8981 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8982 })
8983 })
8984 .children(keybind),
8985 )
8986 .into_any();
8987
8988 let longest_row =
8989 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8990 let longest_line_width = if visible_row_range.contains(&longest_row) {
8991 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8992 } else {
8993 layout_line(
8994 longest_row,
8995 editor_snapshot,
8996 style,
8997 editor_width,
8998 |_| false,
8999 window,
9000 cx,
9001 )
9002 .width
9003 };
9004
9005 let viewport_bounds =
9006 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9007 right: -right_margin,
9008 ..Default::default()
9009 });
9010
9011 let x_after_longest = Pixels::from(
9012 ScrollPixelOffset::from(
9013 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9014 ) - scroll_pixel_position.x,
9015 );
9016
9017 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9018
9019 // Fully visible if it can be displayed within the window (allow overlapping other
9020 // panes). However, this is only allowed if the popover starts within text_bounds.
9021 let can_position_to_the_right = x_after_longest < text_bounds.right()
9022 && x_after_longest + element_bounds.width < viewport_bounds.right();
9023
9024 let mut origin = if can_position_to_the_right {
9025 point(
9026 x_after_longest,
9027 text_bounds.origin.y
9028 + Pixels::from(
9029 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9030 - scroll_pixel_position.y,
9031 ),
9032 )
9033 } else {
9034 let cursor_row = newest_selection_head.map(|head| head.row());
9035 let above_edit = edit_start
9036 .row()
9037 .0
9038 .checked_sub(line_count as u32)
9039 .map(DisplayRow);
9040 let below_edit = Some(edit_end.row() + 1);
9041 let above_cursor =
9042 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9043 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9044
9045 // Place the edit popover adjacent to the edit if there is a location
9046 // available that is onscreen and does not obscure the cursor. Otherwise,
9047 // place it adjacent to the cursor.
9048 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9049 .into_iter()
9050 .flatten()
9051 .find(|&start_row| {
9052 let end_row = start_row + line_count as u32;
9053 visible_row_range.contains(&start_row)
9054 && visible_row_range.contains(&end_row)
9055 && cursor_row
9056 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9057 })?;
9058
9059 content_origin
9060 + point(
9061 Pixels::from(-scroll_pixel_position.x),
9062 Pixels::from(
9063 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9064 ),
9065 )
9066 };
9067
9068 origin.x -= BORDER_WIDTH;
9069
9070 window.defer_draw(element, origin, 1);
9071
9072 // Do not return an element, since it will already be drawn due to defer_draw.
9073 None
9074 }
9075
9076 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9077 px(30.)
9078 }
9079
9080 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9081 if self.read_only(cx) {
9082 cx.theme().players().read_only()
9083 } else {
9084 self.style.as_ref().unwrap().local_player
9085 }
9086 }
9087
9088 fn render_edit_prediction_accept_keybind(
9089 &self,
9090 window: &mut Window,
9091 cx: &mut App,
9092 ) -> Option<AnyElement> {
9093 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9094 let accept_keystroke = accept_binding.keystroke()?;
9095
9096 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9097
9098 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9099 Color::Accent
9100 } else {
9101 Color::Muted
9102 };
9103
9104 h_flex()
9105 .px_0p5()
9106 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9107 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9108 .text_size(TextSize::XSmall.rems(cx))
9109 .child(h_flex().children(ui::render_modifiers(
9110 accept_keystroke.modifiers(),
9111 PlatformStyle::platform(),
9112 Some(modifiers_color),
9113 Some(IconSize::XSmall.rems().into()),
9114 true,
9115 )))
9116 .when(is_platform_style_mac, |parent| {
9117 parent.child(accept_keystroke.key().to_string())
9118 })
9119 .when(!is_platform_style_mac, |parent| {
9120 parent.child(
9121 Key::new(
9122 util::capitalize(accept_keystroke.key()),
9123 Some(Color::Default),
9124 )
9125 .size(Some(IconSize::XSmall.rems().into())),
9126 )
9127 })
9128 .into_any()
9129 .into()
9130 }
9131
9132 fn render_edit_prediction_line_popover(
9133 &self,
9134 label: impl Into<SharedString>,
9135 icon: Option<IconName>,
9136 window: &mut Window,
9137 cx: &mut App,
9138 ) -> Stateful<Div> {
9139 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9140
9141 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9142 let has_keybind = keybind.is_some();
9143
9144 h_flex()
9145 .id("ep-line-popover")
9146 .py_0p5()
9147 .pl_1()
9148 .pr(padding_right)
9149 .gap_1()
9150 .rounded_md()
9151 .border_1()
9152 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9153 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9154 .shadow_xs()
9155 .when(!has_keybind, |el| {
9156 let status_colors = cx.theme().status();
9157
9158 el.bg(status_colors.error_background)
9159 .border_color(status_colors.error.opacity(0.6))
9160 .pl_2()
9161 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9162 .cursor_default()
9163 .hoverable_tooltip(move |_window, cx| {
9164 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9165 })
9166 })
9167 .children(keybind)
9168 .child(
9169 Label::new(label)
9170 .size(LabelSize::Small)
9171 .when(!has_keybind, |el| {
9172 el.color(cx.theme().status().error.into()).strikethrough()
9173 }),
9174 )
9175 .when(!has_keybind, |el| {
9176 el.child(
9177 h_flex().ml_1().child(
9178 Icon::new(IconName::Info)
9179 .size(IconSize::Small)
9180 .color(cx.theme().status().error.into()),
9181 ),
9182 )
9183 })
9184 .when_some(icon, |element, icon| {
9185 element.child(
9186 div()
9187 .mt(px(1.5))
9188 .child(Icon::new(icon).size(IconSize::Small)),
9189 )
9190 })
9191 }
9192
9193 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9194 let accent_color = cx.theme().colors().text_accent;
9195 let editor_bg_color = cx.theme().colors().editor_background;
9196 editor_bg_color.blend(accent_color.opacity(0.1))
9197 }
9198
9199 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9200 let accent_color = cx.theme().colors().text_accent;
9201 let editor_bg_color = cx.theme().colors().editor_background;
9202 editor_bg_color.blend(accent_color.opacity(0.6))
9203 }
9204 fn get_prediction_provider_icon_name(
9205 provider: &Option<RegisteredEditPredictionProvider>,
9206 ) -> IconName {
9207 match provider {
9208 Some(provider) => match provider.provider.name() {
9209 "copilot" => IconName::Copilot,
9210 "supermaven" => IconName::Supermaven,
9211 _ => IconName::ZedPredict,
9212 },
9213 None => IconName::ZedPredict,
9214 }
9215 }
9216
9217 fn render_edit_prediction_cursor_popover(
9218 &self,
9219 min_width: Pixels,
9220 max_width: Pixels,
9221 cursor_point: Point,
9222 style: &EditorStyle,
9223 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9224 _window: &Window,
9225 cx: &mut Context<Editor>,
9226 ) -> Option<AnyElement> {
9227 let provider = self.edit_prediction_provider.as_ref()?;
9228 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9229
9230 let is_refreshing = provider.provider.is_refreshing(cx);
9231
9232 fn pending_completion_container(icon: IconName) -> Div {
9233 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9234 }
9235
9236 let completion = match &self.active_edit_prediction {
9237 Some(prediction) => {
9238 if !self.has_visible_completions_menu() {
9239 const RADIUS: Pixels = px(6.);
9240 const BORDER_WIDTH: Pixels = px(1.);
9241
9242 return Some(
9243 h_flex()
9244 .elevation_2(cx)
9245 .border(BORDER_WIDTH)
9246 .border_color(cx.theme().colors().border)
9247 .when(accept_keystroke.is_none(), |el| {
9248 el.border_color(cx.theme().status().error)
9249 })
9250 .rounded(RADIUS)
9251 .rounded_tl(px(0.))
9252 .overflow_hidden()
9253 .child(div().px_1p5().child(match &prediction.completion {
9254 EditPrediction::MoveWithin { target, snapshot } => {
9255 use text::ToPoint as _;
9256 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9257 {
9258 Icon::new(IconName::ZedPredictDown)
9259 } else {
9260 Icon::new(IconName::ZedPredictUp)
9261 }
9262 }
9263 EditPrediction::MoveOutside { .. } => {
9264 // TODO [zeta2] custom icon for external jump?
9265 Icon::new(provider_icon)
9266 }
9267 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9268 }))
9269 .child(
9270 h_flex()
9271 .gap_1()
9272 .py_1()
9273 .px_2()
9274 .rounded_r(RADIUS - BORDER_WIDTH)
9275 .border_l_1()
9276 .border_color(cx.theme().colors().border)
9277 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9278 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9279 el.child(
9280 Label::new("Hold")
9281 .size(LabelSize::Small)
9282 .when(accept_keystroke.is_none(), |el| {
9283 el.strikethrough()
9284 })
9285 .line_height_style(LineHeightStyle::UiLabel),
9286 )
9287 })
9288 .id("edit_prediction_cursor_popover_keybind")
9289 .when(accept_keystroke.is_none(), |el| {
9290 let status_colors = cx.theme().status();
9291
9292 el.bg(status_colors.error_background)
9293 .border_color(status_colors.error.opacity(0.6))
9294 .child(Icon::new(IconName::Info).color(Color::Error))
9295 .cursor_default()
9296 .hoverable_tooltip(move |_window, cx| {
9297 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9298 .into()
9299 })
9300 })
9301 .when_some(
9302 accept_keystroke.as_ref(),
9303 |el, accept_keystroke| {
9304 el.child(h_flex().children(ui::render_modifiers(
9305 accept_keystroke.modifiers(),
9306 PlatformStyle::platform(),
9307 Some(Color::Default),
9308 Some(IconSize::XSmall.rems().into()),
9309 false,
9310 )))
9311 },
9312 ),
9313 )
9314 .into_any(),
9315 );
9316 }
9317
9318 self.render_edit_prediction_cursor_popover_preview(
9319 prediction,
9320 cursor_point,
9321 style,
9322 cx,
9323 )?
9324 }
9325
9326 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9327 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9328 stale_completion,
9329 cursor_point,
9330 style,
9331 cx,
9332 )?,
9333
9334 None => pending_completion_container(provider_icon)
9335 .child(Label::new("...").size(LabelSize::Small)),
9336 },
9337
9338 None => pending_completion_container(provider_icon)
9339 .child(Label::new("...").size(LabelSize::Small)),
9340 };
9341
9342 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9343 completion
9344 .with_animation(
9345 "loading-completion",
9346 Animation::new(Duration::from_secs(2))
9347 .repeat()
9348 .with_easing(pulsating_between(0.4, 0.8)),
9349 |label, delta| label.opacity(delta),
9350 )
9351 .into_any_element()
9352 } else {
9353 completion.into_any_element()
9354 };
9355
9356 let has_completion = self.active_edit_prediction.is_some();
9357
9358 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9359 Some(
9360 h_flex()
9361 .min_w(min_width)
9362 .max_w(max_width)
9363 .flex_1()
9364 .elevation_2(cx)
9365 .border_color(cx.theme().colors().border)
9366 .child(
9367 div()
9368 .flex_1()
9369 .py_1()
9370 .px_2()
9371 .overflow_hidden()
9372 .child(completion),
9373 )
9374 .when_some(accept_keystroke, |el, accept_keystroke| {
9375 if !accept_keystroke.modifiers().modified() {
9376 return el;
9377 }
9378
9379 el.child(
9380 h_flex()
9381 .h_full()
9382 .border_l_1()
9383 .rounded_r_lg()
9384 .border_color(cx.theme().colors().border)
9385 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9386 .gap_1()
9387 .py_1()
9388 .px_2()
9389 .child(
9390 h_flex()
9391 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9392 .when(is_platform_style_mac, |parent| parent.gap_1())
9393 .child(h_flex().children(ui::render_modifiers(
9394 accept_keystroke.modifiers(),
9395 PlatformStyle::platform(),
9396 Some(if !has_completion {
9397 Color::Muted
9398 } else {
9399 Color::Default
9400 }),
9401 None,
9402 false,
9403 ))),
9404 )
9405 .child(Label::new("Preview").into_any_element())
9406 .opacity(if has_completion { 1.0 } else { 0.4 }),
9407 )
9408 })
9409 .into_any(),
9410 )
9411 }
9412
9413 fn render_edit_prediction_cursor_popover_preview(
9414 &self,
9415 completion: &EditPredictionState,
9416 cursor_point: Point,
9417 style: &EditorStyle,
9418 cx: &mut Context<Editor>,
9419 ) -> Option<Div> {
9420 use text::ToPoint as _;
9421
9422 fn render_relative_row_jump(
9423 prefix: impl Into<String>,
9424 current_row: u32,
9425 target_row: u32,
9426 ) -> Div {
9427 let (row_diff, arrow) = if target_row < current_row {
9428 (current_row - target_row, IconName::ArrowUp)
9429 } else {
9430 (target_row - current_row, IconName::ArrowDown)
9431 };
9432
9433 h_flex()
9434 .child(
9435 Label::new(format!("{}{}", prefix.into(), row_diff))
9436 .color(Color::Muted)
9437 .size(LabelSize::Small),
9438 )
9439 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9440 }
9441
9442 let supports_jump = self
9443 .edit_prediction_provider
9444 .as_ref()
9445 .map(|provider| provider.provider.supports_jump_to_edit())
9446 .unwrap_or(true);
9447
9448 match &completion.completion {
9449 EditPrediction::MoveWithin {
9450 target, snapshot, ..
9451 } => {
9452 if !supports_jump {
9453 return None;
9454 }
9455
9456 Some(
9457 h_flex()
9458 .px_2()
9459 .gap_2()
9460 .flex_1()
9461 .child(
9462 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9463 Icon::new(IconName::ZedPredictDown)
9464 } else {
9465 Icon::new(IconName::ZedPredictUp)
9466 },
9467 )
9468 .child(Label::new("Jump to Edit")),
9469 )
9470 }
9471 EditPrediction::MoveOutside { snapshot, .. } => {
9472 let file_name = snapshot
9473 .file()
9474 .map(|file| file.file_name(cx))
9475 .unwrap_or("untitled");
9476 Some(
9477 h_flex()
9478 .px_2()
9479 .gap_2()
9480 .flex_1()
9481 .child(Icon::new(IconName::ZedPredict))
9482 .child(Label::new(format!("Jump to {file_name}"))),
9483 )
9484 }
9485 EditPrediction::Edit {
9486 edits,
9487 edit_preview,
9488 snapshot,
9489 display_mode: _,
9490 } => {
9491 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9492
9493 let (highlighted_edits, has_more_lines) =
9494 if let Some(edit_preview) = edit_preview.as_ref() {
9495 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9496 .first_line_preview()
9497 } else {
9498 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9499 };
9500
9501 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9502 .with_default_highlights(&style.text, highlighted_edits.highlights);
9503
9504 let preview = h_flex()
9505 .gap_1()
9506 .min_w_16()
9507 .child(styled_text)
9508 .when(has_more_lines, |parent| parent.child("…"));
9509
9510 let left = if supports_jump && first_edit_row != cursor_point.row {
9511 render_relative_row_jump("", cursor_point.row, first_edit_row)
9512 .into_any_element()
9513 } else {
9514 let icon_name =
9515 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9516 Icon::new(icon_name).into_any_element()
9517 };
9518
9519 Some(
9520 h_flex()
9521 .h_full()
9522 .flex_1()
9523 .gap_2()
9524 .pr_1()
9525 .overflow_x_hidden()
9526 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9527 .child(left)
9528 .child(preview),
9529 )
9530 }
9531 }
9532 }
9533
9534 pub fn render_context_menu(
9535 &self,
9536 style: &EditorStyle,
9537 max_height_in_lines: u32,
9538 window: &mut Window,
9539 cx: &mut Context<Editor>,
9540 ) -> Option<AnyElement> {
9541 let menu = self.context_menu.borrow();
9542 let menu = menu.as_ref()?;
9543 if !menu.visible() {
9544 return None;
9545 };
9546 Some(menu.render(style, max_height_in_lines, window, cx))
9547 }
9548
9549 fn render_context_menu_aside(
9550 &mut self,
9551 max_size: Size<Pixels>,
9552 window: &mut Window,
9553 cx: &mut Context<Editor>,
9554 ) -> Option<AnyElement> {
9555 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9556 if menu.visible() {
9557 menu.render_aside(max_size, window, cx)
9558 } else {
9559 None
9560 }
9561 })
9562 }
9563
9564 fn hide_context_menu(
9565 &mut self,
9566 window: &mut Window,
9567 cx: &mut Context<Self>,
9568 ) -> Option<CodeContextMenu> {
9569 cx.notify();
9570 self.completion_tasks.clear();
9571 let context_menu = self.context_menu.borrow_mut().take();
9572 self.stale_edit_prediction_in_menu.take();
9573 self.update_visible_edit_prediction(window, cx);
9574 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9575 && let Some(completion_provider) = &self.completion_provider
9576 {
9577 completion_provider.selection_changed(None, window, cx);
9578 }
9579 context_menu
9580 }
9581
9582 fn show_snippet_choices(
9583 &mut self,
9584 choices: &Vec<String>,
9585 selection: Range<Anchor>,
9586 cx: &mut Context<Self>,
9587 ) {
9588 let Some((_, buffer, _)) = self
9589 .buffer()
9590 .read(cx)
9591 .excerpt_containing(selection.start, cx)
9592 else {
9593 return;
9594 };
9595 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9596 else {
9597 return;
9598 };
9599 if buffer != end_buffer {
9600 log::error!("expected anchor range to have matching buffer IDs");
9601 return;
9602 }
9603
9604 let id = post_inc(&mut self.next_completion_id);
9605 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9606 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9607 CompletionsMenu::new_snippet_choices(
9608 id,
9609 true,
9610 choices,
9611 selection,
9612 buffer,
9613 snippet_sort_order,
9614 ),
9615 ));
9616 }
9617
9618 pub fn insert_snippet(
9619 &mut self,
9620 insertion_ranges: &[Range<usize>],
9621 snippet: Snippet,
9622 window: &mut Window,
9623 cx: &mut Context<Self>,
9624 ) -> Result<()> {
9625 struct Tabstop<T> {
9626 is_end_tabstop: bool,
9627 ranges: Vec<Range<T>>,
9628 choices: Option<Vec<String>>,
9629 }
9630
9631 let tabstops = self.buffer.update(cx, |buffer, cx| {
9632 let snippet_text: Arc<str> = snippet.text.clone().into();
9633 let edits = insertion_ranges
9634 .iter()
9635 .cloned()
9636 .map(|range| (range, snippet_text.clone()));
9637 let autoindent_mode = AutoindentMode::Block {
9638 original_indent_columns: Vec::new(),
9639 };
9640 buffer.edit(edits, Some(autoindent_mode), cx);
9641
9642 let snapshot = &*buffer.read(cx);
9643 let snippet = &snippet;
9644 snippet
9645 .tabstops
9646 .iter()
9647 .map(|tabstop| {
9648 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9649 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9650 });
9651 let mut tabstop_ranges = tabstop
9652 .ranges
9653 .iter()
9654 .flat_map(|tabstop_range| {
9655 let mut delta = 0_isize;
9656 insertion_ranges.iter().map(move |insertion_range| {
9657 let insertion_start = insertion_range.start as isize + delta;
9658 delta +=
9659 snippet.text.len() as isize - insertion_range.len() as isize;
9660
9661 let start = ((insertion_start + tabstop_range.start) as usize)
9662 .min(snapshot.len());
9663 let end = ((insertion_start + tabstop_range.end) as usize)
9664 .min(snapshot.len());
9665 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9666 })
9667 })
9668 .collect::<Vec<_>>();
9669 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9670
9671 Tabstop {
9672 is_end_tabstop,
9673 ranges: tabstop_ranges,
9674 choices: tabstop.choices.clone(),
9675 }
9676 })
9677 .collect::<Vec<_>>()
9678 });
9679 if let Some(tabstop) = tabstops.first() {
9680 self.change_selections(Default::default(), window, cx, |s| {
9681 // Reverse order so that the first range is the newest created selection.
9682 // Completions will use it and autoscroll will prioritize it.
9683 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9684 });
9685
9686 if let Some(choices) = &tabstop.choices
9687 && let Some(selection) = tabstop.ranges.first()
9688 {
9689 self.show_snippet_choices(choices, selection.clone(), cx)
9690 }
9691
9692 // If we're already at the last tabstop and it's at the end of the snippet,
9693 // we're done, we don't need to keep the state around.
9694 if !tabstop.is_end_tabstop {
9695 let choices = tabstops
9696 .iter()
9697 .map(|tabstop| tabstop.choices.clone())
9698 .collect();
9699
9700 let ranges = tabstops
9701 .into_iter()
9702 .map(|tabstop| tabstop.ranges)
9703 .collect::<Vec<_>>();
9704
9705 self.snippet_stack.push(SnippetState {
9706 active_index: 0,
9707 ranges,
9708 choices,
9709 });
9710 }
9711
9712 // Check whether the just-entered snippet ends with an auto-closable bracket.
9713 if self.autoclose_regions.is_empty() {
9714 let snapshot = self.buffer.read(cx).snapshot(cx);
9715 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9716 let selection_head = selection.head();
9717 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9718 continue;
9719 };
9720
9721 let mut bracket_pair = None;
9722 let max_lookup_length = scope
9723 .brackets()
9724 .map(|(pair, _)| {
9725 pair.start
9726 .as_str()
9727 .chars()
9728 .count()
9729 .max(pair.end.as_str().chars().count())
9730 })
9731 .max();
9732 if let Some(max_lookup_length) = max_lookup_length {
9733 let next_text = snapshot
9734 .chars_at(selection_head)
9735 .take(max_lookup_length)
9736 .collect::<String>();
9737 let prev_text = snapshot
9738 .reversed_chars_at(selection_head)
9739 .take(max_lookup_length)
9740 .collect::<String>();
9741
9742 for (pair, enabled) in scope.brackets() {
9743 if enabled
9744 && pair.close
9745 && prev_text.starts_with(pair.start.as_str())
9746 && next_text.starts_with(pair.end.as_str())
9747 {
9748 bracket_pair = Some(pair.clone());
9749 break;
9750 }
9751 }
9752 }
9753
9754 if let Some(pair) = bracket_pair {
9755 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9756 let autoclose_enabled =
9757 self.use_autoclose && snapshot_settings.use_autoclose;
9758 if autoclose_enabled {
9759 let start = snapshot.anchor_after(selection_head);
9760 let end = snapshot.anchor_after(selection_head);
9761 self.autoclose_regions.push(AutocloseRegion {
9762 selection_id: selection.id,
9763 range: start..end,
9764 pair,
9765 });
9766 }
9767 }
9768 }
9769 }
9770 }
9771 Ok(())
9772 }
9773
9774 pub fn move_to_next_snippet_tabstop(
9775 &mut self,
9776 window: &mut Window,
9777 cx: &mut Context<Self>,
9778 ) -> bool {
9779 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9780 }
9781
9782 pub fn move_to_prev_snippet_tabstop(
9783 &mut self,
9784 window: &mut Window,
9785 cx: &mut Context<Self>,
9786 ) -> bool {
9787 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9788 }
9789
9790 pub fn move_to_snippet_tabstop(
9791 &mut self,
9792 bias: Bias,
9793 window: &mut Window,
9794 cx: &mut Context<Self>,
9795 ) -> bool {
9796 if let Some(mut snippet) = self.snippet_stack.pop() {
9797 match bias {
9798 Bias::Left => {
9799 if snippet.active_index > 0 {
9800 snippet.active_index -= 1;
9801 } else {
9802 self.snippet_stack.push(snippet);
9803 return false;
9804 }
9805 }
9806 Bias::Right => {
9807 if snippet.active_index + 1 < snippet.ranges.len() {
9808 snippet.active_index += 1;
9809 } else {
9810 self.snippet_stack.push(snippet);
9811 return false;
9812 }
9813 }
9814 }
9815 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9816 self.change_selections(Default::default(), window, cx, |s| {
9817 // Reverse order so that the first range is the newest created selection.
9818 // Completions will use it and autoscroll will prioritize it.
9819 s.select_ranges(current_ranges.iter().rev().cloned())
9820 });
9821
9822 if let Some(choices) = &snippet.choices[snippet.active_index]
9823 && let Some(selection) = current_ranges.first()
9824 {
9825 self.show_snippet_choices(choices, selection.clone(), cx);
9826 }
9827
9828 // If snippet state is not at the last tabstop, push it back on the stack
9829 if snippet.active_index + 1 < snippet.ranges.len() {
9830 self.snippet_stack.push(snippet);
9831 }
9832 return true;
9833 }
9834 }
9835
9836 false
9837 }
9838
9839 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9840 self.transact(window, cx, |this, window, cx| {
9841 this.select_all(&SelectAll, window, cx);
9842 this.insert("", window, cx);
9843 });
9844 }
9845
9846 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9847 if self.read_only(cx) {
9848 return;
9849 }
9850 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9851 self.transact(window, cx, |this, window, cx| {
9852 this.select_autoclose_pair(window, cx);
9853
9854 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9855
9856 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9857 if !this.linked_edit_ranges.is_empty() {
9858 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9859 let snapshot = this.buffer.read(cx).snapshot(cx);
9860
9861 for selection in selections.iter() {
9862 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9863 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9864 if selection_start.buffer_id != selection_end.buffer_id {
9865 continue;
9866 }
9867 if let Some(ranges) =
9868 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9869 {
9870 for (buffer, entries) in ranges {
9871 linked_ranges.entry(buffer).or_default().extend(entries);
9872 }
9873 }
9874 }
9875 }
9876
9877 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9878 for selection in &mut selections {
9879 if selection.is_empty() {
9880 let old_head = selection.head();
9881 let mut new_head =
9882 movement::left(&display_map, old_head.to_display_point(&display_map))
9883 .to_point(&display_map);
9884 if let Some((buffer, line_buffer_range)) = display_map
9885 .buffer_snapshot()
9886 .buffer_line_for_row(MultiBufferRow(old_head.row))
9887 {
9888 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9889 let indent_len = match indent_size.kind {
9890 IndentKind::Space => {
9891 buffer.settings_at(line_buffer_range.start, cx).tab_size
9892 }
9893 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9894 };
9895 if old_head.column <= indent_size.len && old_head.column > 0 {
9896 let indent_len = indent_len.get();
9897 new_head = cmp::min(
9898 new_head,
9899 MultiBufferPoint::new(
9900 old_head.row,
9901 ((old_head.column - 1) / indent_len) * indent_len,
9902 ),
9903 );
9904 }
9905 }
9906
9907 selection.set_head(new_head, SelectionGoal::None);
9908 }
9909 }
9910
9911 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9912 this.insert("", window, cx);
9913 let empty_str: Arc<str> = Arc::from("");
9914 for (buffer, edits) in linked_ranges {
9915 let snapshot = buffer.read(cx).snapshot();
9916 use text::ToPoint as TP;
9917
9918 let edits = edits
9919 .into_iter()
9920 .map(|range| {
9921 let end_point = TP::to_point(&range.end, &snapshot);
9922 let mut start_point = TP::to_point(&range.start, &snapshot);
9923
9924 if end_point == start_point {
9925 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9926 .saturating_sub(1);
9927 start_point =
9928 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9929 };
9930
9931 (start_point..end_point, empty_str.clone())
9932 })
9933 .sorted_by_key(|(range, _)| range.start)
9934 .collect::<Vec<_>>();
9935 buffer.update(cx, |this, cx| {
9936 this.edit(edits, None, cx);
9937 })
9938 }
9939 this.refresh_edit_prediction(true, false, window, cx);
9940 refresh_linked_ranges(this, window, cx);
9941 });
9942 }
9943
9944 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9945 if self.read_only(cx) {
9946 return;
9947 }
9948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9949 self.transact(window, cx, |this, window, cx| {
9950 this.change_selections(Default::default(), window, cx, |s| {
9951 s.move_with(|map, selection| {
9952 if selection.is_empty() {
9953 let cursor = movement::right(map, selection.head());
9954 selection.end = cursor;
9955 selection.reversed = true;
9956 selection.goal = SelectionGoal::None;
9957 }
9958 })
9959 });
9960 this.insert("", window, cx);
9961 this.refresh_edit_prediction(true, false, window, cx);
9962 });
9963 }
9964
9965 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9966 if self.mode.is_single_line() {
9967 cx.propagate();
9968 return;
9969 }
9970
9971 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9972 if self.move_to_prev_snippet_tabstop(window, cx) {
9973 return;
9974 }
9975 self.outdent(&Outdent, window, cx);
9976 }
9977
9978 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9979 if self.mode.is_single_line() {
9980 cx.propagate();
9981 return;
9982 }
9983
9984 if self.move_to_next_snippet_tabstop(window, cx) {
9985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9986 return;
9987 }
9988 if self.read_only(cx) {
9989 return;
9990 }
9991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9992 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9993 let buffer = self.buffer.read(cx);
9994 let snapshot = buffer.snapshot(cx);
9995 let rows_iter = selections.iter().map(|s| s.head().row);
9996 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9997
9998 let has_some_cursor_in_whitespace = selections
9999 .iter()
10000 .filter(|selection| selection.is_empty())
10001 .any(|selection| {
10002 let cursor = selection.head();
10003 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10004 cursor.column < current_indent.len
10005 });
10006
10007 let mut edits = Vec::new();
10008 let mut prev_edited_row = 0;
10009 let mut row_delta = 0;
10010 for selection in &mut selections {
10011 if selection.start.row != prev_edited_row {
10012 row_delta = 0;
10013 }
10014 prev_edited_row = selection.end.row;
10015
10016 // If the selection is non-empty, then increase the indentation of the selected lines.
10017 if !selection.is_empty() {
10018 row_delta =
10019 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10020 continue;
10021 }
10022
10023 let cursor = selection.head();
10024 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10025 if let Some(suggested_indent) =
10026 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10027 {
10028 // Don't do anything if already at suggested indent
10029 // and there is any other cursor which is not
10030 if has_some_cursor_in_whitespace
10031 && cursor.column == current_indent.len
10032 && current_indent.len == suggested_indent.len
10033 {
10034 continue;
10035 }
10036
10037 // Adjust line and move cursor to suggested indent
10038 // if cursor is not at suggested indent
10039 if cursor.column < suggested_indent.len
10040 && cursor.column <= current_indent.len
10041 && current_indent.len <= suggested_indent.len
10042 {
10043 selection.start = Point::new(cursor.row, suggested_indent.len);
10044 selection.end = selection.start;
10045 if row_delta == 0 {
10046 edits.extend(Buffer::edit_for_indent_size_adjustment(
10047 cursor.row,
10048 current_indent,
10049 suggested_indent,
10050 ));
10051 row_delta = suggested_indent.len - current_indent.len;
10052 }
10053 continue;
10054 }
10055
10056 // If current indent is more than suggested indent
10057 // only move cursor to current indent and skip indent
10058 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10059 selection.start = Point::new(cursor.row, current_indent.len);
10060 selection.end = selection.start;
10061 continue;
10062 }
10063 }
10064
10065 // Otherwise, insert a hard or soft tab.
10066 let settings = buffer.language_settings_at(cursor, cx);
10067 let tab_size = if settings.hard_tabs {
10068 IndentSize::tab()
10069 } else {
10070 let tab_size = settings.tab_size.get();
10071 let indent_remainder = snapshot
10072 .text_for_range(Point::new(cursor.row, 0)..cursor)
10073 .flat_map(str::chars)
10074 .fold(row_delta % tab_size, |counter: u32, c| {
10075 if c == '\t' {
10076 0
10077 } else {
10078 (counter + 1) % tab_size
10079 }
10080 });
10081
10082 let chars_to_next_tab_stop = tab_size - indent_remainder;
10083 IndentSize::spaces(chars_to_next_tab_stop)
10084 };
10085 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10086 selection.end = selection.start;
10087 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10088 row_delta += tab_size.len;
10089 }
10090
10091 self.transact(window, cx, |this, window, cx| {
10092 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10093 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10094 this.refresh_edit_prediction(true, false, window, cx);
10095 });
10096 }
10097
10098 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10099 if self.read_only(cx) {
10100 return;
10101 }
10102 if self.mode.is_single_line() {
10103 cx.propagate();
10104 return;
10105 }
10106
10107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10108 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10109 let mut prev_edited_row = 0;
10110 let mut row_delta = 0;
10111 let mut edits = Vec::new();
10112 let buffer = self.buffer.read(cx);
10113 let snapshot = buffer.snapshot(cx);
10114 for selection in &mut selections {
10115 if selection.start.row != prev_edited_row {
10116 row_delta = 0;
10117 }
10118 prev_edited_row = selection.end.row;
10119
10120 row_delta =
10121 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10122 }
10123
10124 self.transact(window, cx, |this, window, cx| {
10125 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10126 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10127 });
10128 }
10129
10130 fn indent_selection(
10131 buffer: &MultiBuffer,
10132 snapshot: &MultiBufferSnapshot,
10133 selection: &mut Selection<Point>,
10134 edits: &mut Vec<(Range<Point>, String)>,
10135 delta_for_start_row: u32,
10136 cx: &App,
10137 ) -> u32 {
10138 let settings = buffer.language_settings_at(selection.start, cx);
10139 let tab_size = settings.tab_size.get();
10140 let indent_kind = if settings.hard_tabs {
10141 IndentKind::Tab
10142 } else {
10143 IndentKind::Space
10144 };
10145 let mut start_row = selection.start.row;
10146 let mut end_row = selection.end.row + 1;
10147
10148 // If a selection ends at the beginning of a line, don't indent
10149 // that last line.
10150 if selection.end.column == 0 && selection.end.row > selection.start.row {
10151 end_row -= 1;
10152 }
10153
10154 // Avoid re-indenting a row that has already been indented by a
10155 // previous selection, but still update this selection's column
10156 // to reflect that indentation.
10157 if delta_for_start_row > 0 {
10158 start_row += 1;
10159 selection.start.column += delta_for_start_row;
10160 if selection.end.row == selection.start.row {
10161 selection.end.column += delta_for_start_row;
10162 }
10163 }
10164
10165 let mut delta_for_end_row = 0;
10166 let has_multiple_rows = start_row + 1 != end_row;
10167 for row in start_row..end_row {
10168 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10169 let indent_delta = match (current_indent.kind, indent_kind) {
10170 (IndentKind::Space, IndentKind::Space) => {
10171 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10172 IndentSize::spaces(columns_to_next_tab_stop)
10173 }
10174 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10175 (_, IndentKind::Tab) => IndentSize::tab(),
10176 };
10177
10178 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10179 0
10180 } else {
10181 selection.start.column
10182 };
10183 let row_start = Point::new(row, start);
10184 edits.push((
10185 row_start..row_start,
10186 indent_delta.chars().collect::<String>(),
10187 ));
10188
10189 // Update this selection's endpoints to reflect the indentation.
10190 if row == selection.start.row {
10191 selection.start.column += indent_delta.len;
10192 }
10193 if row == selection.end.row {
10194 selection.end.column += indent_delta.len;
10195 delta_for_end_row = indent_delta.len;
10196 }
10197 }
10198
10199 if selection.start.row == selection.end.row {
10200 delta_for_start_row + delta_for_end_row
10201 } else {
10202 delta_for_end_row
10203 }
10204 }
10205
10206 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10207 if self.read_only(cx) {
10208 return;
10209 }
10210 if self.mode.is_single_line() {
10211 cx.propagate();
10212 return;
10213 }
10214
10215 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10216 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10217 let selections = self.selections.all::<Point>(&display_map);
10218 let mut deletion_ranges = Vec::new();
10219 let mut last_outdent = None;
10220 {
10221 let buffer = self.buffer.read(cx);
10222 let snapshot = buffer.snapshot(cx);
10223 for selection in &selections {
10224 let settings = buffer.language_settings_at(selection.start, cx);
10225 let tab_size = settings.tab_size.get();
10226 let mut rows = selection.spanned_rows(false, &display_map);
10227
10228 // Avoid re-outdenting a row that has already been outdented by a
10229 // previous selection.
10230 if let Some(last_row) = last_outdent
10231 && last_row == rows.start
10232 {
10233 rows.start = rows.start.next_row();
10234 }
10235 let has_multiple_rows = rows.len() > 1;
10236 for row in rows.iter_rows() {
10237 let indent_size = snapshot.indent_size_for_line(row);
10238 if indent_size.len > 0 {
10239 let deletion_len = match indent_size.kind {
10240 IndentKind::Space => {
10241 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10242 if columns_to_prev_tab_stop == 0 {
10243 tab_size
10244 } else {
10245 columns_to_prev_tab_stop
10246 }
10247 }
10248 IndentKind::Tab => 1,
10249 };
10250 let start = if has_multiple_rows
10251 || deletion_len > selection.start.column
10252 || indent_size.len < selection.start.column
10253 {
10254 0
10255 } else {
10256 selection.start.column - deletion_len
10257 };
10258 deletion_ranges.push(
10259 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10260 );
10261 last_outdent = Some(row);
10262 }
10263 }
10264 }
10265 }
10266
10267 self.transact(window, cx, |this, window, cx| {
10268 this.buffer.update(cx, |buffer, cx| {
10269 let empty_str: Arc<str> = Arc::default();
10270 buffer.edit(
10271 deletion_ranges
10272 .into_iter()
10273 .map(|range| (range, empty_str.clone())),
10274 None,
10275 cx,
10276 );
10277 });
10278 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10279 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10280 });
10281 }
10282
10283 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10284 if self.read_only(cx) {
10285 return;
10286 }
10287 if self.mode.is_single_line() {
10288 cx.propagate();
10289 return;
10290 }
10291
10292 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10293 let selections = self
10294 .selections
10295 .all::<usize>(&self.display_snapshot(cx))
10296 .into_iter()
10297 .map(|s| s.range());
10298
10299 self.transact(window, cx, |this, window, cx| {
10300 this.buffer.update(cx, |buffer, cx| {
10301 buffer.autoindent_ranges(selections, cx);
10302 });
10303 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10304 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10305 });
10306 }
10307
10308 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10311 let selections = self.selections.all::<Point>(&display_map);
10312
10313 let mut new_cursors = Vec::new();
10314 let mut edit_ranges = Vec::new();
10315 let mut selections = selections.iter().peekable();
10316 while let Some(selection) = selections.next() {
10317 let mut rows = selection.spanned_rows(false, &display_map);
10318
10319 // Accumulate contiguous regions of rows that we want to delete.
10320 while let Some(next_selection) = selections.peek() {
10321 let next_rows = next_selection.spanned_rows(false, &display_map);
10322 if next_rows.start <= rows.end {
10323 rows.end = next_rows.end;
10324 selections.next().unwrap();
10325 } else {
10326 break;
10327 }
10328 }
10329
10330 let buffer = display_map.buffer_snapshot();
10331 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10332 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10333 // If there's a line after the range, delete the \n from the end of the row range
10334 (
10335 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10336 rows.end,
10337 )
10338 } else {
10339 // If there isn't a line after the range, delete the \n from the line before the
10340 // start of the row range
10341 edit_start = edit_start.saturating_sub(1);
10342 (buffer.len(), rows.start.previous_row())
10343 };
10344
10345 let text_layout_details = self.text_layout_details(window);
10346 let x = display_map.x_for_display_point(
10347 selection.head().to_display_point(&display_map),
10348 &text_layout_details,
10349 );
10350 let row = Point::new(target_row.0, 0)
10351 .to_display_point(&display_map)
10352 .row();
10353 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10354
10355 new_cursors.push((
10356 selection.id,
10357 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10358 SelectionGoal::None,
10359 ));
10360 edit_ranges.push(edit_start..edit_end);
10361 }
10362
10363 self.transact(window, cx, |this, window, cx| {
10364 let buffer = this.buffer.update(cx, |buffer, cx| {
10365 let empty_str: Arc<str> = Arc::default();
10366 buffer.edit(
10367 edit_ranges
10368 .into_iter()
10369 .map(|range| (range, empty_str.clone())),
10370 None,
10371 cx,
10372 );
10373 buffer.snapshot(cx)
10374 });
10375 let new_selections = new_cursors
10376 .into_iter()
10377 .map(|(id, cursor, goal)| {
10378 let cursor = cursor.to_point(&buffer);
10379 Selection {
10380 id,
10381 start: cursor,
10382 end: cursor,
10383 reversed: false,
10384 goal,
10385 }
10386 })
10387 .collect();
10388
10389 this.change_selections(Default::default(), window, cx, |s| {
10390 s.select(new_selections);
10391 });
10392 });
10393 }
10394
10395 pub fn join_lines_impl(
10396 &mut self,
10397 insert_whitespace: bool,
10398 window: &mut Window,
10399 cx: &mut Context<Self>,
10400 ) {
10401 if self.read_only(cx) {
10402 return;
10403 }
10404 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10405 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10406 let start = MultiBufferRow(selection.start.row);
10407 // Treat single line selections as if they include the next line. Otherwise this action
10408 // would do nothing for single line selections individual cursors.
10409 let end = if selection.start.row == selection.end.row {
10410 MultiBufferRow(selection.start.row + 1)
10411 } else {
10412 MultiBufferRow(selection.end.row)
10413 };
10414
10415 if let Some(last_row_range) = row_ranges.last_mut()
10416 && start <= last_row_range.end
10417 {
10418 last_row_range.end = end;
10419 continue;
10420 }
10421 row_ranges.push(start..end);
10422 }
10423
10424 let snapshot = self.buffer.read(cx).snapshot(cx);
10425 let mut cursor_positions = Vec::new();
10426 for row_range in &row_ranges {
10427 let anchor = snapshot.anchor_before(Point::new(
10428 row_range.end.previous_row().0,
10429 snapshot.line_len(row_range.end.previous_row()),
10430 ));
10431 cursor_positions.push(anchor..anchor);
10432 }
10433
10434 self.transact(window, cx, |this, window, cx| {
10435 for row_range in row_ranges.into_iter().rev() {
10436 for row in row_range.iter_rows().rev() {
10437 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10438 let next_line_row = row.next_row();
10439 let indent = snapshot.indent_size_for_line(next_line_row);
10440 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10441
10442 let replace =
10443 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10444 " "
10445 } else {
10446 ""
10447 };
10448
10449 this.buffer.update(cx, |buffer, cx| {
10450 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10451 });
10452 }
10453 }
10454
10455 this.change_selections(Default::default(), window, cx, |s| {
10456 s.select_anchor_ranges(cursor_positions)
10457 });
10458 });
10459 }
10460
10461 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10462 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10463 self.join_lines_impl(true, window, cx);
10464 }
10465
10466 pub fn sort_lines_case_sensitive(
10467 &mut self,
10468 _: &SortLinesCaseSensitive,
10469 window: &mut Window,
10470 cx: &mut Context<Self>,
10471 ) {
10472 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10473 }
10474
10475 pub fn sort_lines_by_length(
10476 &mut self,
10477 _: &SortLinesByLength,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_immutable_lines(window, cx, |lines| {
10482 lines.sort_by_key(|&line| line.chars().count())
10483 })
10484 }
10485
10486 pub fn sort_lines_case_insensitive(
10487 &mut self,
10488 _: &SortLinesCaseInsensitive,
10489 window: &mut Window,
10490 cx: &mut Context<Self>,
10491 ) {
10492 self.manipulate_immutable_lines(window, cx, |lines| {
10493 lines.sort_by_key(|line| line.to_lowercase())
10494 })
10495 }
10496
10497 pub fn unique_lines_case_insensitive(
10498 &mut self,
10499 _: &UniqueLinesCaseInsensitive,
10500 window: &mut Window,
10501 cx: &mut Context<Self>,
10502 ) {
10503 self.manipulate_immutable_lines(window, cx, |lines| {
10504 let mut seen = HashSet::default();
10505 lines.retain(|line| seen.insert(line.to_lowercase()));
10506 })
10507 }
10508
10509 pub fn unique_lines_case_sensitive(
10510 &mut self,
10511 _: &UniqueLinesCaseSensitive,
10512 window: &mut Window,
10513 cx: &mut Context<Self>,
10514 ) {
10515 self.manipulate_immutable_lines(window, cx, |lines| {
10516 let mut seen = HashSet::default();
10517 lines.retain(|line| seen.insert(*line));
10518 })
10519 }
10520
10521 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10522 let snapshot = self.buffer.read(cx).snapshot(cx);
10523 for selection in self.selections.disjoint_anchors_arc().iter() {
10524 if snapshot
10525 .language_at(selection.start)
10526 .and_then(|lang| lang.config().wrap_characters.as_ref())
10527 .is_some()
10528 {
10529 return true;
10530 }
10531 }
10532 false
10533 }
10534
10535 fn wrap_selections_in_tag(
10536 &mut self,
10537 _: &WrapSelectionsInTag,
10538 window: &mut Window,
10539 cx: &mut Context<Self>,
10540 ) {
10541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10542
10543 let snapshot = self.buffer.read(cx).snapshot(cx);
10544
10545 let mut edits = Vec::new();
10546 let mut boundaries = Vec::new();
10547
10548 for selection in self
10549 .selections
10550 .all_adjusted(&self.display_snapshot(cx))
10551 .iter()
10552 {
10553 let Some(wrap_config) = snapshot
10554 .language_at(selection.start)
10555 .and_then(|lang| lang.config().wrap_characters.clone())
10556 else {
10557 continue;
10558 };
10559
10560 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10561 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10562
10563 let start_before = snapshot.anchor_before(selection.start);
10564 let end_after = snapshot.anchor_after(selection.end);
10565
10566 edits.push((start_before..start_before, open_tag));
10567 edits.push((end_after..end_after, close_tag));
10568
10569 boundaries.push((
10570 start_before,
10571 end_after,
10572 wrap_config.start_prefix.len(),
10573 wrap_config.end_suffix.len(),
10574 ));
10575 }
10576
10577 if edits.is_empty() {
10578 return;
10579 }
10580
10581 self.transact(window, cx, |this, window, cx| {
10582 let buffer = this.buffer.update(cx, |buffer, cx| {
10583 buffer.edit(edits, None, cx);
10584 buffer.snapshot(cx)
10585 });
10586
10587 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10588 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10589 boundaries.into_iter()
10590 {
10591 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10592 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10593 new_selections.push(open_offset..open_offset);
10594 new_selections.push(close_offset..close_offset);
10595 }
10596
10597 this.change_selections(Default::default(), window, cx, |s| {
10598 s.select_ranges(new_selections);
10599 });
10600
10601 this.request_autoscroll(Autoscroll::fit(), cx);
10602 });
10603 }
10604
10605 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10606 let Some(project) = self.project.clone() else {
10607 return;
10608 };
10609 self.reload(project, window, cx)
10610 .detach_and_notify_err(window, cx);
10611 }
10612
10613 pub fn restore_file(
10614 &mut self,
10615 _: &::git::RestoreFile,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10620 let mut buffer_ids = HashSet::default();
10621 let snapshot = self.buffer().read(cx).snapshot(cx);
10622 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10623 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10624 }
10625
10626 let buffer = self.buffer().read(cx);
10627 let ranges = buffer_ids
10628 .into_iter()
10629 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10630 .collect::<Vec<_>>();
10631
10632 self.restore_hunks_in_ranges(ranges, window, cx);
10633 }
10634
10635 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10637 let selections = self
10638 .selections
10639 .all(&self.display_snapshot(cx))
10640 .into_iter()
10641 .map(|s| s.range())
10642 .collect();
10643 self.restore_hunks_in_ranges(selections, window, cx);
10644 }
10645
10646 pub fn restore_hunks_in_ranges(
10647 &mut self,
10648 ranges: Vec<Range<Point>>,
10649 window: &mut Window,
10650 cx: &mut Context<Editor>,
10651 ) {
10652 let mut revert_changes = HashMap::default();
10653 let chunk_by = self
10654 .snapshot(window, cx)
10655 .hunks_for_ranges(ranges)
10656 .into_iter()
10657 .chunk_by(|hunk| hunk.buffer_id);
10658 for (buffer_id, hunks) in &chunk_by {
10659 let hunks = hunks.collect::<Vec<_>>();
10660 for hunk in &hunks {
10661 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10662 }
10663 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10664 }
10665 drop(chunk_by);
10666 if !revert_changes.is_empty() {
10667 self.transact(window, cx, |editor, window, cx| {
10668 editor.restore(revert_changes, window, cx);
10669 });
10670 }
10671 }
10672
10673 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10674 if let Some(status) = self
10675 .addons
10676 .iter()
10677 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10678 {
10679 return Some(status);
10680 }
10681 self.project
10682 .as_ref()?
10683 .read(cx)
10684 .status_for_buffer_id(buffer_id, cx)
10685 }
10686
10687 pub fn open_active_item_in_terminal(
10688 &mut self,
10689 _: &OpenInTerminal,
10690 window: &mut Window,
10691 cx: &mut Context<Self>,
10692 ) {
10693 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10694 let project_path = buffer.read(cx).project_path(cx)?;
10695 let project = self.project()?.read(cx);
10696 let entry = project.entry_for_path(&project_path, cx)?;
10697 let parent = match &entry.canonical_path {
10698 Some(canonical_path) => canonical_path.to_path_buf(),
10699 None => project.absolute_path(&project_path, cx)?,
10700 }
10701 .parent()?
10702 .to_path_buf();
10703 Some(parent)
10704 }) {
10705 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10706 }
10707 }
10708
10709 fn set_breakpoint_context_menu(
10710 &mut self,
10711 display_row: DisplayRow,
10712 position: Option<Anchor>,
10713 clicked_point: gpui::Point<Pixels>,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) {
10717 let source = self
10718 .buffer
10719 .read(cx)
10720 .snapshot(cx)
10721 .anchor_before(Point::new(display_row.0, 0u32));
10722
10723 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10724
10725 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10726 self,
10727 source,
10728 clicked_point,
10729 context_menu,
10730 window,
10731 cx,
10732 );
10733 }
10734
10735 fn add_edit_breakpoint_block(
10736 &mut self,
10737 anchor: Anchor,
10738 breakpoint: &Breakpoint,
10739 edit_action: BreakpointPromptEditAction,
10740 window: &mut Window,
10741 cx: &mut Context<Self>,
10742 ) {
10743 let weak_editor = cx.weak_entity();
10744 let bp_prompt = cx.new(|cx| {
10745 BreakpointPromptEditor::new(
10746 weak_editor,
10747 anchor,
10748 breakpoint.clone(),
10749 edit_action,
10750 window,
10751 cx,
10752 )
10753 });
10754
10755 let height = bp_prompt.update(cx, |this, cx| {
10756 this.prompt
10757 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10758 });
10759 let cloned_prompt = bp_prompt.clone();
10760 let blocks = vec![BlockProperties {
10761 style: BlockStyle::Sticky,
10762 placement: BlockPlacement::Above(anchor),
10763 height: Some(height),
10764 render: Arc::new(move |cx| {
10765 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10766 cloned_prompt.clone().into_any_element()
10767 }),
10768 priority: 0,
10769 }];
10770
10771 let focus_handle = bp_prompt.focus_handle(cx);
10772 window.focus(&focus_handle);
10773
10774 let block_ids = self.insert_blocks(blocks, None, cx);
10775 bp_prompt.update(cx, |prompt, _| {
10776 prompt.add_block_ids(block_ids);
10777 });
10778 }
10779
10780 pub(crate) fn breakpoint_at_row(
10781 &self,
10782 row: u32,
10783 window: &mut Window,
10784 cx: &mut Context<Self>,
10785 ) -> Option<(Anchor, Breakpoint)> {
10786 let snapshot = self.snapshot(window, cx);
10787 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10788
10789 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10790 }
10791
10792 pub(crate) fn breakpoint_at_anchor(
10793 &self,
10794 breakpoint_position: Anchor,
10795 snapshot: &EditorSnapshot,
10796 cx: &mut Context<Self>,
10797 ) -> Option<(Anchor, Breakpoint)> {
10798 let buffer = self
10799 .buffer
10800 .read(cx)
10801 .buffer_for_anchor(breakpoint_position, cx)?;
10802
10803 let enclosing_excerpt = breakpoint_position.excerpt_id;
10804 let buffer_snapshot = buffer.read(cx).snapshot();
10805
10806 let row = buffer_snapshot
10807 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10808 .row;
10809
10810 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10811 let anchor_end = snapshot
10812 .buffer_snapshot()
10813 .anchor_after(Point::new(row, line_len));
10814
10815 self.breakpoint_store
10816 .as_ref()?
10817 .read_with(cx, |breakpoint_store, cx| {
10818 breakpoint_store
10819 .breakpoints(
10820 &buffer,
10821 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10822 &buffer_snapshot,
10823 cx,
10824 )
10825 .next()
10826 .and_then(|(bp, _)| {
10827 let breakpoint_row = buffer_snapshot
10828 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10829 .row;
10830
10831 if breakpoint_row == row {
10832 snapshot
10833 .buffer_snapshot()
10834 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10835 .map(|position| (position, bp.bp.clone()))
10836 } else {
10837 None
10838 }
10839 })
10840 })
10841 }
10842
10843 pub fn edit_log_breakpoint(
10844 &mut self,
10845 _: &EditLogBreakpoint,
10846 window: &mut Window,
10847 cx: &mut Context<Self>,
10848 ) {
10849 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10850 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10851 message: None,
10852 state: BreakpointState::Enabled,
10853 condition: None,
10854 hit_condition: None,
10855 });
10856
10857 self.add_edit_breakpoint_block(
10858 anchor,
10859 &breakpoint,
10860 BreakpointPromptEditAction::Log,
10861 window,
10862 cx,
10863 );
10864 }
10865 }
10866
10867 fn breakpoints_at_cursors(
10868 &self,
10869 window: &mut Window,
10870 cx: &mut Context<Self>,
10871 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10872 let snapshot = self.snapshot(window, cx);
10873 let cursors = self
10874 .selections
10875 .disjoint_anchors_arc()
10876 .iter()
10877 .map(|selection| {
10878 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10879
10880 let breakpoint_position = self
10881 .breakpoint_at_row(cursor_position.row, window, cx)
10882 .map(|bp| bp.0)
10883 .unwrap_or_else(|| {
10884 snapshot
10885 .display_snapshot
10886 .buffer_snapshot()
10887 .anchor_after(Point::new(cursor_position.row, 0))
10888 });
10889
10890 let breakpoint = self
10891 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10892 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10893
10894 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10895 })
10896 // 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.
10897 .collect::<HashMap<Anchor, _>>();
10898
10899 cursors.into_iter().collect()
10900 }
10901
10902 pub fn enable_breakpoint(
10903 &mut self,
10904 _: &crate::actions::EnableBreakpoint,
10905 window: &mut Window,
10906 cx: &mut Context<Self>,
10907 ) {
10908 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10909 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10910 continue;
10911 };
10912 self.edit_breakpoint_at_anchor(
10913 anchor,
10914 breakpoint,
10915 BreakpointEditAction::InvertState,
10916 cx,
10917 );
10918 }
10919 }
10920
10921 pub fn disable_breakpoint(
10922 &mut self,
10923 _: &crate::actions::DisableBreakpoint,
10924 window: &mut Window,
10925 cx: &mut Context<Self>,
10926 ) {
10927 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10928 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10929 continue;
10930 };
10931 self.edit_breakpoint_at_anchor(
10932 anchor,
10933 breakpoint,
10934 BreakpointEditAction::InvertState,
10935 cx,
10936 );
10937 }
10938 }
10939
10940 pub fn toggle_breakpoint(
10941 &mut self,
10942 _: &crate::actions::ToggleBreakpoint,
10943 window: &mut Window,
10944 cx: &mut Context<Self>,
10945 ) {
10946 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10947 if let Some(breakpoint) = breakpoint {
10948 self.edit_breakpoint_at_anchor(
10949 anchor,
10950 breakpoint,
10951 BreakpointEditAction::Toggle,
10952 cx,
10953 );
10954 } else {
10955 self.edit_breakpoint_at_anchor(
10956 anchor,
10957 Breakpoint::new_standard(),
10958 BreakpointEditAction::Toggle,
10959 cx,
10960 );
10961 }
10962 }
10963 }
10964
10965 pub fn edit_breakpoint_at_anchor(
10966 &mut self,
10967 breakpoint_position: Anchor,
10968 breakpoint: Breakpoint,
10969 edit_action: BreakpointEditAction,
10970 cx: &mut Context<Self>,
10971 ) {
10972 let Some(breakpoint_store) = &self.breakpoint_store else {
10973 return;
10974 };
10975
10976 let Some(buffer) = self
10977 .buffer
10978 .read(cx)
10979 .buffer_for_anchor(breakpoint_position, cx)
10980 else {
10981 return;
10982 };
10983
10984 breakpoint_store.update(cx, |breakpoint_store, cx| {
10985 breakpoint_store.toggle_breakpoint(
10986 buffer,
10987 BreakpointWithPosition {
10988 position: breakpoint_position.text_anchor,
10989 bp: breakpoint,
10990 },
10991 edit_action,
10992 cx,
10993 );
10994 });
10995
10996 cx.notify();
10997 }
10998
10999 #[cfg(any(test, feature = "test-support"))]
11000 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11001 self.breakpoint_store.clone()
11002 }
11003
11004 pub fn prepare_restore_change(
11005 &self,
11006 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11007 hunk: &MultiBufferDiffHunk,
11008 cx: &mut App,
11009 ) -> Option<()> {
11010 if hunk.is_created_file() {
11011 return None;
11012 }
11013 let buffer = self.buffer.read(cx);
11014 let diff = buffer.diff_for(hunk.buffer_id)?;
11015 let buffer = buffer.buffer(hunk.buffer_id)?;
11016 let buffer = buffer.read(cx);
11017 let original_text = diff
11018 .read(cx)
11019 .base_text()
11020 .as_rope()
11021 .slice(hunk.diff_base_byte_range.clone());
11022 let buffer_snapshot = buffer.snapshot();
11023 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11024 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11025 probe
11026 .0
11027 .start
11028 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11029 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11030 }) {
11031 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11032 Some(())
11033 } else {
11034 None
11035 }
11036 }
11037
11038 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11039 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11040 }
11041
11042 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11043 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11044 }
11045
11046 fn manipulate_lines<M>(
11047 &mut self,
11048 window: &mut Window,
11049 cx: &mut Context<Self>,
11050 mut manipulate: M,
11051 ) where
11052 M: FnMut(&str) -> LineManipulationResult,
11053 {
11054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11055
11056 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11057 let buffer = self.buffer.read(cx).snapshot(cx);
11058
11059 let mut edits = Vec::new();
11060
11061 let selections = self.selections.all::<Point>(&display_map);
11062 let mut selections = selections.iter().peekable();
11063 let mut contiguous_row_selections = Vec::new();
11064 let mut new_selections = Vec::new();
11065 let mut added_lines = 0;
11066 let mut removed_lines = 0;
11067
11068 while let Some(selection) = selections.next() {
11069 let (start_row, end_row) = consume_contiguous_rows(
11070 &mut contiguous_row_selections,
11071 selection,
11072 &display_map,
11073 &mut selections,
11074 );
11075
11076 let start_point = Point::new(start_row.0, 0);
11077 let end_point = Point::new(
11078 end_row.previous_row().0,
11079 buffer.line_len(end_row.previous_row()),
11080 );
11081 let text = buffer
11082 .text_for_range(start_point..end_point)
11083 .collect::<String>();
11084
11085 let LineManipulationResult {
11086 new_text,
11087 line_count_before,
11088 line_count_after,
11089 } = manipulate(&text);
11090
11091 edits.push((start_point..end_point, new_text));
11092
11093 // Selections must change based on added and removed line count
11094 let start_row =
11095 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11096 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11097 new_selections.push(Selection {
11098 id: selection.id,
11099 start: start_row,
11100 end: end_row,
11101 goal: SelectionGoal::None,
11102 reversed: selection.reversed,
11103 });
11104
11105 if line_count_after > line_count_before {
11106 added_lines += line_count_after - line_count_before;
11107 } else if line_count_before > line_count_after {
11108 removed_lines += line_count_before - line_count_after;
11109 }
11110 }
11111
11112 self.transact(window, cx, |this, window, cx| {
11113 let buffer = this.buffer.update(cx, |buffer, cx| {
11114 buffer.edit(edits, None, cx);
11115 buffer.snapshot(cx)
11116 });
11117
11118 // Recalculate offsets on newly edited buffer
11119 let new_selections = new_selections
11120 .iter()
11121 .map(|s| {
11122 let start_point = Point::new(s.start.0, 0);
11123 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11124 Selection {
11125 id: s.id,
11126 start: buffer.point_to_offset(start_point),
11127 end: buffer.point_to_offset(end_point),
11128 goal: s.goal,
11129 reversed: s.reversed,
11130 }
11131 })
11132 .collect();
11133
11134 this.change_selections(Default::default(), window, cx, |s| {
11135 s.select(new_selections);
11136 });
11137
11138 this.request_autoscroll(Autoscroll::fit(), cx);
11139 });
11140 }
11141
11142 fn manipulate_immutable_lines<Fn>(
11143 &mut self,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 mut callback: Fn,
11147 ) where
11148 Fn: FnMut(&mut Vec<&str>),
11149 {
11150 self.manipulate_lines(window, cx, |text| {
11151 let mut lines: Vec<&str> = text.split('\n').collect();
11152 let line_count_before = lines.len();
11153
11154 callback(&mut lines);
11155
11156 LineManipulationResult {
11157 new_text: lines.join("\n"),
11158 line_count_before,
11159 line_count_after: lines.len(),
11160 }
11161 });
11162 }
11163
11164 fn manipulate_mutable_lines<Fn>(
11165 &mut self,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 mut callback: Fn,
11169 ) where
11170 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11171 {
11172 self.manipulate_lines(window, cx, |text| {
11173 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11174 let line_count_before = lines.len();
11175
11176 callback(&mut lines);
11177
11178 LineManipulationResult {
11179 new_text: lines.join("\n"),
11180 line_count_before,
11181 line_count_after: lines.len(),
11182 }
11183 });
11184 }
11185
11186 pub fn convert_indentation_to_spaces(
11187 &mut self,
11188 _: &ConvertIndentationToSpaces,
11189 window: &mut Window,
11190 cx: &mut Context<Self>,
11191 ) {
11192 let settings = self.buffer.read(cx).language_settings(cx);
11193 let tab_size = settings.tab_size.get() as usize;
11194
11195 self.manipulate_mutable_lines(window, cx, |lines| {
11196 // Allocates a reasonably sized scratch buffer once for the whole loop
11197 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11198 // Avoids recomputing spaces that could be inserted many times
11199 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11200 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11201 .collect();
11202
11203 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11204 let mut chars = line.as_ref().chars();
11205 let mut col = 0;
11206 let mut changed = false;
11207
11208 for ch in chars.by_ref() {
11209 match ch {
11210 ' ' => {
11211 reindented_line.push(' ');
11212 col += 1;
11213 }
11214 '\t' => {
11215 // \t are converted to spaces depending on the current column
11216 let spaces_len = tab_size - (col % tab_size);
11217 reindented_line.extend(&space_cache[spaces_len - 1]);
11218 col += spaces_len;
11219 changed = true;
11220 }
11221 _ => {
11222 // If we dont append before break, the character is consumed
11223 reindented_line.push(ch);
11224 break;
11225 }
11226 }
11227 }
11228
11229 if !changed {
11230 reindented_line.clear();
11231 continue;
11232 }
11233 // Append the rest of the line and replace old reference with new one
11234 reindented_line.extend(chars);
11235 *line = Cow::Owned(reindented_line.clone());
11236 reindented_line.clear();
11237 }
11238 });
11239 }
11240
11241 pub fn convert_indentation_to_tabs(
11242 &mut self,
11243 _: &ConvertIndentationToTabs,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 let settings = self.buffer.read(cx).language_settings(cx);
11248 let tab_size = settings.tab_size.get() as usize;
11249
11250 self.manipulate_mutable_lines(window, cx, |lines| {
11251 // Allocates a reasonably sized buffer once for the whole loop
11252 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11253 // Avoids recomputing spaces that could be inserted many times
11254 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11255 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11256 .collect();
11257
11258 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11259 let mut chars = line.chars();
11260 let mut spaces_count = 0;
11261 let mut first_non_indent_char = None;
11262 let mut changed = false;
11263
11264 for ch in chars.by_ref() {
11265 match ch {
11266 ' ' => {
11267 // Keep track of spaces. Append \t when we reach tab_size
11268 spaces_count += 1;
11269 changed = true;
11270 if spaces_count == tab_size {
11271 reindented_line.push('\t');
11272 spaces_count = 0;
11273 }
11274 }
11275 '\t' => {
11276 reindented_line.push('\t');
11277 spaces_count = 0;
11278 }
11279 _ => {
11280 // Dont append it yet, we might have remaining spaces
11281 first_non_indent_char = Some(ch);
11282 break;
11283 }
11284 }
11285 }
11286
11287 if !changed {
11288 reindented_line.clear();
11289 continue;
11290 }
11291 // Remaining spaces that didn't make a full tab stop
11292 if spaces_count > 0 {
11293 reindented_line.extend(&space_cache[spaces_count - 1]);
11294 }
11295 // If we consume an extra character that was not indentation, add it back
11296 if let Some(extra_char) = first_non_indent_char {
11297 reindented_line.push(extra_char);
11298 }
11299 // Append the rest of the line and replace old reference with new one
11300 reindented_line.extend(chars);
11301 *line = Cow::Owned(reindented_line.clone());
11302 reindented_line.clear();
11303 }
11304 });
11305 }
11306
11307 pub fn convert_to_upper_case(
11308 &mut self,
11309 _: &ConvertToUpperCase,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 self.manipulate_text(window, cx, |text| text.to_uppercase())
11314 }
11315
11316 pub fn convert_to_lower_case(
11317 &mut self,
11318 _: &ConvertToLowerCase,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 self.manipulate_text(window, cx, |text| text.to_lowercase())
11323 }
11324
11325 pub fn convert_to_title_case(
11326 &mut self,
11327 _: &ConvertToTitleCase,
11328 window: &mut Window,
11329 cx: &mut Context<Self>,
11330 ) {
11331 self.manipulate_text(window, cx, |text| {
11332 text.split('\n')
11333 .map(|line| line.to_case(Case::Title))
11334 .join("\n")
11335 })
11336 }
11337
11338 pub fn convert_to_snake_case(
11339 &mut self,
11340 _: &ConvertToSnakeCase,
11341 window: &mut Window,
11342 cx: &mut Context<Self>,
11343 ) {
11344 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11345 }
11346
11347 pub fn convert_to_kebab_case(
11348 &mut self,
11349 _: &ConvertToKebabCase,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11354 }
11355
11356 pub fn convert_to_upper_camel_case(
11357 &mut self,
11358 _: &ConvertToUpperCamelCase,
11359 window: &mut Window,
11360 cx: &mut Context<Self>,
11361 ) {
11362 self.manipulate_text(window, cx, |text| {
11363 text.split('\n')
11364 .map(|line| line.to_case(Case::UpperCamel))
11365 .join("\n")
11366 })
11367 }
11368
11369 pub fn convert_to_lower_camel_case(
11370 &mut self,
11371 _: &ConvertToLowerCamelCase,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11376 }
11377
11378 pub fn convert_to_opposite_case(
11379 &mut self,
11380 _: &ConvertToOppositeCase,
11381 window: &mut Window,
11382 cx: &mut Context<Self>,
11383 ) {
11384 self.manipulate_text(window, cx, |text| {
11385 text.chars()
11386 .fold(String::with_capacity(text.len()), |mut t, c| {
11387 if c.is_uppercase() {
11388 t.extend(c.to_lowercase());
11389 } else {
11390 t.extend(c.to_uppercase());
11391 }
11392 t
11393 })
11394 })
11395 }
11396
11397 pub fn convert_to_sentence_case(
11398 &mut self,
11399 _: &ConvertToSentenceCase,
11400 window: &mut Window,
11401 cx: &mut Context<Self>,
11402 ) {
11403 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11404 }
11405
11406 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11407 self.manipulate_text(window, cx, |text| {
11408 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11409 if has_upper_case_characters {
11410 text.to_lowercase()
11411 } else {
11412 text.to_uppercase()
11413 }
11414 })
11415 }
11416
11417 pub fn convert_to_rot13(
11418 &mut self,
11419 _: &ConvertToRot13,
11420 window: &mut Window,
11421 cx: &mut Context<Self>,
11422 ) {
11423 self.manipulate_text(window, cx, |text| {
11424 text.chars()
11425 .map(|c| match c {
11426 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11427 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11428 _ => c,
11429 })
11430 .collect()
11431 })
11432 }
11433
11434 pub fn convert_to_rot47(
11435 &mut self,
11436 _: &ConvertToRot47,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_text(window, cx, |text| {
11441 text.chars()
11442 .map(|c| {
11443 let code_point = c as u32;
11444 if code_point >= 33 && code_point <= 126 {
11445 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11446 }
11447 c
11448 })
11449 .collect()
11450 })
11451 }
11452
11453 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11454 where
11455 Fn: FnMut(&str) -> String,
11456 {
11457 let buffer = self.buffer.read(cx).snapshot(cx);
11458
11459 let mut new_selections = Vec::new();
11460 let mut edits = Vec::new();
11461 let mut selection_adjustment = 0i32;
11462
11463 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11464 let selection_is_empty = selection.is_empty();
11465
11466 let (start, end) = if selection_is_empty {
11467 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11468 (word_range.start, word_range.end)
11469 } else {
11470 (
11471 buffer.point_to_offset(selection.start),
11472 buffer.point_to_offset(selection.end),
11473 )
11474 };
11475
11476 let text = buffer.text_for_range(start..end).collect::<String>();
11477 let old_length = text.len() as i32;
11478 let text = callback(&text);
11479
11480 new_selections.push(Selection {
11481 start: (start as i32 - selection_adjustment) as usize,
11482 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11483 goal: SelectionGoal::None,
11484 id: selection.id,
11485 reversed: selection.reversed,
11486 });
11487
11488 selection_adjustment += old_length - text.len() as i32;
11489
11490 edits.push((start..end, text));
11491 }
11492
11493 self.transact(window, cx, |this, window, cx| {
11494 this.buffer.update(cx, |buffer, cx| {
11495 buffer.edit(edits, None, cx);
11496 });
11497
11498 this.change_selections(Default::default(), window, cx, |s| {
11499 s.select(new_selections);
11500 });
11501
11502 this.request_autoscroll(Autoscroll::fit(), cx);
11503 });
11504 }
11505
11506 pub fn move_selection_on_drop(
11507 &mut self,
11508 selection: &Selection<Anchor>,
11509 target: DisplayPoint,
11510 is_cut: bool,
11511 window: &mut Window,
11512 cx: &mut Context<Self>,
11513 ) {
11514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11515 let buffer = display_map.buffer_snapshot();
11516 let mut edits = Vec::new();
11517 let insert_point = display_map
11518 .clip_point(target, Bias::Left)
11519 .to_point(&display_map);
11520 let text = buffer
11521 .text_for_range(selection.start..selection.end)
11522 .collect::<String>();
11523 if is_cut {
11524 edits.push(((selection.start..selection.end), String::new()));
11525 }
11526 let insert_anchor = buffer.anchor_before(insert_point);
11527 edits.push(((insert_anchor..insert_anchor), text));
11528 let last_edit_start = insert_anchor.bias_left(buffer);
11529 let last_edit_end = insert_anchor.bias_right(buffer);
11530 self.transact(window, cx, |this, window, cx| {
11531 this.buffer.update(cx, |buffer, cx| {
11532 buffer.edit(edits, None, cx);
11533 });
11534 this.change_selections(Default::default(), window, cx, |s| {
11535 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11536 });
11537 });
11538 }
11539
11540 pub fn clear_selection_drag_state(&mut self) {
11541 self.selection_drag_state = SelectionDragState::None;
11542 }
11543
11544 pub fn duplicate(
11545 &mut self,
11546 upwards: bool,
11547 whole_lines: bool,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11552
11553 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11554 let buffer = display_map.buffer_snapshot();
11555 let selections = self.selections.all::<Point>(&display_map);
11556
11557 let mut edits = Vec::new();
11558 let mut selections_iter = selections.iter().peekable();
11559 while let Some(selection) = selections_iter.next() {
11560 let mut rows = selection.spanned_rows(false, &display_map);
11561 // duplicate line-wise
11562 if whole_lines || selection.start == selection.end {
11563 // Avoid duplicating the same lines twice.
11564 while let Some(next_selection) = selections_iter.peek() {
11565 let next_rows = next_selection.spanned_rows(false, &display_map);
11566 if next_rows.start < rows.end {
11567 rows.end = next_rows.end;
11568 selections_iter.next().unwrap();
11569 } else {
11570 break;
11571 }
11572 }
11573
11574 // Copy the text from the selected row region and splice it either at the start
11575 // or end of the region.
11576 let start = Point::new(rows.start.0, 0);
11577 let end = Point::new(
11578 rows.end.previous_row().0,
11579 buffer.line_len(rows.end.previous_row()),
11580 );
11581
11582 let mut text = buffer.text_for_range(start..end).collect::<String>();
11583
11584 let insert_location = if upwards {
11585 // When duplicating upward, we need to insert before the current line.
11586 // If we're on the last line and it doesn't end with a newline,
11587 // we need to add a newline before the duplicated content.
11588 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11589 && buffer.max_point().column > 0
11590 && !text.ends_with('\n');
11591
11592 if needs_leading_newline {
11593 text.insert(0, '\n');
11594 end
11595 } else {
11596 text.push('\n');
11597 Point::new(rows.start.0, 0)
11598 }
11599 } else {
11600 text.push('\n');
11601 start
11602 };
11603 edits.push((insert_location..insert_location, text));
11604 } else {
11605 // duplicate character-wise
11606 let start = selection.start;
11607 let end = selection.end;
11608 let text = buffer.text_for_range(start..end).collect::<String>();
11609 edits.push((selection.end..selection.end, text));
11610 }
11611 }
11612
11613 self.transact(window, cx, |this, window, cx| {
11614 this.buffer.update(cx, |buffer, cx| {
11615 buffer.edit(edits, None, cx);
11616 });
11617
11618 // When duplicating upward with whole lines, move the cursor to the duplicated line
11619 if upwards && whole_lines {
11620 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11621
11622 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11623 let mut new_ranges = Vec::new();
11624 let selections = s.all::<Point>(&display_map);
11625 let mut selections_iter = selections.iter().peekable();
11626
11627 while let Some(first_selection) = selections_iter.next() {
11628 // Group contiguous selections together to find the total row span
11629 let mut group_selections = vec![first_selection];
11630 let mut rows = first_selection.spanned_rows(false, &display_map);
11631
11632 while let Some(next_selection) = selections_iter.peek() {
11633 let next_rows = next_selection.spanned_rows(false, &display_map);
11634 if next_rows.start < rows.end {
11635 rows.end = next_rows.end;
11636 group_selections.push(selections_iter.next().unwrap());
11637 } else {
11638 break;
11639 }
11640 }
11641
11642 let row_count = rows.end.0 - rows.start.0;
11643
11644 // Move all selections in this group up by the total number of duplicated rows
11645 for selection in group_selections {
11646 let new_start = Point::new(
11647 selection.start.row.saturating_sub(row_count),
11648 selection.start.column,
11649 );
11650
11651 let new_end = Point::new(
11652 selection.end.row.saturating_sub(row_count),
11653 selection.end.column,
11654 );
11655
11656 new_ranges.push(new_start..new_end);
11657 }
11658 }
11659
11660 s.select_ranges(new_ranges);
11661 });
11662 }
11663
11664 this.request_autoscroll(Autoscroll::fit(), cx);
11665 });
11666 }
11667
11668 pub fn duplicate_line_up(
11669 &mut self,
11670 _: &DuplicateLineUp,
11671 window: &mut Window,
11672 cx: &mut Context<Self>,
11673 ) {
11674 self.duplicate(true, true, window, cx);
11675 }
11676
11677 pub fn duplicate_line_down(
11678 &mut self,
11679 _: &DuplicateLineDown,
11680 window: &mut Window,
11681 cx: &mut Context<Self>,
11682 ) {
11683 self.duplicate(false, true, window, cx);
11684 }
11685
11686 pub fn duplicate_selection(
11687 &mut self,
11688 _: &DuplicateSelection,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 self.duplicate(false, false, window, cx);
11693 }
11694
11695 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11697 if self.mode.is_single_line() {
11698 cx.propagate();
11699 return;
11700 }
11701
11702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11703 let buffer = self.buffer.read(cx).snapshot(cx);
11704
11705 let mut edits = Vec::new();
11706 let mut unfold_ranges = Vec::new();
11707 let mut refold_creases = Vec::new();
11708
11709 let selections = self.selections.all::<Point>(&display_map);
11710 let mut selections = selections.iter().peekable();
11711 let mut contiguous_row_selections = Vec::new();
11712 let mut new_selections = Vec::new();
11713
11714 while let Some(selection) = selections.next() {
11715 // Find all the selections that span a contiguous row range
11716 let (start_row, end_row) = consume_contiguous_rows(
11717 &mut contiguous_row_selections,
11718 selection,
11719 &display_map,
11720 &mut selections,
11721 );
11722
11723 // Move the text spanned by the row range to be before the line preceding the row range
11724 if start_row.0 > 0 {
11725 let range_to_move = Point::new(
11726 start_row.previous_row().0,
11727 buffer.line_len(start_row.previous_row()),
11728 )
11729 ..Point::new(
11730 end_row.previous_row().0,
11731 buffer.line_len(end_row.previous_row()),
11732 );
11733 let insertion_point = display_map
11734 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11735 .0;
11736
11737 // Don't move lines across excerpts
11738 if buffer
11739 .excerpt_containing(insertion_point..range_to_move.end)
11740 .is_some()
11741 {
11742 let text = buffer
11743 .text_for_range(range_to_move.clone())
11744 .flat_map(|s| s.chars())
11745 .skip(1)
11746 .chain(['\n'])
11747 .collect::<String>();
11748
11749 edits.push((
11750 buffer.anchor_after(range_to_move.start)
11751 ..buffer.anchor_before(range_to_move.end),
11752 String::new(),
11753 ));
11754 let insertion_anchor = buffer.anchor_after(insertion_point);
11755 edits.push((insertion_anchor..insertion_anchor, text));
11756
11757 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11758
11759 // Move selections up
11760 new_selections.extend(contiguous_row_selections.drain(..).map(
11761 |mut selection| {
11762 selection.start.row -= row_delta;
11763 selection.end.row -= row_delta;
11764 selection
11765 },
11766 ));
11767
11768 // Move folds up
11769 unfold_ranges.push(range_to_move.clone());
11770 for fold in display_map.folds_in_range(
11771 buffer.anchor_before(range_to_move.start)
11772 ..buffer.anchor_after(range_to_move.end),
11773 ) {
11774 let mut start = fold.range.start.to_point(&buffer);
11775 let mut end = fold.range.end.to_point(&buffer);
11776 start.row -= row_delta;
11777 end.row -= row_delta;
11778 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11779 }
11780 }
11781 }
11782
11783 // If we didn't move line(s), preserve the existing selections
11784 new_selections.append(&mut contiguous_row_selections);
11785 }
11786
11787 self.transact(window, cx, |this, window, cx| {
11788 this.unfold_ranges(&unfold_ranges, true, true, cx);
11789 this.buffer.update(cx, |buffer, cx| {
11790 for (range, text) in edits {
11791 buffer.edit([(range, text)], None, cx);
11792 }
11793 });
11794 this.fold_creases(refold_creases, true, window, cx);
11795 this.change_selections(Default::default(), window, cx, |s| {
11796 s.select(new_selections);
11797 })
11798 });
11799 }
11800
11801 pub fn move_line_down(
11802 &mut self,
11803 _: &MoveLineDown,
11804 window: &mut Window,
11805 cx: &mut Context<Self>,
11806 ) {
11807 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11808 if self.mode.is_single_line() {
11809 cx.propagate();
11810 return;
11811 }
11812
11813 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11814 let buffer = self.buffer.read(cx).snapshot(cx);
11815
11816 let mut edits = Vec::new();
11817 let mut unfold_ranges = Vec::new();
11818 let mut refold_creases = Vec::new();
11819
11820 let selections = self.selections.all::<Point>(&display_map);
11821 let mut selections = selections.iter().peekable();
11822 let mut contiguous_row_selections = Vec::new();
11823 let mut new_selections = Vec::new();
11824
11825 while let Some(selection) = selections.next() {
11826 // Find all the selections that span a contiguous row range
11827 let (start_row, end_row) = consume_contiguous_rows(
11828 &mut contiguous_row_selections,
11829 selection,
11830 &display_map,
11831 &mut selections,
11832 );
11833
11834 // Move the text spanned by the row range to be after the last line of the row range
11835 if end_row.0 <= buffer.max_point().row {
11836 let range_to_move =
11837 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11838 let insertion_point = display_map
11839 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11840 .0;
11841
11842 // Don't move lines across excerpt boundaries
11843 if buffer
11844 .excerpt_containing(range_to_move.start..insertion_point)
11845 .is_some()
11846 {
11847 let mut text = String::from("\n");
11848 text.extend(buffer.text_for_range(range_to_move.clone()));
11849 text.pop(); // Drop trailing newline
11850 edits.push((
11851 buffer.anchor_after(range_to_move.start)
11852 ..buffer.anchor_before(range_to_move.end),
11853 String::new(),
11854 ));
11855 let insertion_anchor = buffer.anchor_after(insertion_point);
11856 edits.push((insertion_anchor..insertion_anchor, text));
11857
11858 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11859
11860 // Move selections down
11861 new_selections.extend(contiguous_row_selections.drain(..).map(
11862 |mut selection| {
11863 selection.start.row += row_delta;
11864 selection.end.row += row_delta;
11865 selection
11866 },
11867 ));
11868
11869 // Move folds down
11870 unfold_ranges.push(range_to_move.clone());
11871 for fold in display_map.folds_in_range(
11872 buffer.anchor_before(range_to_move.start)
11873 ..buffer.anchor_after(range_to_move.end),
11874 ) {
11875 let mut start = fold.range.start.to_point(&buffer);
11876 let mut end = fold.range.end.to_point(&buffer);
11877 start.row += row_delta;
11878 end.row += row_delta;
11879 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11880 }
11881 }
11882 }
11883
11884 // If we didn't move line(s), preserve the existing selections
11885 new_selections.append(&mut contiguous_row_selections);
11886 }
11887
11888 self.transact(window, cx, |this, window, cx| {
11889 this.unfold_ranges(&unfold_ranges, true, true, cx);
11890 this.buffer.update(cx, |buffer, cx| {
11891 for (range, text) in edits {
11892 buffer.edit([(range, text)], None, cx);
11893 }
11894 });
11895 this.fold_creases(refold_creases, true, window, cx);
11896 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11897 });
11898 }
11899
11900 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11901 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11902 let text_layout_details = &self.text_layout_details(window);
11903 self.transact(window, cx, |this, window, cx| {
11904 let edits = this.change_selections(Default::default(), window, cx, |s| {
11905 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11906 s.move_with(|display_map, selection| {
11907 if !selection.is_empty() {
11908 return;
11909 }
11910
11911 let mut head = selection.head();
11912 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11913 if head.column() == display_map.line_len(head.row()) {
11914 transpose_offset = display_map
11915 .buffer_snapshot()
11916 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11917 }
11918
11919 if transpose_offset == 0 {
11920 return;
11921 }
11922
11923 *head.column_mut() += 1;
11924 head = display_map.clip_point(head, Bias::Right);
11925 let goal = SelectionGoal::HorizontalPosition(
11926 display_map
11927 .x_for_display_point(head, text_layout_details)
11928 .into(),
11929 );
11930 selection.collapse_to(head, goal);
11931
11932 let transpose_start = display_map
11933 .buffer_snapshot()
11934 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11935 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11936 let transpose_end = display_map
11937 .buffer_snapshot()
11938 .clip_offset(transpose_offset + 1, Bias::Right);
11939 if let Some(ch) = display_map
11940 .buffer_snapshot()
11941 .chars_at(transpose_start)
11942 .next()
11943 {
11944 edits.push((transpose_start..transpose_offset, String::new()));
11945 edits.push((transpose_end..transpose_end, ch.to_string()));
11946 }
11947 }
11948 });
11949 edits
11950 });
11951 this.buffer
11952 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11953 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11954 this.change_selections(Default::default(), window, cx, |s| {
11955 s.select(selections);
11956 });
11957 });
11958 }
11959
11960 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11962 if self.mode.is_single_line() {
11963 cx.propagate();
11964 return;
11965 }
11966
11967 self.rewrap_impl(RewrapOptions::default(), cx)
11968 }
11969
11970 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11971 let buffer = self.buffer.read(cx).snapshot(cx);
11972 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11973
11974 #[derive(Clone, Debug, PartialEq)]
11975 enum CommentFormat {
11976 /// single line comment, with prefix for line
11977 Line(String),
11978 /// single line within a block comment, with prefix for line
11979 BlockLine(String),
11980 /// a single line of a block comment that includes the initial delimiter
11981 BlockCommentWithStart(BlockCommentConfig),
11982 /// a single line of a block comment that includes the ending delimiter
11983 BlockCommentWithEnd(BlockCommentConfig),
11984 }
11985
11986 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11987 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11988 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11989 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11990 .peekable();
11991
11992 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11993 row
11994 } else {
11995 return Vec::new();
11996 };
11997
11998 let language_settings = buffer.language_settings_at(selection.head(), cx);
11999 let language_scope = buffer.language_scope_at(selection.head());
12000
12001 let indent_and_prefix_for_row =
12002 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12003 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12004 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12005 &language_scope
12006 {
12007 let indent_end = Point::new(row, indent.len);
12008 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12009 let line_text_after_indent = buffer
12010 .text_for_range(indent_end..line_end)
12011 .collect::<String>();
12012
12013 let is_within_comment_override = buffer
12014 .language_scope_at(indent_end)
12015 .is_some_and(|scope| scope.override_name() == Some("comment"));
12016 let comment_delimiters = if is_within_comment_override {
12017 // we are within a comment syntax node, but we don't
12018 // yet know what kind of comment: block, doc or line
12019 match (
12020 language_scope.documentation_comment(),
12021 language_scope.block_comment(),
12022 ) {
12023 (Some(config), _) | (_, Some(config))
12024 if buffer.contains_str_at(indent_end, &config.start) =>
12025 {
12026 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12027 }
12028 (Some(config), _) | (_, Some(config))
12029 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12030 {
12031 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12032 }
12033 (Some(config), _) | (_, Some(config))
12034 if buffer.contains_str_at(indent_end, &config.prefix) =>
12035 {
12036 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12037 }
12038 (_, _) => language_scope
12039 .line_comment_prefixes()
12040 .iter()
12041 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12042 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12043 }
12044 } else {
12045 // we not in an overridden comment node, but we may
12046 // be within a non-overridden line comment node
12047 language_scope
12048 .line_comment_prefixes()
12049 .iter()
12050 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12051 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12052 };
12053
12054 let rewrap_prefix = language_scope
12055 .rewrap_prefixes()
12056 .iter()
12057 .find_map(|prefix_regex| {
12058 prefix_regex.find(&line_text_after_indent).map(|mat| {
12059 if mat.start() == 0 {
12060 Some(mat.as_str().to_string())
12061 } else {
12062 None
12063 }
12064 })
12065 })
12066 .flatten();
12067 (comment_delimiters, rewrap_prefix)
12068 } else {
12069 (None, None)
12070 };
12071 (indent, comment_prefix, rewrap_prefix)
12072 };
12073
12074 let mut ranges = Vec::new();
12075 let from_empty_selection = selection.is_empty();
12076
12077 let mut current_range_start = first_row;
12078 let mut prev_row = first_row;
12079 let (
12080 mut current_range_indent,
12081 mut current_range_comment_delimiters,
12082 mut current_range_rewrap_prefix,
12083 ) = indent_and_prefix_for_row(first_row);
12084
12085 for row in non_blank_rows_iter.skip(1) {
12086 let has_paragraph_break = row > prev_row + 1;
12087
12088 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12089 indent_and_prefix_for_row(row);
12090
12091 let has_indent_change = row_indent != current_range_indent;
12092 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12093
12094 let has_boundary_change = has_comment_change
12095 || row_rewrap_prefix.is_some()
12096 || (has_indent_change && current_range_comment_delimiters.is_some());
12097
12098 if has_paragraph_break || has_boundary_change {
12099 ranges.push((
12100 language_settings.clone(),
12101 Point::new(current_range_start, 0)
12102 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12103 current_range_indent,
12104 current_range_comment_delimiters.clone(),
12105 current_range_rewrap_prefix.clone(),
12106 from_empty_selection,
12107 ));
12108 current_range_start = row;
12109 current_range_indent = row_indent;
12110 current_range_comment_delimiters = row_comment_delimiters;
12111 current_range_rewrap_prefix = row_rewrap_prefix;
12112 }
12113 prev_row = row;
12114 }
12115
12116 ranges.push((
12117 language_settings.clone(),
12118 Point::new(current_range_start, 0)
12119 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12120 current_range_indent,
12121 current_range_comment_delimiters,
12122 current_range_rewrap_prefix,
12123 from_empty_selection,
12124 ));
12125
12126 ranges
12127 });
12128
12129 let mut edits = Vec::new();
12130 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12131
12132 for (
12133 language_settings,
12134 wrap_range,
12135 mut indent_size,
12136 comment_prefix,
12137 rewrap_prefix,
12138 from_empty_selection,
12139 ) in wrap_ranges
12140 {
12141 let mut start_row = wrap_range.start.row;
12142 let mut end_row = wrap_range.end.row;
12143
12144 // Skip selections that overlap with a range that has already been rewrapped.
12145 let selection_range = start_row..end_row;
12146 if rewrapped_row_ranges
12147 .iter()
12148 .any(|range| range.overlaps(&selection_range))
12149 {
12150 continue;
12151 }
12152
12153 let tab_size = language_settings.tab_size;
12154
12155 let (line_prefix, inside_comment) = match &comment_prefix {
12156 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12157 (Some(prefix.as_str()), true)
12158 }
12159 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12160 (Some(prefix.as_ref()), true)
12161 }
12162 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12163 start: _,
12164 end: _,
12165 prefix,
12166 tab_size,
12167 })) => {
12168 indent_size.len += tab_size;
12169 (Some(prefix.as_ref()), true)
12170 }
12171 None => (None, false),
12172 };
12173 let indent_prefix = indent_size.chars().collect::<String>();
12174 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12175
12176 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12177 RewrapBehavior::InComments => inside_comment,
12178 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12179 RewrapBehavior::Anywhere => true,
12180 };
12181
12182 let should_rewrap = options.override_language_settings
12183 || allow_rewrap_based_on_language
12184 || self.hard_wrap.is_some();
12185 if !should_rewrap {
12186 continue;
12187 }
12188
12189 if from_empty_selection {
12190 'expand_upwards: while start_row > 0 {
12191 let prev_row = start_row - 1;
12192 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12193 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12194 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12195 {
12196 start_row = prev_row;
12197 } else {
12198 break 'expand_upwards;
12199 }
12200 }
12201
12202 'expand_downwards: while end_row < buffer.max_point().row {
12203 let next_row = end_row + 1;
12204 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12205 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12206 && !buffer.is_line_blank(MultiBufferRow(next_row))
12207 {
12208 end_row = next_row;
12209 } else {
12210 break 'expand_downwards;
12211 }
12212 }
12213 }
12214
12215 let start = Point::new(start_row, 0);
12216 let start_offset = ToOffset::to_offset(&start, &buffer);
12217 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12218 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12219 let mut first_line_delimiter = None;
12220 let mut last_line_delimiter = None;
12221 let Some(lines_without_prefixes) = selection_text
12222 .lines()
12223 .enumerate()
12224 .map(|(ix, line)| {
12225 let line_trimmed = line.trim_start();
12226 if rewrap_prefix.is_some() && ix > 0 {
12227 Ok(line_trimmed)
12228 } else if let Some(
12229 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12230 start,
12231 prefix,
12232 end,
12233 tab_size,
12234 })
12235 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12236 start,
12237 prefix,
12238 end,
12239 tab_size,
12240 }),
12241 ) = &comment_prefix
12242 {
12243 let line_trimmed = line_trimmed
12244 .strip_prefix(start.as_ref())
12245 .map(|s| {
12246 let mut indent_size = indent_size;
12247 indent_size.len -= tab_size;
12248 let indent_prefix: String = indent_size.chars().collect();
12249 first_line_delimiter = Some((indent_prefix, start));
12250 s.trim_start()
12251 })
12252 .unwrap_or(line_trimmed);
12253 let line_trimmed = line_trimmed
12254 .strip_suffix(end.as_ref())
12255 .map(|s| {
12256 last_line_delimiter = Some(end);
12257 s.trim_end()
12258 })
12259 .unwrap_or(line_trimmed);
12260 let line_trimmed = line_trimmed
12261 .strip_prefix(prefix.as_ref())
12262 .unwrap_or(line_trimmed);
12263 Ok(line_trimmed)
12264 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12265 line_trimmed.strip_prefix(prefix).with_context(|| {
12266 format!("line did not start with prefix {prefix:?}: {line:?}")
12267 })
12268 } else {
12269 line_trimmed
12270 .strip_prefix(&line_prefix.trim_start())
12271 .with_context(|| {
12272 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12273 })
12274 }
12275 })
12276 .collect::<Result<Vec<_>, _>>()
12277 .log_err()
12278 else {
12279 continue;
12280 };
12281
12282 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12283 buffer
12284 .language_settings_at(Point::new(start_row, 0), cx)
12285 .preferred_line_length as usize
12286 });
12287
12288 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12289 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12290 } else {
12291 line_prefix.clone()
12292 };
12293
12294 let wrapped_text = {
12295 let mut wrapped_text = wrap_with_prefix(
12296 line_prefix,
12297 subsequent_lines_prefix,
12298 lines_without_prefixes.join("\n"),
12299 wrap_column,
12300 tab_size,
12301 options.preserve_existing_whitespace,
12302 );
12303
12304 if let Some((indent, delimiter)) = first_line_delimiter {
12305 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12306 }
12307 if let Some(last_line) = last_line_delimiter {
12308 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12309 }
12310
12311 wrapped_text
12312 };
12313
12314 // TODO: should always use char-based diff while still supporting cursor behavior that
12315 // matches vim.
12316 let mut diff_options = DiffOptions::default();
12317 if options.override_language_settings {
12318 diff_options.max_word_diff_len = 0;
12319 diff_options.max_word_diff_line_count = 0;
12320 } else {
12321 diff_options.max_word_diff_len = usize::MAX;
12322 diff_options.max_word_diff_line_count = usize::MAX;
12323 }
12324
12325 for (old_range, new_text) in
12326 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12327 {
12328 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12329 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12330 edits.push((edit_start..edit_end, new_text));
12331 }
12332
12333 rewrapped_row_ranges.push(start_row..=end_row);
12334 }
12335
12336 self.buffer
12337 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12338 }
12339
12340 pub fn cut_common(
12341 &mut self,
12342 cut_no_selection_line: bool,
12343 window: &mut Window,
12344 cx: &mut Context<Self>,
12345 ) -> ClipboardItem {
12346 let mut text = String::new();
12347 let buffer = self.buffer.read(cx).snapshot(cx);
12348 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12349 let mut clipboard_selections = Vec::with_capacity(selections.len());
12350 {
12351 let max_point = buffer.max_point();
12352 let mut is_first = true;
12353 for selection in &mut selections {
12354 let is_entire_line =
12355 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12356 if is_entire_line {
12357 selection.start = Point::new(selection.start.row, 0);
12358 if !selection.is_empty() && selection.end.column == 0 {
12359 selection.end = cmp::min(max_point, selection.end);
12360 } else {
12361 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12362 }
12363 selection.goal = SelectionGoal::None;
12364 }
12365 if is_first {
12366 is_first = false;
12367 } else {
12368 text += "\n";
12369 }
12370 let mut len = 0;
12371 for chunk in buffer.text_for_range(selection.start..selection.end) {
12372 text.push_str(chunk);
12373 len += chunk.len();
12374 }
12375 clipboard_selections.push(ClipboardSelection {
12376 len,
12377 is_entire_line,
12378 first_line_indent: buffer
12379 .indent_size_for_line(MultiBufferRow(selection.start.row))
12380 .len,
12381 });
12382 }
12383 }
12384
12385 self.transact(window, cx, |this, window, cx| {
12386 this.change_selections(Default::default(), window, cx, |s| {
12387 s.select(selections);
12388 });
12389 this.insert("", window, cx);
12390 });
12391 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12392 }
12393
12394 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12396 let item = self.cut_common(true, window, cx);
12397 cx.write_to_clipboard(item);
12398 }
12399
12400 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12402 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12403 s.move_with(|snapshot, sel| {
12404 if sel.is_empty() {
12405 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12406 }
12407 if sel.is_empty() {
12408 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12409 }
12410 });
12411 });
12412 let item = self.cut_common(false, window, cx);
12413 cx.set_global(KillRing(item))
12414 }
12415
12416 pub fn kill_ring_yank(
12417 &mut self,
12418 _: &KillRingYank,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12423 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12424 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12425 (kill_ring.text().to_string(), kill_ring.metadata_json())
12426 } else {
12427 return;
12428 }
12429 } else {
12430 return;
12431 };
12432 self.do_paste(&text, metadata, false, window, cx);
12433 }
12434
12435 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12436 self.do_copy(true, cx);
12437 }
12438
12439 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12440 self.do_copy(false, cx);
12441 }
12442
12443 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12444 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12445 let buffer = self.buffer.read(cx).read(cx);
12446 let mut text = String::new();
12447
12448 let mut clipboard_selections = Vec::with_capacity(selections.len());
12449 {
12450 let max_point = buffer.max_point();
12451 let mut is_first = true;
12452 for selection in &selections {
12453 let mut start = selection.start;
12454 let mut end = selection.end;
12455 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12456 let mut add_trailing_newline = false;
12457 if is_entire_line {
12458 start = Point::new(start.row, 0);
12459 let next_line_start = Point::new(end.row + 1, 0);
12460 if next_line_start <= max_point {
12461 end = next_line_start;
12462 } else {
12463 // We're on the last line without a trailing newline.
12464 // Copy to the end of the line and add a newline afterwards.
12465 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12466 add_trailing_newline = true;
12467 }
12468 }
12469
12470 let mut trimmed_selections = Vec::new();
12471 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12472 let row = MultiBufferRow(start.row);
12473 let first_indent = buffer.indent_size_for_line(row);
12474 if first_indent.len == 0 || start.column > first_indent.len {
12475 trimmed_selections.push(start..end);
12476 } else {
12477 trimmed_selections.push(
12478 Point::new(row.0, first_indent.len)
12479 ..Point::new(row.0, buffer.line_len(row)),
12480 );
12481 for row in start.row + 1..=end.row {
12482 let mut line_len = buffer.line_len(MultiBufferRow(row));
12483 if row == end.row {
12484 line_len = end.column;
12485 }
12486 if line_len == 0 {
12487 trimmed_selections
12488 .push(Point::new(row, 0)..Point::new(row, line_len));
12489 continue;
12490 }
12491 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12492 if row_indent_size.len >= first_indent.len {
12493 trimmed_selections.push(
12494 Point::new(row, first_indent.len)..Point::new(row, line_len),
12495 );
12496 } else {
12497 trimmed_selections.clear();
12498 trimmed_selections.push(start..end);
12499 break;
12500 }
12501 }
12502 }
12503 } else {
12504 trimmed_selections.push(start..end);
12505 }
12506
12507 for trimmed_range in trimmed_selections {
12508 if is_first {
12509 is_first = false;
12510 } else {
12511 text += "\n";
12512 }
12513 let mut len = 0;
12514 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12515 text.push_str(chunk);
12516 len += chunk.len();
12517 }
12518 if add_trailing_newline {
12519 text.push('\n');
12520 len += 1;
12521 }
12522 clipboard_selections.push(ClipboardSelection {
12523 len,
12524 is_entire_line,
12525 first_line_indent: buffer
12526 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12527 .len,
12528 });
12529 }
12530 }
12531 }
12532
12533 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12534 text,
12535 clipboard_selections,
12536 ));
12537 }
12538
12539 pub fn do_paste(
12540 &mut self,
12541 text: &String,
12542 clipboard_selections: Option<Vec<ClipboardSelection>>,
12543 handle_entire_lines: bool,
12544 window: &mut Window,
12545 cx: &mut Context<Self>,
12546 ) {
12547 if self.read_only(cx) {
12548 return;
12549 }
12550
12551 let clipboard_text = Cow::Borrowed(text.as_str());
12552
12553 self.transact(window, cx, |this, window, cx| {
12554 let had_active_edit_prediction = this.has_active_edit_prediction();
12555 let display_map = this.display_snapshot(cx);
12556 let old_selections = this.selections.all::<usize>(&display_map);
12557 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12558
12559 if let Some(mut clipboard_selections) = clipboard_selections {
12560 let all_selections_were_entire_line =
12561 clipboard_selections.iter().all(|s| s.is_entire_line);
12562 let first_selection_indent_column =
12563 clipboard_selections.first().map(|s| s.first_line_indent);
12564 if clipboard_selections.len() != old_selections.len() {
12565 clipboard_selections.drain(..);
12566 }
12567 let mut auto_indent_on_paste = true;
12568
12569 this.buffer.update(cx, |buffer, cx| {
12570 let snapshot = buffer.read(cx);
12571 auto_indent_on_paste = snapshot
12572 .language_settings_at(cursor_offset, cx)
12573 .auto_indent_on_paste;
12574
12575 let mut start_offset = 0;
12576 let mut edits = Vec::new();
12577 let mut original_indent_columns = Vec::new();
12578 for (ix, selection) in old_selections.iter().enumerate() {
12579 let to_insert;
12580 let entire_line;
12581 let original_indent_column;
12582 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12583 let end_offset = start_offset + clipboard_selection.len;
12584 to_insert = &clipboard_text[start_offset..end_offset];
12585 entire_line = clipboard_selection.is_entire_line;
12586 start_offset = end_offset + 1;
12587 original_indent_column = Some(clipboard_selection.first_line_indent);
12588 } else {
12589 to_insert = &*clipboard_text;
12590 entire_line = all_selections_were_entire_line;
12591 original_indent_column = first_selection_indent_column
12592 }
12593
12594 let (range, to_insert) =
12595 if selection.is_empty() && handle_entire_lines && entire_line {
12596 // If the corresponding selection was empty when this slice of the
12597 // clipboard text was written, then the entire line containing the
12598 // selection was copied. If this selection is also currently empty,
12599 // then paste the line before the current line of the buffer.
12600 let column = selection.start.to_point(&snapshot).column as usize;
12601 let line_start = selection.start - column;
12602 (line_start..line_start, Cow::Borrowed(to_insert))
12603 } else {
12604 let language = snapshot.language_at(selection.head());
12605 let range = selection.range();
12606 if let Some(language) = language
12607 && language.name() == "Markdown".into()
12608 {
12609 edit_for_markdown_paste(
12610 &snapshot,
12611 range,
12612 to_insert,
12613 url::Url::parse(to_insert).ok(),
12614 )
12615 } else {
12616 (range, Cow::Borrowed(to_insert))
12617 }
12618 };
12619
12620 edits.push((range, to_insert));
12621 original_indent_columns.push(original_indent_column);
12622 }
12623 drop(snapshot);
12624
12625 buffer.edit(
12626 edits,
12627 if auto_indent_on_paste {
12628 Some(AutoindentMode::Block {
12629 original_indent_columns,
12630 })
12631 } else {
12632 None
12633 },
12634 cx,
12635 );
12636 });
12637
12638 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12639 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12640 } else {
12641 let url = url::Url::parse(&clipboard_text).ok();
12642
12643 let auto_indent_mode = if !clipboard_text.is_empty() {
12644 Some(AutoindentMode::Block {
12645 original_indent_columns: Vec::new(),
12646 })
12647 } else {
12648 None
12649 };
12650
12651 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12652 let snapshot = buffer.snapshot(cx);
12653
12654 let anchors = old_selections
12655 .iter()
12656 .map(|s| {
12657 let anchor = snapshot.anchor_after(s.head());
12658 s.map(|_| anchor)
12659 })
12660 .collect::<Vec<_>>();
12661
12662 let mut edits = Vec::new();
12663
12664 for selection in old_selections.iter() {
12665 let language = snapshot.language_at(selection.head());
12666 let range = selection.range();
12667
12668 let (edit_range, edit_text) = if let Some(language) = language
12669 && language.name() == "Markdown".into()
12670 {
12671 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12672 } else {
12673 (range, clipboard_text.clone())
12674 };
12675
12676 edits.push((edit_range, edit_text));
12677 }
12678
12679 drop(snapshot);
12680 buffer.edit(edits, auto_indent_mode, cx);
12681
12682 anchors
12683 });
12684
12685 this.change_selections(Default::default(), window, cx, |s| {
12686 s.select_anchors(selection_anchors);
12687 });
12688 }
12689
12690 let trigger_in_words =
12691 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12692
12693 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12694 });
12695 }
12696
12697 pub fn diff_clipboard_with_selection(
12698 &mut self,
12699 _: &DiffClipboardWithSelection,
12700 window: &mut Window,
12701 cx: &mut Context<Self>,
12702 ) {
12703 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12704
12705 if selections.is_empty() {
12706 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12707 return;
12708 };
12709
12710 let clipboard_text = match cx.read_from_clipboard() {
12711 Some(item) => match item.entries().first() {
12712 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12713 _ => None,
12714 },
12715 None => None,
12716 };
12717
12718 let Some(clipboard_text) = clipboard_text else {
12719 log::warn!("Clipboard doesn't contain text.");
12720 return;
12721 };
12722
12723 window.dispatch_action(
12724 Box::new(DiffClipboardWithSelectionData {
12725 clipboard_text,
12726 editor: cx.entity(),
12727 }),
12728 cx,
12729 );
12730 }
12731
12732 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12734 if let Some(item) = cx.read_from_clipboard() {
12735 let entries = item.entries();
12736
12737 match entries.first() {
12738 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12739 // of all the pasted entries.
12740 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12741 .do_paste(
12742 clipboard_string.text(),
12743 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12744 true,
12745 window,
12746 cx,
12747 ),
12748 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12749 }
12750 }
12751 }
12752
12753 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12754 if self.read_only(cx) {
12755 return;
12756 }
12757
12758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12759
12760 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12761 if let Some((selections, _)) =
12762 self.selection_history.transaction(transaction_id).cloned()
12763 {
12764 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12765 s.select_anchors(selections.to_vec());
12766 });
12767 } else {
12768 log::error!(
12769 "No entry in selection_history found for undo. \
12770 This may correspond to a bug where undo does not update the selection. \
12771 If this is occurring, please add details to \
12772 https://github.com/zed-industries/zed/issues/22692"
12773 );
12774 }
12775 self.request_autoscroll(Autoscroll::fit(), cx);
12776 self.unmark_text(window, cx);
12777 self.refresh_edit_prediction(true, false, window, cx);
12778 cx.emit(EditorEvent::Edited { transaction_id });
12779 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12780 }
12781 }
12782
12783 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12784 if self.read_only(cx) {
12785 return;
12786 }
12787
12788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12789
12790 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12791 if let Some((_, Some(selections))) =
12792 self.selection_history.transaction(transaction_id).cloned()
12793 {
12794 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12795 s.select_anchors(selections.to_vec());
12796 });
12797 } else {
12798 log::error!(
12799 "No entry in selection_history found for redo. \
12800 This may correspond to a bug where undo does not update the selection. \
12801 If this is occurring, please add details to \
12802 https://github.com/zed-industries/zed/issues/22692"
12803 );
12804 }
12805 self.request_autoscroll(Autoscroll::fit(), cx);
12806 self.unmark_text(window, cx);
12807 self.refresh_edit_prediction(true, false, window, cx);
12808 cx.emit(EditorEvent::Edited { transaction_id });
12809 }
12810 }
12811
12812 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12813 self.buffer
12814 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12815 }
12816
12817 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12818 self.buffer
12819 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12820 }
12821
12822 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12824 self.change_selections(Default::default(), window, cx, |s| {
12825 s.move_with(|map, selection| {
12826 let cursor = if selection.is_empty() {
12827 movement::left(map, selection.start)
12828 } else {
12829 selection.start
12830 };
12831 selection.collapse_to(cursor, SelectionGoal::None);
12832 });
12833 })
12834 }
12835
12836 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12837 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12838 self.change_selections(Default::default(), window, cx, |s| {
12839 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12840 })
12841 }
12842
12843 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12845 self.change_selections(Default::default(), window, cx, |s| {
12846 s.move_with(|map, selection| {
12847 let cursor = if selection.is_empty() {
12848 movement::right(map, selection.end)
12849 } else {
12850 selection.end
12851 };
12852 selection.collapse_to(cursor, SelectionGoal::None)
12853 });
12854 })
12855 }
12856
12857 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12859 self.change_selections(Default::default(), window, cx, |s| {
12860 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12861 });
12862 }
12863
12864 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12865 if self.take_rename(true, window, cx).is_some() {
12866 return;
12867 }
12868
12869 if self.mode.is_single_line() {
12870 cx.propagate();
12871 return;
12872 }
12873
12874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12875
12876 let text_layout_details = &self.text_layout_details(window);
12877 let selection_count = self.selections.count();
12878 let first_selection = self.selections.first_anchor();
12879
12880 self.change_selections(Default::default(), window, cx, |s| {
12881 s.move_with(|map, selection| {
12882 if !selection.is_empty() {
12883 selection.goal = SelectionGoal::None;
12884 }
12885 let (cursor, goal) = movement::up(
12886 map,
12887 selection.start,
12888 selection.goal,
12889 false,
12890 text_layout_details,
12891 );
12892 selection.collapse_to(cursor, goal);
12893 });
12894 });
12895
12896 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12897 {
12898 cx.propagate();
12899 }
12900 }
12901
12902 pub fn move_up_by_lines(
12903 &mut self,
12904 action: &MoveUpByLines,
12905 window: &mut Window,
12906 cx: &mut Context<Self>,
12907 ) {
12908 if self.take_rename(true, window, cx).is_some() {
12909 return;
12910 }
12911
12912 if self.mode.is_single_line() {
12913 cx.propagate();
12914 return;
12915 }
12916
12917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12918
12919 let text_layout_details = &self.text_layout_details(window);
12920
12921 self.change_selections(Default::default(), window, cx, |s| {
12922 s.move_with(|map, selection| {
12923 if !selection.is_empty() {
12924 selection.goal = SelectionGoal::None;
12925 }
12926 let (cursor, goal) = movement::up_by_rows(
12927 map,
12928 selection.start,
12929 action.lines,
12930 selection.goal,
12931 false,
12932 text_layout_details,
12933 );
12934 selection.collapse_to(cursor, goal);
12935 });
12936 })
12937 }
12938
12939 pub fn move_down_by_lines(
12940 &mut self,
12941 action: &MoveDownByLines,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 if self.take_rename(true, window, cx).is_some() {
12946 return;
12947 }
12948
12949 if self.mode.is_single_line() {
12950 cx.propagate();
12951 return;
12952 }
12953
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955
12956 let text_layout_details = &self.text_layout_details(window);
12957
12958 self.change_selections(Default::default(), window, cx, |s| {
12959 s.move_with(|map, selection| {
12960 if !selection.is_empty() {
12961 selection.goal = SelectionGoal::None;
12962 }
12963 let (cursor, goal) = movement::down_by_rows(
12964 map,
12965 selection.start,
12966 action.lines,
12967 selection.goal,
12968 false,
12969 text_layout_details,
12970 );
12971 selection.collapse_to(cursor, goal);
12972 });
12973 })
12974 }
12975
12976 pub fn select_down_by_lines(
12977 &mut self,
12978 action: &SelectDownByLines,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12983 let text_layout_details = &self.text_layout_details(window);
12984 self.change_selections(Default::default(), window, cx, |s| {
12985 s.move_heads_with(|map, head, goal| {
12986 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12987 })
12988 })
12989 }
12990
12991 pub fn select_up_by_lines(
12992 &mut self,
12993 action: &SelectUpByLines,
12994 window: &mut Window,
12995 cx: &mut Context<Self>,
12996 ) {
12997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12998 let text_layout_details = &self.text_layout_details(window);
12999 self.change_selections(Default::default(), window, cx, |s| {
13000 s.move_heads_with(|map, head, goal| {
13001 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13002 })
13003 })
13004 }
13005
13006 pub fn select_page_up(
13007 &mut self,
13008 _: &SelectPageUp,
13009 window: &mut Window,
13010 cx: &mut Context<Self>,
13011 ) {
13012 let Some(row_count) = self.visible_row_count() else {
13013 return;
13014 };
13015
13016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13017
13018 let text_layout_details = &self.text_layout_details(window);
13019
13020 self.change_selections(Default::default(), window, cx, |s| {
13021 s.move_heads_with(|map, head, goal| {
13022 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13023 })
13024 })
13025 }
13026
13027 pub fn move_page_up(
13028 &mut self,
13029 action: &MovePageUp,
13030 window: &mut Window,
13031 cx: &mut Context<Self>,
13032 ) {
13033 if self.take_rename(true, window, cx).is_some() {
13034 return;
13035 }
13036
13037 if self
13038 .context_menu
13039 .borrow_mut()
13040 .as_mut()
13041 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13042 .unwrap_or(false)
13043 {
13044 return;
13045 }
13046
13047 if matches!(self.mode, EditorMode::SingleLine) {
13048 cx.propagate();
13049 return;
13050 }
13051
13052 let Some(row_count) = self.visible_row_count() else {
13053 return;
13054 };
13055
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13057
13058 let effects = if action.center_cursor {
13059 SelectionEffects::scroll(Autoscroll::center())
13060 } else {
13061 SelectionEffects::default()
13062 };
13063
13064 let text_layout_details = &self.text_layout_details(window);
13065
13066 self.change_selections(effects, window, cx, |s| {
13067 s.move_with(|map, selection| {
13068 if !selection.is_empty() {
13069 selection.goal = SelectionGoal::None;
13070 }
13071 let (cursor, goal) = movement::up_by_rows(
13072 map,
13073 selection.end,
13074 row_count,
13075 selection.goal,
13076 false,
13077 text_layout_details,
13078 );
13079 selection.collapse_to(cursor, goal);
13080 });
13081 });
13082 }
13083
13084 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13086 let text_layout_details = &self.text_layout_details(window);
13087 self.change_selections(Default::default(), window, cx, |s| {
13088 s.move_heads_with(|map, head, goal| {
13089 movement::up(map, head, goal, false, text_layout_details)
13090 })
13091 })
13092 }
13093
13094 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13095 self.take_rename(true, window, cx);
13096
13097 if self.mode.is_single_line() {
13098 cx.propagate();
13099 return;
13100 }
13101
13102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13103
13104 let text_layout_details = &self.text_layout_details(window);
13105 let selection_count = self.selections.count();
13106 let first_selection = self.selections.first_anchor();
13107
13108 self.change_selections(Default::default(), window, cx, |s| {
13109 s.move_with(|map, selection| {
13110 if !selection.is_empty() {
13111 selection.goal = SelectionGoal::None;
13112 }
13113 let (cursor, goal) = movement::down(
13114 map,
13115 selection.end,
13116 selection.goal,
13117 false,
13118 text_layout_details,
13119 );
13120 selection.collapse_to(cursor, goal);
13121 });
13122 });
13123
13124 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13125 {
13126 cx.propagate();
13127 }
13128 }
13129
13130 pub fn select_page_down(
13131 &mut self,
13132 _: &SelectPageDown,
13133 window: &mut Window,
13134 cx: &mut Context<Self>,
13135 ) {
13136 let Some(row_count) = self.visible_row_count() else {
13137 return;
13138 };
13139
13140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13141
13142 let text_layout_details = &self.text_layout_details(window);
13143
13144 self.change_selections(Default::default(), window, cx, |s| {
13145 s.move_heads_with(|map, head, goal| {
13146 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13147 })
13148 })
13149 }
13150
13151 pub fn move_page_down(
13152 &mut self,
13153 action: &MovePageDown,
13154 window: &mut Window,
13155 cx: &mut Context<Self>,
13156 ) {
13157 if self.take_rename(true, window, cx).is_some() {
13158 return;
13159 }
13160
13161 if self
13162 .context_menu
13163 .borrow_mut()
13164 .as_mut()
13165 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13166 .unwrap_or(false)
13167 {
13168 return;
13169 }
13170
13171 if matches!(self.mode, EditorMode::SingleLine) {
13172 cx.propagate();
13173 return;
13174 }
13175
13176 let Some(row_count) = self.visible_row_count() else {
13177 return;
13178 };
13179
13180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13181
13182 let effects = if action.center_cursor {
13183 SelectionEffects::scroll(Autoscroll::center())
13184 } else {
13185 SelectionEffects::default()
13186 };
13187
13188 let text_layout_details = &self.text_layout_details(window);
13189 self.change_selections(effects, window, cx, |s| {
13190 s.move_with(|map, selection| {
13191 if !selection.is_empty() {
13192 selection.goal = SelectionGoal::None;
13193 }
13194 let (cursor, goal) = movement::down_by_rows(
13195 map,
13196 selection.end,
13197 row_count,
13198 selection.goal,
13199 false,
13200 text_layout_details,
13201 );
13202 selection.collapse_to(cursor, goal);
13203 });
13204 });
13205 }
13206
13207 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13209 let text_layout_details = &self.text_layout_details(window);
13210 self.change_selections(Default::default(), window, cx, |s| {
13211 s.move_heads_with(|map, head, goal| {
13212 movement::down(map, head, goal, false, text_layout_details)
13213 })
13214 });
13215 }
13216
13217 pub fn context_menu_first(
13218 &mut self,
13219 _: &ContextMenuFirst,
13220 window: &mut Window,
13221 cx: &mut Context<Self>,
13222 ) {
13223 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13224 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13225 }
13226 }
13227
13228 pub fn context_menu_prev(
13229 &mut self,
13230 _: &ContextMenuPrevious,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) {
13234 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13235 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13236 }
13237 }
13238
13239 pub fn context_menu_next(
13240 &mut self,
13241 _: &ContextMenuNext,
13242 window: &mut Window,
13243 cx: &mut Context<Self>,
13244 ) {
13245 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13246 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13247 }
13248 }
13249
13250 pub fn context_menu_last(
13251 &mut self,
13252 _: &ContextMenuLast,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) {
13256 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13257 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13258 }
13259 }
13260
13261 pub fn signature_help_prev(
13262 &mut self,
13263 _: &SignatureHelpPrevious,
13264 _: &mut Window,
13265 cx: &mut Context<Self>,
13266 ) {
13267 if let Some(popover) = self.signature_help_state.popover_mut() {
13268 if popover.current_signature == 0 {
13269 popover.current_signature = popover.signatures.len() - 1;
13270 } else {
13271 popover.current_signature -= 1;
13272 }
13273 cx.notify();
13274 }
13275 }
13276
13277 pub fn signature_help_next(
13278 &mut self,
13279 _: &SignatureHelpNext,
13280 _: &mut Window,
13281 cx: &mut Context<Self>,
13282 ) {
13283 if let Some(popover) = self.signature_help_state.popover_mut() {
13284 if popover.current_signature + 1 == popover.signatures.len() {
13285 popover.current_signature = 0;
13286 } else {
13287 popover.current_signature += 1;
13288 }
13289 cx.notify();
13290 }
13291 }
13292
13293 pub fn move_to_previous_word_start(
13294 &mut self,
13295 _: &MoveToPreviousWordStart,
13296 window: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13300 self.change_selections(Default::default(), window, cx, |s| {
13301 s.move_cursors_with(|map, head, _| {
13302 (
13303 movement::previous_word_start(map, head),
13304 SelectionGoal::None,
13305 )
13306 });
13307 })
13308 }
13309
13310 pub fn move_to_previous_subword_start(
13311 &mut self,
13312 _: &MoveToPreviousSubwordStart,
13313 window: &mut Window,
13314 cx: &mut Context<Self>,
13315 ) {
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317 self.change_selections(Default::default(), window, cx, |s| {
13318 s.move_cursors_with(|map, head, _| {
13319 (
13320 movement::previous_subword_start(map, head),
13321 SelectionGoal::None,
13322 )
13323 });
13324 })
13325 }
13326
13327 pub fn select_to_previous_word_start(
13328 &mut self,
13329 _: &SelectToPreviousWordStart,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13334 self.change_selections(Default::default(), window, cx, |s| {
13335 s.move_heads_with(|map, head, _| {
13336 (
13337 movement::previous_word_start(map, head),
13338 SelectionGoal::None,
13339 )
13340 });
13341 })
13342 }
13343
13344 pub fn select_to_previous_subword_start(
13345 &mut self,
13346 _: &SelectToPreviousSubwordStart,
13347 window: &mut Window,
13348 cx: &mut Context<Self>,
13349 ) {
13350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_heads_with(|map, head, _| {
13353 (
13354 movement::previous_subword_start(map, head),
13355 SelectionGoal::None,
13356 )
13357 });
13358 })
13359 }
13360
13361 pub fn delete_to_previous_word_start(
13362 &mut self,
13363 action: &DeleteToPreviousWordStart,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13368 self.transact(window, cx, |this, window, cx| {
13369 this.select_autoclose_pair(window, cx);
13370 this.change_selections(Default::default(), window, cx, |s| {
13371 s.move_with(|map, selection| {
13372 if selection.is_empty() {
13373 let mut cursor = if action.ignore_newlines {
13374 movement::previous_word_start(map, selection.head())
13375 } else {
13376 movement::previous_word_start_or_newline(map, selection.head())
13377 };
13378 cursor = movement::adjust_greedy_deletion(
13379 map,
13380 selection.head(),
13381 cursor,
13382 action.ignore_brackets,
13383 );
13384 selection.set_head(cursor, SelectionGoal::None);
13385 }
13386 });
13387 });
13388 this.insert("", window, cx);
13389 });
13390 }
13391
13392 pub fn delete_to_previous_subword_start(
13393 &mut self,
13394 _: &DeleteToPreviousSubwordStart,
13395 window: &mut Window,
13396 cx: &mut Context<Self>,
13397 ) {
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13399 self.transact(window, cx, |this, window, cx| {
13400 this.select_autoclose_pair(window, cx);
13401 this.change_selections(Default::default(), window, cx, |s| {
13402 s.move_with(|map, selection| {
13403 if selection.is_empty() {
13404 let mut cursor = movement::previous_subword_start(map, selection.head());
13405 cursor =
13406 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13407 selection.set_head(cursor, SelectionGoal::None);
13408 }
13409 });
13410 });
13411 this.insert("", window, cx);
13412 });
13413 }
13414
13415 pub fn move_to_next_word_end(
13416 &mut self,
13417 _: &MoveToNextWordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_cursors_with(|map, head, _| {
13424 (movement::next_word_end(map, head), SelectionGoal::None)
13425 });
13426 })
13427 }
13428
13429 pub fn move_to_next_subword_end(
13430 &mut self,
13431 _: &MoveToNextSubwordEnd,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_cursors_with(|map, head, _| {
13438 (movement::next_subword_end(map, head), SelectionGoal::None)
13439 });
13440 })
13441 }
13442
13443 pub fn select_to_next_word_end(
13444 &mut self,
13445 _: &SelectToNextWordEnd,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.move_heads_with(|map, head, _| {
13452 (movement::next_word_end(map, head), SelectionGoal::None)
13453 });
13454 })
13455 }
13456
13457 pub fn select_to_next_subword_end(
13458 &mut self,
13459 _: &SelectToNextSubwordEnd,
13460 window: &mut Window,
13461 cx: &mut Context<Self>,
13462 ) {
13463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13464 self.change_selections(Default::default(), window, cx, |s| {
13465 s.move_heads_with(|map, head, _| {
13466 (movement::next_subword_end(map, head), SelectionGoal::None)
13467 });
13468 })
13469 }
13470
13471 pub fn delete_to_next_word_end(
13472 &mut self,
13473 action: &DeleteToNextWordEnd,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13478 self.transact(window, cx, |this, window, cx| {
13479 this.change_selections(Default::default(), window, cx, |s| {
13480 s.move_with(|map, selection| {
13481 if selection.is_empty() {
13482 let mut cursor = if action.ignore_newlines {
13483 movement::next_word_end(map, selection.head())
13484 } else {
13485 movement::next_word_end_or_newline(map, selection.head())
13486 };
13487 cursor = movement::adjust_greedy_deletion(
13488 map,
13489 selection.head(),
13490 cursor,
13491 action.ignore_brackets,
13492 );
13493 selection.set_head(cursor, SelectionGoal::None);
13494 }
13495 });
13496 });
13497 this.insert("", window, cx);
13498 });
13499 }
13500
13501 pub fn delete_to_next_subword_end(
13502 &mut self,
13503 _: &DeleteToNextSubwordEnd,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13508 self.transact(window, cx, |this, window, cx| {
13509 this.change_selections(Default::default(), window, cx, |s| {
13510 s.move_with(|map, selection| {
13511 if selection.is_empty() {
13512 let mut cursor = movement::next_subword_end(map, selection.head());
13513 cursor =
13514 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13515 selection.set_head(cursor, SelectionGoal::None);
13516 }
13517 });
13518 });
13519 this.insert("", window, cx);
13520 });
13521 }
13522
13523 pub fn move_to_beginning_of_line(
13524 &mut self,
13525 action: &MoveToBeginningOfLine,
13526 window: &mut Window,
13527 cx: &mut Context<Self>,
13528 ) {
13529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13530 self.change_selections(Default::default(), window, cx, |s| {
13531 s.move_cursors_with(|map, head, _| {
13532 (
13533 movement::indented_line_beginning(
13534 map,
13535 head,
13536 action.stop_at_soft_wraps,
13537 action.stop_at_indent,
13538 ),
13539 SelectionGoal::None,
13540 )
13541 });
13542 })
13543 }
13544
13545 pub fn select_to_beginning_of_line(
13546 &mut self,
13547 action: &SelectToBeginningOfLine,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, _| {
13554 (
13555 movement::indented_line_beginning(
13556 map,
13557 head,
13558 action.stop_at_soft_wraps,
13559 action.stop_at_indent,
13560 ),
13561 SelectionGoal::None,
13562 )
13563 });
13564 });
13565 }
13566
13567 pub fn delete_to_beginning_of_line(
13568 &mut self,
13569 action: &DeleteToBeginningOfLine,
13570 window: &mut Window,
13571 cx: &mut Context<Self>,
13572 ) {
13573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13574 self.transact(window, cx, |this, window, cx| {
13575 this.change_selections(Default::default(), window, cx, |s| {
13576 s.move_with(|_, selection| {
13577 selection.reversed = true;
13578 });
13579 });
13580
13581 this.select_to_beginning_of_line(
13582 &SelectToBeginningOfLine {
13583 stop_at_soft_wraps: false,
13584 stop_at_indent: action.stop_at_indent,
13585 },
13586 window,
13587 cx,
13588 );
13589 this.backspace(&Backspace, window, cx);
13590 });
13591 }
13592
13593 pub fn move_to_end_of_line(
13594 &mut self,
13595 action: &MoveToEndOfLine,
13596 window: &mut Window,
13597 cx: &mut Context<Self>,
13598 ) {
13599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13600 self.change_selections(Default::default(), window, cx, |s| {
13601 s.move_cursors_with(|map, head, _| {
13602 (
13603 movement::line_end(map, head, action.stop_at_soft_wraps),
13604 SelectionGoal::None,
13605 )
13606 });
13607 })
13608 }
13609
13610 pub fn select_to_end_of_line(
13611 &mut self,
13612 action: &SelectToEndOfLine,
13613 window: &mut Window,
13614 cx: &mut Context<Self>,
13615 ) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13617 self.change_selections(Default::default(), window, cx, |s| {
13618 s.move_heads_with(|map, head, _| {
13619 (
13620 movement::line_end(map, head, action.stop_at_soft_wraps),
13621 SelectionGoal::None,
13622 )
13623 });
13624 })
13625 }
13626
13627 pub fn delete_to_end_of_line(
13628 &mut self,
13629 _: &DeleteToEndOfLine,
13630 window: &mut Window,
13631 cx: &mut Context<Self>,
13632 ) {
13633 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13634 self.transact(window, cx, |this, window, cx| {
13635 this.select_to_end_of_line(
13636 &SelectToEndOfLine {
13637 stop_at_soft_wraps: false,
13638 },
13639 window,
13640 cx,
13641 );
13642 this.delete(&Delete, window, cx);
13643 });
13644 }
13645
13646 pub fn cut_to_end_of_line(
13647 &mut self,
13648 action: &CutToEndOfLine,
13649 window: &mut Window,
13650 cx: &mut Context<Self>,
13651 ) {
13652 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13653 self.transact(window, cx, |this, window, cx| {
13654 this.select_to_end_of_line(
13655 &SelectToEndOfLine {
13656 stop_at_soft_wraps: false,
13657 },
13658 window,
13659 cx,
13660 );
13661 if !action.stop_at_newlines {
13662 this.change_selections(Default::default(), window, cx, |s| {
13663 s.move_with(|_, sel| {
13664 if sel.is_empty() {
13665 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13666 }
13667 });
13668 });
13669 }
13670 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13671 let item = this.cut_common(false, window, cx);
13672 cx.write_to_clipboard(item);
13673 });
13674 }
13675
13676 pub fn move_to_start_of_paragraph(
13677 &mut self,
13678 _: &MoveToStartOfParagraph,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 if matches!(self.mode, EditorMode::SingleLine) {
13683 cx.propagate();
13684 return;
13685 }
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_with(|map, selection| {
13689 selection.collapse_to(
13690 movement::start_of_paragraph(map, selection.head(), 1),
13691 SelectionGoal::None,
13692 )
13693 });
13694 })
13695 }
13696
13697 pub fn move_to_end_of_paragraph(
13698 &mut self,
13699 _: &MoveToEndOfParagraph,
13700 window: &mut Window,
13701 cx: &mut Context<Self>,
13702 ) {
13703 if matches!(self.mode, EditorMode::SingleLine) {
13704 cx.propagate();
13705 return;
13706 }
13707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13708 self.change_selections(Default::default(), window, cx, |s| {
13709 s.move_with(|map, selection| {
13710 selection.collapse_to(
13711 movement::end_of_paragraph(map, selection.head(), 1),
13712 SelectionGoal::None,
13713 )
13714 });
13715 })
13716 }
13717
13718 pub fn select_to_start_of_paragraph(
13719 &mut self,
13720 _: &SelectToStartOfParagraph,
13721 window: &mut Window,
13722 cx: &mut Context<Self>,
13723 ) {
13724 if matches!(self.mode, EditorMode::SingleLine) {
13725 cx.propagate();
13726 return;
13727 }
13728 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13729 self.change_selections(Default::default(), window, cx, |s| {
13730 s.move_heads_with(|map, head, _| {
13731 (
13732 movement::start_of_paragraph(map, head, 1),
13733 SelectionGoal::None,
13734 )
13735 });
13736 })
13737 }
13738
13739 pub fn select_to_end_of_paragraph(
13740 &mut self,
13741 _: &SelectToEndOfParagraph,
13742 window: &mut Window,
13743 cx: &mut Context<Self>,
13744 ) {
13745 if matches!(self.mode, EditorMode::SingleLine) {
13746 cx.propagate();
13747 return;
13748 }
13749 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13750 self.change_selections(Default::default(), window, cx, |s| {
13751 s.move_heads_with(|map, head, _| {
13752 (
13753 movement::end_of_paragraph(map, head, 1),
13754 SelectionGoal::None,
13755 )
13756 });
13757 })
13758 }
13759
13760 pub fn move_to_start_of_excerpt(
13761 &mut self,
13762 _: &MoveToStartOfExcerpt,
13763 window: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 if matches!(self.mode, EditorMode::SingleLine) {
13767 cx.propagate();
13768 return;
13769 }
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13771 self.change_selections(Default::default(), window, cx, |s| {
13772 s.move_with(|map, selection| {
13773 selection.collapse_to(
13774 movement::start_of_excerpt(
13775 map,
13776 selection.head(),
13777 workspace::searchable::Direction::Prev,
13778 ),
13779 SelectionGoal::None,
13780 )
13781 });
13782 })
13783 }
13784
13785 pub fn move_to_start_of_next_excerpt(
13786 &mut self,
13787 _: &MoveToStartOfNextExcerpt,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 if matches!(self.mode, EditorMode::SingleLine) {
13792 cx.propagate();
13793 return;
13794 }
13795
13796 self.change_selections(Default::default(), window, cx, |s| {
13797 s.move_with(|map, selection| {
13798 selection.collapse_to(
13799 movement::start_of_excerpt(
13800 map,
13801 selection.head(),
13802 workspace::searchable::Direction::Next,
13803 ),
13804 SelectionGoal::None,
13805 )
13806 });
13807 })
13808 }
13809
13810 pub fn move_to_end_of_excerpt(
13811 &mut self,
13812 _: &MoveToEndOfExcerpt,
13813 window: &mut Window,
13814 cx: &mut Context<Self>,
13815 ) {
13816 if matches!(self.mode, EditorMode::SingleLine) {
13817 cx.propagate();
13818 return;
13819 }
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13821 self.change_selections(Default::default(), window, cx, |s| {
13822 s.move_with(|map, selection| {
13823 selection.collapse_to(
13824 movement::end_of_excerpt(
13825 map,
13826 selection.head(),
13827 workspace::searchable::Direction::Next,
13828 ),
13829 SelectionGoal::None,
13830 )
13831 });
13832 })
13833 }
13834
13835 pub fn move_to_end_of_previous_excerpt(
13836 &mut self,
13837 _: &MoveToEndOfPreviousExcerpt,
13838 window: &mut Window,
13839 cx: &mut Context<Self>,
13840 ) {
13841 if matches!(self.mode, EditorMode::SingleLine) {
13842 cx.propagate();
13843 return;
13844 }
13845 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13846 self.change_selections(Default::default(), window, cx, |s| {
13847 s.move_with(|map, selection| {
13848 selection.collapse_to(
13849 movement::end_of_excerpt(
13850 map,
13851 selection.head(),
13852 workspace::searchable::Direction::Prev,
13853 ),
13854 SelectionGoal::None,
13855 )
13856 });
13857 })
13858 }
13859
13860 pub fn select_to_start_of_excerpt(
13861 &mut self,
13862 _: &SelectToStartOfExcerpt,
13863 window: &mut Window,
13864 cx: &mut Context<Self>,
13865 ) {
13866 if matches!(self.mode, EditorMode::SingleLine) {
13867 cx.propagate();
13868 return;
13869 }
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13871 self.change_selections(Default::default(), window, cx, |s| {
13872 s.move_heads_with(|map, head, _| {
13873 (
13874 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13875 SelectionGoal::None,
13876 )
13877 });
13878 })
13879 }
13880
13881 pub fn select_to_start_of_next_excerpt(
13882 &mut self,
13883 _: &SelectToStartOfNextExcerpt,
13884 window: &mut Window,
13885 cx: &mut Context<Self>,
13886 ) {
13887 if matches!(self.mode, EditorMode::SingleLine) {
13888 cx.propagate();
13889 return;
13890 }
13891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13892 self.change_selections(Default::default(), window, cx, |s| {
13893 s.move_heads_with(|map, head, _| {
13894 (
13895 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13896 SelectionGoal::None,
13897 )
13898 });
13899 })
13900 }
13901
13902 pub fn select_to_end_of_excerpt(
13903 &mut self,
13904 _: &SelectToEndOfExcerpt,
13905 window: &mut Window,
13906 cx: &mut Context<Self>,
13907 ) {
13908 if matches!(self.mode, EditorMode::SingleLine) {
13909 cx.propagate();
13910 return;
13911 }
13912 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13913 self.change_selections(Default::default(), window, cx, |s| {
13914 s.move_heads_with(|map, head, _| {
13915 (
13916 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13917 SelectionGoal::None,
13918 )
13919 });
13920 })
13921 }
13922
13923 pub fn select_to_end_of_previous_excerpt(
13924 &mut self,
13925 _: &SelectToEndOfPreviousExcerpt,
13926 window: &mut Window,
13927 cx: &mut Context<Self>,
13928 ) {
13929 if matches!(self.mode, EditorMode::SingleLine) {
13930 cx.propagate();
13931 return;
13932 }
13933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13934 self.change_selections(Default::default(), window, cx, |s| {
13935 s.move_heads_with(|map, head, _| {
13936 (
13937 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13938 SelectionGoal::None,
13939 )
13940 });
13941 })
13942 }
13943
13944 pub fn move_to_beginning(
13945 &mut self,
13946 _: &MoveToBeginning,
13947 window: &mut Window,
13948 cx: &mut Context<Self>,
13949 ) {
13950 if matches!(self.mode, EditorMode::SingleLine) {
13951 cx.propagate();
13952 return;
13953 }
13954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13955 self.change_selections(Default::default(), window, cx, |s| {
13956 s.select_ranges(vec![0..0]);
13957 });
13958 }
13959
13960 pub fn select_to_beginning(
13961 &mut self,
13962 _: &SelectToBeginning,
13963 window: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13967 selection.set_head(Point::zero(), SelectionGoal::None);
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 self.change_selections(Default::default(), window, cx, |s| {
13970 s.select(vec![selection]);
13971 });
13972 }
13973
13974 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13975 if matches!(self.mode, EditorMode::SingleLine) {
13976 cx.propagate();
13977 return;
13978 }
13979 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13980 let cursor = self.buffer.read(cx).read(cx).len();
13981 self.change_selections(Default::default(), window, cx, |s| {
13982 s.select_ranges(vec![cursor..cursor])
13983 });
13984 }
13985
13986 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13987 self.nav_history = nav_history;
13988 }
13989
13990 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13991 self.nav_history.as_ref()
13992 }
13993
13994 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13995 self.push_to_nav_history(
13996 self.selections.newest_anchor().head(),
13997 None,
13998 false,
13999 true,
14000 cx,
14001 );
14002 }
14003
14004 fn push_to_nav_history(
14005 &mut self,
14006 cursor_anchor: Anchor,
14007 new_position: Option<Point>,
14008 is_deactivate: bool,
14009 always: bool,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if let Some(nav_history) = self.nav_history.as_mut() {
14013 let buffer = self.buffer.read(cx).read(cx);
14014 let cursor_position = cursor_anchor.to_point(&buffer);
14015 let scroll_state = self.scroll_manager.anchor();
14016 let scroll_top_row = scroll_state.top_row(&buffer);
14017 drop(buffer);
14018
14019 if let Some(new_position) = new_position {
14020 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14021 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14022 return;
14023 }
14024 }
14025
14026 nav_history.push(
14027 Some(NavigationData {
14028 cursor_anchor,
14029 cursor_position,
14030 scroll_anchor: scroll_state,
14031 scroll_top_row,
14032 }),
14033 cx,
14034 );
14035 cx.emit(EditorEvent::PushedToNavHistory {
14036 anchor: cursor_anchor,
14037 is_deactivate,
14038 })
14039 }
14040 }
14041
14042 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14044 let buffer = self.buffer.read(cx).snapshot(cx);
14045 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14046 selection.set_head(buffer.len(), SelectionGoal::None);
14047 self.change_selections(Default::default(), window, cx, |s| {
14048 s.select(vec![selection]);
14049 });
14050 }
14051
14052 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14054 let end = self.buffer.read(cx).read(cx).len();
14055 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14056 s.select_ranges(vec![0..end]);
14057 });
14058 }
14059
14060 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14061 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14063 let mut selections = self.selections.all::<Point>(&display_map);
14064 let max_point = display_map.buffer_snapshot().max_point();
14065 for selection in &mut selections {
14066 let rows = selection.spanned_rows(true, &display_map);
14067 selection.start = Point::new(rows.start.0, 0);
14068 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14069 selection.reversed = false;
14070 }
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.select(selections);
14073 });
14074 }
14075
14076 pub fn split_selection_into_lines(
14077 &mut self,
14078 action: &SplitSelectionIntoLines,
14079 window: &mut Window,
14080 cx: &mut Context<Self>,
14081 ) {
14082 let selections = self
14083 .selections
14084 .all::<Point>(&self.display_snapshot(cx))
14085 .into_iter()
14086 .map(|selection| selection.start..selection.end)
14087 .collect::<Vec<_>>();
14088 self.unfold_ranges(&selections, true, true, cx);
14089
14090 let mut new_selection_ranges = Vec::new();
14091 {
14092 let buffer = self.buffer.read(cx).read(cx);
14093 for selection in selections {
14094 for row in selection.start.row..selection.end.row {
14095 let line_start = Point::new(row, 0);
14096 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14097
14098 if action.keep_selections {
14099 // Keep the selection range for each line
14100 let selection_start = if row == selection.start.row {
14101 selection.start
14102 } else {
14103 line_start
14104 };
14105 new_selection_ranges.push(selection_start..line_end);
14106 } else {
14107 // Collapse to cursor at end of line
14108 new_selection_ranges.push(line_end..line_end);
14109 }
14110 }
14111
14112 let is_multiline_selection = selection.start.row != selection.end.row;
14113 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14114 // so this action feels more ergonomic when paired with other selection operations
14115 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14116 if !should_skip_last {
14117 if action.keep_selections {
14118 if is_multiline_selection {
14119 let line_start = Point::new(selection.end.row, 0);
14120 new_selection_ranges.push(line_start..selection.end);
14121 } else {
14122 new_selection_ranges.push(selection.start..selection.end);
14123 }
14124 } else {
14125 new_selection_ranges.push(selection.end..selection.end);
14126 }
14127 }
14128 }
14129 }
14130 self.change_selections(Default::default(), window, cx, |s| {
14131 s.select_ranges(new_selection_ranges);
14132 });
14133 }
14134
14135 pub fn add_selection_above(
14136 &mut self,
14137 action: &AddSelectionAbove,
14138 window: &mut Window,
14139 cx: &mut Context<Self>,
14140 ) {
14141 self.add_selection(true, action.skip_soft_wrap, window, cx);
14142 }
14143
14144 pub fn add_selection_below(
14145 &mut self,
14146 action: &AddSelectionBelow,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) {
14150 self.add_selection(false, action.skip_soft_wrap, window, cx);
14151 }
14152
14153 fn add_selection(
14154 &mut self,
14155 above: bool,
14156 skip_soft_wrap: bool,
14157 window: &mut Window,
14158 cx: &mut Context<Self>,
14159 ) {
14160 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14161
14162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14163 let all_selections = self.selections.all::<Point>(&display_map);
14164 let text_layout_details = self.text_layout_details(window);
14165
14166 let (mut columnar_selections, new_selections_to_columnarize) = {
14167 if let Some(state) = self.add_selections_state.as_ref() {
14168 let columnar_selection_ids: HashSet<_> = state
14169 .groups
14170 .iter()
14171 .flat_map(|group| group.stack.iter())
14172 .copied()
14173 .collect();
14174
14175 all_selections
14176 .into_iter()
14177 .partition(|s| columnar_selection_ids.contains(&s.id))
14178 } else {
14179 (Vec::new(), all_selections)
14180 }
14181 };
14182
14183 let mut state = self
14184 .add_selections_state
14185 .take()
14186 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14187
14188 for selection in new_selections_to_columnarize {
14189 let range = selection.display_range(&display_map).sorted();
14190 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14191 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14192 let positions = start_x.min(end_x)..start_x.max(end_x);
14193 let mut stack = Vec::new();
14194 for row in range.start.row().0..=range.end.row().0 {
14195 if let Some(selection) = self.selections.build_columnar_selection(
14196 &display_map,
14197 DisplayRow(row),
14198 &positions,
14199 selection.reversed,
14200 &text_layout_details,
14201 ) {
14202 stack.push(selection.id);
14203 columnar_selections.push(selection);
14204 }
14205 }
14206 if !stack.is_empty() {
14207 if above {
14208 stack.reverse();
14209 }
14210 state.groups.push(AddSelectionsGroup { above, stack });
14211 }
14212 }
14213
14214 let mut final_selections = Vec::new();
14215 let end_row = if above {
14216 DisplayRow(0)
14217 } else {
14218 display_map.max_point().row()
14219 };
14220
14221 let mut last_added_item_per_group = HashMap::default();
14222 for group in state.groups.iter_mut() {
14223 if let Some(last_id) = group.stack.last() {
14224 last_added_item_per_group.insert(*last_id, group);
14225 }
14226 }
14227
14228 for selection in columnar_selections {
14229 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14230 if above == group.above {
14231 let range = selection.display_range(&display_map).sorted();
14232 debug_assert_eq!(range.start.row(), range.end.row());
14233 let mut row = range.start.row();
14234 let positions =
14235 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14236 Pixels::from(start)..Pixels::from(end)
14237 } else {
14238 let start_x =
14239 display_map.x_for_display_point(range.start, &text_layout_details);
14240 let end_x =
14241 display_map.x_for_display_point(range.end, &text_layout_details);
14242 start_x.min(end_x)..start_x.max(end_x)
14243 };
14244
14245 let mut maybe_new_selection = None;
14246 let direction = if above { -1 } else { 1 };
14247
14248 while row != end_row {
14249 if skip_soft_wrap {
14250 row = display_map
14251 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14252 .row();
14253 } else if above {
14254 row.0 -= 1;
14255 } else {
14256 row.0 += 1;
14257 }
14258
14259 if let Some(new_selection) = self.selections.build_columnar_selection(
14260 &display_map,
14261 row,
14262 &positions,
14263 selection.reversed,
14264 &text_layout_details,
14265 ) {
14266 maybe_new_selection = Some(new_selection);
14267 break;
14268 }
14269 }
14270
14271 if let Some(new_selection) = maybe_new_selection {
14272 group.stack.push(new_selection.id);
14273 if above {
14274 final_selections.push(new_selection);
14275 final_selections.push(selection);
14276 } else {
14277 final_selections.push(selection);
14278 final_selections.push(new_selection);
14279 }
14280 } else {
14281 final_selections.push(selection);
14282 }
14283 } else {
14284 group.stack.pop();
14285 }
14286 } else {
14287 final_selections.push(selection);
14288 }
14289 }
14290
14291 self.change_selections(Default::default(), window, cx, |s| {
14292 s.select(final_selections);
14293 });
14294
14295 let final_selection_ids: HashSet<_> = self
14296 .selections
14297 .all::<Point>(&display_map)
14298 .iter()
14299 .map(|s| s.id)
14300 .collect();
14301 state.groups.retain_mut(|group| {
14302 // selections might get merged above so we remove invalid items from stacks
14303 group.stack.retain(|id| final_selection_ids.contains(id));
14304
14305 // single selection in stack can be treated as initial state
14306 group.stack.len() > 1
14307 });
14308
14309 if !state.groups.is_empty() {
14310 self.add_selections_state = Some(state);
14311 }
14312 }
14313
14314 fn select_match_ranges(
14315 &mut self,
14316 range: Range<usize>,
14317 reversed: bool,
14318 replace_newest: bool,
14319 auto_scroll: Option<Autoscroll>,
14320 window: &mut Window,
14321 cx: &mut Context<Editor>,
14322 ) {
14323 self.unfold_ranges(
14324 std::slice::from_ref(&range),
14325 false,
14326 auto_scroll.is_some(),
14327 cx,
14328 );
14329 let effects = if let Some(scroll) = auto_scroll {
14330 SelectionEffects::scroll(scroll)
14331 } else {
14332 SelectionEffects::no_scroll()
14333 };
14334 self.change_selections(effects, window, cx, |s| {
14335 if replace_newest {
14336 s.delete(s.newest_anchor().id);
14337 }
14338 if reversed {
14339 s.insert_range(range.end..range.start);
14340 } else {
14341 s.insert_range(range);
14342 }
14343 });
14344 }
14345
14346 pub fn select_next_match_internal(
14347 &mut self,
14348 display_map: &DisplaySnapshot,
14349 replace_newest: bool,
14350 autoscroll: Option<Autoscroll>,
14351 window: &mut Window,
14352 cx: &mut Context<Self>,
14353 ) -> Result<()> {
14354 let buffer = display_map.buffer_snapshot();
14355 let mut selections = self.selections.all::<usize>(&display_map);
14356 if let Some(mut select_next_state) = self.select_next_state.take() {
14357 let query = &select_next_state.query;
14358 if !select_next_state.done {
14359 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14360 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14361 let mut next_selected_range = None;
14362
14363 let bytes_after_last_selection =
14364 buffer.bytes_in_range(last_selection.end..buffer.len());
14365 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14366 let query_matches = query
14367 .stream_find_iter(bytes_after_last_selection)
14368 .map(|result| (last_selection.end, result))
14369 .chain(
14370 query
14371 .stream_find_iter(bytes_before_first_selection)
14372 .map(|result| (0, result)),
14373 );
14374
14375 for (start_offset, query_match) in query_matches {
14376 let query_match = query_match.unwrap(); // can only fail due to I/O
14377 let offset_range =
14378 start_offset + query_match.start()..start_offset + query_match.end();
14379
14380 if !select_next_state.wordwise
14381 || (!buffer.is_inside_word(offset_range.start, None)
14382 && !buffer.is_inside_word(offset_range.end, None))
14383 {
14384 let idx = selections
14385 .partition_point(|selection| selection.end <= offset_range.start);
14386 let overlaps = selections
14387 .get(idx)
14388 .map_or(false, |selection| selection.start < offset_range.end);
14389
14390 if !overlaps {
14391 next_selected_range = Some(offset_range);
14392 break;
14393 }
14394 }
14395 }
14396
14397 if let Some(next_selected_range) = next_selected_range {
14398 self.select_match_ranges(
14399 next_selected_range,
14400 last_selection.reversed,
14401 replace_newest,
14402 autoscroll,
14403 window,
14404 cx,
14405 );
14406 } else {
14407 select_next_state.done = true;
14408 }
14409 }
14410
14411 self.select_next_state = Some(select_next_state);
14412 } else {
14413 let mut only_carets = true;
14414 let mut same_text_selected = true;
14415 let mut selected_text = None;
14416
14417 let mut selections_iter = selections.iter().peekable();
14418 while let Some(selection) = selections_iter.next() {
14419 if selection.start != selection.end {
14420 only_carets = false;
14421 }
14422
14423 if same_text_selected {
14424 if selected_text.is_none() {
14425 selected_text =
14426 Some(buffer.text_for_range(selection.range()).collect::<String>());
14427 }
14428
14429 if let Some(next_selection) = selections_iter.peek() {
14430 if next_selection.range().len() == selection.range().len() {
14431 let next_selected_text = buffer
14432 .text_for_range(next_selection.range())
14433 .collect::<String>();
14434 if Some(next_selected_text) != selected_text {
14435 same_text_selected = false;
14436 selected_text = None;
14437 }
14438 } else {
14439 same_text_selected = false;
14440 selected_text = None;
14441 }
14442 }
14443 }
14444 }
14445
14446 if only_carets {
14447 for selection in &mut selections {
14448 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14449 selection.start = word_range.start;
14450 selection.end = word_range.end;
14451 selection.goal = SelectionGoal::None;
14452 selection.reversed = false;
14453 self.select_match_ranges(
14454 selection.start..selection.end,
14455 selection.reversed,
14456 replace_newest,
14457 autoscroll,
14458 window,
14459 cx,
14460 );
14461 }
14462
14463 if selections.len() == 1 {
14464 let selection = selections
14465 .last()
14466 .expect("ensured that there's only one selection");
14467 let query = buffer
14468 .text_for_range(selection.start..selection.end)
14469 .collect::<String>();
14470 let is_empty = query.is_empty();
14471 let select_state = SelectNextState {
14472 query: AhoCorasick::new(&[query])?,
14473 wordwise: true,
14474 done: is_empty,
14475 };
14476 self.select_next_state = Some(select_state);
14477 } else {
14478 self.select_next_state = None;
14479 }
14480 } else if let Some(selected_text) = selected_text {
14481 self.select_next_state = Some(SelectNextState {
14482 query: AhoCorasick::new(&[selected_text])?,
14483 wordwise: false,
14484 done: false,
14485 });
14486 self.select_next_match_internal(
14487 display_map,
14488 replace_newest,
14489 autoscroll,
14490 window,
14491 cx,
14492 )?;
14493 }
14494 }
14495 Ok(())
14496 }
14497
14498 pub fn select_all_matches(
14499 &mut self,
14500 _action: &SelectAllMatches,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) -> Result<()> {
14504 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14505
14506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14507
14508 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14509 let Some(select_next_state) = self.select_next_state.as_mut() else {
14510 return Ok(());
14511 };
14512 if select_next_state.done {
14513 return Ok(());
14514 }
14515
14516 let mut new_selections = Vec::new();
14517
14518 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14519 let buffer = display_map.buffer_snapshot();
14520 let query_matches = select_next_state
14521 .query
14522 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14523
14524 for query_match in query_matches.into_iter() {
14525 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14526 let offset_range = if reversed {
14527 query_match.end()..query_match.start()
14528 } else {
14529 query_match.start()..query_match.end()
14530 };
14531
14532 if !select_next_state.wordwise
14533 || (!buffer.is_inside_word(offset_range.start, None)
14534 && !buffer.is_inside_word(offset_range.end, None))
14535 {
14536 new_selections.push(offset_range.start..offset_range.end);
14537 }
14538 }
14539
14540 select_next_state.done = true;
14541
14542 if new_selections.is_empty() {
14543 log::error!("bug: new_selections is empty in select_all_matches");
14544 return Ok(());
14545 }
14546
14547 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14548 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14549 selections.select_ranges(new_selections)
14550 });
14551
14552 Ok(())
14553 }
14554
14555 pub fn select_next(
14556 &mut self,
14557 action: &SelectNext,
14558 window: &mut Window,
14559 cx: &mut Context<Self>,
14560 ) -> Result<()> {
14561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14562 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14563 self.select_next_match_internal(
14564 &display_map,
14565 action.replace_newest,
14566 Some(Autoscroll::newest()),
14567 window,
14568 cx,
14569 )?;
14570 Ok(())
14571 }
14572
14573 pub fn select_previous(
14574 &mut self,
14575 action: &SelectPrevious,
14576 window: &mut Window,
14577 cx: &mut Context<Self>,
14578 ) -> Result<()> {
14579 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14581 let buffer = display_map.buffer_snapshot();
14582 let mut selections = self.selections.all::<usize>(&display_map);
14583 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14584 let query = &select_prev_state.query;
14585 if !select_prev_state.done {
14586 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14587 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14588 let mut next_selected_range = None;
14589 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14590 let bytes_before_last_selection =
14591 buffer.reversed_bytes_in_range(0..last_selection.start);
14592 let bytes_after_first_selection =
14593 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14594 let query_matches = query
14595 .stream_find_iter(bytes_before_last_selection)
14596 .map(|result| (last_selection.start, result))
14597 .chain(
14598 query
14599 .stream_find_iter(bytes_after_first_selection)
14600 .map(|result| (buffer.len(), result)),
14601 );
14602 for (end_offset, query_match) in query_matches {
14603 let query_match = query_match.unwrap(); // can only fail due to I/O
14604 let offset_range =
14605 end_offset - query_match.end()..end_offset - query_match.start();
14606
14607 if !select_prev_state.wordwise
14608 || (!buffer.is_inside_word(offset_range.start, None)
14609 && !buffer.is_inside_word(offset_range.end, None))
14610 {
14611 next_selected_range = Some(offset_range);
14612 break;
14613 }
14614 }
14615
14616 if let Some(next_selected_range) = next_selected_range {
14617 self.select_match_ranges(
14618 next_selected_range,
14619 last_selection.reversed,
14620 action.replace_newest,
14621 Some(Autoscroll::newest()),
14622 window,
14623 cx,
14624 );
14625 } else {
14626 select_prev_state.done = true;
14627 }
14628 }
14629
14630 self.select_prev_state = Some(select_prev_state);
14631 } else {
14632 let mut only_carets = true;
14633 let mut same_text_selected = true;
14634 let mut selected_text = None;
14635
14636 let mut selections_iter = selections.iter().peekable();
14637 while let Some(selection) = selections_iter.next() {
14638 if selection.start != selection.end {
14639 only_carets = false;
14640 }
14641
14642 if same_text_selected {
14643 if selected_text.is_none() {
14644 selected_text =
14645 Some(buffer.text_for_range(selection.range()).collect::<String>());
14646 }
14647
14648 if let Some(next_selection) = selections_iter.peek() {
14649 if next_selection.range().len() == selection.range().len() {
14650 let next_selected_text = buffer
14651 .text_for_range(next_selection.range())
14652 .collect::<String>();
14653 if Some(next_selected_text) != selected_text {
14654 same_text_selected = false;
14655 selected_text = None;
14656 }
14657 } else {
14658 same_text_selected = false;
14659 selected_text = None;
14660 }
14661 }
14662 }
14663 }
14664
14665 if only_carets {
14666 for selection in &mut selections {
14667 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14668 selection.start = word_range.start;
14669 selection.end = word_range.end;
14670 selection.goal = SelectionGoal::None;
14671 selection.reversed = false;
14672 self.select_match_ranges(
14673 selection.start..selection.end,
14674 selection.reversed,
14675 action.replace_newest,
14676 Some(Autoscroll::newest()),
14677 window,
14678 cx,
14679 );
14680 }
14681 if selections.len() == 1 {
14682 let selection = selections
14683 .last()
14684 .expect("ensured that there's only one selection");
14685 let query = buffer
14686 .text_for_range(selection.start..selection.end)
14687 .collect::<String>();
14688 let is_empty = query.is_empty();
14689 let select_state = SelectNextState {
14690 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14691 wordwise: true,
14692 done: is_empty,
14693 };
14694 self.select_prev_state = Some(select_state);
14695 } else {
14696 self.select_prev_state = None;
14697 }
14698 } else if let Some(selected_text) = selected_text {
14699 self.select_prev_state = Some(SelectNextState {
14700 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14701 wordwise: false,
14702 done: false,
14703 });
14704 self.select_previous(action, window, cx)?;
14705 }
14706 }
14707 Ok(())
14708 }
14709
14710 pub fn find_next_match(
14711 &mut self,
14712 _: &FindNextMatch,
14713 window: &mut Window,
14714 cx: &mut Context<Self>,
14715 ) -> Result<()> {
14716 let selections = self.selections.disjoint_anchors_arc();
14717 match selections.first() {
14718 Some(first) if selections.len() >= 2 => {
14719 self.change_selections(Default::default(), window, cx, |s| {
14720 s.select_ranges([first.range()]);
14721 });
14722 }
14723 _ => self.select_next(
14724 &SelectNext {
14725 replace_newest: true,
14726 },
14727 window,
14728 cx,
14729 )?,
14730 }
14731 Ok(())
14732 }
14733
14734 pub fn find_previous_match(
14735 &mut self,
14736 _: &FindPreviousMatch,
14737 window: &mut Window,
14738 cx: &mut Context<Self>,
14739 ) -> Result<()> {
14740 let selections = self.selections.disjoint_anchors_arc();
14741 match selections.last() {
14742 Some(last) if selections.len() >= 2 => {
14743 self.change_selections(Default::default(), window, cx, |s| {
14744 s.select_ranges([last.range()]);
14745 });
14746 }
14747 _ => self.select_previous(
14748 &SelectPrevious {
14749 replace_newest: true,
14750 },
14751 window,
14752 cx,
14753 )?,
14754 }
14755 Ok(())
14756 }
14757
14758 pub fn toggle_comments(
14759 &mut self,
14760 action: &ToggleComments,
14761 window: &mut Window,
14762 cx: &mut Context<Self>,
14763 ) {
14764 if self.read_only(cx) {
14765 return;
14766 }
14767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14768 let text_layout_details = &self.text_layout_details(window);
14769 self.transact(window, cx, |this, window, cx| {
14770 let mut selections = this
14771 .selections
14772 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14773 let mut edits = Vec::new();
14774 let mut selection_edit_ranges = Vec::new();
14775 let mut last_toggled_row = None;
14776 let snapshot = this.buffer.read(cx).read(cx);
14777 let empty_str: Arc<str> = Arc::default();
14778 let mut suffixes_inserted = Vec::new();
14779 let ignore_indent = action.ignore_indent;
14780
14781 fn comment_prefix_range(
14782 snapshot: &MultiBufferSnapshot,
14783 row: MultiBufferRow,
14784 comment_prefix: &str,
14785 comment_prefix_whitespace: &str,
14786 ignore_indent: bool,
14787 ) -> Range<Point> {
14788 let indent_size = if ignore_indent {
14789 0
14790 } else {
14791 snapshot.indent_size_for_line(row).len
14792 };
14793
14794 let start = Point::new(row.0, indent_size);
14795
14796 let mut line_bytes = snapshot
14797 .bytes_in_range(start..snapshot.max_point())
14798 .flatten()
14799 .copied();
14800
14801 // If this line currently begins with the line comment prefix, then record
14802 // the range containing the prefix.
14803 if line_bytes
14804 .by_ref()
14805 .take(comment_prefix.len())
14806 .eq(comment_prefix.bytes())
14807 {
14808 // Include any whitespace that matches the comment prefix.
14809 let matching_whitespace_len = line_bytes
14810 .zip(comment_prefix_whitespace.bytes())
14811 .take_while(|(a, b)| a == b)
14812 .count() as u32;
14813 let end = Point::new(
14814 start.row,
14815 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14816 );
14817 start..end
14818 } else {
14819 start..start
14820 }
14821 }
14822
14823 fn comment_suffix_range(
14824 snapshot: &MultiBufferSnapshot,
14825 row: MultiBufferRow,
14826 comment_suffix: &str,
14827 comment_suffix_has_leading_space: bool,
14828 ) -> Range<Point> {
14829 let end = Point::new(row.0, snapshot.line_len(row));
14830 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14831
14832 let mut line_end_bytes = snapshot
14833 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14834 .flatten()
14835 .copied();
14836
14837 let leading_space_len = if suffix_start_column > 0
14838 && line_end_bytes.next() == Some(b' ')
14839 && comment_suffix_has_leading_space
14840 {
14841 1
14842 } else {
14843 0
14844 };
14845
14846 // If this line currently begins with the line comment prefix, then record
14847 // the range containing the prefix.
14848 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14849 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14850 start..end
14851 } else {
14852 end..end
14853 }
14854 }
14855
14856 // TODO: Handle selections that cross excerpts
14857 for selection in &mut selections {
14858 let start_column = snapshot
14859 .indent_size_for_line(MultiBufferRow(selection.start.row))
14860 .len;
14861 let language = if let Some(language) =
14862 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14863 {
14864 language
14865 } else {
14866 continue;
14867 };
14868
14869 selection_edit_ranges.clear();
14870
14871 // If multiple selections contain a given row, avoid processing that
14872 // row more than once.
14873 let mut start_row = MultiBufferRow(selection.start.row);
14874 if last_toggled_row == Some(start_row) {
14875 start_row = start_row.next_row();
14876 }
14877 let end_row =
14878 if selection.end.row > selection.start.row && selection.end.column == 0 {
14879 MultiBufferRow(selection.end.row - 1)
14880 } else {
14881 MultiBufferRow(selection.end.row)
14882 };
14883 last_toggled_row = Some(end_row);
14884
14885 if start_row > end_row {
14886 continue;
14887 }
14888
14889 // If the language has line comments, toggle those.
14890 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14891
14892 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14893 if ignore_indent {
14894 full_comment_prefixes = full_comment_prefixes
14895 .into_iter()
14896 .map(|s| Arc::from(s.trim_end()))
14897 .collect();
14898 }
14899
14900 if !full_comment_prefixes.is_empty() {
14901 let first_prefix = full_comment_prefixes
14902 .first()
14903 .expect("prefixes is non-empty");
14904 let prefix_trimmed_lengths = full_comment_prefixes
14905 .iter()
14906 .map(|p| p.trim_end_matches(' ').len())
14907 .collect::<SmallVec<[usize; 4]>>();
14908
14909 let mut all_selection_lines_are_comments = true;
14910
14911 for row in start_row.0..=end_row.0 {
14912 let row = MultiBufferRow(row);
14913 if start_row < end_row && snapshot.is_line_blank(row) {
14914 continue;
14915 }
14916
14917 let prefix_range = full_comment_prefixes
14918 .iter()
14919 .zip(prefix_trimmed_lengths.iter().copied())
14920 .map(|(prefix, trimmed_prefix_len)| {
14921 comment_prefix_range(
14922 snapshot.deref(),
14923 row,
14924 &prefix[..trimmed_prefix_len],
14925 &prefix[trimmed_prefix_len..],
14926 ignore_indent,
14927 )
14928 })
14929 .max_by_key(|range| range.end.column - range.start.column)
14930 .expect("prefixes is non-empty");
14931
14932 if prefix_range.is_empty() {
14933 all_selection_lines_are_comments = false;
14934 }
14935
14936 selection_edit_ranges.push(prefix_range);
14937 }
14938
14939 if all_selection_lines_are_comments {
14940 edits.extend(
14941 selection_edit_ranges
14942 .iter()
14943 .cloned()
14944 .map(|range| (range, empty_str.clone())),
14945 );
14946 } else {
14947 let min_column = selection_edit_ranges
14948 .iter()
14949 .map(|range| range.start.column)
14950 .min()
14951 .unwrap_or(0);
14952 edits.extend(selection_edit_ranges.iter().map(|range| {
14953 let position = Point::new(range.start.row, min_column);
14954 (position..position, first_prefix.clone())
14955 }));
14956 }
14957 } else if let Some(BlockCommentConfig {
14958 start: full_comment_prefix,
14959 end: comment_suffix,
14960 ..
14961 }) = language.block_comment()
14962 {
14963 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14964 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14965 let prefix_range = comment_prefix_range(
14966 snapshot.deref(),
14967 start_row,
14968 comment_prefix,
14969 comment_prefix_whitespace,
14970 ignore_indent,
14971 );
14972 let suffix_range = comment_suffix_range(
14973 snapshot.deref(),
14974 end_row,
14975 comment_suffix.trim_start_matches(' '),
14976 comment_suffix.starts_with(' '),
14977 );
14978
14979 if prefix_range.is_empty() || suffix_range.is_empty() {
14980 edits.push((
14981 prefix_range.start..prefix_range.start,
14982 full_comment_prefix.clone(),
14983 ));
14984 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14985 suffixes_inserted.push((end_row, comment_suffix.len()));
14986 } else {
14987 edits.push((prefix_range, empty_str.clone()));
14988 edits.push((suffix_range, empty_str.clone()));
14989 }
14990 } else {
14991 continue;
14992 }
14993 }
14994
14995 drop(snapshot);
14996 this.buffer.update(cx, |buffer, cx| {
14997 buffer.edit(edits, None, cx);
14998 });
14999
15000 // Adjust selections so that they end before any comment suffixes that
15001 // were inserted.
15002 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15003 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15004 let snapshot = this.buffer.read(cx).read(cx);
15005 for selection in &mut selections {
15006 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15007 match row.cmp(&MultiBufferRow(selection.end.row)) {
15008 Ordering::Less => {
15009 suffixes_inserted.next();
15010 continue;
15011 }
15012 Ordering::Greater => break,
15013 Ordering::Equal => {
15014 if selection.end.column == snapshot.line_len(row) {
15015 if selection.is_empty() {
15016 selection.start.column -= suffix_len as u32;
15017 }
15018 selection.end.column -= suffix_len as u32;
15019 }
15020 break;
15021 }
15022 }
15023 }
15024 }
15025
15026 drop(snapshot);
15027 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15028
15029 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15030 let selections_on_single_row = selections.windows(2).all(|selections| {
15031 selections[0].start.row == selections[1].start.row
15032 && selections[0].end.row == selections[1].end.row
15033 && selections[0].start.row == selections[0].end.row
15034 });
15035 let selections_selecting = selections
15036 .iter()
15037 .any(|selection| selection.start != selection.end);
15038 let advance_downwards = action.advance_downwards
15039 && selections_on_single_row
15040 && !selections_selecting
15041 && !matches!(this.mode, EditorMode::SingleLine);
15042
15043 if advance_downwards {
15044 let snapshot = this.buffer.read(cx).snapshot(cx);
15045
15046 this.change_selections(Default::default(), window, cx, |s| {
15047 s.move_cursors_with(|display_snapshot, display_point, _| {
15048 let mut point = display_point.to_point(display_snapshot);
15049 point.row += 1;
15050 point = snapshot.clip_point(point, Bias::Left);
15051 let display_point = point.to_display_point(display_snapshot);
15052 let goal = SelectionGoal::HorizontalPosition(
15053 display_snapshot
15054 .x_for_display_point(display_point, text_layout_details)
15055 .into(),
15056 );
15057 (display_point, goal)
15058 })
15059 });
15060 }
15061 });
15062 }
15063
15064 pub fn select_enclosing_symbol(
15065 &mut self,
15066 _: &SelectEnclosingSymbol,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) {
15070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15071
15072 let buffer = self.buffer.read(cx).snapshot(cx);
15073 let old_selections = self
15074 .selections
15075 .all::<usize>(&self.display_snapshot(cx))
15076 .into_boxed_slice();
15077
15078 fn update_selection(
15079 selection: &Selection<usize>,
15080 buffer_snap: &MultiBufferSnapshot,
15081 ) -> Option<Selection<usize>> {
15082 let cursor = selection.head();
15083 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15084 for symbol in symbols.iter().rev() {
15085 let start = symbol.range.start.to_offset(buffer_snap);
15086 let end = symbol.range.end.to_offset(buffer_snap);
15087 let new_range = start..end;
15088 if start < selection.start || end > selection.end {
15089 return Some(Selection {
15090 id: selection.id,
15091 start: new_range.start,
15092 end: new_range.end,
15093 goal: SelectionGoal::None,
15094 reversed: selection.reversed,
15095 });
15096 }
15097 }
15098 None
15099 }
15100
15101 let mut selected_larger_symbol = false;
15102 let new_selections = old_selections
15103 .iter()
15104 .map(|selection| match update_selection(selection, &buffer) {
15105 Some(new_selection) => {
15106 if new_selection.range() != selection.range() {
15107 selected_larger_symbol = true;
15108 }
15109 new_selection
15110 }
15111 None => selection.clone(),
15112 })
15113 .collect::<Vec<_>>();
15114
15115 if selected_larger_symbol {
15116 self.change_selections(Default::default(), window, cx, |s| {
15117 s.select(new_selections);
15118 });
15119 }
15120 }
15121
15122 pub fn select_larger_syntax_node(
15123 &mut self,
15124 _: &SelectLargerSyntaxNode,
15125 window: &mut Window,
15126 cx: &mut Context<Self>,
15127 ) {
15128 let Some(visible_row_count) = self.visible_row_count() else {
15129 return;
15130 };
15131 let old_selections: Box<[_]> = self
15132 .selections
15133 .all::<usize>(&self.display_snapshot(cx))
15134 .into();
15135 if old_selections.is_empty() {
15136 return;
15137 }
15138
15139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15140
15141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15142 let buffer = self.buffer.read(cx).snapshot(cx);
15143
15144 let mut selected_larger_node = false;
15145 let mut new_selections = old_selections
15146 .iter()
15147 .map(|selection| {
15148 let old_range = selection.start..selection.end;
15149
15150 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15151 // manually select word at selection
15152 if ["string_content", "inline"].contains(&node.kind()) {
15153 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15154 // ignore if word is already selected
15155 if !word_range.is_empty() && old_range != word_range {
15156 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15157 // only select word if start and end point belongs to same word
15158 if word_range == last_word_range {
15159 selected_larger_node = true;
15160 return Selection {
15161 id: selection.id,
15162 start: word_range.start,
15163 end: word_range.end,
15164 goal: SelectionGoal::None,
15165 reversed: selection.reversed,
15166 };
15167 }
15168 }
15169 }
15170 }
15171
15172 let mut new_range = old_range.clone();
15173 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15174 new_range = range;
15175 if !node.is_named() {
15176 continue;
15177 }
15178 if !display_map.intersects_fold(new_range.start)
15179 && !display_map.intersects_fold(new_range.end)
15180 {
15181 break;
15182 }
15183 }
15184
15185 selected_larger_node |= new_range != old_range;
15186 Selection {
15187 id: selection.id,
15188 start: new_range.start,
15189 end: new_range.end,
15190 goal: SelectionGoal::None,
15191 reversed: selection.reversed,
15192 }
15193 })
15194 .collect::<Vec<_>>();
15195
15196 if !selected_larger_node {
15197 return; // don't put this call in the history
15198 }
15199
15200 // scroll based on transformation done to the last selection created by the user
15201 let (last_old, last_new) = old_selections
15202 .last()
15203 .zip(new_selections.last().cloned())
15204 .expect("old_selections isn't empty");
15205
15206 // revert selection
15207 let is_selection_reversed = {
15208 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15209 new_selections.last_mut().expect("checked above").reversed =
15210 should_newest_selection_be_reversed;
15211 should_newest_selection_be_reversed
15212 };
15213
15214 if selected_larger_node {
15215 self.select_syntax_node_history.disable_clearing = true;
15216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15217 s.select(new_selections.clone());
15218 });
15219 self.select_syntax_node_history.disable_clearing = false;
15220 }
15221
15222 let start_row = last_new.start.to_display_point(&display_map).row().0;
15223 let end_row = last_new.end.to_display_point(&display_map).row().0;
15224 let selection_height = end_row - start_row + 1;
15225 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15226
15227 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15228 let scroll_behavior = if fits_on_the_screen {
15229 self.request_autoscroll(Autoscroll::fit(), cx);
15230 SelectSyntaxNodeScrollBehavior::FitSelection
15231 } else if is_selection_reversed {
15232 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15233 SelectSyntaxNodeScrollBehavior::CursorTop
15234 } else {
15235 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15236 SelectSyntaxNodeScrollBehavior::CursorBottom
15237 };
15238
15239 self.select_syntax_node_history.push((
15240 old_selections,
15241 scroll_behavior,
15242 is_selection_reversed,
15243 ));
15244 }
15245
15246 pub fn select_smaller_syntax_node(
15247 &mut self,
15248 _: &SelectSmallerSyntaxNode,
15249 window: &mut Window,
15250 cx: &mut Context<Self>,
15251 ) {
15252 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15253
15254 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15255 self.select_syntax_node_history.pop()
15256 {
15257 if let Some(selection) = selections.last_mut() {
15258 selection.reversed = is_selection_reversed;
15259 }
15260
15261 self.select_syntax_node_history.disable_clearing = true;
15262 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15263 s.select(selections.to_vec());
15264 });
15265 self.select_syntax_node_history.disable_clearing = false;
15266
15267 match scroll_behavior {
15268 SelectSyntaxNodeScrollBehavior::CursorTop => {
15269 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15270 }
15271 SelectSyntaxNodeScrollBehavior::FitSelection => {
15272 self.request_autoscroll(Autoscroll::fit(), cx);
15273 }
15274 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15275 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15276 }
15277 }
15278 }
15279 }
15280
15281 pub fn unwrap_syntax_node(
15282 &mut self,
15283 _: &UnwrapSyntaxNode,
15284 window: &mut Window,
15285 cx: &mut Context<Self>,
15286 ) {
15287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15288
15289 let buffer = self.buffer.read(cx).snapshot(cx);
15290 let selections = self
15291 .selections
15292 .all::<usize>(&self.display_snapshot(cx))
15293 .into_iter()
15294 // subtracting the offset requires sorting
15295 .sorted_by_key(|i| i.start);
15296
15297 let full_edits = selections
15298 .into_iter()
15299 .filter_map(|selection| {
15300 let child = if selection.is_empty()
15301 && let Some((_, ancestor_range)) =
15302 buffer.syntax_ancestor(selection.start..selection.end)
15303 {
15304 ancestor_range
15305 } else {
15306 selection.range()
15307 };
15308
15309 let mut parent = child.clone();
15310 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15311 parent = ancestor_range;
15312 if parent.start < child.start || parent.end > child.end {
15313 break;
15314 }
15315 }
15316
15317 if parent == child {
15318 return None;
15319 }
15320 let text = buffer.text_for_range(child).collect::<String>();
15321 Some((selection.id, parent, text))
15322 })
15323 .collect::<Vec<_>>();
15324 if full_edits.is_empty() {
15325 return;
15326 }
15327
15328 self.transact(window, cx, |this, window, cx| {
15329 this.buffer.update(cx, |buffer, cx| {
15330 buffer.edit(
15331 full_edits
15332 .iter()
15333 .map(|(_, p, t)| (p.clone(), t.clone()))
15334 .collect::<Vec<_>>(),
15335 None,
15336 cx,
15337 );
15338 });
15339 this.change_selections(Default::default(), window, cx, |s| {
15340 let mut offset = 0;
15341 let mut selections = vec![];
15342 for (id, parent, text) in full_edits {
15343 let start = parent.start - offset;
15344 offset += parent.len() - text.len();
15345 selections.push(Selection {
15346 id,
15347 start,
15348 end: start + text.len(),
15349 reversed: false,
15350 goal: Default::default(),
15351 });
15352 }
15353 s.select(selections);
15354 });
15355 });
15356 }
15357
15358 pub fn select_next_syntax_node(
15359 &mut self,
15360 _: &SelectNextSyntaxNode,
15361 window: &mut Window,
15362 cx: &mut Context<Self>,
15363 ) {
15364 let old_selections: Box<[_]> = self
15365 .selections
15366 .all::<usize>(&self.display_snapshot(cx))
15367 .into();
15368 if old_selections.is_empty() {
15369 return;
15370 }
15371
15372 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15373
15374 let buffer = self.buffer.read(cx).snapshot(cx);
15375 let mut selected_sibling = false;
15376
15377 let new_selections = old_selections
15378 .iter()
15379 .map(|selection| {
15380 let old_range = selection.start..selection.end;
15381
15382 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15383 let new_range = node.byte_range();
15384 selected_sibling = true;
15385 Selection {
15386 id: selection.id,
15387 start: new_range.start,
15388 end: new_range.end,
15389 goal: SelectionGoal::None,
15390 reversed: selection.reversed,
15391 }
15392 } else {
15393 selection.clone()
15394 }
15395 })
15396 .collect::<Vec<_>>();
15397
15398 if selected_sibling {
15399 self.change_selections(
15400 SelectionEffects::scroll(Autoscroll::fit()),
15401 window,
15402 cx,
15403 |s| {
15404 s.select(new_selections);
15405 },
15406 );
15407 }
15408 }
15409
15410 pub fn select_prev_syntax_node(
15411 &mut self,
15412 _: &SelectPreviousSyntaxNode,
15413 window: &mut Window,
15414 cx: &mut Context<Self>,
15415 ) {
15416 let old_selections: Box<[_]> = self
15417 .selections
15418 .all::<usize>(&self.display_snapshot(cx))
15419 .into();
15420 if old_selections.is_empty() {
15421 return;
15422 }
15423
15424 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15425
15426 let buffer = self.buffer.read(cx).snapshot(cx);
15427 let mut selected_sibling = false;
15428
15429 let new_selections = old_selections
15430 .iter()
15431 .map(|selection| {
15432 let old_range = selection.start..selection.end;
15433
15434 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15435 let new_range = node.byte_range();
15436 selected_sibling = true;
15437 Selection {
15438 id: selection.id,
15439 start: new_range.start,
15440 end: new_range.end,
15441 goal: SelectionGoal::None,
15442 reversed: selection.reversed,
15443 }
15444 } else {
15445 selection.clone()
15446 }
15447 })
15448 .collect::<Vec<_>>();
15449
15450 if selected_sibling {
15451 self.change_selections(
15452 SelectionEffects::scroll(Autoscroll::fit()),
15453 window,
15454 cx,
15455 |s| {
15456 s.select(new_selections);
15457 },
15458 );
15459 }
15460 }
15461
15462 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15463 if !EditorSettings::get_global(cx).gutter.runnables {
15464 self.clear_tasks();
15465 return Task::ready(());
15466 }
15467 let project = self.project().map(Entity::downgrade);
15468 let task_sources = self.lsp_task_sources(cx);
15469 let multi_buffer = self.buffer.downgrade();
15470 cx.spawn_in(window, async move |editor, cx| {
15471 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15472 let Some(project) = project.and_then(|p| p.upgrade()) else {
15473 return;
15474 };
15475 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15476 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15477 }) else {
15478 return;
15479 };
15480
15481 let hide_runnables = project
15482 .update(cx, |project, _| project.is_via_collab())
15483 .unwrap_or(true);
15484 if hide_runnables {
15485 return;
15486 }
15487 let new_rows =
15488 cx.background_spawn({
15489 let snapshot = display_snapshot.clone();
15490 async move {
15491 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15492 }
15493 })
15494 .await;
15495 let Ok(lsp_tasks) =
15496 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15497 else {
15498 return;
15499 };
15500 let lsp_tasks = lsp_tasks.await;
15501
15502 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15503 lsp_tasks
15504 .into_iter()
15505 .flat_map(|(kind, tasks)| {
15506 tasks.into_iter().filter_map(move |(location, task)| {
15507 Some((kind.clone(), location?, task))
15508 })
15509 })
15510 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15511 let buffer = location.target.buffer;
15512 let buffer_snapshot = buffer.read(cx).snapshot();
15513 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15514 |(excerpt_id, snapshot, _)| {
15515 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15516 display_snapshot
15517 .buffer_snapshot()
15518 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15519 } else {
15520 None
15521 }
15522 },
15523 );
15524 if let Some(offset) = offset {
15525 let task_buffer_range =
15526 location.target.range.to_point(&buffer_snapshot);
15527 let context_buffer_range =
15528 task_buffer_range.to_offset(&buffer_snapshot);
15529 let context_range = BufferOffset(context_buffer_range.start)
15530 ..BufferOffset(context_buffer_range.end);
15531
15532 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15533 .or_insert_with(|| RunnableTasks {
15534 templates: Vec::new(),
15535 offset,
15536 column: task_buffer_range.start.column,
15537 extra_variables: HashMap::default(),
15538 context_range,
15539 })
15540 .templates
15541 .push((kind, task.original_task().clone()));
15542 }
15543
15544 acc
15545 })
15546 }) else {
15547 return;
15548 };
15549
15550 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15551 buffer.language_settings(cx).tasks.prefer_lsp
15552 }) else {
15553 return;
15554 };
15555
15556 let rows = Self::runnable_rows(
15557 project,
15558 display_snapshot,
15559 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15560 new_rows,
15561 cx.clone(),
15562 )
15563 .await;
15564 editor
15565 .update(cx, |editor, _| {
15566 editor.clear_tasks();
15567 for (key, mut value) in rows {
15568 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15569 value.templates.extend(lsp_tasks.templates);
15570 }
15571
15572 editor.insert_tasks(key, value);
15573 }
15574 for (key, value) in lsp_tasks_by_rows {
15575 editor.insert_tasks(key, value);
15576 }
15577 })
15578 .ok();
15579 })
15580 }
15581 fn fetch_runnable_ranges(
15582 snapshot: &DisplaySnapshot,
15583 range: Range<Anchor>,
15584 ) -> Vec<language::RunnableRange> {
15585 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15586 }
15587
15588 fn runnable_rows(
15589 project: Entity<Project>,
15590 snapshot: DisplaySnapshot,
15591 prefer_lsp: bool,
15592 runnable_ranges: Vec<RunnableRange>,
15593 cx: AsyncWindowContext,
15594 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15595 cx.spawn(async move |cx| {
15596 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15597 for mut runnable in runnable_ranges {
15598 let Some(tasks) = cx
15599 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15600 .ok()
15601 else {
15602 continue;
15603 };
15604 let mut tasks = tasks.await;
15605
15606 if prefer_lsp {
15607 tasks.retain(|(task_kind, _)| {
15608 !matches!(task_kind, TaskSourceKind::Language { .. })
15609 });
15610 }
15611 if tasks.is_empty() {
15612 continue;
15613 }
15614
15615 let point = runnable
15616 .run_range
15617 .start
15618 .to_point(&snapshot.buffer_snapshot());
15619 let Some(row) = snapshot
15620 .buffer_snapshot()
15621 .buffer_line_for_row(MultiBufferRow(point.row))
15622 .map(|(_, range)| range.start.row)
15623 else {
15624 continue;
15625 };
15626
15627 let context_range =
15628 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15629 runnable_rows.push((
15630 (runnable.buffer_id, row),
15631 RunnableTasks {
15632 templates: tasks,
15633 offset: snapshot
15634 .buffer_snapshot()
15635 .anchor_before(runnable.run_range.start),
15636 context_range,
15637 column: point.column,
15638 extra_variables: runnable.extra_captures,
15639 },
15640 ));
15641 }
15642 runnable_rows
15643 })
15644 }
15645
15646 fn templates_with_tags(
15647 project: &Entity<Project>,
15648 runnable: &mut Runnable,
15649 cx: &mut App,
15650 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15651 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15652 let (worktree_id, file) = project
15653 .buffer_for_id(runnable.buffer, cx)
15654 .and_then(|buffer| buffer.read(cx).file())
15655 .map(|file| (file.worktree_id(cx), file.clone()))
15656 .unzip();
15657
15658 (
15659 project.task_store().read(cx).task_inventory().cloned(),
15660 worktree_id,
15661 file,
15662 )
15663 });
15664
15665 let tags = mem::take(&mut runnable.tags);
15666 let language = runnable.language.clone();
15667 cx.spawn(async move |cx| {
15668 let mut templates_with_tags = Vec::new();
15669 if let Some(inventory) = inventory {
15670 for RunnableTag(tag) in tags {
15671 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15672 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15673 }) else {
15674 return templates_with_tags;
15675 };
15676 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15677 move |(_, template)| {
15678 template.tags.iter().any(|source_tag| source_tag == &tag)
15679 },
15680 ));
15681 }
15682 }
15683 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15684
15685 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15686 // Strongest source wins; if we have worktree tag binding, prefer that to
15687 // global and language bindings;
15688 // if we have a global binding, prefer that to language binding.
15689 let first_mismatch = templates_with_tags
15690 .iter()
15691 .position(|(tag_source, _)| tag_source != leading_tag_source);
15692 if let Some(index) = first_mismatch {
15693 templates_with_tags.truncate(index);
15694 }
15695 }
15696
15697 templates_with_tags
15698 })
15699 }
15700
15701 pub fn move_to_enclosing_bracket(
15702 &mut self,
15703 _: &MoveToEnclosingBracket,
15704 window: &mut Window,
15705 cx: &mut Context<Self>,
15706 ) {
15707 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15708 self.change_selections(Default::default(), window, cx, |s| {
15709 s.move_offsets_with(|snapshot, selection| {
15710 let Some(enclosing_bracket_ranges) =
15711 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15712 else {
15713 return;
15714 };
15715
15716 let mut best_length = usize::MAX;
15717 let mut best_inside = false;
15718 let mut best_in_bracket_range = false;
15719 let mut best_destination = None;
15720 for (open, close) in enclosing_bracket_ranges {
15721 let close = close.to_inclusive();
15722 let length = close.end() - open.start;
15723 let inside = selection.start >= open.end && selection.end <= *close.start();
15724 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15725 || close.contains(&selection.head());
15726
15727 // If best is next to a bracket and current isn't, skip
15728 if !in_bracket_range && best_in_bracket_range {
15729 continue;
15730 }
15731
15732 // Prefer smaller lengths unless best is inside and current isn't
15733 if length > best_length && (best_inside || !inside) {
15734 continue;
15735 }
15736
15737 best_length = length;
15738 best_inside = inside;
15739 best_in_bracket_range = in_bracket_range;
15740 best_destination = Some(
15741 if close.contains(&selection.start) && close.contains(&selection.end) {
15742 if inside { open.end } else { open.start }
15743 } else if inside {
15744 *close.start()
15745 } else {
15746 *close.end()
15747 },
15748 );
15749 }
15750
15751 if let Some(destination) = best_destination {
15752 selection.collapse_to(destination, SelectionGoal::None);
15753 }
15754 })
15755 });
15756 }
15757
15758 pub fn undo_selection(
15759 &mut self,
15760 _: &UndoSelection,
15761 window: &mut Window,
15762 cx: &mut Context<Self>,
15763 ) {
15764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15765 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15766 self.selection_history.mode = SelectionHistoryMode::Undoing;
15767 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15768 this.end_selection(window, cx);
15769 this.change_selections(
15770 SelectionEffects::scroll(Autoscroll::newest()),
15771 window,
15772 cx,
15773 |s| s.select_anchors(entry.selections.to_vec()),
15774 );
15775 });
15776 self.selection_history.mode = SelectionHistoryMode::Normal;
15777
15778 self.select_next_state = entry.select_next_state;
15779 self.select_prev_state = entry.select_prev_state;
15780 self.add_selections_state = entry.add_selections_state;
15781 }
15782 }
15783
15784 pub fn redo_selection(
15785 &mut self,
15786 _: &RedoSelection,
15787 window: &mut Window,
15788 cx: &mut Context<Self>,
15789 ) {
15790 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15791 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15792 self.selection_history.mode = SelectionHistoryMode::Redoing;
15793 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15794 this.end_selection(window, cx);
15795 this.change_selections(
15796 SelectionEffects::scroll(Autoscroll::newest()),
15797 window,
15798 cx,
15799 |s| s.select_anchors(entry.selections.to_vec()),
15800 );
15801 });
15802 self.selection_history.mode = SelectionHistoryMode::Normal;
15803
15804 self.select_next_state = entry.select_next_state;
15805 self.select_prev_state = entry.select_prev_state;
15806 self.add_selections_state = entry.add_selections_state;
15807 }
15808 }
15809
15810 pub fn expand_excerpts(
15811 &mut self,
15812 action: &ExpandExcerpts,
15813 _: &mut Window,
15814 cx: &mut Context<Self>,
15815 ) {
15816 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15817 }
15818
15819 pub fn expand_excerpts_down(
15820 &mut self,
15821 action: &ExpandExcerptsDown,
15822 _: &mut Window,
15823 cx: &mut Context<Self>,
15824 ) {
15825 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15826 }
15827
15828 pub fn expand_excerpts_up(
15829 &mut self,
15830 action: &ExpandExcerptsUp,
15831 _: &mut Window,
15832 cx: &mut Context<Self>,
15833 ) {
15834 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15835 }
15836
15837 pub fn expand_excerpts_for_direction(
15838 &mut self,
15839 lines: u32,
15840 direction: ExpandExcerptDirection,
15841
15842 cx: &mut Context<Self>,
15843 ) {
15844 let selections = self.selections.disjoint_anchors_arc();
15845
15846 let lines = if lines == 0 {
15847 EditorSettings::get_global(cx).expand_excerpt_lines
15848 } else {
15849 lines
15850 };
15851
15852 self.buffer.update(cx, |buffer, cx| {
15853 let snapshot = buffer.snapshot(cx);
15854 let mut excerpt_ids = selections
15855 .iter()
15856 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15857 .collect::<Vec<_>>();
15858 excerpt_ids.sort();
15859 excerpt_ids.dedup();
15860 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15861 })
15862 }
15863
15864 pub fn expand_excerpt(
15865 &mut self,
15866 excerpt: ExcerptId,
15867 direction: ExpandExcerptDirection,
15868 window: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) {
15871 let current_scroll_position = self.scroll_position(cx);
15872 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15873 let mut scroll = None;
15874
15875 if direction == ExpandExcerptDirection::Down {
15876 let multi_buffer = self.buffer.read(cx);
15877 let snapshot = multi_buffer.snapshot(cx);
15878 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15879 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15880 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15881 {
15882 let buffer_snapshot = buffer.read(cx).snapshot();
15883 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15884 let last_row = buffer_snapshot.max_point().row;
15885 let lines_below = last_row.saturating_sub(excerpt_end_row);
15886 if lines_below >= lines_to_expand {
15887 scroll = Some(
15888 current_scroll_position
15889 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15890 );
15891 }
15892 }
15893 }
15894 if direction == ExpandExcerptDirection::Up
15895 && self
15896 .buffer
15897 .read(cx)
15898 .snapshot(cx)
15899 .excerpt_before(excerpt)
15900 .is_none()
15901 {
15902 scroll = Some(current_scroll_position);
15903 }
15904
15905 self.buffer.update(cx, |buffer, cx| {
15906 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15907 });
15908
15909 if let Some(new_scroll_position) = scroll {
15910 self.set_scroll_position(new_scroll_position, window, cx);
15911 }
15912 }
15913
15914 pub fn go_to_singleton_buffer_point(
15915 &mut self,
15916 point: Point,
15917 window: &mut Window,
15918 cx: &mut Context<Self>,
15919 ) {
15920 self.go_to_singleton_buffer_range(point..point, window, cx);
15921 }
15922
15923 pub fn go_to_singleton_buffer_range(
15924 &mut self,
15925 range: Range<Point>,
15926 window: &mut Window,
15927 cx: &mut Context<Self>,
15928 ) {
15929 let multibuffer = self.buffer().read(cx);
15930 let Some(buffer) = multibuffer.as_singleton() else {
15931 return;
15932 };
15933 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15934 return;
15935 };
15936 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15937 return;
15938 };
15939 self.change_selections(
15940 SelectionEffects::default().nav_history(true),
15941 window,
15942 cx,
15943 |s| s.select_anchor_ranges([start..end]),
15944 );
15945 }
15946
15947 pub fn go_to_diagnostic(
15948 &mut self,
15949 action: &GoToDiagnostic,
15950 window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 if !self.diagnostics_enabled() {
15954 return;
15955 }
15956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15957 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15958 }
15959
15960 pub fn go_to_prev_diagnostic(
15961 &mut self,
15962 action: &GoToPreviousDiagnostic,
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::Prev, action.severity, window, cx)
15971 }
15972
15973 pub fn go_to_diagnostic_impl(
15974 &mut self,
15975 direction: Direction,
15976 severity: GoToDiagnosticSeverityFilter,
15977 window: &mut Window,
15978 cx: &mut Context<Self>,
15979 ) {
15980 let buffer = self.buffer.read(cx).snapshot(cx);
15981 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15982
15983 let mut active_group_id = None;
15984 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15985 && active_group.active_range.start.to_offset(&buffer) == selection.start
15986 {
15987 active_group_id = Some(active_group.group_id);
15988 }
15989
15990 fn filtered<'a>(
15991 severity: GoToDiagnosticSeverityFilter,
15992 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15993 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15994 diagnostics
15995 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15996 .filter(|entry| entry.range.start != entry.range.end)
15997 .filter(|entry| !entry.diagnostic.is_unnecessary)
15998 }
15999
16000 let before = filtered(
16001 severity,
16002 buffer
16003 .diagnostics_in_range(0..selection.start)
16004 .filter(|entry| entry.range.start <= selection.start),
16005 );
16006 let after = filtered(
16007 severity,
16008 buffer
16009 .diagnostics_in_range(selection.start..buffer.len())
16010 .filter(|entry| entry.range.start >= selection.start),
16011 );
16012
16013 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16014 if direction == Direction::Prev {
16015 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16016 {
16017 for diagnostic in prev_diagnostics.into_iter().rev() {
16018 if diagnostic.range.start != selection.start
16019 || active_group_id
16020 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16021 {
16022 found = Some(diagnostic);
16023 break 'outer;
16024 }
16025 }
16026 }
16027 } else {
16028 for diagnostic in after.chain(before) {
16029 if diagnostic.range.start != selection.start
16030 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16031 {
16032 found = Some(diagnostic);
16033 break;
16034 }
16035 }
16036 }
16037 let Some(next_diagnostic) = found else {
16038 return;
16039 };
16040
16041 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16042 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16043 return;
16044 };
16045 let snapshot = self.snapshot(window, cx);
16046 if snapshot.intersects_fold(next_diagnostic.range.start) {
16047 self.unfold_ranges(
16048 std::slice::from_ref(&next_diagnostic.range),
16049 true,
16050 false,
16051 cx,
16052 );
16053 }
16054 self.change_selections(Default::default(), window, cx, |s| {
16055 s.select_ranges(vec![
16056 next_diagnostic.range.start..next_diagnostic.range.start,
16057 ])
16058 });
16059 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16060 self.refresh_edit_prediction(false, true, window, cx);
16061 }
16062
16063 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16065 let snapshot = self.snapshot(window, cx);
16066 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16067 self.go_to_hunk_before_or_after_position(
16068 &snapshot,
16069 selection.head(),
16070 Direction::Next,
16071 window,
16072 cx,
16073 );
16074 }
16075
16076 pub fn go_to_hunk_before_or_after_position(
16077 &mut self,
16078 snapshot: &EditorSnapshot,
16079 position: Point,
16080 direction: Direction,
16081 window: &mut Window,
16082 cx: &mut Context<Editor>,
16083 ) {
16084 let row = if direction == Direction::Next {
16085 self.hunk_after_position(snapshot, position)
16086 .map(|hunk| hunk.row_range.start)
16087 } else {
16088 self.hunk_before_position(snapshot, position)
16089 };
16090
16091 if let Some(row) = row {
16092 let destination = Point::new(row.0, 0);
16093 let autoscroll = Autoscroll::center();
16094
16095 self.unfold_ranges(&[destination..destination], false, false, cx);
16096 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16097 s.select_ranges([destination..destination]);
16098 });
16099 }
16100 }
16101
16102 fn hunk_after_position(
16103 &mut self,
16104 snapshot: &EditorSnapshot,
16105 position: Point,
16106 ) -> Option<MultiBufferDiffHunk> {
16107 snapshot
16108 .buffer_snapshot()
16109 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16110 .find(|hunk| hunk.row_range.start.0 > position.row)
16111 .or_else(|| {
16112 snapshot
16113 .buffer_snapshot()
16114 .diff_hunks_in_range(Point::zero()..position)
16115 .find(|hunk| hunk.row_range.end.0 < position.row)
16116 })
16117 }
16118
16119 fn go_to_prev_hunk(
16120 &mut self,
16121 _: &GoToPreviousHunk,
16122 window: &mut Window,
16123 cx: &mut Context<Self>,
16124 ) {
16125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16126 let snapshot = self.snapshot(window, cx);
16127 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16128 self.go_to_hunk_before_or_after_position(
16129 &snapshot,
16130 selection.head(),
16131 Direction::Prev,
16132 window,
16133 cx,
16134 );
16135 }
16136
16137 fn hunk_before_position(
16138 &mut self,
16139 snapshot: &EditorSnapshot,
16140 position: Point,
16141 ) -> Option<MultiBufferRow> {
16142 snapshot
16143 .buffer_snapshot()
16144 .diff_hunk_before(position)
16145 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16146 }
16147
16148 fn go_to_next_change(
16149 &mut self,
16150 _: &GoToNextChange,
16151 window: &mut Window,
16152 cx: &mut Context<Self>,
16153 ) {
16154 if let Some(selections) = self
16155 .change_list
16156 .next_change(1, Direction::Next)
16157 .map(|s| s.to_vec())
16158 {
16159 self.change_selections(Default::default(), window, cx, |s| {
16160 let map = s.display_map();
16161 s.select_display_ranges(selections.iter().map(|a| {
16162 let point = a.to_display_point(&map);
16163 point..point
16164 }))
16165 })
16166 }
16167 }
16168
16169 fn go_to_previous_change(
16170 &mut self,
16171 _: &GoToPreviousChange,
16172 window: &mut Window,
16173 cx: &mut Context<Self>,
16174 ) {
16175 if let Some(selections) = self
16176 .change_list
16177 .next_change(1, Direction::Prev)
16178 .map(|s| s.to_vec())
16179 {
16180 self.change_selections(Default::default(), window, cx, |s| {
16181 let map = s.display_map();
16182 s.select_display_ranges(selections.iter().map(|a| {
16183 let point = a.to_display_point(&map);
16184 point..point
16185 }))
16186 })
16187 }
16188 }
16189
16190 pub fn go_to_next_document_highlight(
16191 &mut self,
16192 _: &GoToNextDocumentHighlight,
16193 window: &mut Window,
16194 cx: &mut Context<Self>,
16195 ) {
16196 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16197 }
16198
16199 pub fn go_to_prev_document_highlight(
16200 &mut self,
16201 _: &GoToPreviousDocumentHighlight,
16202 window: &mut Window,
16203 cx: &mut Context<Self>,
16204 ) {
16205 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16206 }
16207
16208 pub fn go_to_document_highlight_before_or_after_position(
16209 &mut self,
16210 direction: Direction,
16211 window: &mut Window,
16212 cx: &mut Context<Editor>,
16213 ) {
16214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16215 let snapshot = self.snapshot(window, cx);
16216 let buffer = &snapshot.buffer_snapshot();
16217 let position = self
16218 .selections
16219 .newest::<Point>(&snapshot.display_snapshot)
16220 .head();
16221 let anchor_position = buffer.anchor_after(position);
16222
16223 // Get all document highlights (both read and write)
16224 let mut all_highlights = Vec::new();
16225
16226 if let Some((_, read_highlights)) = self
16227 .background_highlights
16228 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16229 {
16230 all_highlights.extend(read_highlights.iter());
16231 }
16232
16233 if let Some((_, write_highlights)) = self
16234 .background_highlights
16235 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16236 {
16237 all_highlights.extend(write_highlights.iter());
16238 }
16239
16240 if all_highlights.is_empty() {
16241 return;
16242 }
16243
16244 // Sort highlights by position
16245 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16246
16247 let target_highlight = match direction {
16248 Direction::Next => {
16249 // Find the first highlight after the current position
16250 all_highlights
16251 .iter()
16252 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16253 }
16254 Direction::Prev => {
16255 // Find the last highlight before the current position
16256 all_highlights
16257 .iter()
16258 .rev()
16259 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16260 }
16261 };
16262
16263 if let Some(highlight) = target_highlight {
16264 let destination = highlight.start.to_point(buffer);
16265 let autoscroll = Autoscroll::center();
16266
16267 self.unfold_ranges(&[destination..destination], false, false, cx);
16268 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16269 s.select_ranges([destination..destination]);
16270 });
16271 }
16272 }
16273
16274 fn go_to_line<T: 'static>(
16275 &mut self,
16276 position: Anchor,
16277 highlight_color: Option<Hsla>,
16278 window: &mut Window,
16279 cx: &mut Context<Self>,
16280 ) {
16281 let snapshot = self.snapshot(window, cx).display_snapshot;
16282 let position = position.to_point(&snapshot.buffer_snapshot());
16283 let start = snapshot
16284 .buffer_snapshot()
16285 .clip_point(Point::new(position.row, 0), Bias::Left);
16286 let end = start + Point::new(1, 0);
16287 let start = snapshot.buffer_snapshot().anchor_before(start);
16288 let end = snapshot.buffer_snapshot().anchor_before(end);
16289
16290 self.highlight_rows::<T>(
16291 start..end,
16292 highlight_color
16293 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16294 Default::default(),
16295 cx,
16296 );
16297
16298 if self.buffer.read(cx).is_singleton() {
16299 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16300 }
16301 }
16302
16303 pub fn go_to_definition(
16304 &mut self,
16305 _: &GoToDefinition,
16306 window: &mut Window,
16307 cx: &mut Context<Self>,
16308 ) -> Task<Result<Navigated>> {
16309 let definition =
16310 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16311 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16312 cx.spawn_in(window, async move |editor, cx| {
16313 if definition.await? == Navigated::Yes {
16314 return Ok(Navigated::Yes);
16315 }
16316 match fallback_strategy {
16317 GoToDefinitionFallback::None => Ok(Navigated::No),
16318 GoToDefinitionFallback::FindAllReferences => {
16319 match editor.update_in(cx, |editor, window, cx| {
16320 editor.find_all_references(&FindAllReferences, window, cx)
16321 })? {
16322 Some(references) => references.await,
16323 None => Ok(Navigated::No),
16324 }
16325 }
16326 }
16327 })
16328 }
16329
16330 pub fn go_to_declaration(
16331 &mut self,
16332 _: &GoToDeclaration,
16333 window: &mut Window,
16334 cx: &mut Context<Self>,
16335 ) -> Task<Result<Navigated>> {
16336 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16337 }
16338
16339 pub fn go_to_declaration_split(
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, true, window, cx)
16346 }
16347
16348 pub fn go_to_implementation(
16349 &mut self,
16350 _: &GoToImplementation,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) -> Task<Result<Navigated>> {
16354 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16355 }
16356
16357 pub fn go_to_implementation_split(
16358 &mut self,
16359 _: &GoToImplementationSplit,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) -> Task<Result<Navigated>> {
16363 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16364 }
16365
16366 pub fn go_to_type_definition(
16367 &mut self,
16368 _: &GoToTypeDefinition,
16369 window: &mut Window,
16370 cx: &mut Context<Self>,
16371 ) -> Task<Result<Navigated>> {
16372 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16373 }
16374
16375 pub fn go_to_definition_split(
16376 &mut self,
16377 _: &GoToDefinitionSplit,
16378 window: &mut Window,
16379 cx: &mut Context<Self>,
16380 ) -> Task<Result<Navigated>> {
16381 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16382 }
16383
16384 pub fn go_to_type_definition_split(
16385 &mut self,
16386 _: &GoToTypeDefinitionSplit,
16387 window: &mut Window,
16388 cx: &mut Context<Self>,
16389 ) -> Task<Result<Navigated>> {
16390 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16391 }
16392
16393 fn go_to_definition_of_kind(
16394 &mut self,
16395 kind: GotoDefinitionKind,
16396 split: bool,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) -> Task<Result<Navigated>> {
16400 let Some(provider) = self.semantics_provider.clone() else {
16401 return Task::ready(Ok(Navigated::No));
16402 };
16403 let head = self
16404 .selections
16405 .newest::<usize>(&self.display_snapshot(cx))
16406 .head();
16407 let buffer = self.buffer.read(cx);
16408 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16409 return Task::ready(Ok(Navigated::No));
16410 };
16411 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16412 return Task::ready(Ok(Navigated::No));
16413 };
16414
16415 cx.spawn_in(window, async move |editor, cx| {
16416 let Some(definitions) = definitions.await? else {
16417 return Ok(Navigated::No);
16418 };
16419 let navigated = editor
16420 .update_in(cx, |editor, window, cx| {
16421 editor.navigate_to_hover_links(
16422 Some(kind),
16423 definitions
16424 .into_iter()
16425 .filter(|location| {
16426 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16427 })
16428 .map(HoverLink::Text)
16429 .collect::<Vec<_>>(),
16430 split,
16431 window,
16432 cx,
16433 )
16434 })?
16435 .await?;
16436 anyhow::Ok(navigated)
16437 })
16438 }
16439
16440 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16441 let selection = self.selections.newest_anchor();
16442 let head = selection.head();
16443 let tail = selection.tail();
16444
16445 let Some((buffer, start_position)) =
16446 self.buffer.read(cx).text_anchor_for_position(head, cx)
16447 else {
16448 return;
16449 };
16450
16451 let end_position = if head != tail {
16452 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16453 return;
16454 };
16455 Some(pos)
16456 } else {
16457 None
16458 };
16459
16460 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16461 let url = if let Some(end_pos) = end_position {
16462 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16463 } else {
16464 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16465 };
16466
16467 if let Some(url) = url {
16468 cx.update(|window, cx| {
16469 if parse_zed_link(&url, cx).is_some() {
16470 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16471 } else {
16472 cx.open_url(&url);
16473 }
16474 })?;
16475 }
16476
16477 anyhow::Ok(())
16478 });
16479
16480 url_finder.detach();
16481 }
16482
16483 pub fn open_selected_filename(
16484 &mut self,
16485 _: &OpenSelectedFilename,
16486 window: &mut Window,
16487 cx: &mut Context<Self>,
16488 ) {
16489 let Some(workspace) = self.workspace() else {
16490 return;
16491 };
16492
16493 let position = self.selections.newest_anchor().head();
16494
16495 let Some((buffer, buffer_position)) =
16496 self.buffer.read(cx).text_anchor_for_position(position, cx)
16497 else {
16498 return;
16499 };
16500
16501 let project = self.project.clone();
16502
16503 cx.spawn_in(window, async move |_, cx| {
16504 let result = find_file(&buffer, project, buffer_position, cx).await;
16505
16506 if let Some((_, path)) = result {
16507 workspace
16508 .update_in(cx, |workspace, window, cx| {
16509 workspace.open_resolved_path(path, window, cx)
16510 })?
16511 .await?;
16512 }
16513 anyhow::Ok(())
16514 })
16515 .detach();
16516 }
16517
16518 pub(crate) fn navigate_to_hover_links(
16519 &mut self,
16520 kind: Option<GotoDefinitionKind>,
16521 definitions: Vec<HoverLink>,
16522 split: bool,
16523 window: &mut Window,
16524 cx: &mut Context<Editor>,
16525 ) -> Task<Result<Navigated>> {
16526 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16527 let mut first_url_or_file = None;
16528 let definitions: Vec<_> = definitions
16529 .into_iter()
16530 .filter_map(|def| match def {
16531 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16532 HoverLink::InlayHint(lsp_location, server_id) => {
16533 let computation =
16534 self.compute_target_location(lsp_location, server_id, window, cx);
16535 Some(cx.background_spawn(computation))
16536 }
16537 HoverLink::Url(url) => {
16538 first_url_or_file = Some(Either::Left(url));
16539 None
16540 }
16541 HoverLink::File(path) => {
16542 first_url_or_file = Some(Either::Right(path));
16543 None
16544 }
16545 })
16546 .collect();
16547
16548 let workspace = self.workspace();
16549
16550 cx.spawn_in(window, async move |editor, cx| {
16551 let locations: Vec<Location> = future::join_all(definitions)
16552 .await
16553 .into_iter()
16554 .filter_map(|location| location.transpose())
16555 .collect::<Result<_>>()
16556 .context("location tasks")?;
16557 let mut locations = cx.update(|_, cx| {
16558 locations
16559 .into_iter()
16560 .map(|location| {
16561 let buffer = location.buffer.read(cx);
16562 (location.buffer, location.range.to_point(buffer))
16563 })
16564 .into_group_map()
16565 })?;
16566 let mut num_locations = 0;
16567 for ranges in locations.values_mut() {
16568 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16569 ranges.dedup();
16570 num_locations += ranges.len();
16571 }
16572
16573 if num_locations > 1 {
16574 let Some(workspace) = workspace else {
16575 return Ok(Navigated::No);
16576 };
16577
16578 let tab_kind = match kind {
16579 Some(GotoDefinitionKind::Implementation) => "Implementations",
16580 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16581 Some(GotoDefinitionKind::Declaration) => "Declarations",
16582 Some(GotoDefinitionKind::Type) => "Types",
16583 };
16584 let title = editor
16585 .update_in(cx, |_, _, cx| {
16586 let target = locations
16587 .iter()
16588 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16589 .map(|(buffer, location)| {
16590 buffer
16591 .read(cx)
16592 .text_for_range(location.clone())
16593 .collect::<String>()
16594 })
16595 .filter(|text| !text.contains('\n'))
16596 .unique()
16597 .take(3)
16598 .join(", ");
16599 if target.is_empty() {
16600 tab_kind.to_owned()
16601 } else {
16602 format!("{tab_kind} for {target}")
16603 }
16604 })
16605 .context("buffer title")?;
16606
16607 let opened = workspace
16608 .update_in(cx, |workspace, window, cx| {
16609 Self::open_locations_in_multibuffer(
16610 workspace,
16611 locations,
16612 title,
16613 split,
16614 MultibufferSelectionMode::First,
16615 window,
16616 cx,
16617 )
16618 })
16619 .is_ok();
16620
16621 anyhow::Ok(Navigated::from_bool(opened))
16622 } else if num_locations == 0 {
16623 // If there is one url or file, open it directly
16624 match first_url_or_file {
16625 Some(Either::Left(url)) => {
16626 cx.update(|_, cx| cx.open_url(&url))?;
16627 Ok(Navigated::Yes)
16628 }
16629 Some(Either::Right(path)) => {
16630 let Some(workspace) = workspace else {
16631 return Ok(Navigated::No);
16632 };
16633
16634 workspace
16635 .update_in(cx, |workspace, window, cx| {
16636 workspace.open_resolved_path(path, window, cx)
16637 })?
16638 .await?;
16639 Ok(Navigated::Yes)
16640 }
16641 None => Ok(Navigated::No),
16642 }
16643 } else {
16644 let Some(workspace) = workspace else {
16645 return Ok(Navigated::No);
16646 };
16647
16648 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16649 let target_range = target_ranges.first().unwrap().clone();
16650
16651 editor.update_in(cx, |editor, window, cx| {
16652 let range = target_range.to_point(target_buffer.read(cx));
16653 let range = editor.range_for_match(&range, false);
16654 let range = collapse_multiline_range(range);
16655
16656 if !split
16657 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16658 {
16659 editor.go_to_singleton_buffer_range(range, window, cx);
16660 } else {
16661 let pane = workspace.read(cx).active_pane().clone();
16662 window.defer(cx, move |window, cx| {
16663 let target_editor: Entity<Self> =
16664 workspace.update(cx, |workspace, cx| {
16665 let pane = if split {
16666 workspace.adjacent_pane(window, cx)
16667 } else {
16668 workspace.active_pane().clone()
16669 };
16670
16671 workspace.open_project_item(
16672 pane,
16673 target_buffer.clone(),
16674 true,
16675 true,
16676 window,
16677 cx,
16678 )
16679 });
16680 target_editor.update(cx, |target_editor, cx| {
16681 // When selecting a definition in a different buffer, disable the nav history
16682 // to avoid creating a history entry at the previous cursor location.
16683 pane.update(cx, |pane, _| pane.disable_history());
16684 target_editor.go_to_singleton_buffer_range(range, window, cx);
16685 pane.update(cx, |pane, _| pane.enable_history());
16686 });
16687 });
16688 }
16689 Navigated::Yes
16690 })
16691 }
16692 })
16693 }
16694
16695 fn compute_target_location(
16696 &self,
16697 lsp_location: lsp::Location,
16698 server_id: LanguageServerId,
16699 window: &mut Window,
16700 cx: &mut Context<Self>,
16701 ) -> Task<anyhow::Result<Option<Location>>> {
16702 let Some(project) = self.project.clone() else {
16703 return Task::ready(Ok(None));
16704 };
16705
16706 cx.spawn_in(window, async move |editor, cx| {
16707 let location_task = editor.update(cx, |_, cx| {
16708 project.update(cx, |project, cx| {
16709 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16710 })
16711 })?;
16712 let location = Some({
16713 let target_buffer_handle = location_task.await.context("open local buffer")?;
16714 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16715 let target_start = target_buffer
16716 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16717 let target_end = target_buffer
16718 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16719 target_buffer.anchor_after(target_start)
16720 ..target_buffer.anchor_before(target_end)
16721 })?;
16722 Location {
16723 buffer: target_buffer_handle,
16724 range,
16725 }
16726 });
16727 Ok(location)
16728 })
16729 }
16730
16731 fn go_to_next_reference(
16732 &mut self,
16733 _: &GoToNextReference,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) {
16737 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16738 if let Some(task) = task {
16739 task.detach();
16740 };
16741 }
16742
16743 fn go_to_prev_reference(
16744 &mut self,
16745 _: &GoToPreviousReference,
16746 window: &mut Window,
16747 cx: &mut Context<Self>,
16748 ) {
16749 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16750 if let Some(task) = task {
16751 task.detach();
16752 };
16753 }
16754
16755 pub fn go_to_reference_before_or_after_position(
16756 &mut self,
16757 direction: Direction,
16758 count: usize,
16759 window: &mut Window,
16760 cx: &mut Context<Self>,
16761 ) -> Option<Task<Result<()>>> {
16762 let selection = self.selections.newest_anchor();
16763 let head = selection.head();
16764
16765 let multi_buffer = self.buffer.read(cx);
16766
16767 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16768 let workspace = self.workspace()?;
16769 let project = workspace.read(cx).project().clone();
16770 let references =
16771 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16772 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16773 let Some(locations) = references.await? else {
16774 return Ok(());
16775 };
16776
16777 if locations.is_empty() {
16778 // totally normal - the cursor may be on something which is not
16779 // a symbol (e.g. a keyword)
16780 log::info!("no references found under cursor");
16781 return Ok(());
16782 }
16783
16784 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16785
16786 let multi_buffer_snapshot =
16787 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16788
16789 let (locations, current_location_index) =
16790 multi_buffer.update(cx, |multi_buffer, cx| {
16791 let mut locations = locations
16792 .into_iter()
16793 .filter_map(|loc| {
16794 let start = multi_buffer.buffer_anchor_to_anchor(
16795 &loc.buffer,
16796 loc.range.start,
16797 cx,
16798 )?;
16799 let end = multi_buffer.buffer_anchor_to_anchor(
16800 &loc.buffer,
16801 loc.range.end,
16802 cx,
16803 )?;
16804 Some(start..end)
16805 })
16806 .collect::<Vec<_>>();
16807
16808 // There is an O(n) implementation, but given this list will be
16809 // small (usually <100 items), the extra O(log(n)) factor isn't
16810 // worth the (surprisingly large amount of) extra complexity.
16811 locations
16812 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16813
16814 let head_offset = head.to_offset(&multi_buffer_snapshot);
16815
16816 let current_location_index = locations.iter().position(|loc| {
16817 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16818 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16819 });
16820
16821 (locations, current_location_index)
16822 })?;
16823
16824 let Some(current_location_index) = current_location_index else {
16825 // This indicates something has gone wrong, because we already
16826 // handle the "no references" case above
16827 log::error!(
16828 "failed to find current reference under cursor. Total references: {}",
16829 locations.len()
16830 );
16831 return Ok(());
16832 };
16833
16834 let destination_location_index = match direction {
16835 Direction::Next => (current_location_index + count) % locations.len(),
16836 Direction::Prev => {
16837 (current_location_index + locations.len() - count % locations.len())
16838 % locations.len()
16839 }
16840 };
16841
16842 // TODO(cameron): is this needed?
16843 // the thinking is to avoid "jumping to the current location" (avoid
16844 // polluting "jumplist" in vim terms)
16845 if current_location_index == destination_location_index {
16846 return Ok(());
16847 }
16848
16849 let Range { start, end } = locations[destination_location_index];
16850
16851 editor.update_in(cx, |editor, window, cx| {
16852 let effects = SelectionEffects::default();
16853
16854 editor.unfold_ranges(&[start..end], false, false, cx);
16855 editor.change_selections(effects, window, cx, |s| {
16856 s.select_ranges([start..start]);
16857 });
16858 })?;
16859
16860 Ok(())
16861 }))
16862 }
16863
16864 pub fn find_all_references(
16865 &mut self,
16866 _: &FindAllReferences,
16867 window: &mut Window,
16868 cx: &mut Context<Self>,
16869 ) -> Option<Task<Result<Navigated>>> {
16870 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16871 let multi_buffer = self.buffer.read(cx);
16872 let head = selection.head();
16873
16874 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16875 let head_anchor = multi_buffer_snapshot.anchor_at(
16876 head,
16877 if head < selection.tail() {
16878 Bias::Right
16879 } else {
16880 Bias::Left
16881 },
16882 );
16883
16884 match self
16885 .find_all_references_task_sources
16886 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16887 {
16888 Ok(_) => {
16889 log::info!(
16890 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16891 );
16892 return None;
16893 }
16894 Err(i) => {
16895 self.find_all_references_task_sources.insert(i, head_anchor);
16896 }
16897 }
16898
16899 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16900 let workspace = self.workspace()?;
16901 let project = workspace.read(cx).project().clone();
16902 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16903 Some(cx.spawn_in(window, async move |editor, cx| {
16904 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16905 if let Ok(i) = editor
16906 .find_all_references_task_sources
16907 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16908 {
16909 editor.find_all_references_task_sources.remove(i);
16910 }
16911 });
16912
16913 let Some(locations) = references.await? else {
16914 return anyhow::Ok(Navigated::No);
16915 };
16916 let mut locations = cx.update(|_, cx| {
16917 locations
16918 .into_iter()
16919 .map(|location| {
16920 let buffer = location.buffer.read(cx);
16921 (location.buffer, location.range.to_point(buffer))
16922 })
16923 .into_group_map()
16924 })?;
16925 if locations.is_empty() {
16926 return anyhow::Ok(Navigated::No);
16927 }
16928 for ranges in locations.values_mut() {
16929 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16930 ranges.dedup();
16931 }
16932
16933 workspace.update_in(cx, |workspace, window, cx| {
16934 let target = locations
16935 .iter()
16936 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16937 .map(|(buffer, location)| {
16938 buffer
16939 .read(cx)
16940 .text_for_range(location.clone())
16941 .collect::<String>()
16942 })
16943 .filter(|text| !text.contains('\n'))
16944 .unique()
16945 .take(3)
16946 .join(", ");
16947 let title = if target.is_empty() {
16948 "References".to_owned()
16949 } else {
16950 format!("References to {target}")
16951 };
16952 Self::open_locations_in_multibuffer(
16953 workspace,
16954 locations,
16955 title,
16956 false,
16957 MultibufferSelectionMode::First,
16958 window,
16959 cx,
16960 );
16961 Navigated::Yes
16962 })
16963 }))
16964 }
16965
16966 /// Opens a multibuffer with the given project locations in it
16967 pub fn open_locations_in_multibuffer(
16968 workspace: &mut Workspace,
16969 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16970 title: String,
16971 split: bool,
16972 multibuffer_selection_mode: MultibufferSelectionMode,
16973 window: &mut Window,
16974 cx: &mut Context<Workspace>,
16975 ) {
16976 if locations.is_empty() {
16977 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16978 return;
16979 }
16980
16981 let capability = workspace.project().read(cx).capability();
16982 let mut ranges = <Vec<Range<Anchor>>>::new();
16983
16984 // a key to find existing multibuffer editors with the same set of locations
16985 // to prevent us from opening more and more multibuffer tabs for searches and the like
16986 let mut key = (title.clone(), vec![]);
16987 let excerpt_buffer = cx.new(|cx| {
16988 let key = &mut key.1;
16989 let mut multibuffer = MultiBuffer::new(capability);
16990 for (buffer, mut ranges_for_buffer) in locations {
16991 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16992 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16993 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16994 PathKey::for_buffer(&buffer, cx),
16995 buffer.clone(),
16996 ranges_for_buffer,
16997 multibuffer_context_lines(cx),
16998 cx,
16999 );
17000 ranges.extend(new_ranges)
17001 }
17002
17003 multibuffer.with_title(title)
17004 });
17005 let existing = workspace.active_pane().update(cx, |pane, cx| {
17006 pane.items()
17007 .filter_map(|item| item.downcast::<Editor>())
17008 .find(|editor| {
17009 editor
17010 .read(cx)
17011 .lookup_key
17012 .as_ref()
17013 .and_then(|it| {
17014 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17015 })
17016 .is_some_and(|it| *it == key)
17017 })
17018 });
17019 let editor = existing.unwrap_or_else(|| {
17020 cx.new(|cx| {
17021 let mut editor = Editor::for_multibuffer(
17022 excerpt_buffer,
17023 Some(workspace.project().clone()),
17024 window,
17025 cx,
17026 );
17027 editor.lookup_key = Some(Box::new(key));
17028 editor
17029 })
17030 });
17031 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17032 MultibufferSelectionMode::First => {
17033 if let Some(first_range) = ranges.first() {
17034 editor.change_selections(
17035 SelectionEffects::no_scroll(),
17036 window,
17037 cx,
17038 |selections| {
17039 selections.clear_disjoint();
17040 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17041 },
17042 );
17043 }
17044 editor.highlight_background::<Self>(
17045 &ranges,
17046 |theme| theme.colors().editor_highlighted_line_background,
17047 cx,
17048 );
17049 }
17050 MultibufferSelectionMode::All => {
17051 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17052 selections.clear_disjoint();
17053 selections.select_anchor_ranges(ranges);
17054 });
17055 }
17056 });
17057
17058 let item = Box::new(editor);
17059 let item_id = item.item_id();
17060
17061 if split {
17062 let pane = workspace.adjacent_pane(window, cx);
17063 workspace.add_item(pane, item, None, true, true, window, cx);
17064 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17065 let (preview_item_id, preview_item_idx) =
17066 workspace.active_pane().read_with(cx, |pane, _| {
17067 (pane.preview_item_id(), pane.preview_item_idx())
17068 });
17069
17070 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17071
17072 if let Some(preview_item_id) = preview_item_id {
17073 workspace.active_pane().update(cx, |pane, cx| {
17074 pane.remove_item(preview_item_id, false, false, window, cx);
17075 });
17076 }
17077 } else {
17078 workspace.add_item_to_active_pane(item, None, true, window, cx);
17079 }
17080 workspace.active_pane().update(cx, |pane, cx| {
17081 pane.set_preview_item_id(Some(item_id), cx);
17082 });
17083 }
17084
17085 pub fn rename(
17086 &mut self,
17087 _: &Rename,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) -> Option<Task<Result<()>>> {
17091 use language::ToOffset as _;
17092
17093 let provider = self.semantics_provider.clone()?;
17094 let selection = self.selections.newest_anchor().clone();
17095 let (cursor_buffer, cursor_buffer_position) = self
17096 .buffer
17097 .read(cx)
17098 .text_anchor_for_position(selection.head(), cx)?;
17099 let (tail_buffer, cursor_buffer_position_end) = self
17100 .buffer
17101 .read(cx)
17102 .text_anchor_for_position(selection.tail(), cx)?;
17103 if tail_buffer != cursor_buffer {
17104 return None;
17105 }
17106
17107 let snapshot = cursor_buffer.read(cx).snapshot();
17108 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17109 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17110 let prepare_rename = provider
17111 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17112 .unwrap_or_else(|| Task::ready(Ok(None)));
17113 drop(snapshot);
17114
17115 Some(cx.spawn_in(window, async move |this, cx| {
17116 let rename_range = if let Some(range) = prepare_rename.await? {
17117 Some(range)
17118 } else {
17119 this.update(cx, |this, cx| {
17120 let buffer = this.buffer.read(cx).snapshot(cx);
17121 let mut buffer_highlights = this
17122 .document_highlights_for_position(selection.head(), &buffer)
17123 .filter(|highlight| {
17124 highlight.start.excerpt_id == selection.head().excerpt_id
17125 && highlight.end.excerpt_id == selection.head().excerpt_id
17126 });
17127 buffer_highlights
17128 .next()
17129 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17130 })?
17131 };
17132 if let Some(rename_range) = rename_range {
17133 this.update_in(cx, |this, window, cx| {
17134 let snapshot = cursor_buffer.read(cx).snapshot();
17135 let rename_buffer_range = rename_range.to_offset(&snapshot);
17136 let cursor_offset_in_rename_range =
17137 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17138 let cursor_offset_in_rename_range_end =
17139 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17140
17141 this.take_rename(false, window, cx);
17142 let buffer = this.buffer.read(cx).read(cx);
17143 let cursor_offset = selection.head().to_offset(&buffer);
17144 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17145 let rename_end = rename_start + rename_buffer_range.len();
17146 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17147 let mut old_highlight_id = None;
17148 let old_name: Arc<str> = buffer
17149 .chunks(rename_start..rename_end, true)
17150 .map(|chunk| {
17151 if old_highlight_id.is_none() {
17152 old_highlight_id = chunk.syntax_highlight_id;
17153 }
17154 chunk.text
17155 })
17156 .collect::<String>()
17157 .into();
17158
17159 drop(buffer);
17160
17161 // Position the selection in the rename editor so that it matches the current selection.
17162 this.show_local_selections = false;
17163 let rename_editor = cx.new(|cx| {
17164 let mut editor = Editor::single_line(window, cx);
17165 editor.buffer.update(cx, |buffer, cx| {
17166 buffer.edit([(0..0, old_name.clone())], None, cx)
17167 });
17168 let rename_selection_range = match cursor_offset_in_rename_range
17169 .cmp(&cursor_offset_in_rename_range_end)
17170 {
17171 Ordering::Equal => {
17172 editor.select_all(&SelectAll, window, cx);
17173 return editor;
17174 }
17175 Ordering::Less => {
17176 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17177 }
17178 Ordering::Greater => {
17179 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17180 }
17181 };
17182 if rename_selection_range.end > old_name.len() {
17183 editor.select_all(&SelectAll, window, cx);
17184 } else {
17185 editor.change_selections(Default::default(), window, cx, |s| {
17186 s.select_ranges([rename_selection_range]);
17187 });
17188 }
17189 editor
17190 });
17191 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17192 if e == &EditorEvent::Focused {
17193 cx.emit(EditorEvent::FocusedIn)
17194 }
17195 })
17196 .detach();
17197
17198 let write_highlights =
17199 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17200 let read_highlights =
17201 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17202 let ranges = write_highlights
17203 .iter()
17204 .flat_map(|(_, ranges)| ranges.iter())
17205 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17206 .cloned()
17207 .collect();
17208
17209 this.highlight_text::<Rename>(
17210 ranges,
17211 HighlightStyle {
17212 fade_out: Some(0.6),
17213 ..Default::default()
17214 },
17215 cx,
17216 );
17217 let rename_focus_handle = rename_editor.focus_handle(cx);
17218 window.focus(&rename_focus_handle);
17219 let block_id = this.insert_blocks(
17220 [BlockProperties {
17221 style: BlockStyle::Flex,
17222 placement: BlockPlacement::Below(range.start),
17223 height: Some(1),
17224 render: Arc::new({
17225 let rename_editor = rename_editor.clone();
17226 move |cx: &mut BlockContext| {
17227 let mut text_style = cx.editor_style.text.clone();
17228 if let Some(highlight_style) = old_highlight_id
17229 .and_then(|h| h.style(&cx.editor_style.syntax))
17230 {
17231 text_style = text_style.highlight(highlight_style);
17232 }
17233 div()
17234 .block_mouse_except_scroll()
17235 .pl(cx.anchor_x)
17236 .child(EditorElement::new(
17237 &rename_editor,
17238 EditorStyle {
17239 background: cx.theme().system().transparent,
17240 local_player: cx.editor_style.local_player,
17241 text: text_style,
17242 scrollbar_width: cx.editor_style.scrollbar_width,
17243 syntax: cx.editor_style.syntax.clone(),
17244 status: cx.editor_style.status.clone(),
17245 inlay_hints_style: HighlightStyle {
17246 font_weight: Some(FontWeight::BOLD),
17247 ..make_inlay_hints_style(cx.app)
17248 },
17249 edit_prediction_styles: make_suggestion_styles(
17250 cx.app,
17251 ),
17252 ..EditorStyle::default()
17253 },
17254 ))
17255 .into_any_element()
17256 }
17257 }),
17258 priority: 0,
17259 }],
17260 Some(Autoscroll::fit()),
17261 cx,
17262 )[0];
17263 this.pending_rename = Some(RenameState {
17264 range,
17265 old_name,
17266 editor: rename_editor,
17267 block_id,
17268 });
17269 })?;
17270 }
17271
17272 Ok(())
17273 }))
17274 }
17275
17276 pub fn confirm_rename(
17277 &mut self,
17278 _: &ConfirmRename,
17279 window: &mut Window,
17280 cx: &mut Context<Self>,
17281 ) -> Option<Task<Result<()>>> {
17282 let rename = self.take_rename(false, window, cx)?;
17283 let workspace = self.workspace()?.downgrade();
17284 let (buffer, start) = self
17285 .buffer
17286 .read(cx)
17287 .text_anchor_for_position(rename.range.start, cx)?;
17288 let (end_buffer, _) = self
17289 .buffer
17290 .read(cx)
17291 .text_anchor_for_position(rename.range.end, cx)?;
17292 if buffer != end_buffer {
17293 return None;
17294 }
17295
17296 let old_name = rename.old_name;
17297 let new_name = rename.editor.read(cx).text(cx);
17298
17299 let rename = self.semantics_provider.as_ref()?.perform_rename(
17300 &buffer,
17301 start,
17302 new_name.clone(),
17303 cx,
17304 )?;
17305
17306 Some(cx.spawn_in(window, async move |editor, cx| {
17307 let project_transaction = rename.await?;
17308 Self::open_project_transaction(
17309 &editor,
17310 workspace,
17311 project_transaction,
17312 format!("Rename: {} → {}", old_name, new_name),
17313 cx,
17314 )
17315 .await?;
17316
17317 editor.update(cx, |editor, cx| {
17318 editor.refresh_document_highlights(cx);
17319 })?;
17320 Ok(())
17321 }))
17322 }
17323
17324 fn take_rename(
17325 &mut self,
17326 moving_cursor: bool,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) -> Option<RenameState> {
17330 let rename = self.pending_rename.take()?;
17331 if rename.editor.focus_handle(cx).is_focused(window) {
17332 window.focus(&self.focus_handle);
17333 }
17334
17335 self.remove_blocks(
17336 [rename.block_id].into_iter().collect(),
17337 Some(Autoscroll::fit()),
17338 cx,
17339 );
17340 self.clear_highlights::<Rename>(cx);
17341 self.show_local_selections = true;
17342
17343 if moving_cursor {
17344 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17345 editor
17346 .selections
17347 .newest::<usize>(&editor.display_snapshot(cx))
17348 .head()
17349 });
17350
17351 // Update the selection to match the position of the selection inside
17352 // the rename editor.
17353 let snapshot = self.buffer.read(cx).read(cx);
17354 let rename_range = rename.range.to_offset(&snapshot);
17355 let cursor_in_editor = snapshot
17356 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17357 .min(rename_range.end);
17358 drop(snapshot);
17359
17360 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17361 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17362 });
17363 } else {
17364 self.refresh_document_highlights(cx);
17365 }
17366
17367 Some(rename)
17368 }
17369
17370 pub fn pending_rename(&self) -> Option<&RenameState> {
17371 self.pending_rename.as_ref()
17372 }
17373
17374 fn format(
17375 &mut self,
17376 _: &Format,
17377 window: &mut Window,
17378 cx: &mut Context<Self>,
17379 ) -> Option<Task<Result<()>>> {
17380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17381
17382 let project = match &self.project {
17383 Some(project) => project.clone(),
17384 None => return None,
17385 };
17386
17387 Some(self.perform_format(
17388 project,
17389 FormatTrigger::Manual,
17390 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17391 window,
17392 cx,
17393 ))
17394 }
17395
17396 fn format_selections(
17397 &mut self,
17398 _: &FormatSelections,
17399 window: &mut Window,
17400 cx: &mut Context<Self>,
17401 ) -> Option<Task<Result<()>>> {
17402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17403
17404 let project = match &self.project {
17405 Some(project) => project.clone(),
17406 None => return None,
17407 };
17408
17409 let ranges = self
17410 .selections
17411 .all_adjusted(&self.display_snapshot(cx))
17412 .into_iter()
17413 .map(|selection| selection.range())
17414 .collect_vec();
17415
17416 Some(self.perform_format(
17417 project,
17418 FormatTrigger::Manual,
17419 FormatTarget::Ranges(ranges),
17420 window,
17421 cx,
17422 ))
17423 }
17424
17425 fn perform_format(
17426 &mut self,
17427 project: Entity<Project>,
17428 trigger: FormatTrigger,
17429 target: FormatTarget,
17430 window: &mut Window,
17431 cx: &mut Context<Self>,
17432 ) -> Task<Result<()>> {
17433 let buffer = self.buffer.clone();
17434 let (buffers, target) = match target {
17435 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17436 FormatTarget::Ranges(selection_ranges) => {
17437 let multi_buffer = buffer.read(cx);
17438 let snapshot = multi_buffer.read(cx);
17439 let mut buffers = HashSet::default();
17440 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17441 BTreeMap::new();
17442 for selection_range in selection_ranges {
17443 for (buffer, buffer_range, _) in
17444 snapshot.range_to_buffer_ranges(selection_range)
17445 {
17446 let buffer_id = buffer.remote_id();
17447 let start = buffer.anchor_before(buffer_range.start);
17448 let end = buffer.anchor_after(buffer_range.end);
17449 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17450 buffer_id_to_ranges
17451 .entry(buffer_id)
17452 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17453 .or_insert_with(|| vec![start..end]);
17454 }
17455 }
17456 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17457 }
17458 };
17459
17460 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17461 let selections_prev = transaction_id_prev
17462 .and_then(|transaction_id_prev| {
17463 // default to selections as they were after the last edit, if we have them,
17464 // instead of how they are now.
17465 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17466 // will take you back to where you made the last edit, instead of staying where you scrolled
17467 self.selection_history
17468 .transaction(transaction_id_prev)
17469 .map(|t| t.0.clone())
17470 })
17471 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17472
17473 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17474 let format = project.update(cx, |project, cx| {
17475 project.format(buffers, target, true, trigger, cx)
17476 });
17477
17478 cx.spawn_in(window, async move |editor, cx| {
17479 let transaction = futures::select_biased! {
17480 transaction = format.log_err().fuse() => transaction,
17481 () = timeout => {
17482 log::warn!("timed out waiting for formatting");
17483 None
17484 }
17485 };
17486
17487 buffer
17488 .update(cx, |buffer, cx| {
17489 if let Some(transaction) = transaction
17490 && !buffer.is_singleton()
17491 {
17492 buffer.push_transaction(&transaction.0, cx);
17493 }
17494 cx.notify();
17495 })
17496 .ok();
17497
17498 if let Some(transaction_id_now) =
17499 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17500 {
17501 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17502 if has_new_transaction {
17503 _ = editor.update(cx, |editor, _| {
17504 editor
17505 .selection_history
17506 .insert_transaction(transaction_id_now, selections_prev);
17507 });
17508 }
17509 }
17510
17511 Ok(())
17512 })
17513 }
17514
17515 fn organize_imports(
17516 &mut self,
17517 _: &OrganizeImports,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) -> Option<Task<Result<()>>> {
17521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17522 let project = match &self.project {
17523 Some(project) => project.clone(),
17524 None => return None,
17525 };
17526 Some(self.perform_code_action_kind(
17527 project,
17528 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17529 window,
17530 cx,
17531 ))
17532 }
17533
17534 fn perform_code_action_kind(
17535 &mut self,
17536 project: Entity<Project>,
17537 kind: CodeActionKind,
17538 window: &mut Window,
17539 cx: &mut Context<Self>,
17540 ) -> Task<Result<()>> {
17541 let buffer = self.buffer.clone();
17542 let buffers = buffer.read(cx).all_buffers();
17543 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17544 let apply_action = project.update(cx, |project, cx| {
17545 project.apply_code_action_kind(buffers, kind, true, cx)
17546 });
17547 cx.spawn_in(window, async move |_, cx| {
17548 let transaction = futures::select_biased! {
17549 () = timeout => {
17550 log::warn!("timed out waiting for executing code action");
17551 None
17552 }
17553 transaction = apply_action.log_err().fuse() => transaction,
17554 };
17555 buffer
17556 .update(cx, |buffer, cx| {
17557 // check if we need this
17558 if let Some(transaction) = transaction
17559 && !buffer.is_singleton()
17560 {
17561 buffer.push_transaction(&transaction.0, cx);
17562 }
17563 cx.notify();
17564 })
17565 .ok();
17566 Ok(())
17567 })
17568 }
17569
17570 pub fn restart_language_server(
17571 &mut self,
17572 _: &RestartLanguageServer,
17573 _: &mut Window,
17574 cx: &mut Context<Self>,
17575 ) {
17576 if let Some(project) = self.project.clone() {
17577 self.buffer.update(cx, |multi_buffer, cx| {
17578 project.update(cx, |project, cx| {
17579 project.restart_language_servers_for_buffers(
17580 multi_buffer.all_buffers().into_iter().collect(),
17581 HashSet::default(),
17582 cx,
17583 );
17584 });
17585 })
17586 }
17587 }
17588
17589 pub fn stop_language_server(
17590 &mut self,
17591 _: &StopLanguageServer,
17592 _: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) {
17595 if let Some(project) = self.project.clone() {
17596 self.buffer.update(cx, |multi_buffer, cx| {
17597 project.update(cx, |project, cx| {
17598 project.stop_language_servers_for_buffers(
17599 multi_buffer.all_buffers().into_iter().collect(),
17600 HashSet::default(),
17601 cx,
17602 );
17603 });
17604 });
17605 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17606 }
17607 }
17608
17609 fn cancel_language_server_work(
17610 workspace: &mut Workspace,
17611 _: &actions::CancelLanguageServerWork,
17612 _: &mut Window,
17613 cx: &mut Context<Workspace>,
17614 ) {
17615 let project = workspace.project();
17616 let buffers = workspace
17617 .active_item(cx)
17618 .and_then(|item| item.act_as::<Editor>(cx))
17619 .map_or(HashSet::default(), |editor| {
17620 editor.read(cx).buffer.read(cx).all_buffers()
17621 });
17622 project.update(cx, |project, cx| {
17623 project.cancel_language_server_work_for_buffers(buffers, cx);
17624 });
17625 }
17626
17627 fn show_character_palette(
17628 &mut self,
17629 _: &ShowCharacterPalette,
17630 window: &mut Window,
17631 _: &mut Context<Self>,
17632 ) {
17633 window.show_character_palette();
17634 }
17635
17636 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17637 if !self.diagnostics_enabled() {
17638 return;
17639 }
17640
17641 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17642 let buffer = self.buffer.read(cx).snapshot(cx);
17643 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17644 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17645 let is_valid = buffer
17646 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17647 .any(|entry| {
17648 entry.diagnostic.is_primary
17649 && !entry.range.is_empty()
17650 && entry.range.start == primary_range_start
17651 && entry.diagnostic.message == active_diagnostics.active_message
17652 });
17653
17654 if !is_valid {
17655 self.dismiss_diagnostics(cx);
17656 }
17657 }
17658 }
17659
17660 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17661 match &self.active_diagnostics {
17662 ActiveDiagnostic::Group(group) => Some(group),
17663 _ => None,
17664 }
17665 }
17666
17667 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17668 if !self.diagnostics_enabled() {
17669 return;
17670 }
17671 self.dismiss_diagnostics(cx);
17672 self.active_diagnostics = ActiveDiagnostic::All;
17673 }
17674
17675 fn activate_diagnostics(
17676 &mut self,
17677 buffer_id: BufferId,
17678 diagnostic: DiagnosticEntryRef<'_, usize>,
17679 window: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) {
17682 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17683 return;
17684 }
17685 self.dismiss_diagnostics(cx);
17686 let snapshot = self.snapshot(window, cx);
17687 let buffer = self.buffer.read(cx).snapshot(cx);
17688 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17689 return;
17690 };
17691
17692 let diagnostic_group = buffer
17693 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17694 .collect::<Vec<_>>();
17695
17696 let blocks =
17697 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17698
17699 let blocks = self.display_map.update(cx, |display_map, cx| {
17700 display_map.insert_blocks(blocks, cx).into_iter().collect()
17701 });
17702 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17703 active_range: buffer.anchor_before(diagnostic.range.start)
17704 ..buffer.anchor_after(diagnostic.range.end),
17705 active_message: diagnostic.diagnostic.message.clone(),
17706 group_id: diagnostic.diagnostic.group_id,
17707 blocks,
17708 });
17709 cx.notify();
17710 }
17711
17712 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17713 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17714 return;
17715 };
17716
17717 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17718 if let ActiveDiagnostic::Group(group) = prev {
17719 self.display_map.update(cx, |display_map, cx| {
17720 display_map.remove_blocks(group.blocks, cx);
17721 });
17722 cx.notify();
17723 }
17724 }
17725
17726 /// Disable inline diagnostics rendering for this editor.
17727 pub fn disable_inline_diagnostics(&mut self) {
17728 self.inline_diagnostics_enabled = false;
17729 self.inline_diagnostics_update = Task::ready(());
17730 self.inline_diagnostics.clear();
17731 }
17732
17733 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17734 self.diagnostics_enabled = false;
17735 self.dismiss_diagnostics(cx);
17736 self.inline_diagnostics_update = Task::ready(());
17737 self.inline_diagnostics.clear();
17738 }
17739
17740 pub fn disable_word_completions(&mut self) {
17741 self.word_completions_enabled = false;
17742 }
17743
17744 pub fn diagnostics_enabled(&self) -> bool {
17745 self.diagnostics_enabled && self.mode.is_full()
17746 }
17747
17748 pub fn inline_diagnostics_enabled(&self) -> bool {
17749 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17750 }
17751
17752 pub fn show_inline_diagnostics(&self) -> bool {
17753 self.show_inline_diagnostics
17754 }
17755
17756 pub fn toggle_inline_diagnostics(
17757 &mut self,
17758 _: &ToggleInlineDiagnostics,
17759 window: &mut Window,
17760 cx: &mut Context<Editor>,
17761 ) {
17762 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17763 self.refresh_inline_diagnostics(false, window, cx);
17764 }
17765
17766 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17767 self.diagnostics_max_severity = severity;
17768 self.display_map.update(cx, |display_map, _| {
17769 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17770 });
17771 }
17772
17773 pub fn toggle_diagnostics(
17774 &mut self,
17775 _: &ToggleDiagnostics,
17776 window: &mut Window,
17777 cx: &mut Context<Editor>,
17778 ) {
17779 if !self.diagnostics_enabled() {
17780 return;
17781 }
17782
17783 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17784 EditorSettings::get_global(cx)
17785 .diagnostics_max_severity
17786 .filter(|severity| severity != &DiagnosticSeverity::Off)
17787 .unwrap_or(DiagnosticSeverity::Hint)
17788 } else {
17789 DiagnosticSeverity::Off
17790 };
17791 self.set_max_diagnostics_severity(new_severity, cx);
17792 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17793 self.active_diagnostics = ActiveDiagnostic::None;
17794 self.inline_diagnostics_update = Task::ready(());
17795 self.inline_diagnostics.clear();
17796 } else {
17797 self.refresh_inline_diagnostics(false, window, cx);
17798 }
17799
17800 cx.notify();
17801 }
17802
17803 pub fn toggle_minimap(
17804 &mut self,
17805 _: &ToggleMinimap,
17806 window: &mut Window,
17807 cx: &mut Context<Editor>,
17808 ) {
17809 if self.supports_minimap(cx) {
17810 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17811 }
17812 }
17813
17814 fn refresh_inline_diagnostics(
17815 &mut self,
17816 debounce: bool,
17817 window: &mut Window,
17818 cx: &mut Context<Self>,
17819 ) {
17820 let max_severity = ProjectSettings::get_global(cx)
17821 .diagnostics
17822 .inline
17823 .max_severity
17824 .unwrap_or(self.diagnostics_max_severity);
17825
17826 if !self.inline_diagnostics_enabled()
17827 || !self.diagnostics_enabled()
17828 || !self.show_inline_diagnostics
17829 || max_severity == DiagnosticSeverity::Off
17830 {
17831 self.inline_diagnostics_update = Task::ready(());
17832 self.inline_diagnostics.clear();
17833 return;
17834 }
17835
17836 let debounce_ms = ProjectSettings::get_global(cx)
17837 .diagnostics
17838 .inline
17839 .update_debounce_ms;
17840 let debounce = if debounce && debounce_ms > 0 {
17841 Some(Duration::from_millis(debounce_ms))
17842 } else {
17843 None
17844 };
17845 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17846 if let Some(debounce) = debounce {
17847 cx.background_executor().timer(debounce).await;
17848 }
17849 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17850 editor
17851 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17852 .ok()
17853 }) else {
17854 return;
17855 };
17856
17857 let new_inline_diagnostics = cx
17858 .background_spawn(async move {
17859 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17860 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17861 let message = diagnostic_entry
17862 .diagnostic
17863 .message
17864 .split_once('\n')
17865 .map(|(line, _)| line)
17866 .map(SharedString::new)
17867 .unwrap_or_else(|| {
17868 SharedString::new(&*diagnostic_entry.diagnostic.message)
17869 });
17870 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17871 let (Ok(i) | Err(i)) = inline_diagnostics
17872 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17873 inline_diagnostics.insert(
17874 i,
17875 (
17876 start_anchor,
17877 InlineDiagnostic {
17878 message,
17879 group_id: diagnostic_entry.diagnostic.group_id,
17880 start: diagnostic_entry.range.start.to_point(&snapshot),
17881 is_primary: diagnostic_entry.diagnostic.is_primary,
17882 severity: diagnostic_entry.diagnostic.severity,
17883 },
17884 ),
17885 );
17886 }
17887 inline_diagnostics
17888 })
17889 .await;
17890
17891 editor
17892 .update(cx, |editor, cx| {
17893 editor.inline_diagnostics = new_inline_diagnostics;
17894 cx.notify();
17895 })
17896 .ok();
17897 });
17898 }
17899
17900 fn pull_diagnostics(
17901 &mut self,
17902 buffer_id: Option<BufferId>,
17903 window: &Window,
17904 cx: &mut Context<Self>,
17905 ) -> Option<()> {
17906 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17907 return None;
17908 }
17909 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17910 .diagnostics
17911 .lsp_pull_diagnostics;
17912 if !pull_diagnostics_settings.enabled {
17913 return None;
17914 }
17915 let project = self.project()?.downgrade();
17916 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17917 let mut buffers = self.buffer.read(cx).all_buffers();
17918 buffers.retain(|buffer| {
17919 let buffer_id_to_retain = buffer.read(cx).remote_id();
17920 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17921 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17922 });
17923 if buffers.is_empty() {
17924 self.pull_diagnostics_task = Task::ready(());
17925 return None;
17926 }
17927
17928 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17929 cx.background_executor().timer(debounce).await;
17930
17931 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17932 buffers
17933 .into_iter()
17934 .filter_map(|buffer| {
17935 project
17936 .update(cx, |project, cx| {
17937 project.lsp_store().update(cx, |lsp_store, cx| {
17938 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17939 })
17940 })
17941 .ok()
17942 })
17943 .collect::<FuturesUnordered<_>>()
17944 }) else {
17945 return;
17946 };
17947
17948 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17949 match pull_task {
17950 Ok(()) => {
17951 if editor
17952 .update_in(cx, |editor, window, cx| {
17953 editor.update_diagnostics_state(window, cx);
17954 })
17955 .is_err()
17956 {
17957 return;
17958 }
17959 }
17960 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17961 }
17962 }
17963 });
17964
17965 Some(())
17966 }
17967
17968 pub fn set_selections_from_remote(
17969 &mut self,
17970 selections: Vec<Selection<Anchor>>,
17971 pending_selection: Option<Selection<Anchor>>,
17972 window: &mut Window,
17973 cx: &mut Context<Self>,
17974 ) {
17975 let old_cursor_position = self.selections.newest_anchor().head();
17976 self.selections.change_with(cx, |s| {
17977 s.select_anchors(selections);
17978 if let Some(pending_selection) = pending_selection {
17979 s.set_pending(pending_selection, SelectMode::Character);
17980 } else {
17981 s.clear_pending();
17982 }
17983 });
17984 self.selections_did_change(
17985 false,
17986 &old_cursor_position,
17987 SelectionEffects::default(),
17988 window,
17989 cx,
17990 );
17991 }
17992
17993 pub fn transact(
17994 &mut self,
17995 window: &mut Window,
17996 cx: &mut Context<Self>,
17997 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17998 ) -> Option<TransactionId> {
17999 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18000 this.start_transaction_at(Instant::now(), window, cx);
18001 update(this, window, cx);
18002 this.end_transaction_at(Instant::now(), cx)
18003 })
18004 }
18005
18006 pub fn start_transaction_at(
18007 &mut self,
18008 now: Instant,
18009 window: &mut Window,
18010 cx: &mut Context<Self>,
18011 ) -> Option<TransactionId> {
18012 self.end_selection(window, cx);
18013 if let Some(tx_id) = self
18014 .buffer
18015 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18016 {
18017 self.selection_history
18018 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18019 cx.emit(EditorEvent::TransactionBegun {
18020 transaction_id: tx_id,
18021 });
18022 Some(tx_id)
18023 } else {
18024 None
18025 }
18026 }
18027
18028 pub fn end_transaction_at(
18029 &mut self,
18030 now: Instant,
18031 cx: &mut Context<Self>,
18032 ) -> Option<TransactionId> {
18033 if let Some(transaction_id) = self
18034 .buffer
18035 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18036 {
18037 if let Some((_, end_selections)) =
18038 self.selection_history.transaction_mut(transaction_id)
18039 {
18040 *end_selections = Some(self.selections.disjoint_anchors_arc());
18041 } else {
18042 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18043 }
18044
18045 cx.emit(EditorEvent::Edited { transaction_id });
18046 Some(transaction_id)
18047 } else {
18048 None
18049 }
18050 }
18051
18052 pub fn modify_transaction_selection_history(
18053 &mut self,
18054 transaction_id: TransactionId,
18055 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18056 ) -> bool {
18057 self.selection_history
18058 .transaction_mut(transaction_id)
18059 .map(modify)
18060 .is_some()
18061 }
18062
18063 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18064 if self.selection_mark_mode {
18065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18066 s.move_with(|_, sel| {
18067 sel.collapse_to(sel.head(), SelectionGoal::None);
18068 });
18069 })
18070 }
18071 self.selection_mark_mode = true;
18072 cx.notify();
18073 }
18074
18075 pub fn swap_selection_ends(
18076 &mut self,
18077 _: &actions::SwapSelectionEnds,
18078 window: &mut Window,
18079 cx: &mut Context<Self>,
18080 ) {
18081 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18082 s.move_with(|_, sel| {
18083 if sel.start != sel.end {
18084 sel.reversed = !sel.reversed
18085 }
18086 });
18087 });
18088 self.request_autoscroll(Autoscroll::newest(), cx);
18089 cx.notify();
18090 }
18091
18092 pub fn toggle_focus(
18093 workspace: &mut Workspace,
18094 _: &actions::ToggleFocus,
18095 window: &mut Window,
18096 cx: &mut Context<Workspace>,
18097 ) {
18098 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18099 return;
18100 };
18101 workspace.activate_item(&item, true, true, window, cx);
18102 }
18103
18104 pub fn toggle_fold(
18105 &mut self,
18106 _: &actions::ToggleFold,
18107 window: &mut Window,
18108 cx: &mut Context<Self>,
18109 ) {
18110 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18111 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18112 let selection = self.selections.newest::<Point>(&display_map);
18113
18114 let range = if selection.is_empty() {
18115 let point = selection.head().to_display_point(&display_map);
18116 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18117 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18118 .to_point(&display_map);
18119 start..end
18120 } else {
18121 selection.range()
18122 };
18123 if display_map.folds_in_range(range).next().is_some() {
18124 self.unfold_lines(&Default::default(), window, cx)
18125 } else {
18126 self.fold(&Default::default(), window, cx)
18127 }
18128 } else {
18129 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18130 let buffer_ids: HashSet<_> = self
18131 .selections
18132 .disjoint_anchor_ranges()
18133 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18134 .collect();
18135
18136 let should_unfold = buffer_ids
18137 .iter()
18138 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18139
18140 for buffer_id in buffer_ids {
18141 if should_unfold {
18142 self.unfold_buffer(buffer_id, cx);
18143 } else {
18144 self.fold_buffer(buffer_id, cx);
18145 }
18146 }
18147 }
18148 }
18149
18150 pub fn toggle_fold_recursive(
18151 &mut self,
18152 _: &actions::ToggleFoldRecursive,
18153 window: &mut Window,
18154 cx: &mut Context<Self>,
18155 ) {
18156 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18157
18158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18159 let range = if selection.is_empty() {
18160 let point = selection.head().to_display_point(&display_map);
18161 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18162 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18163 .to_point(&display_map);
18164 start..end
18165 } else {
18166 selection.range()
18167 };
18168 if display_map.folds_in_range(range).next().is_some() {
18169 self.unfold_recursive(&Default::default(), window, cx)
18170 } else {
18171 self.fold_recursive(&Default::default(), window, cx)
18172 }
18173 }
18174
18175 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18176 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18177 let mut to_fold = Vec::new();
18178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18179 let selections = self.selections.all_adjusted(&display_map);
18180
18181 for selection in selections {
18182 let range = selection.range().sorted();
18183 let buffer_start_row = range.start.row;
18184
18185 if range.start.row != range.end.row {
18186 let mut found = false;
18187 let mut row = range.start.row;
18188 while row <= range.end.row {
18189 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18190 {
18191 found = true;
18192 row = crease.range().end.row + 1;
18193 to_fold.push(crease);
18194 } else {
18195 row += 1
18196 }
18197 }
18198 if found {
18199 continue;
18200 }
18201 }
18202
18203 for row in (0..=range.start.row).rev() {
18204 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18205 && crease.range().end.row >= buffer_start_row
18206 {
18207 to_fold.push(crease);
18208 if row <= range.start.row {
18209 break;
18210 }
18211 }
18212 }
18213 }
18214
18215 self.fold_creases(to_fold, true, window, cx);
18216 } else {
18217 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18218 let buffer_ids = self
18219 .selections
18220 .disjoint_anchor_ranges()
18221 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18222 .collect::<HashSet<_>>();
18223 for buffer_id in buffer_ids {
18224 self.fold_buffer(buffer_id, cx);
18225 }
18226 }
18227 }
18228
18229 pub fn toggle_fold_all(
18230 &mut self,
18231 _: &actions::ToggleFoldAll,
18232 window: &mut Window,
18233 cx: &mut Context<Self>,
18234 ) {
18235 if self.buffer.read(cx).is_singleton() {
18236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18237 let has_folds = display_map
18238 .folds_in_range(0..display_map.buffer_snapshot().len())
18239 .next()
18240 .is_some();
18241
18242 if has_folds {
18243 self.unfold_all(&actions::UnfoldAll, window, cx);
18244 } else {
18245 self.fold_all(&actions::FoldAll, window, cx);
18246 }
18247 } else {
18248 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18249 let should_unfold = buffer_ids
18250 .iter()
18251 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18252
18253 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18254 editor
18255 .update_in(cx, |editor, _, cx| {
18256 for buffer_id in buffer_ids {
18257 if should_unfold {
18258 editor.unfold_buffer(buffer_id, cx);
18259 } else {
18260 editor.fold_buffer(buffer_id, cx);
18261 }
18262 }
18263 })
18264 .ok();
18265 });
18266 }
18267 }
18268
18269 fn fold_at_level(
18270 &mut self,
18271 fold_at: &FoldAtLevel,
18272 window: &mut Window,
18273 cx: &mut Context<Self>,
18274 ) {
18275 if !self.buffer.read(cx).is_singleton() {
18276 return;
18277 }
18278
18279 let fold_at_level = fold_at.0;
18280 let snapshot = self.buffer.read(cx).snapshot(cx);
18281 let mut to_fold = Vec::new();
18282 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18283
18284 let row_ranges_to_keep: Vec<Range<u32>> = self
18285 .selections
18286 .all::<Point>(&self.display_snapshot(cx))
18287 .into_iter()
18288 .map(|sel| sel.start.row..sel.end.row)
18289 .collect();
18290
18291 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18292 while start_row < end_row {
18293 match self
18294 .snapshot(window, cx)
18295 .crease_for_buffer_row(MultiBufferRow(start_row))
18296 {
18297 Some(crease) => {
18298 let nested_start_row = crease.range().start.row + 1;
18299 let nested_end_row = crease.range().end.row;
18300
18301 if current_level < fold_at_level {
18302 stack.push((nested_start_row, nested_end_row, current_level + 1));
18303 } else if current_level == fold_at_level {
18304 // Fold iff there is no selection completely contained within the fold region
18305 if !row_ranges_to_keep.iter().any(|selection| {
18306 selection.end >= nested_start_row
18307 && selection.start <= nested_end_row
18308 }) {
18309 to_fold.push(crease);
18310 }
18311 }
18312
18313 start_row = nested_end_row + 1;
18314 }
18315 None => start_row += 1,
18316 }
18317 }
18318 }
18319
18320 self.fold_creases(to_fold, true, window, cx);
18321 }
18322
18323 pub fn fold_at_level_1(
18324 &mut self,
18325 _: &actions::FoldAtLevel1,
18326 window: &mut Window,
18327 cx: &mut Context<Self>,
18328 ) {
18329 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18330 }
18331
18332 pub fn fold_at_level_2(
18333 &mut self,
18334 _: &actions::FoldAtLevel2,
18335 window: &mut Window,
18336 cx: &mut Context<Self>,
18337 ) {
18338 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18339 }
18340
18341 pub fn fold_at_level_3(
18342 &mut self,
18343 _: &actions::FoldAtLevel3,
18344 window: &mut Window,
18345 cx: &mut Context<Self>,
18346 ) {
18347 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18348 }
18349
18350 pub fn fold_at_level_4(
18351 &mut self,
18352 _: &actions::FoldAtLevel4,
18353 window: &mut Window,
18354 cx: &mut Context<Self>,
18355 ) {
18356 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18357 }
18358
18359 pub fn fold_at_level_5(
18360 &mut self,
18361 _: &actions::FoldAtLevel5,
18362 window: &mut Window,
18363 cx: &mut Context<Self>,
18364 ) {
18365 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18366 }
18367
18368 pub fn fold_at_level_6(
18369 &mut self,
18370 _: &actions::FoldAtLevel6,
18371 window: &mut Window,
18372 cx: &mut Context<Self>,
18373 ) {
18374 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18375 }
18376
18377 pub fn fold_at_level_7(
18378 &mut self,
18379 _: &actions::FoldAtLevel7,
18380 window: &mut Window,
18381 cx: &mut Context<Self>,
18382 ) {
18383 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18384 }
18385
18386 pub fn fold_at_level_8(
18387 &mut self,
18388 _: &actions::FoldAtLevel8,
18389 window: &mut Window,
18390 cx: &mut Context<Self>,
18391 ) {
18392 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18393 }
18394
18395 pub fn fold_at_level_9(
18396 &mut self,
18397 _: &actions::FoldAtLevel9,
18398 window: &mut Window,
18399 cx: &mut Context<Self>,
18400 ) {
18401 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18402 }
18403
18404 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18405 if self.buffer.read(cx).is_singleton() {
18406 let mut fold_ranges = Vec::new();
18407 let snapshot = self.buffer.read(cx).snapshot(cx);
18408
18409 for row in 0..snapshot.max_row().0 {
18410 if let Some(foldable_range) = self
18411 .snapshot(window, cx)
18412 .crease_for_buffer_row(MultiBufferRow(row))
18413 {
18414 fold_ranges.push(foldable_range);
18415 }
18416 }
18417
18418 self.fold_creases(fold_ranges, true, window, cx);
18419 } else {
18420 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18421 editor
18422 .update_in(cx, |editor, _, cx| {
18423 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18424 editor.fold_buffer(buffer_id, cx);
18425 }
18426 })
18427 .ok();
18428 });
18429 }
18430 }
18431
18432 pub fn fold_function_bodies(
18433 &mut self,
18434 _: &actions::FoldFunctionBodies,
18435 window: &mut Window,
18436 cx: &mut Context<Self>,
18437 ) {
18438 let snapshot = self.buffer.read(cx).snapshot(cx);
18439
18440 let ranges = snapshot
18441 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18442 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18443 .collect::<Vec<_>>();
18444
18445 let creases = ranges
18446 .into_iter()
18447 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18448 .collect();
18449
18450 self.fold_creases(creases, true, window, cx);
18451 }
18452
18453 pub fn fold_recursive(
18454 &mut self,
18455 _: &actions::FoldRecursive,
18456 window: &mut Window,
18457 cx: &mut Context<Self>,
18458 ) {
18459 let mut to_fold = Vec::new();
18460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18461 let selections = self.selections.all_adjusted(&display_map);
18462
18463 for selection in selections {
18464 let range = selection.range().sorted();
18465 let buffer_start_row = range.start.row;
18466
18467 if range.start.row != range.end.row {
18468 let mut found = false;
18469 for row in range.start.row..=range.end.row {
18470 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18471 found = true;
18472 to_fold.push(crease);
18473 }
18474 }
18475 if found {
18476 continue;
18477 }
18478 }
18479
18480 for row in (0..=range.start.row).rev() {
18481 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18482 if crease.range().end.row >= buffer_start_row {
18483 to_fold.push(crease);
18484 } else {
18485 break;
18486 }
18487 }
18488 }
18489 }
18490
18491 self.fold_creases(to_fold, true, window, cx);
18492 }
18493
18494 pub fn fold_at(
18495 &mut self,
18496 buffer_row: MultiBufferRow,
18497 window: &mut Window,
18498 cx: &mut Context<Self>,
18499 ) {
18500 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18501
18502 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18503 let autoscroll = self
18504 .selections
18505 .all::<Point>(&display_map)
18506 .iter()
18507 .any(|selection| crease.range().overlaps(&selection.range()));
18508
18509 self.fold_creases(vec![crease], autoscroll, window, cx);
18510 }
18511 }
18512
18513 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18514 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18516 let buffer = display_map.buffer_snapshot();
18517 let selections = self.selections.all::<Point>(&display_map);
18518 let ranges = selections
18519 .iter()
18520 .map(|s| {
18521 let range = s.display_range(&display_map).sorted();
18522 let mut start = range.start.to_point(&display_map);
18523 let mut end = range.end.to_point(&display_map);
18524 start.column = 0;
18525 end.column = buffer.line_len(MultiBufferRow(end.row));
18526 start..end
18527 })
18528 .collect::<Vec<_>>();
18529
18530 self.unfold_ranges(&ranges, true, true, cx);
18531 } else {
18532 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18533 let buffer_ids = self
18534 .selections
18535 .disjoint_anchor_ranges()
18536 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18537 .collect::<HashSet<_>>();
18538 for buffer_id in buffer_ids {
18539 self.unfold_buffer(buffer_id, cx);
18540 }
18541 }
18542 }
18543
18544 pub fn unfold_recursive(
18545 &mut self,
18546 _: &UnfoldRecursive,
18547 _window: &mut Window,
18548 cx: &mut Context<Self>,
18549 ) {
18550 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18551 let selections = self.selections.all::<Point>(&display_map);
18552 let ranges = selections
18553 .iter()
18554 .map(|s| {
18555 let mut range = s.display_range(&display_map).sorted();
18556 *range.start.column_mut() = 0;
18557 *range.end.column_mut() = display_map.line_len(range.end.row());
18558 let start = range.start.to_point(&display_map);
18559 let end = range.end.to_point(&display_map);
18560 start..end
18561 })
18562 .collect::<Vec<_>>();
18563
18564 self.unfold_ranges(&ranges, true, true, cx);
18565 }
18566
18567 pub fn unfold_at(
18568 &mut self,
18569 buffer_row: MultiBufferRow,
18570 _window: &mut Window,
18571 cx: &mut Context<Self>,
18572 ) {
18573 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18574
18575 let intersection_range = Point::new(buffer_row.0, 0)
18576 ..Point::new(
18577 buffer_row.0,
18578 display_map.buffer_snapshot().line_len(buffer_row),
18579 );
18580
18581 let autoscroll = self
18582 .selections
18583 .all::<Point>(&display_map)
18584 .iter()
18585 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18586
18587 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18588 }
18589
18590 pub fn unfold_all(
18591 &mut self,
18592 _: &actions::UnfoldAll,
18593 _window: &mut Window,
18594 cx: &mut Context<Self>,
18595 ) {
18596 if self.buffer.read(cx).is_singleton() {
18597 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18598 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18599 } else {
18600 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18601 editor
18602 .update(cx, |editor, cx| {
18603 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18604 editor.unfold_buffer(buffer_id, cx);
18605 }
18606 })
18607 .ok();
18608 });
18609 }
18610 }
18611
18612 pub fn fold_selected_ranges(
18613 &mut self,
18614 _: &FoldSelectedRanges,
18615 window: &mut Window,
18616 cx: &mut Context<Self>,
18617 ) {
18618 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18619 let selections = self.selections.all_adjusted(&display_map);
18620 let ranges = selections
18621 .into_iter()
18622 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18623 .collect::<Vec<_>>();
18624 self.fold_creases(ranges, true, window, cx);
18625 }
18626
18627 pub fn fold_ranges<T: ToOffset + Clone>(
18628 &mut self,
18629 ranges: Vec<Range<T>>,
18630 auto_scroll: bool,
18631 window: &mut Window,
18632 cx: &mut Context<Self>,
18633 ) {
18634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18635 let ranges = ranges
18636 .into_iter()
18637 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18638 .collect::<Vec<_>>();
18639 self.fold_creases(ranges, auto_scroll, window, cx);
18640 }
18641
18642 pub fn fold_creases<T: ToOffset + Clone>(
18643 &mut self,
18644 creases: Vec<Crease<T>>,
18645 auto_scroll: bool,
18646 _window: &mut Window,
18647 cx: &mut Context<Self>,
18648 ) {
18649 if creases.is_empty() {
18650 return;
18651 }
18652
18653 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18654
18655 if auto_scroll {
18656 self.request_autoscroll(Autoscroll::fit(), cx);
18657 }
18658
18659 cx.notify();
18660
18661 self.scrollbar_marker_state.dirty = true;
18662 self.folds_did_change(cx);
18663 }
18664
18665 /// Removes any folds whose ranges intersect any of the given ranges.
18666 pub fn unfold_ranges<T: ToOffset + Clone>(
18667 &mut self,
18668 ranges: &[Range<T>],
18669 inclusive: bool,
18670 auto_scroll: bool,
18671 cx: &mut Context<Self>,
18672 ) {
18673 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18674 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18675 });
18676 self.folds_did_change(cx);
18677 }
18678
18679 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18680 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18681 return;
18682 }
18683 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18684 self.display_map.update(cx, |display_map, cx| {
18685 display_map.fold_buffers([buffer_id], cx)
18686 });
18687 cx.emit(EditorEvent::BufferFoldToggled {
18688 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18689 folded: true,
18690 });
18691 cx.notify();
18692 }
18693
18694 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18695 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18696 return;
18697 }
18698 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18699 self.display_map.update(cx, |display_map, cx| {
18700 display_map.unfold_buffers([buffer_id], cx);
18701 });
18702 cx.emit(EditorEvent::BufferFoldToggled {
18703 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18704 folded: false,
18705 });
18706 cx.notify();
18707 }
18708
18709 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18710 self.display_map.read(cx).is_buffer_folded(buffer)
18711 }
18712
18713 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18714 self.display_map.read(cx).folded_buffers()
18715 }
18716
18717 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18718 self.display_map.update(cx, |display_map, cx| {
18719 display_map.disable_header_for_buffer(buffer_id, cx);
18720 });
18721 cx.notify();
18722 }
18723
18724 /// Removes any folds with the given ranges.
18725 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18726 &mut self,
18727 ranges: &[Range<T>],
18728 type_id: TypeId,
18729 auto_scroll: bool,
18730 cx: &mut Context<Self>,
18731 ) {
18732 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18733 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18734 });
18735 self.folds_did_change(cx);
18736 }
18737
18738 fn remove_folds_with<T: ToOffset + Clone>(
18739 &mut self,
18740 ranges: &[Range<T>],
18741 auto_scroll: bool,
18742 cx: &mut Context<Self>,
18743 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18744 ) {
18745 if ranges.is_empty() {
18746 return;
18747 }
18748
18749 let mut buffers_affected = HashSet::default();
18750 let multi_buffer = self.buffer().read(cx);
18751 for range in ranges {
18752 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18753 buffers_affected.insert(buffer.read(cx).remote_id());
18754 };
18755 }
18756
18757 self.display_map.update(cx, update);
18758
18759 if auto_scroll {
18760 self.request_autoscroll(Autoscroll::fit(), cx);
18761 }
18762
18763 cx.notify();
18764 self.scrollbar_marker_state.dirty = true;
18765 self.active_indent_guides_state.dirty = true;
18766 }
18767
18768 pub fn update_renderer_widths(
18769 &mut self,
18770 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18771 cx: &mut Context<Self>,
18772 ) -> bool {
18773 self.display_map
18774 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18775 }
18776
18777 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18778 self.display_map.read(cx).fold_placeholder.clone()
18779 }
18780
18781 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18782 self.buffer.update(cx, |buffer, cx| {
18783 buffer.set_all_diff_hunks_expanded(cx);
18784 });
18785 }
18786
18787 pub fn expand_all_diff_hunks(
18788 &mut self,
18789 _: &ExpandAllDiffHunks,
18790 _window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 self.buffer.update(cx, |buffer, cx| {
18794 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18795 });
18796 }
18797
18798 pub fn collapse_all_diff_hunks(
18799 &mut self,
18800 _: &CollapseAllDiffHunks,
18801 _window: &mut Window,
18802 cx: &mut Context<Self>,
18803 ) {
18804 self.buffer.update(cx, |buffer, cx| {
18805 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18806 });
18807 }
18808
18809 pub fn toggle_selected_diff_hunks(
18810 &mut self,
18811 _: &ToggleSelectedDiffHunks,
18812 _window: &mut Window,
18813 cx: &mut Context<Self>,
18814 ) {
18815 let ranges: Vec<_> = self
18816 .selections
18817 .disjoint_anchors()
18818 .iter()
18819 .map(|s| s.range())
18820 .collect();
18821 self.toggle_diff_hunks_in_ranges(ranges, cx);
18822 }
18823
18824 pub fn diff_hunks_in_ranges<'a>(
18825 &'a self,
18826 ranges: &'a [Range<Anchor>],
18827 buffer: &'a MultiBufferSnapshot,
18828 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18829 ranges.iter().flat_map(move |range| {
18830 let end_excerpt_id = range.end.excerpt_id;
18831 let range = range.to_point(buffer);
18832 let mut peek_end = range.end;
18833 if range.end.row < buffer.max_row().0 {
18834 peek_end = Point::new(range.end.row + 1, 0);
18835 }
18836 buffer
18837 .diff_hunks_in_range(range.start..peek_end)
18838 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18839 })
18840 }
18841
18842 pub fn has_stageable_diff_hunks_in_ranges(
18843 &self,
18844 ranges: &[Range<Anchor>],
18845 snapshot: &MultiBufferSnapshot,
18846 ) -> bool {
18847 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18848 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18849 }
18850
18851 pub fn toggle_staged_selected_diff_hunks(
18852 &mut self,
18853 _: &::git::ToggleStaged,
18854 _: &mut Window,
18855 cx: &mut Context<Self>,
18856 ) {
18857 let snapshot = self.buffer.read(cx).snapshot(cx);
18858 let ranges: Vec<_> = self
18859 .selections
18860 .disjoint_anchors()
18861 .iter()
18862 .map(|s| s.range())
18863 .collect();
18864 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18865 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18866 }
18867
18868 pub fn set_render_diff_hunk_controls(
18869 &mut self,
18870 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18871 cx: &mut Context<Self>,
18872 ) {
18873 self.render_diff_hunk_controls = render_diff_hunk_controls;
18874 cx.notify();
18875 }
18876
18877 pub fn stage_and_next(
18878 &mut self,
18879 _: &::git::StageAndNext,
18880 window: &mut Window,
18881 cx: &mut Context<Self>,
18882 ) {
18883 self.do_stage_or_unstage_and_next(true, window, cx);
18884 }
18885
18886 pub fn unstage_and_next(
18887 &mut self,
18888 _: &::git::UnstageAndNext,
18889 window: &mut Window,
18890 cx: &mut Context<Self>,
18891 ) {
18892 self.do_stage_or_unstage_and_next(false, window, cx);
18893 }
18894
18895 pub fn stage_or_unstage_diff_hunks(
18896 &mut self,
18897 stage: bool,
18898 ranges: Vec<Range<Anchor>>,
18899 cx: &mut Context<Self>,
18900 ) {
18901 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18902 cx.spawn(async move |this, cx| {
18903 task.await?;
18904 this.update(cx, |this, cx| {
18905 let snapshot = this.buffer.read(cx).snapshot(cx);
18906 let chunk_by = this
18907 .diff_hunks_in_ranges(&ranges, &snapshot)
18908 .chunk_by(|hunk| hunk.buffer_id);
18909 for (buffer_id, hunks) in &chunk_by {
18910 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18911 }
18912 })
18913 })
18914 .detach_and_log_err(cx);
18915 }
18916
18917 fn save_buffers_for_ranges_if_needed(
18918 &mut self,
18919 ranges: &[Range<Anchor>],
18920 cx: &mut Context<Editor>,
18921 ) -> Task<Result<()>> {
18922 let multibuffer = self.buffer.read(cx);
18923 let snapshot = multibuffer.read(cx);
18924 let buffer_ids: HashSet<_> = ranges
18925 .iter()
18926 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18927 .collect();
18928 drop(snapshot);
18929
18930 let mut buffers = HashSet::default();
18931 for buffer_id in buffer_ids {
18932 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18933 let buffer = buffer_entity.read(cx);
18934 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18935 {
18936 buffers.insert(buffer_entity);
18937 }
18938 }
18939 }
18940
18941 if let Some(project) = &self.project {
18942 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18943 } else {
18944 Task::ready(Ok(()))
18945 }
18946 }
18947
18948 fn do_stage_or_unstage_and_next(
18949 &mut self,
18950 stage: bool,
18951 window: &mut Window,
18952 cx: &mut Context<Self>,
18953 ) {
18954 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18955
18956 if ranges.iter().any(|range| range.start != range.end) {
18957 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18958 return;
18959 }
18960
18961 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18962 let snapshot = self.snapshot(window, cx);
18963 let position = self
18964 .selections
18965 .newest::<Point>(&snapshot.display_snapshot)
18966 .head();
18967 let mut row = snapshot
18968 .buffer_snapshot()
18969 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18970 .find(|hunk| hunk.row_range.start.0 > position.row)
18971 .map(|hunk| hunk.row_range.start);
18972
18973 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18974 // Outside of the project diff editor, wrap around to the beginning.
18975 if !all_diff_hunks_expanded {
18976 row = row.or_else(|| {
18977 snapshot
18978 .buffer_snapshot()
18979 .diff_hunks_in_range(Point::zero()..position)
18980 .find(|hunk| hunk.row_range.end.0 < position.row)
18981 .map(|hunk| hunk.row_range.start)
18982 });
18983 }
18984
18985 if let Some(row) = row {
18986 let destination = Point::new(row.0, 0);
18987 let autoscroll = Autoscroll::center();
18988
18989 self.unfold_ranges(&[destination..destination], false, false, cx);
18990 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18991 s.select_ranges([destination..destination]);
18992 });
18993 }
18994 }
18995
18996 fn do_stage_or_unstage(
18997 &self,
18998 stage: bool,
18999 buffer_id: BufferId,
19000 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19001 cx: &mut App,
19002 ) -> Option<()> {
19003 let project = self.project()?;
19004 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19005 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19006 let buffer_snapshot = buffer.read(cx).snapshot();
19007 let file_exists = buffer_snapshot
19008 .file()
19009 .is_some_and(|file| file.disk_state().exists());
19010 diff.update(cx, |diff, cx| {
19011 diff.stage_or_unstage_hunks(
19012 stage,
19013 &hunks
19014 .map(|hunk| buffer_diff::DiffHunk {
19015 buffer_range: hunk.buffer_range,
19016 diff_base_byte_range: hunk.diff_base_byte_range,
19017 secondary_status: hunk.secondary_status,
19018 range: Point::zero()..Point::zero(), // unused
19019 })
19020 .collect::<Vec<_>>(),
19021 &buffer_snapshot,
19022 file_exists,
19023 cx,
19024 )
19025 });
19026 None
19027 }
19028
19029 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19030 let ranges: Vec<_> = self
19031 .selections
19032 .disjoint_anchors()
19033 .iter()
19034 .map(|s| s.range())
19035 .collect();
19036 self.buffer
19037 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19038 }
19039
19040 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19041 self.buffer.update(cx, |buffer, cx| {
19042 let ranges = vec![Anchor::min()..Anchor::max()];
19043 if !buffer.all_diff_hunks_expanded()
19044 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19045 {
19046 buffer.collapse_diff_hunks(ranges, cx);
19047 true
19048 } else {
19049 false
19050 }
19051 })
19052 }
19053
19054 fn toggle_diff_hunks_in_ranges(
19055 &mut self,
19056 ranges: Vec<Range<Anchor>>,
19057 cx: &mut Context<Editor>,
19058 ) {
19059 self.buffer.update(cx, |buffer, cx| {
19060 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19061 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19062 })
19063 }
19064
19065 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19066 self.buffer.update(cx, |buffer, cx| {
19067 let snapshot = buffer.snapshot(cx);
19068 let excerpt_id = range.end.excerpt_id;
19069 let point_range = range.to_point(&snapshot);
19070 let expand = !buffer.single_hunk_is_expanded(range, cx);
19071 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19072 })
19073 }
19074
19075 pub(crate) fn apply_all_diff_hunks(
19076 &mut self,
19077 _: &ApplyAllDiffHunks,
19078 window: &mut Window,
19079 cx: &mut Context<Self>,
19080 ) {
19081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19082
19083 let buffers = self.buffer.read(cx).all_buffers();
19084 for branch_buffer in buffers {
19085 branch_buffer.update(cx, |branch_buffer, cx| {
19086 branch_buffer.merge_into_base(Vec::new(), cx);
19087 });
19088 }
19089
19090 if let Some(project) = self.project.clone() {
19091 self.save(
19092 SaveOptions {
19093 format: true,
19094 autosave: false,
19095 },
19096 project,
19097 window,
19098 cx,
19099 )
19100 .detach_and_log_err(cx);
19101 }
19102 }
19103
19104 pub(crate) fn apply_selected_diff_hunks(
19105 &mut self,
19106 _: &ApplyDiffHunk,
19107 window: &mut Window,
19108 cx: &mut Context<Self>,
19109 ) {
19110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19111 let snapshot = self.snapshot(window, cx);
19112 let hunks = snapshot.hunks_for_ranges(
19113 self.selections
19114 .all(&snapshot.display_snapshot)
19115 .into_iter()
19116 .map(|selection| selection.range()),
19117 );
19118 let mut ranges_by_buffer = HashMap::default();
19119 self.transact(window, cx, |editor, _window, cx| {
19120 for hunk in hunks {
19121 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19122 ranges_by_buffer
19123 .entry(buffer.clone())
19124 .or_insert_with(Vec::new)
19125 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19126 }
19127 }
19128
19129 for (buffer, ranges) in ranges_by_buffer {
19130 buffer.update(cx, |buffer, cx| {
19131 buffer.merge_into_base(ranges, cx);
19132 });
19133 }
19134 });
19135
19136 if let Some(project) = self.project.clone() {
19137 self.save(
19138 SaveOptions {
19139 format: true,
19140 autosave: false,
19141 },
19142 project,
19143 window,
19144 cx,
19145 )
19146 .detach_and_log_err(cx);
19147 }
19148 }
19149
19150 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19151 if hovered != self.gutter_hovered {
19152 self.gutter_hovered = hovered;
19153 cx.notify();
19154 }
19155 }
19156
19157 pub fn insert_blocks(
19158 &mut self,
19159 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19160 autoscroll: Option<Autoscroll>,
19161 cx: &mut Context<Self>,
19162 ) -> Vec<CustomBlockId> {
19163 let blocks = self
19164 .display_map
19165 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19166 if let Some(autoscroll) = autoscroll {
19167 self.request_autoscroll(autoscroll, cx);
19168 }
19169 cx.notify();
19170 blocks
19171 }
19172
19173 pub fn resize_blocks(
19174 &mut self,
19175 heights: HashMap<CustomBlockId, u32>,
19176 autoscroll: Option<Autoscroll>,
19177 cx: &mut Context<Self>,
19178 ) {
19179 self.display_map
19180 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19181 if let Some(autoscroll) = autoscroll {
19182 self.request_autoscroll(autoscroll, cx);
19183 }
19184 cx.notify();
19185 }
19186
19187 pub fn replace_blocks(
19188 &mut self,
19189 renderers: HashMap<CustomBlockId, RenderBlock>,
19190 autoscroll: Option<Autoscroll>,
19191 cx: &mut Context<Self>,
19192 ) {
19193 self.display_map
19194 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19195 if let Some(autoscroll) = autoscroll {
19196 self.request_autoscroll(autoscroll, cx);
19197 }
19198 cx.notify();
19199 }
19200
19201 pub fn remove_blocks(
19202 &mut self,
19203 block_ids: HashSet<CustomBlockId>,
19204 autoscroll: Option<Autoscroll>,
19205 cx: &mut Context<Self>,
19206 ) {
19207 self.display_map.update(cx, |display_map, cx| {
19208 display_map.remove_blocks(block_ids, cx)
19209 });
19210 if let Some(autoscroll) = autoscroll {
19211 self.request_autoscroll(autoscroll, cx);
19212 }
19213 cx.notify();
19214 }
19215
19216 pub fn row_for_block(
19217 &self,
19218 block_id: CustomBlockId,
19219 cx: &mut Context<Self>,
19220 ) -> Option<DisplayRow> {
19221 self.display_map
19222 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19223 }
19224
19225 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19226 self.focused_block = Some(focused_block);
19227 }
19228
19229 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19230 self.focused_block.take()
19231 }
19232
19233 pub fn insert_creases(
19234 &mut self,
19235 creases: impl IntoIterator<Item = Crease<Anchor>>,
19236 cx: &mut Context<Self>,
19237 ) -> Vec<CreaseId> {
19238 self.display_map
19239 .update(cx, |map, cx| map.insert_creases(creases, cx))
19240 }
19241
19242 pub fn remove_creases(
19243 &mut self,
19244 ids: impl IntoIterator<Item = CreaseId>,
19245 cx: &mut Context<Self>,
19246 ) -> Vec<(CreaseId, Range<Anchor>)> {
19247 self.display_map
19248 .update(cx, |map, cx| map.remove_creases(ids, cx))
19249 }
19250
19251 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19252 self.display_map
19253 .update(cx, |map, cx| map.snapshot(cx))
19254 .longest_row()
19255 }
19256
19257 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19258 self.display_map
19259 .update(cx, |map, cx| map.snapshot(cx))
19260 .max_point()
19261 }
19262
19263 pub fn text(&self, cx: &App) -> String {
19264 self.buffer.read(cx).read(cx).text()
19265 }
19266
19267 pub fn is_empty(&self, cx: &App) -> bool {
19268 self.buffer.read(cx).read(cx).is_empty()
19269 }
19270
19271 pub fn text_option(&self, cx: &App) -> Option<String> {
19272 let text = self.text(cx);
19273 let text = text.trim();
19274
19275 if text.is_empty() {
19276 return None;
19277 }
19278
19279 Some(text.to_string())
19280 }
19281
19282 pub fn set_text(
19283 &mut self,
19284 text: impl Into<Arc<str>>,
19285 window: &mut Window,
19286 cx: &mut Context<Self>,
19287 ) {
19288 self.transact(window, cx, |this, _, cx| {
19289 this.buffer
19290 .read(cx)
19291 .as_singleton()
19292 .expect("you can only call set_text on editors for singleton buffers")
19293 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19294 });
19295 }
19296
19297 pub fn display_text(&self, cx: &mut App) -> String {
19298 self.display_map
19299 .update(cx, |map, cx| map.snapshot(cx))
19300 .text()
19301 }
19302
19303 fn create_minimap(
19304 &self,
19305 minimap_settings: MinimapSettings,
19306 window: &mut Window,
19307 cx: &mut Context<Self>,
19308 ) -> Option<Entity<Self>> {
19309 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19310 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19311 }
19312
19313 fn initialize_new_minimap(
19314 &self,
19315 minimap_settings: MinimapSettings,
19316 window: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) -> Entity<Self> {
19319 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19320
19321 let mut minimap = Editor::new_internal(
19322 EditorMode::Minimap {
19323 parent: cx.weak_entity(),
19324 },
19325 self.buffer.clone(),
19326 None,
19327 Some(self.display_map.clone()),
19328 window,
19329 cx,
19330 );
19331 minimap.scroll_manager.clone_state(&self.scroll_manager);
19332 minimap.set_text_style_refinement(TextStyleRefinement {
19333 font_size: Some(MINIMAP_FONT_SIZE),
19334 font_weight: Some(MINIMAP_FONT_WEIGHT),
19335 ..Default::default()
19336 });
19337 minimap.update_minimap_configuration(minimap_settings, cx);
19338 cx.new(|_| minimap)
19339 }
19340
19341 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19342 let current_line_highlight = minimap_settings
19343 .current_line_highlight
19344 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19345 self.set_current_line_highlight(Some(current_line_highlight));
19346 }
19347
19348 pub fn minimap(&self) -> Option<&Entity<Self>> {
19349 self.minimap
19350 .as_ref()
19351 .filter(|_| self.minimap_visibility.visible())
19352 }
19353
19354 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19355 let mut wrap_guides = smallvec![];
19356
19357 if self.show_wrap_guides == Some(false) {
19358 return wrap_guides;
19359 }
19360
19361 let settings = self.buffer.read(cx).language_settings(cx);
19362 if settings.show_wrap_guides {
19363 match self.soft_wrap_mode(cx) {
19364 SoftWrap::Column(soft_wrap) => {
19365 wrap_guides.push((soft_wrap as usize, true));
19366 }
19367 SoftWrap::Bounded(soft_wrap) => {
19368 wrap_guides.push((soft_wrap as usize, true));
19369 }
19370 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19371 }
19372 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19373 }
19374
19375 wrap_guides
19376 }
19377
19378 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19379 let settings = self.buffer.read(cx).language_settings(cx);
19380 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19381 match mode {
19382 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19383 SoftWrap::None
19384 }
19385 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19386 language_settings::SoftWrap::PreferredLineLength => {
19387 SoftWrap::Column(settings.preferred_line_length)
19388 }
19389 language_settings::SoftWrap::Bounded => {
19390 SoftWrap::Bounded(settings.preferred_line_length)
19391 }
19392 }
19393 }
19394
19395 pub fn set_soft_wrap_mode(
19396 &mut self,
19397 mode: language_settings::SoftWrap,
19398
19399 cx: &mut Context<Self>,
19400 ) {
19401 self.soft_wrap_mode_override = Some(mode);
19402 cx.notify();
19403 }
19404
19405 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19406 self.hard_wrap = hard_wrap;
19407 cx.notify();
19408 }
19409
19410 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19411 self.text_style_refinement = Some(style);
19412 }
19413
19414 /// called by the Element so we know what style we were most recently rendered with.
19415 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19416 // We intentionally do not inform the display map about the minimap style
19417 // so that wrapping is not recalculated and stays consistent for the editor
19418 // and its linked minimap.
19419 if !self.mode.is_minimap() {
19420 let font = style.text.font();
19421 let font_size = style.text.font_size.to_pixels(window.rem_size());
19422 let display_map = self
19423 .placeholder_display_map
19424 .as_ref()
19425 .filter(|_| self.is_empty(cx))
19426 .unwrap_or(&self.display_map);
19427
19428 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19429 }
19430 self.style = Some(style);
19431 }
19432
19433 pub fn style(&self) -> Option<&EditorStyle> {
19434 self.style.as_ref()
19435 }
19436
19437 // Called by the element. This method is not designed to be called outside of the editor
19438 // element's layout code because it does not notify when rewrapping is computed synchronously.
19439 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19440 if self.is_empty(cx) {
19441 self.placeholder_display_map
19442 .as_ref()
19443 .map_or(false, |display_map| {
19444 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19445 })
19446 } else {
19447 self.display_map
19448 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19449 }
19450 }
19451
19452 pub fn set_soft_wrap(&mut self) {
19453 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19454 }
19455
19456 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19457 if self.soft_wrap_mode_override.is_some() {
19458 self.soft_wrap_mode_override.take();
19459 } else {
19460 let soft_wrap = match self.soft_wrap_mode(cx) {
19461 SoftWrap::GitDiff => return,
19462 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19463 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19464 language_settings::SoftWrap::None
19465 }
19466 };
19467 self.soft_wrap_mode_override = Some(soft_wrap);
19468 }
19469 cx.notify();
19470 }
19471
19472 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19473 let Some(workspace) = self.workspace() else {
19474 return;
19475 };
19476 let fs = workspace.read(cx).app_state().fs.clone();
19477 let current_show = TabBarSettings::get_global(cx).show;
19478 update_settings_file(fs, cx, move |setting, _| {
19479 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19480 });
19481 }
19482
19483 pub fn toggle_indent_guides(
19484 &mut self,
19485 _: &ToggleIndentGuides,
19486 _: &mut Window,
19487 cx: &mut Context<Self>,
19488 ) {
19489 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19490 self.buffer
19491 .read(cx)
19492 .language_settings(cx)
19493 .indent_guides
19494 .enabled
19495 });
19496 self.show_indent_guides = Some(!currently_enabled);
19497 cx.notify();
19498 }
19499
19500 fn should_show_indent_guides(&self) -> Option<bool> {
19501 self.show_indent_guides
19502 }
19503
19504 pub fn toggle_line_numbers(
19505 &mut self,
19506 _: &ToggleLineNumbers,
19507 _: &mut Window,
19508 cx: &mut Context<Self>,
19509 ) {
19510 let mut editor_settings = EditorSettings::get_global(cx).clone();
19511 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19512 EditorSettings::override_global(editor_settings, cx);
19513 }
19514
19515 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19516 if let Some(show_line_numbers) = self.show_line_numbers {
19517 return show_line_numbers;
19518 }
19519 EditorSettings::get_global(cx).gutter.line_numbers
19520 }
19521
19522 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19523 match (
19524 self.use_relative_line_numbers,
19525 EditorSettings::get_global(cx).relative_line_numbers,
19526 ) {
19527 (None, setting) => setting,
19528 (Some(false), _) => RelativeLineNumbers::Disabled,
19529 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19530 (Some(true), _) => RelativeLineNumbers::Enabled,
19531 }
19532 }
19533
19534 pub fn toggle_relative_line_numbers(
19535 &mut self,
19536 _: &ToggleRelativeLineNumbers,
19537 _: &mut Window,
19538 cx: &mut Context<Self>,
19539 ) {
19540 let is_relative = self.relative_line_numbers(cx);
19541 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19542 }
19543
19544 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19545 self.use_relative_line_numbers = is_relative;
19546 cx.notify();
19547 }
19548
19549 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19550 self.show_gutter = show_gutter;
19551 cx.notify();
19552 }
19553
19554 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19555 self.show_scrollbars = ScrollbarAxes {
19556 horizontal: show,
19557 vertical: show,
19558 };
19559 cx.notify();
19560 }
19561
19562 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19563 self.show_scrollbars.vertical = show;
19564 cx.notify();
19565 }
19566
19567 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19568 self.show_scrollbars.horizontal = show;
19569 cx.notify();
19570 }
19571
19572 pub fn set_minimap_visibility(
19573 &mut self,
19574 minimap_visibility: MinimapVisibility,
19575 window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 if self.minimap_visibility != minimap_visibility {
19579 if minimap_visibility.visible() && self.minimap.is_none() {
19580 let minimap_settings = EditorSettings::get_global(cx).minimap;
19581 self.minimap =
19582 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19583 }
19584 self.minimap_visibility = minimap_visibility;
19585 cx.notify();
19586 }
19587 }
19588
19589 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19590 self.set_show_scrollbars(false, cx);
19591 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19592 }
19593
19594 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19595 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19596 }
19597
19598 /// Normally the text in full mode and auto height editors is padded on the
19599 /// left side by roughly half a character width for improved hit testing.
19600 ///
19601 /// Use this method to disable this for cases where this is not wanted (e.g.
19602 /// if you want to align the editor text with some other text above or below)
19603 /// or if you want to add this padding to single-line editors.
19604 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19605 self.offset_content = offset_content;
19606 cx.notify();
19607 }
19608
19609 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19610 self.show_line_numbers = Some(show_line_numbers);
19611 cx.notify();
19612 }
19613
19614 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19615 self.disable_expand_excerpt_buttons = true;
19616 cx.notify();
19617 }
19618
19619 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19620 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19621 cx.notify();
19622 }
19623
19624 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19625 self.show_code_actions = Some(show_code_actions);
19626 cx.notify();
19627 }
19628
19629 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19630 self.show_runnables = Some(show_runnables);
19631 cx.notify();
19632 }
19633
19634 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19635 self.show_breakpoints = Some(show_breakpoints);
19636 cx.notify();
19637 }
19638
19639 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19640 if self.display_map.read(cx).masked != masked {
19641 self.display_map.update(cx, |map, _| map.masked = masked);
19642 }
19643 cx.notify()
19644 }
19645
19646 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19647 self.show_wrap_guides = Some(show_wrap_guides);
19648 cx.notify();
19649 }
19650
19651 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19652 self.show_indent_guides = Some(show_indent_guides);
19653 cx.notify();
19654 }
19655
19656 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19657 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19658 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19659 && let Some(dir) = file.abs_path(cx).parent()
19660 {
19661 return Some(dir.to_owned());
19662 }
19663 }
19664
19665 None
19666 }
19667
19668 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19669 self.active_excerpt(cx)?
19670 .1
19671 .read(cx)
19672 .file()
19673 .and_then(|f| f.as_local())
19674 }
19675
19676 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19677 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19678 let buffer = buffer.read(cx);
19679 if let Some(project_path) = buffer.project_path(cx) {
19680 let project = self.project()?.read(cx);
19681 project.absolute_path(&project_path, cx)
19682 } else {
19683 buffer
19684 .file()
19685 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19686 }
19687 })
19688 }
19689
19690 pub fn reveal_in_finder(
19691 &mut self,
19692 _: &RevealInFileManager,
19693 _window: &mut Window,
19694 cx: &mut Context<Self>,
19695 ) {
19696 if let Some(target) = self.target_file(cx) {
19697 cx.reveal_path(&target.abs_path(cx));
19698 }
19699 }
19700
19701 pub fn copy_path(
19702 &mut self,
19703 _: &zed_actions::workspace::CopyPath,
19704 _window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) {
19707 if let Some(path) = self.target_file_abs_path(cx)
19708 && let Some(path) = path.to_str()
19709 {
19710 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19711 } else {
19712 cx.propagate();
19713 }
19714 }
19715
19716 pub fn copy_relative_path(
19717 &mut self,
19718 _: &zed_actions::workspace::CopyRelativePath,
19719 _window: &mut Window,
19720 cx: &mut Context<Self>,
19721 ) {
19722 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19723 let project = self.project()?.read(cx);
19724 let path = buffer.read(cx).file()?.path();
19725 let path = path.display(project.path_style(cx));
19726 Some(path)
19727 }) {
19728 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19729 } else {
19730 cx.propagate();
19731 }
19732 }
19733
19734 /// Returns the project path for the editor's buffer, if any buffer is
19735 /// opened in the editor.
19736 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19737 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19738 buffer.read(cx).project_path(cx)
19739 } else {
19740 None
19741 }
19742 }
19743
19744 // Returns true if the editor handled a go-to-line request
19745 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19746 maybe!({
19747 let breakpoint_store = self.breakpoint_store.as_ref()?;
19748
19749 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19750 else {
19751 self.clear_row_highlights::<ActiveDebugLine>();
19752 return None;
19753 };
19754
19755 let position = active_stack_frame.position;
19756 let buffer_id = position.buffer_id?;
19757 let snapshot = self
19758 .project
19759 .as_ref()?
19760 .read(cx)
19761 .buffer_for_id(buffer_id, cx)?
19762 .read(cx)
19763 .snapshot();
19764
19765 let mut handled = false;
19766 for (id, ExcerptRange { context, .. }) in
19767 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19768 {
19769 if context.start.cmp(&position, &snapshot).is_ge()
19770 || context.end.cmp(&position, &snapshot).is_lt()
19771 {
19772 continue;
19773 }
19774 let snapshot = self.buffer.read(cx).snapshot(cx);
19775 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19776
19777 handled = true;
19778 self.clear_row_highlights::<ActiveDebugLine>();
19779
19780 self.go_to_line::<ActiveDebugLine>(
19781 multibuffer_anchor,
19782 Some(cx.theme().colors().editor_debugger_active_line_background),
19783 window,
19784 cx,
19785 );
19786
19787 cx.notify();
19788 }
19789
19790 handled.then_some(())
19791 })
19792 .is_some()
19793 }
19794
19795 pub fn copy_file_name_without_extension(
19796 &mut self,
19797 _: &CopyFileNameWithoutExtension,
19798 _: &mut Window,
19799 cx: &mut Context<Self>,
19800 ) {
19801 if let Some(file) = self.target_file(cx)
19802 && let Some(file_stem) = file.path().file_stem()
19803 {
19804 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19805 }
19806 }
19807
19808 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19809 if let Some(file) = self.target_file(cx)
19810 && let Some(name) = file.path().file_name()
19811 {
19812 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19813 }
19814 }
19815
19816 pub fn toggle_git_blame(
19817 &mut self,
19818 _: &::git::Blame,
19819 window: &mut Window,
19820 cx: &mut Context<Self>,
19821 ) {
19822 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19823
19824 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19825 self.start_git_blame(true, window, cx);
19826 }
19827
19828 cx.notify();
19829 }
19830
19831 pub fn toggle_git_blame_inline(
19832 &mut self,
19833 _: &ToggleGitBlameInline,
19834 window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.toggle_git_blame_inline_internal(true, window, cx);
19838 cx.notify();
19839 }
19840
19841 pub fn open_git_blame_commit(
19842 &mut self,
19843 _: &OpenGitBlameCommit,
19844 window: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 self.open_git_blame_commit_internal(window, cx);
19848 }
19849
19850 fn open_git_blame_commit_internal(
19851 &mut self,
19852 window: &mut Window,
19853 cx: &mut Context<Self>,
19854 ) -> Option<()> {
19855 let blame = self.blame.as_ref()?;
19856 let snapshot = self.snapshot(window, cx);
19857 let cursor = self
19858 .selections
19859 .newest::<Point>(&snapshot.display_snapshot)
19860 .head();
19861 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19862 let (_, blame_entry) = blame
19863 .update(cx, |blame, cx| {
19864 blame
19865 .blame_for_rows(
19866 &[RowInfo {
19867 buffer_id: Some(buffer.remote_id()),
19868 buffer_row: Some(point.row),
19869 ..Default::default()
19870 }],
19871 cx,
19872 )
19873 .next()
19874 })
19875 .flatten()?;
19876 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19877 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19878 let workspace = self.workspace()?.downgrade();
19879 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19880 None
19881 }
19882
19883 pub fn git_blame_inline_enabled(&self) -> bool {
19884 self.git_blame_inline_enabled
19885 }
19886
19887 pub fn toggle_selection_menu(
19888 &mut self,
19889 _: &ToggleSelectionMenu,
19890 _: &mut Window,
19891 cx: &mut Context<Self>,
19892 ) {
19893 self.show_selection_menu = self
19894 .show_selection_menu
19895 .map(|show_selections_menu| !show_selections_menu)
19896 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19897
19898 cx.notify();
19899 }
19900
19901 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19902 self.show_selection_menu
19903 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19904 }
19905
19906 fn start_git_blame(
19907 &mut self,
19908 user_triggered: bool,
19909 window: &mut Window,
19910 cx: &mut Context<Self>,
19911 ) {
19912 if let Some(project) = self.project() {
19913 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19914 && buffer.read(cx).file().is_none()
19915 {
19916 return;
19917 }
19918
19919 let focused = self.focus_handle(cx).contains_focused(window, cx);
19920
19921 let project = project.clone();
19922 let blame = cx
19923 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19924 self.blame_subscription =
19925 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19926 self.blame = Some(blame);
19927 }
19928 }
19929
19930 fn toggle_git_blame_inline_internal(
19931 &mut self,
19932 user_triggered: bool,
19933 window: &mut Window,
19934 cx: &mut Context<Self>,
19935 ) {
19936 if self.git_blame_inline_enabled {
19937 self.git_blame_inline_enabled = false;
19938 self.show_git_blame_inline = false;
19939 self.show_git_blame_inline_delay_task.take();
19940 } else {
19941 self.git_blame_inline_enabled = true;
19942 self.start_git_blame_inline(user_triggered, window, cx);
19943 }
19944
19945 cx.notify();
19946 }
19947
19948 fn start_git_blame_inline(
19949 &mut self,
19950 user_triggered: bool,
19951 window: &mut Window,
19952 cx: &mut Context<Self>,
19953 ) {
19954 self.start_git_blame(user_triggered, window, cx);
19955
19956 if ProjectSettings::get_global(cx)
19957 .git
19958 .inline_blame_delay()
19959 .is_some()
19960 {
19961 self.start_inline_blame_timer(window, cx);
19962 } else {
19963 self.show_git_blame_inline = true
19964 }
19965 }
19966
19967 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19968 self.blame.as_ref()
19969 }
19970
19971 pub fn show_git_blame_gutter(&self) -> bool {
19972 self.show_git_blame_gutter
19973 }
19974
19975 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19976 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19977 }
19978
19979 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19980 self.show_git_blame_inline
19981 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19982 && !self.newest_selection_head_on_empty_line(cx)
19983 && self.has_blame_entries(cx)
19984 }
19985
19986 fn has_blame_entries(&self, cx: &App) -> bool {
19987 self.blame()
19988 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19989 }
19990
19991 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19992 let cursor_anchor = self.selections.newest_anchor().head();
19993
19994 let snapshot = self.buffer.read(cx).snapshot(cx);
19995 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19996
19997 snapshot.line_len(buffer_row) == 0
19998 }
19999
20000 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20001 let buffer_and_selection = maybe!({
20002 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20003 let selection_range = selection.range();
20004
20005 let multi_buffer = self.buffer().read(cx);
20006 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20007 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20008
20009 let (buffer, range, _) = if selection.reversed {
20010 buffer_ranges.first()
20011 } else {
20012 buffer_ranges.last()
20013 }?;
20014
20015 let selection = text::ToPoint::to_point(&range.start, buffer).row
20016 ..text::ToPoint::to_point(&range.end, buffer).row;
20017 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20018 });
20019
20020 let Some((buffer, selection)) = buffer_and_selection else {
20021 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20022 };
20023
20024 let Some(project) = self.project() else {
20025 return Task::ready(Err(anyhow!("editor does not have project")));
20026 };
20027
20028 project.update(cx, |project, cx| {
20029 project.get_permalink_to_line(&buffer, selection, cx)
20030 })
20031 }
20032
20033 pub fn copy_permalink_to_line(
20034 &mut self,
20035 _: &CopyPermalinkToLine,
20036 window: &mut Window,
20037 cx: &mut Context<Self>,
20038 ) {
20039 let permalink_task = self.get_permalink_to_line(cx);
20040 let workspace = self.workspace();
20041
20042 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20043 Ok(permalink) => {
20044 cx.update(|_, cx| {
20045 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20046 })
20047 .ok();
20048 }
20049 Err(err) => {
20050 let message = format!("Failed to copy permalink: {err}");
20051
20052 anyhow::Result::<()>::Err(err).log_err();
20053
20054 if let Some(workspace) = workspace {
20055 workspace
20056 .update_in(cx, |workspace, _, cx| {
20057 struct CopyPermalinkToLine;
20058
20059 workspace.show_toast(
20060 Toast::new(
20061 NotificationId::unique::<CopyPermalinkToLine>(),
20062 message,
20063 ),
20064 cx,
20065 )
20066 })
20067 .ok();
20068 }
20069 }
20070 })
20071 .detach();
20072 }
20073
20074 pub fn copy_file_location(
20075 &mut self,
20076 _: &CopyFileLocation,
20077 _: &mut Window,
20078 cx: &mut Context<Self>,
20079 ) {
20080 let selection = self
20081 .selections
20082 .newest::<Point>(&self.display_snapshot(cx))
20083 .start
20084 .row
20085 + 1;
20086 if let Some(file) = self.target_file(cx) {
20087 let path = file.path().display(file.path_style(cx));
20088 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20089 }
20090 }
20091
20092 pub fn open_permalink_to_line(
20093 &mut self,
20094 _: &OpenPermalinkToLine,
20095 window: &mut Window,
20096 cx: &mut Context<Self>,
20097 ) {
20098 let permalink_task = self.get_permalink_to_line(cx);
20099 let workspace = self.workspace();
20100
20101 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20102 Ok(permalink) => {
20103 cx.update(|_, cx| {
20104 cx.open_url(permalink.as_ref());
20105 })
20106 .ok();
20107 }
20108 Err(err) => {
20109 let message = format!("Failed to open permalink: {err}");
20110
20111 anyhow::Result::<()>::Err(err).log_err();
20112
20113 if let Some(workspace) = workspace {
20114 workspace
20115 .update(cx, |workspace, cx| {
20116 struct OpenPermalinkToLine;
20117
20118 workspace.show_toast(
20119 Toast::new(
20120 NotificationId::unique::<OpenPermalinkToLine>(),
20121 message,
20122 ),
20123 cx,
20124 )
20125 })
20126 .ok();
20127 }
20128 }
20129 })
20130 .detach();
20131 }
20132
20133 pub fn insert_uuid_v4(
20134 &mut self,
20135 _: &InsertUuidV4,
20136 window: &mut Window,
20137 cx: &mut Context<Self>,
20138 ) {
20139 self.insert_uuid(UuidVersion::V4, window, cx);
20140 }
20141
20142 pub fn insert_uuid_v7(
20143 &mut self,
20144 _: &InsertUuidV7,
20145 window: &mut Window,
20146 cx: &mut Context<Self>,
20147 ) {
20148 self.insert_uuid(UuidVersion::V7, window, cx);
20149 }
20150
20151 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20153 self.transact(window, cx, |this, window, cx| {
20154 let edits = this
20155 .selections
20156 .all::<Point>(&this.display_snapshot(cx))
20157 .into_iter()
20158 .map(|selection| {
20159 let uuid = match version {
20160 UuidVersion::V4 => uuid::Uuid::new_v4(),
20161 UuidVersion::V7 => uuid::Uuid::now_v7(),
20162 };
20163
20164 (selection.range(), uuid.to_string())
20165 });
20166 this.edit(edits, cx);
20167 this.refresh_edit_prediction(true, false, window, cx);
20168 });
20169 }
20170
20171 pub fn open_selections_in_multibuffer(
20172 &mut self,
20173 _: &OpenSelectionsInMultibuffer,
20174 window: &mut Window,
20175 cx: &mut Context<Self>,
20176 ) {
20177 let multibuffer = self.buffer.read(cx);
20178
20179 let Some(buffer) = multibuffer.as_singleton() else {
20180 return;
20181 };
20182
20183 let Some(workspace) = self.workspace() else {
20184 return;
20185 };
20186
20187 let title = multibuffer.title(cx).to_string();
20188
20189 let locations = self
20190 .selections
20191 .all_anchors(cx)
20192 .iter()
20193 .map(|selection| {
20194 (
20195 buffer.clone(),
20196 (selection.start.text_anchor..selection.end.text_anchor)
20197 .to_point(buffer.read(cx)),
20198 )
20199 })
20200 .into_group_map();
20201
20202 cx.spawn_in(window, async move |_, cx| {
20203 workspace.update_in(cx, |workspace, window, cx| {
20204 Self::open_locations_in_multibuffer(
20205 workspace,
20206 locations,
20207 format!("Selections for '{title}'"),
20208 false,
20209 MultibufferSelectionMode::All,
20210 window,
20211 cx,
20212 );
20213 })
20214 })
20215 .detach();
20216 }
20217
20218 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20219 /// last highlight added will be used.
20220 ///
20221 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20222 pub fn highlight_rows<T: 'static>(
20223 &mut self,
20224 range: Range<Anchor>,
20225 color: Hsla,
20226 options: RowHighlightOptions,
20227 cx: &mut Context<Self>,
20228 ) {
20229 let snapshot = self.buffer().read(cx).snapshot(cx);
20230 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20231 let ix = row_highlights.binary_search_by(|highlight| {
20232 Ordering::Equal
20233 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20234 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20235 });
20236
20237 if let Err(mut ix) = ix {
20238 let index = post_inc(&mut self.highlight_order);
20239
20240 // If this range intersects with the preceding highlight, then merge it with
20241 // the preceding highlight. Otherwise insert a new highlight.
20242 let mut merged = false;
20243 if ix > 0 {
20244 let prev_highlight = &mut row_highlights[ix - 1];
20245 if prev_highlight
20246 .range
20247 .end
20248 .cmp(&range.start, &snapshot)
20249 .is_ge()
20250 {
20251 ix -= 1;
20252 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20253 prev_highlight.range.end = range.end;
20254 }
20255 merged = true;
20256 prev_highlight.index = index;
20257 prev_highlight.color = color;
20258 prev_highlight.options = options;
20259 }
20260 }
20261
20262 if !merged {
20263 row_highlights.insert(
20264 ix,
20265 RowHighlight {
20266 range,
20267 index,
20268 color,
20269 options,
20270 type_id: TypeId::of::<T>(),
20271 },
20272 );
20273 }
20274
20275 // If any of the following highlights intersect with this one, merge them.
20276 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20277 let highlight = &row_highlights[ix];
20278 if next_highlight
20279 .range
20280 .start
20281 .cmp(&highlight.range.end, &snapshot)
20282 .is_le()
20283 {
20284 if next_highlight
20285 .range
20286 .end
20287 .cmp(&highlight.range.end, &snapshot)
20288 .is_gt()
20289 {
20290 row_highlights[ix].range.end = next_highlight.range.end;
20291 }
20292 row_highlights.remove(ix + 1);
20293 } else {
20294 break;
20295 }
20296 }
20297 }
20298 }
20299
20300 /// Remove any highlighted row ranges of the given type that intersect the
20301 /// given ranges.
20302 pub fn remove_highlighted_rows<T: 'static>(
20303 &mut self,
20304 ranges_to_remove: Vec<Range<Anchor>>,
20305 cx: &mut Context<Self>,
20306 ) {
20307 let snapshot = self.buffer().read(cx).snapshot(cx);
20308 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20309 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20310 row_highlights.retain(|highlight| {
20311 while let Some(range_to_remove) = ranges_to_remove.peek() {
20312 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20313 Ordering::Less | Ordering::Equal => {
20314 ranges_to_remove.next();
20315 }
20316 Ordering::Greater => {
20317 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20318 Ordering::Less | Ordering::Equal => {
20319 return false;
20320 }
20321 Ordering::Greater => break,
20322 }
20323 }
20324 }
20325 }
20326
20327 true
20328 })
20329 }
20330
20331 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20332 pub fn clear_row_highlights<T: 'static>(&mut self) {
20333 self.highlighted_rows.remove(&TypeId::of::<T>());
20334 }
20335
20336 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20337 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20338 self.highlighted_rows
20339 .get(&TypeId::of::<T>())
20340 .map_or(&[] as &[_], |vec| vec.as_slice())
20341 .iter()
20342 .map(|highlight| (highlight.range.clone(), highlight.color))
20343 }
20344
20345 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20346 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20347 /// Allows to ignore certain kinds of highlights.
20348 pub fn highlighted_display_rows(
20349 &self,
20350 window: &mut Window,
20351 cx: &mut App,
20352 ) -> BTreeMap<DisplayRow, LineHighlight> {
20353 let snapshot = self.snapshot(window, cx);
20354 let mut used_highlight_orders = HashMap::default();
20355 self.highlighted_rows
20356 .iter()
20357 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20358 .fold(
20359 BTreeMap::<DisplayRow, LineHighlight>::new(),
20360 |mut unique_rows, highlight| {
20361 let start = highlight.range.start.to_display_point(&snapshot);
20362 let end = highlight.range.end.to_display_point(&snapshot);
20363 let start_row = start.row().0;
20364 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20365 && end.column() == 0
20366 {
20367 end.row().0.saturating_sub(1)
20368 } else {
20369 end.row().0
20370 };
20371 for row in start_row..=end_row {
20372 let used_index =
20373 used_highlight_orders.entry(row).or_insert(highlight.index);
20374 if highlight.index >= *used_index {
20375 *used_index = highlight.index;
20376 unique_rows.insert(
20377 DisplayRow(row),
20378 LineHighlight {
20379 include_gutter: highlight.options.include_gutter,
20380 border: None,
20381 background: highlight.color.into(),
20382 type_id: Some(highlight.type_id),
20383 },
20384 );
20385 }
20386 }
20387 unique_rows
20388 },
20389 )
20390 }
20391
20392 pub fn highlighted_display_row_for_autoscroll(
20393 &self,
20394 snapshot: &DisplaySnapshot,
20395 ) -> Option<DisplayRow> {
20396 self.highlighted_rows
20397 .values()
20398 .flat_map(|highlighted_rows| highlighted_rows.iter())
20399 .filter_map(|highlight| {
20400 if highlight.options.autoscroll {
20401 Some(highlight.range.start.to_display_point(snapshot).row())
20402 } else {
20403 None
20404 }
20405 })
20406 .min()
20407 }
20408
20409 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20410 self.highlight_background::<SearchWithinRange>(
20411 ranges,
20412 |colors| colors.colors().editor_document_highlight_read_background,
20413 cx,
20414 )
20415 }
20416
20417 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20418 self.breadcrumb_header = Some(new_header);
20419 }
20420
20421 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20422 self.clear_background_highlights::<SearchWithinRange>(cx);
20423 }
20424
20425 pub fn highlight_background<T: 'static>(
20426 &mut self,
20427 ranges: &[Range<Anchor>],
20428 color_fetcher: fn(&Theme) -> Hsla,
20429 cx: &mut Context<Self>,
20430 ) {
20431 self.background_highlights.insert(
20432 HighlightKey::Type(TypeId::of::<T>()),
20433 (color_fetcher, Arc::from(ranges)),
20434 );
20435 self.scrollbar_marker_state.dirty = true;
20436 cx.notify();
20437 }
20438
20439 pub fn highlight_background_key<T: 'static>(
20440 &mut self,
20441 key: usize,
20442 ranges: &[Range<Anchor>],
20443 color_fetcher: fn(&Theme) -> Hsla,
20444 cx: &mut Context<Self>,
20445 ) {
20446 self.background_highlights.insert(
20447 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20448 (color_fetcher, Arc::from(ranges)),
20449 );
20450 self.scrollbar_marker_state.dirty = true;
20451 cx.notify();
20452 }
20453
20454 pub fn clear_background_highlights<T: 'static>(
20455 &mut self,
20456 cx: &mut Context<Self>,
20457 ) -> Option<BackgroundHighlight> {
20458 let text_highlights = self
20459 .background_highlights
20460 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20461 if !text_highlights.1.is_empty() {
20462 self.scrollbar_marker_state.dirty = true;
20463 cx.notify();
20464 }
20465 Some(text_highlights)
20466 }
20467
20468 pub fn highlight_gutter<T: 'static>(
20469 &mut self,
20470 ranges: impl Into<Vec<Range<Anchor>>>,
20471 color_fetcher: fn(&App) -> Hsla,
20472 cx: &mut Context<Self>,
20473 ) {
20474 self.gutter_highlights
20475 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20476 cx.notify();
20477 }
20478
20479 pub fn clear_gutter_highlights<T: 'static>(
20480 &mut self,
20481 cx: &mut Context<Self>,
20482 ) -> Option<GutterHighlight> {
20483 cx.notify();
20484 self.gutter_highlights.remove(&TypeId::of::<T>())
20485 }
20486
20487 pub fn insert_gutter_highlight<T: 'static>(
20488 &mut self,
20489 range: Range<Anchor>,
20490 color_fetcher: fn(&App) -> Hsla,
20491 cx: &mut Context<Self>,
20492 ) {
20493 let snapshot = self.buffer().read(cx).snapshot(cx);
20494 let mut highlights = self
20495 .gutter_highlights
20496 .remove(&TypeId::of::<T>())
20497 .map(|(_, highlights)| highlights)
20498 .unwrap_or_default();
20499 let ix = highlights.binary_search_by(|highlight| {
20500 Ordering::Equal
20501 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20502 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20503 });
20504 if let Err(ix) = ix {
20505 highlights.insert(ix, range);
20506 }
20507 self.gutter_highlights
20508 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20509 }
20510
20511 pub fn remove_gutter_highlights<T: 'static>(
20512 &mut self,
20513 ranges_to_remove: Vec<Range<Anchor>>,
20514 cx: &mut Context<Self>,
20515 ) {
20516 let snapshot = self.buffer().read(cx).snapshot(cx);
20517 let Some((color_fetcher, mut gutter_highlights)) =
20518 self.gutter_highlights.remove(&TypeId::of::<T>())
20519 else {
20520 return;
20521 };
20522 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20523 gutter_highlights.retain(|highlight| {
20524 while let Some(range_to_remove) = ranges_to_remove.peek() {
20525 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20526 Ordering::Less | Ordering::Equal => {
20527 ranges_to_remove.next();
20528 }
20529 Ordering::Greater => {
20530 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20531 Ordering::Less | Ordering::Equal => {
20532 return false;
20533 }
20534 Ordering::Greater => break,
20535 }
20536 }
20537 }
20538 }
20539
20540 true
20541 });
20542 self.gutter_highlights
20543 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20544 }
20545
20546 #[cfg(feature = "test-support")]
20547 pub fn all_text_highlights(
20548 &self,
20549 window: &mut Window,
20550 cx: &mut Context<Self>,
20551 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20552 let snapshot = self.snapshot(window, cx);
20553 self.display_map.update(cx, |display_map, _| {
20554 display_map
20555 .all_text_highlights()
20556 .map(|highlight| {
20557 let (style, ranges) = highlight.as_ref();
20558 (
20559 *style,
20560 ranges
20561 .iter()
20562 .map(|range| range.clone().to_display_points(&snapshot))
20563 .collect(),
20564 )
20565 })
20566 .collect()
20567 })
20568 }
20569
20570 #[cfg(feature = "test-support")]
20571 pub fn all_text_background_highlights(
20572 &self,
20573 window: &mut Window,
20574 cx: &mut Context<Self>,
20575 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20576 let snapshot = self.snapshot(window, cx);
20577 let buffer = &snapshot.buffer_snapshot();
20578 let start = buffer.anchor_before(0);
20579 let end = buffer.anchor_after(buffer.len());
20580 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20581 }
20582
20583 #[cfg(any(test, feature = "test-support"))]
20584 pub fn sorted_background_highlights_in_range(
20585 &self,
20586 search_range: Range<Anchor>,
20587 display_snapshot: &DisplaySnapshot,
20588 theme: &Theme,
20589 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20590 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20591 res.sort_by(|a, b| {
20592 a.0.start
20593 .cmp(&b.0.start)
20594 .then_with(|| a.0.end.cmp(&b.0.end))
20595 .then_with(|| a.1.cmp(&b.1))
20596 });
20597 res
20598 }
20599
20600 #[cfg(feature = "test-support")]
20601 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20602 let snapshot = self.buffer().read(cx).snapshot(cx);
20603
20604 let highlights = self
20605 .background_highlights
20606 .get(&HighlightKey::Type(TypeId::of::<
20607 items::BufferSearchHighlights,
20608 >()));
20609
20610 if let Some((_color, ranges)) = highlights {
20611 ranges
20612 .iter()
20613 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20614 .collect_vec()
20615 } else {
20616 vec![]
20617 }
20618 }
20619
20620 fn document_highlights_for_position<'a>(
20621 &'a self,
20622 position: Anchor,
20623 buffer: &'a MultiBufferSnapshot,
20624 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20625 let read_highlights = self
20626 .background_highlights
20627 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20628 .map(|h| &h.1);
20629 let write_highlights = self
20630 .background_highlights
20631 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20632 .map(|h| &h.1);
20633 let left_position = position.bias_left(buffer);
20634 let right_position = position.bias_right(buffer);
20635 read_highlights
20636 .into_iter()
20637 .chain(write_highlights)
20638 .flat_map(move |ranges| {
20639 let start_ix = match ranges.binary_search_by(|probe| {
20640 let cmp = probe.end.cmp(&left_position, buffer);
20641 if cmp.is_ge() {
20642 Ordering::Greater
20643 } else {
20644 Ordering::Less
20645 }
20646 }) {
20647 Ok(i) | Err(i) => i,
20648 };
20649
20650 ranges[start_ix..]
20651 .iter()
20652 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20653 })
20654 }
20655
20656 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20657 self.background_highlights
20658 .get(&HighlightKey::Type(TypeId::of::<T>()))
20659 .is_some_and(|(_, highlights)| !highlights.is_empty())
20660 }
20661
20662 /// Returns all background highlights for a given range.
20663 ///
20664 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20665 pub fn background_highlights_in_range(
20666 &self,
20667 search_range: Range<Anchor>,
20668 display_snapshot: &DisplaySnapshot,
20669 theme: &Theme,
20670 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20671 let mut results = Vec::new();
20672 for (color_fetcher, ranges) in self.background_highlights.values() {
20673 let color = color_fetcher(theme);
20674 let start_ix = match ranges.binary_search_by(|probe| {
20675 let cmp = probe
20676 .end
20677 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20678 if cmp.is_gt() {
20679 Ordering::Greater
20680 } else {
20681 Ordering::Less
20682 }
20683 }) {
20684 Ok(i) | Err(i) => i,
20685 };
20686 for range in &ranges[start_ix..] {
20687 if range
20688 .start
20689 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20690 .is_ge()
20691 {
20692 break;
20693 }
20694
20695 let start = range.start.to_display_point(display_snapshot);
20696 let end = range.end.to_display_point(display_snapshot);
20697 results.push((start..end, color))
20698 }
20699 }
20700 results
20701 }
20702
20703 pub fn gutter_highlights_in_range(
20704 &self,
20705 search_range: Range<Anchor>,
20706 display_snapshot: &DisplaySnapshot,
20707 cx: &App,
20708 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20709 let mut results = Vec::new();
20710 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20711 let color = color_fetcher(cx);
20712 let start_ix = match ranges.binary_search_by(|probe| {
20713 let cmp = probe
20714 .end
20715 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20716 if cmp.is_gt() {
20717 Ordering::Greater
20718 } else {
20719 Ordering::Less
20720 }
20721 }) {
20722 Ok(i) | Err(i) => i,
20723 };
20724 for range in &ranges[start_ix..] {
20725 if range
20726 .start
20727 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20728 .is_ge()
20729 {
20730 break;
20731 }
20732
20733 let start = range.start.to_display_point(display_snapshot);
20734 let end = range.end.to_display_point(display_snapshot);
20735 results.push((start..end, color))
20736 }
20737 }
20738 results
20739 }
20740
20741 /// Get the text ranges corresponding to the redaction query
20742 pub fn redacted_ranges(
20743 &self,
20744 search_range: Range<Anchor>,
20745 display_snapshot: &DisplaySnapshot,
20746 cx: &App,
20747 ) -> Vec<Range<DisplayPoint>> {
20748 display_snapshot
20749 .buffer_snapshot()
20750 .redacted_ranges(search_range, |file| {
20751 if let Some(file) = file {
20752 file.is_private()
20753 && EditorSettings::get(
20754 Some(SettingsLocation {
20755 worktree_id: file.worktree_id(cx),
20756 path: file.path().as_ref(),
20757 }),
20758 cx,
20759 )
20760 .redact_private_values
20761 } else {
20762 false
20763 }
20764 })
20765 .map(|range| {
20766 range.start.to_display_point(display_snapshot)
20767 ..range.end.to_display_point(display_snapshot)
20768 })
20769 .collect()
20770 }
20771
20772 pub fn highlight_text_key<T: 'static>(
20773 &mut self,
20774 key: usize,
20775 ranges: Vec<Range<Anchor>>,
20776 style: HighlightStyle,
20777 cx: &mut Context<Self>,
20778 ) {
20779 self.display_map.update(cx, |map, _| {
20780 map.highlight_text(
20781 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20782 ranges,
20783 style,
20784 );
20785 });
20786 cx.notify();
20787 }
20788
20789 pub fn highlight_text<T: 'static>(
20790 &mut self,
20791 ranges: Vec<Range<Anchor>>,
20792 style: HighlightStyle,
20793 cx: &mut Context<Self>,
20794 ) {
20795 self.display_map.update(cx, |map, _| {
20796 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20797 });
20798 cx.notify();
20799 }
20800
20801 pub fn text_highlights<'a, T: 'static>(
20802 &'a self,
20803 cx: &'a App,
20804 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20805 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20806 }
20807
20808 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20809 let cleared = self
20810 .display_map
20811 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20812 if cleared {
20813 cx.notify();
20814 }
20815 }
20816
20817 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20818 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20819 && self.focus_handle.is_focused(window)
20820 }
20821
20822 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20823 self.show_cursor_when_unfocused = is_enabled;
20824 cx.notify();
20825 }
20826
20827 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20828 cx.notify();
20829 }
20830
20831 fn on_debug_session_event(
20832 &mut self,
20833 _session: Entity<Session>,
20834 event: &SessionEvent,
20835 cx: &mut Context<Self>,
20836 ) {
20837 if let SessionEvent::InvalidateInlineValue = event {
20838 self.refresh_inline_values(cx);
20839 }
20840 }
20841
20842 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20843 let Some(project) = self.project.clone() else {
20844 return;
20845 };
20846
20847 if !self.inline_value_cache.enabled {
20848 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20849 self.splice_inlays(&inlays, Vec::new(), cx);
20850 return;
20851 }
20852
20853 let current_execution_position = self
20854 .highlighted_rows
20855 .get(&TypeId::of::<ActiveDebugLine>())
20856 .and_then(|lines| lines.last().map(|line| line.range.end));
20857
20858 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20859 let inline_values = editor
20860 .update(cx, |editor, cx| {
20861 let Some(current_execution_position) = current_execution_position else {
20862 return Some(Task::ready(Ok(Vec::new())));
20863 };
20864
20865 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20866 let snapshot = buffer.snapshot(cx);
20867
20868 let excerpt = snapshot.excerpt_containing(
20869 current_execution_position..current_execution_position,
20870 )?;
20871
20872 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20873 })?;
20874
20875 let range =
20876 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20877
20878 project.inline_values(buffer, range, cx)
20879 })
20880 .ok()
20881 .flatten()?
20882 .await
20883 .context("refreshing debugger inlays")
20884 .log_err()?;
20885
20886 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20887
20888 for (buffer_id, inline_value) in inline_values
20889 .into_iter()
20890 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20891 {
20892 buffer_inline_values
20893 .entry(buffer_id)
20894 .or_default()
20895 .push(inline_value);
20896 }
20897
20898 editor
20899 .update(cx, |editor, cx| {
20900 let snapshot = editor.buffer.read(cx).snapshot(cx);
20901 let mut new_inlays = Vec::default();
20902
20903 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20904 let buffer_id = buffer_snapshot.remote_id();
20905 buffer_inline_values
20906 .get(&buffer_id)
20907 .into_iter()
20908 .flatten()
20909 .for_each(|hint| {
20910 let inlay = Inlay::debugger(
20911 post_inc(&mut editor.next_inlay_id),
20912 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20913 hint.text(),
20914 );
20915 if !inlay.text().chars().contains(&'\n') {
20916 new_inlays.push(inlay);
20917 }
20918 });
20919 }
20920
20921 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20922 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20923
20924 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20925 })
20926 .ok()?;
20927 Some(())
20928 });
20929 }
20930
20931 fn on_buffer_event(
20932 &mut self,
20933 multibuffer: &Entity<MultiBuffer>,
20934 event: &multi_buffer::Event,
20935 window: &mut Window,
20936 cx: &mut Context<Self>,
20937 ) {
20938 match event {
20939 multi_buffer::Event::Edited { edited_buffer } => {
20940 self.scrollbar_marker_state.dirty = true;
20941 self.active_indent_guides_state.dirty = true;
20942 self.refresh_active_diagnostics(cx);
20943 self.refresh_code_actions(window, cx);
20944 self.refresh_selected_text_highlights(true, window, cx);
20945 self.refresh_single_line_folds(window, cx);
20946 self.refresh_matching_bracket_highlights(window, cx);
20947 if self.has_active_edit_prediction() {
20948 self.update_visible_edit_prediction(window, cx);
20949 }
20950
20951 if let Some(buffer) = edited_buffer {
20952 if buffer.read(cx).file().is_none() {
20953 cx.emit(EditorEvent::TitleChanged);
20954 }
20955
20956 if self.project.is_some() {
20957 let buffer_id = buffer.read(cx).remote_id();
20958 self.register_buffer(buffer_id, cx);
20959 self.update_lsp_data(Some(buffer_id), window, cx);
20960 self.refresh_inlay_hints(
20961 InlayHintRefreshReason::BufferEdited(buffer_id),
20962 cx,
20963 );
20964 }
20965 }
20966
20967 cx.emit(EditorEvent::BufferEdited);
20968 cx.emit(SearchEvent::MatchesInvalidated);
20969
20970 let Some(project) = &self.project else { return };
20971 let (telemetry, is_via_ssh) = {
20972 let project = project.read(cx);
20973 let telemetry = project.client().telemetry().clone();
20974 let is_via_ssh = project.is_via_remote_server();
20975 (telemetry, is_via_ssh)
20976 };
20977 telemetry.log_edit_event("editor", is_via_ssh);
20978 }
20979 multi_buffer::Event::ExcerptsAdded {
20980 buffer,
20981 predecessor,
20982 excerpts,
20983 } => {
20984 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20985 let buffer_id = buffer.read(cx).remote_id();
20986 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20987 && let Some(project) = &self.project
20988 {
20989 update_uncommitted_diff_for_buffer(
20990 cx.entity(),
20991 project,
20992 [buffer.clone()],
20993 self.buffer.clone(),
20994 cx,
20995 )
20996 .detach();
20997 }
20998 self.update_lsp_data(Some(buffer_id), window, cx);
20999 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21000 cx.emit(EditorEvent::ExcerptsAdded {
21001 buffer: buffer.clone(),
21002 predecessor: *predecessor,
21003 excerpts: excerpts.clone(),
21004 });
21005 }
21006 multi_buffer::Event::ExcerptsRemoved {
21007 ids,
21008 removed_buffer_ids,
21009 } => {
21010 if let Some(inlay_hints) = &mut self.inlay_hints {
21011 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21012 }
21013 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21014 for buffer_id in removed_buffer_ids {
21015 self.registered_buffers.remove(buffer_id);
21016 }
21017 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21018 cx.emit(EditorEvent::ExcerptsRemoved {
21019 ids: ids.clone(),
21020 removed_buffer_ids: removed_buffer_ids.clone(),
21021 });
21022 }
21023 multi_buffer::Event::ExcerptsEdited {
21024 excerpt_ids,
21025 buffer_ids,
21026 } => {
21027 self.display_map.update(cx, |map, cx| {
21028 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21029 });
21030 cx.emit(EditorEvent::ExcerptsEdited {
21031 ids: excerpt_ids.clone(),
21032 });
21033 }
21034 multi_buffer::Event::ExcerptsExpanded { ids } => {
21035 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21036 self.refresh_document_highlights(cx);
21037 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21038 }
21039 multi_buffer::Event::Reparsed(buffer_id) => {
21040 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21041 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21042
21043 cx.emit(EditorEvent::Reparsed(*buffer_id));
21044 }
21045 multi_buffer::Event::DiffHunksToggled => {
21046 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21047 }
21048 multi_buffer::Event::LanguageChanged(buffer_id) => {
21049 self.registered_buffers.remove(&buffer_id);
21050 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21051 cx.emit(EditorEvent::Reparsed(*buffer_id));
21052 cx.notify();
21053 }
21054 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21055 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21056 multi_buffer::Event::FileHandleChanged
21057 | multi_buffer::Event::Reloaded
21058 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21059 multi_buffer::Event::DiagnosticsUpdated => {
21060 self.update_diagnostics_state(window, cx);
21061 }
21062 _ => {}
21063 };
21064 }
21065
21066 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21067 if !self.diagnostics_enabled() {
21068 return;
21069 }
21070 self.refresh_active_diagnostics(cx);
21071 self.refresh_inline_diagnostics(true, window, cx);
21072 self.scrollbar_marker_state.dirty = true;
21073 cx.notify();
21074 }
21075
21076 pub fn start_temporary_diff_override(&mut self) {
21077 self.load_diff_task.take();
21078 self.temporary_diff_override = true;
21079 }
21080
21081 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21082 self.temporary_diff_override = false;
21083 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21084 self.buffer.update(cx, |buffer, cx| {
21085 buffer.set_all_diff_hunks_collapsed(cx);
21086 });
21087
21088 if let Some(project) = self.project.clone() {
21089 self.load_diff_task = Some(
21090 update_uncommitted_diff_for_buffer(
21091 cx.entity(),
21092 &project,
21093 self.buffer.read(cx).all_buffers(),
21094 self.buffer.clone(),
21095 cx,
21096 )
21097 .shared(),
21098 );
21099 }
21100 }
21101
21102 fn on_display_map_changed(
21103 &mut self,
21104 _: Entity<DisplayMap>,
21105 _: &mut Window,
21106 cx: &mut Context<Self>,
21107 ) {
21108 cx.notify();
21109 }
21110
21111 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21112 if self.diagnostics_enabled() {
21113 let new_severity = EditorSettings::get_global(cx)
21114 .diagnostics_max_severity
21115 .unwrap_or(DiagnosticSeverity::Hint);
21116 self.set_max_diagnostics_severity(new_severity, cx);
21117 }
21118 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21119 self.update_edit_prediction_settings(cx);
21120 self.refresh_edit_prediction(true, false, window, cx);
21121 self.refresh_inline_values(cx);
21122 self.refresh_inlay_hints(
21123 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21124 self.selections.newest_anchor().head(),
21125 &self.buffer.read(cx).snapshot(cx),
21126 cx,
21127 )),
21128 cx,
21129 );
21130
21131 let old_cursor_shape = self.cursor_shape;
21132 let old_show_breadcrumbs = self.show_breadcrumbs;
21133
21134 {
21135 let editor_settings = EditorSettings::get_global(cx);
21136 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21137 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21138 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21139 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21140 }
21141
21142 if old_cursor_shape != self.cursor_shape {
21143 cx.emit(EditorEvent::CursorShapeChanged);
21144 }
21145
21146 if old_show_breadcrumbs != self.show_breadcrumbs {
21147 cx.emit(EditorEvent::BreadcrumbsChanged);
21148 }
21149
21150 let project_settings = ProjectSettings::get_global(cx);
21151 self.serialize_dirty_buffers =
21152 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21153
21154 if self.mode.is_full() {
21155 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21156 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21157 if self.show_inline_diagnostics != show_inline_diagnostics {
21158 self.show_inline_diagnostics = show_inline_diagnostics;
21159 self.refresh_inline_diagnostics(false, window, cx);
21160 }
21161
21162 if self.git_blame_inline_enabled != inline_blame_enabled {
21163 self.toggle_git_blame_inline_internal(false, window, cx);
21164 }
21165
21166 let minimap_settings = EditorSettings::get_global(cx).minimap;
21167 if self.minimap_visibility != MinimapVisibility::Disabled {
21168 if self.minimap_visibility.settings_visibility()
21169 != minimap_settings.minimap_enabled()
21170 {
21171 self.set_minimap_visibility(
21172 MinimapVisibility::for_mode(self.mode(), cx),
21173 window,
21174 cx,
21175 );
21176 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21177 minimap_entity.update(cx, |minimap_editor, cx| {
21178 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21179 })
21180 }
21181 }
21182 }
21183
21184 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21185 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21186 }) {
21187 if !inlay_splice.is_empty() {
21188 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21189 }
21190 self.refresh_colors_for_visible_range(None, window, cx);
21191 }
21192
21193 cx.notify();
21194 }
21195
21196 pub fn set_searchable(&mut self, searchable: bool) {
21197 self.searchable = searchable;
21198 }
21199
21200 pub fn searchable(&self) -> bool {
21201 self.searchable
21202 }
21203
21204 pub fn open_excerpts_in_split(
21205 &mut self,
21206 _: &OpenExcerptsSplit,
21207 window: &mut Window,
21208 cx: &mut Context<Self>,
21209 ) {
21210 self.open_excerpts_common(None, true, window, cx)
21211 }
21212
21213 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21214 self.open_excerpts_common(None, false, window, cx)
21215 }
21216
21217 fn open_excerpts_common(
21218 &mut self,
21219 jump_data: Option<JumpData>,
21220 split: bool,
21221 window: &mut Window,
21222 cx: &mut Context<Self>,
21223 ) {
21224 let Some(workspace) = self.workspace() else {
21225 cx.propagate();
21226 return;
21227 };
21228
21229 if self.buffer.read(cx).is_singleton() {
21230 cx.propagate();
21231 return;
21232 }
21233
21234 let mut new_selections_by_buffer = HashMap::default();
21235 match &jump_data {
21236 Some(JumpData::MultiBufferPoint {
21237 excerpt_id,
21238 position,
21239 anchor,
21240 line_offset_from_top,
21241 }) => {
21242 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21243 if let Some(buffer) = multi_buffer_snapshot
21244 .buffer_id_for_excerpt(*excerpt_id)
21245 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21246 {
21247 let buffer_snapshot = buffer.read(cx).snapshot();
21248 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21249 language::ToPoint::to_point(anchor, &buffer_snapshot)
21250 } else {
21251 buffer_snapshot.clip_point(*position, Bias::Left)
21252 };
21253 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21254 new_selections_by_buffer.insert(
21255 buffer,
21256 (
21257 vec![jump_to_offset..jump_to_offset],
21258 Some(*line_offset_from_top),
21259 ),
21260 );
21261 }
21262 }
21263 Some(JumpData::MultiBufferRow {
21264 row,
21265 line_offset_from_top,
21266 }) => {
21267 let point = MultiBufferPoint::new(row.0, 0);
21268 if let Some((buffer, buffer_point, _)) =
21269 self.buffer.read(cx).point_to_buffer_point(point, cx)
21270 {
21271 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21272 new_selections_by_buffer
21273 .entry(buffer)
21274 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21275 .0
21276 .push(buffer_offset..buffer_offset)
21277 }
21278 }
21279 None => {
21280 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21281 let multi_buffer = self.buffer.read(cx);
21282 for selection in selections {
21283 for (snapshot, range, _, anchor) in multi_buffer
21284 .snapshot(cx)
21285 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21286 {
21287 if let Some(anchor) = anchor {
21288 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21289 else {
21290 continue;
21291 };
21292 let offset = text::ToOffset::to_offset(
21293 &anchor.text_anchor,
21294 &buffer_handle.read(cx).snapshot(),
21295 );
21296 let range = offset..offset;
21297 new_selections_by_buffer
21298 .entry(buffer_handle)
21299 .or_insert((Vec::new(), None))
21300 .0
21301 .push(range)
21302 } else {
21303 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21304 else {
21305 continue;
21306 };
21307 new_selections_by_buffer
21308 .entry(buffer_handle)
21309 .or_insert((Vec::new(), None))
21310 .0
21311 .push(range)
21312 }
21313 }
21314 }
21315 }
21316 }
21317
21318 new_selections_by_buffer
21319 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21320
21321 if new_selections_by_buffer.is_empty() {
21322 return;
21323 }
21324
21325 // We defer the pane interaction because we ourselves are a workspace item
21326 // and activating a new item causes the pane to call a method on us reentrantly,
21327 // which panics if we're on the stack.
21328 window.defer(cx, move |window, cx| {
21329 workspace.update(cx, |workspace, cx| {
21330 let pane = if split {
21331 workspace.adjacent_pane(window, cx)
21332 } else {
21333 workspace.active_pane().clone()
21334 };
21335
21336 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21337 let editor = buffer
21338 .read(cx)
21339 .file()
21340 .is_none()
21341 .then(|| {
21342 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21343 // so `workspace.open_project_item` will never find them, always opening a new editor.
21344 // Instead, we try to activate the existing editor in the pane first.
21345 let (editor, pane_item_index) =
21346 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21347 let editor = item.downcast::<Editor>()?;
21348 let singleton_buffer =
21349 editor.read(cx).buffer().read(cx).as_singleton()?;
21350 if singleton_buffer == buffer {
21351 Some((editor, i))
21352 } else {
21353 None
21354 }
21355 })?;
21356 pane.update(cx, |pane, cx| {
21357 pane.activate_item(pane_item_index, true, true, window, cx)
21358 });
21359 Some(editor)
21360 })
21361 .flatten()
21362 .unwrap_or_else(|| {
21363 workspace.open_project_item::<Self>(
21364 pane.clone(),
21365 buffer,
21366 true,
21367 true,
21368 window,
21369 cx,
21370 )
21371 });
21372
21373 editor.update(cx, |editor, cx| {
21374 let autoscroll = match scroll_offset {
21375 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21376 None => Autoscroll::newest(),
21377 };
21378 let nav_history = editor.nav_history.take();
21379 editor.change_selections(
21380 SelectionEffects::scroll(autoscroll),
21381 window,
21382 cx,
21383 |s| {
21384 s.select_ranges(ranges);
21385 },
21386 );
21387 editor.nav_history = nav_history;
21388 });
21389 }
21390 })
21391 });
21392 }
21393
21394 // For now, don't allow opening excerpts in buffers that aren't backed by
21395 // regular project files.
21396 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21397 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21398 }
21399
21400 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21401 let snapshot = self.buffer.read(cx).read(cx);
21402 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21403 Some(
21404 ranges
21405 .iter()
21406 .map(move |range| {
21407 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21408 })
21409 .collect(),
21410 )
21411 }
21412
21413 fn selection_replacement_ranges(
21414 &self,
21415 range: Range<OffsetUtf16>,
21416 cx: &mut App,
21417 ) -> Vec<Range<OffsetUtf16>> {
21418 let selections = self
21419 .selections
21420 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21421 let newest_selection = selections
21422 .iter()
21423 .max_by_key(|selection| selection.id)
21424 .unwrap();
21425 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21426 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21427 let snapshot = self.buffer.read(cx).read(cx);
21428 selections
21429 .into_iter()
21430 .map(|mut selection| {
21431 selection.start.0 =
21432 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21433 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21434 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21435 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21436 })
21437 .collect()
21438 }
21439
21440 fn report_editor_event(
21441 &self,
21442 reported_event: ReportEditorEvent,
21443 file_extension: Option<String>,
21444 cx: &App,
21445 ) {
21446 if cfg!(any(test, feature = "test-support")) {
21447 return;
21448 }
21449
21450 let Some(project) = &self.project else { return };
21451
21452 // If None, we are in a file without an extension
21453 let file = self
21454 .buffer
21455 .read(cx)
21456 .as_singleton()
21457 .and_then(|b| b.read(cx).file());
21458 let file_extension = file_extension.or(file
21459 .as_ref()
21460 .and_then(|file| Path::new(file.file_name(cx)).extension())
21461 .and_then(|e| e.to_str())
21462 .map(|a| a.to_string()));
21463
21464 let vim_mode = vim_flavor(cx).is_some();
21465
21466 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21467 let copilot_enabled = edit_predictions_provider
21468 == language::language_settings::EditPredictionProvider::Copilot;
21469 let copilot_enabled_for_language = self
21470 .buffer
21471 .read(cx)
21472 .language_settings(cx)
21473 .show_edit_predictions;
21474
21475 let project = project.read(cx);
21476 let event_type = reported_event.event_type();
21477
21478 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21479 telemetry::event!(
21480 event_type,
21481 type = if auto_saved {"autosave"} else {"manual"},
21482 file_extension,
21483 vim_mode,
21484 copilot_enabled,
21485 copilot_enabled_for_language,
21486 edit_predictions_provider,
21487 is_via_ssh = project.is_via_remote_server(),
21488 );
21489 } else {
21490 telemetry::event!(
21491 event_type,
21492 file_extension,
21493 vim_mode,
21494 copilot_enabled,
21495 copilot_enabled_for_language,
21496 edit_predictions_provider,
21497 is_via_ssh = project.is_via_remote_server(),
21498 );
21499 };
21500 }
21501
21502 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21503 /// with each line being an array of {text, highlight} objects.
21504 fn copy_highlight_json(
21505 &mut self,
21506 _: &CopyHighlightJson,
21507 window: &mut Window,
21508 cx: &mut Context<Self>,
21509 ) {
21510 #[derive(Serialize)]
21511 struct Chunk<'a> {
21512 text: String,
21513 highlight: Option<&'a str>,
21514 }
21515
21516 let snapshot = self.buffer.read(cx).snapshot(cx);
21517 let range = self
21518 .selected_text_range(false, window, cx)
21519 .and_then(|selection| {
21520 if selection.range.is_empty() {
21521 None
21522 } else {
21523 Some(
21524 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21525 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21526 )
21527 }
21528 })
21529 .unwrap_or_else(|| 0..snapshot.len());
21530
21531 let chunks = snapshot.chunks(range, true);
21532 let mut lines = Vec::new();
21533 let mut line: VecDeque<Chunk> = VecDeque::new();
21534
21535 let Some(style) = self.style.as_ref() else {
21536 return;
21537 };
21538
21539 for chunk in chunks {
21540 let highlight = chunk
21541 .syntax_highlight_id
21542 .and_then(|id| id.name(&style.syntax));
21543 let mut chunk_lines = chunk.text.split('\n').peekable();
21544 while let Some(text) = chunk_lines.next() {
21545 let mut merged_with_last_token = false;
21546 if let Some(last_token) = line.back_mut()
21547 && last_token.highlight == highlight
21548 {
21549 last_token.text.push_str(text);
21550 merged_with_last_token = true;
21551 }
21552
21553 if !merged_with_last_token {
21554 line.push_back(Chunk {
21555 text: text.into(),
21556 highlight,
21557 });
21558 }
21559
21560 if chunk_lines.peek().is_some() {
21561 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21562 line.pop_front();
21563 }
21564 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21565 line.pop_back();
21566 }
21567
21568 lines.push(mem::take(&mut line));
21569 }
21570 }
21571 }
21572
21573 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21574 return;
21575 };
21576 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21577 }
21578
21579 pub fn open_context_menu(
21580 &mut self,
21581 _: &OpenContextMenu,
21582 window: &mut Window,
21583 cx: &mut Context<Self>,
21584 ) {
21585 self.request_autoscroll(Autoscroll::newest(), cx);
21586 let position = self
21587 .selections
21588 .newest_display(&self.display_snapshot(cx))
21589 .start;
21590 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21591 }
21592
21593 pub fn replay_insert_event(
21594 &mut self,
21595 text: &str,
21596 relative_utf16_range: Option<Range<isize>>,
21597 window: &mut Window,
21598 cx: &mut Context<Self>,
21599 ) {
21600 if !self.input_enabled {
21601 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21602 return;
21603 }
21604 if let Some(relative_utf16_range) = relative_utf16_range {
21605 let selections = self
21606 .selections
21607 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21608 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21609 let new_ranges = selections.into_iter().map(|range| {
21610 let start = OffsetUtf16(
21611 range
21612 .head()
21613 .0
21614 .saturating_add_signed(relative_utf16_range.start),
21615 );
21616 let end = OffsetUtf16(
21617 range
21618 .head()
21619 .0
21620 .saturating_add_signed(relative_utf16_range.end),
21621 );
21622 start..end
21623 });
21624 s.select_ranges(new_ranges);
21625 });
21626 }
21627
21628 self.handle_input(text, window, cx);
21629 }
21630
21631 pub fn is_focused(&self, window: &Window) -> bool {
21632 self.focus_handle.is_focused(window)
21633 }
21634
21635 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21636 cx.emit(EditorEvent::Focused);
21637
21638 if let Some(descendant) = self
21639 .last_focused_descendant
21640 .take()
21641 .and_then(|descendant| descendant.upgrade())
21642 {
21643 window.focus(&descendant);
21644 } else {
21645 if let Some(blame) = self.blame.as_ref() {
21646 blame.update(cx, GitBlame::focus)
21647 }
21648
21649 self.blink_manager.update(cx, BlinkManager::enable);
21650 self.show_cursor_names(window, cx);
21651 self.buffer.update(cx, |buffer, cx| {
21652 buffer.finalize_last_transaction(cx);
21653 if self.leader_id.is_none() {
21654 buffer.set_active_selections(
21655 &self.selections.disjoint_anchors_arc(),
21656 self.selections.line_mode(),
21657 self.cursor_shape,
21658 cx,
21659 );
21660 }
21661 });
21662 }
21663 }
21664
21665 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21666 cx.emit(EditorEvent::FocusedIn)
21667 }
21668
21669 fn handle_focus_out(
21670 &mut self,
21671 event: FocusOutEvent,
21672 _window: &mut Window,
21673 cx: &mut Context<Self>,
21674 ) {
21675 if event.blurred != self.focus_handle {
21676 self.last_focused_descendant = Some(event.blurred);
21677 }
21678 self.selection_drag_state = SelectionDragState::None;
21679 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21680 }
21681
21682 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21683 self.blink_manager.update(cx, BlinkManager::disable);
21684 self.buffer
21685 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21686
21687 if let Some(blame) = self.blame.as_ref() {
21688 blame.update(cx, GitBlame::blur)
21689 }
21690 if !self.hover_state.focused(window, cx) {
21691 hide_hover(self, cx);
21692 }
21693 if !self
21694 .context_menu
21695 .borrow()
21696 .as_ref()
21697 .is_some_and(|context_menu| context_menu.focused(window, cx))
21698 {
21699 self.hide_context_menu(window, cx);
21700 }
21701 self.take_active_edit_prediction(cx);
21702 cx.emit(EditorEvent::Blurred);
21703 cx.notify();
21704 }
21705
21706 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21707 let mut pending: String = window
21708 .pending_input_keystrokes()
21709 .into_iter()
21710 .flatten()
21711 .filter_map(|keystroke| {
21712 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21713 keystroke.key_char.clone()
21714 } else {
21715 None
21716 }
21717 })
21718 .collect();
21719
21720 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21721 pending = "".to_string();
21722 }
21723
21724 let existing_pending = self
21725 .text_highlights::<PendingInput>(cx)
21726 .map(|(_, ranges)| ranges.to_vec());
21727 if existing_pending.is_none() && pending.is_empty() {
21728 return;
21729 }
21730 let transaction =
21731 self.transact(window, cx, |this, window, cx| {
21732 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21733 let edits = selections
21734 .iter()
21735 .map(|selection| (selection.end..selection.end, pending.clone()));
21736 this.edit(edits, cx);
21737 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21738 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21739 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21740 }));
21741 });
21742 if let Some(existing_ranges) = existing_pending {
21743 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21744 this.edit(edits, cx);
21745 }
21746 });
21747
21748 let snapshot = self.snapshot(window, cx);
21749 let ranges = self
21750 .selections
21751 .all::<usize>(&snapshot.display_snapshot)
21752 .into_iter()
21753 .map(|selection| {
21754 snapshot.buffer_snapshot().anchor_after(selection.end)
21755 ..snapshot
21756 .buffer_snapshot()
21757 .anchor_before(selection.end + pending.len())
21758 })
21759 .collect();
21760
21761 if pending.is_empty() {
21762 self.clear_highlights::<PendingInput>(cx);
21763 } else {
21764 self.highlight_text::<PendingInput>(
21765 ranges,
21766 HighlightStyle {
21767 underline: Some(UnderlineStyle {
21768 thickness: px(1.),
21769 color: None,
21770 wavy: false,
21771 }),
21772 ..Default::default()
21773 },
21774 cx,
21775 );
21776 }
21777
21778 self.ime_transaction = self.ime_transaction.or(transaction);
21779 if let Some(transaction) = self.ime_transaction {
21780 self.buffer.update(cx, |buffer, cx| {
21781 buffer.group_until_transaction(transaction, cx);
21782 });
21783 }
21784
21785 if self.text_highlights::<PendingInput>(cx).is_none() {
21786 self.ime_transaction.take();
21787 }
21788 }
21789
21790 pub fn register_action_renderer(
21791 &mut self,
21792 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21793 ) -> Subscription {
21794 let id = self.next_editor_action_id.post_inc();
21795 self.editor_actions
21796 .borrow_mut()
21797 .insert(id, Box::new(listener));
21798
21799 let editor_actions = self.editor_actions.clone();
21800 Subscription::new(move || {
21801 editor_actions.borrow_mut().remove(&id);
21802 })
21803 }
21804
21805 pub fn register_action<A: Action>(
21806 &mut self,
21807 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21808 ) -> Subscription {
21809 let id = self.next_editor_action_id.post_inc();
21810 let listener = Arc::new(listener);
21811 self.editor_actions.borrow_mut().insert(
21812 id,
21813 Box::new(move |_, window, _| {
21814 let listener = listener.clone();
21815 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21816 let action = action.downcast_ref().unwrap();
21817 if phase == DispatchPhase::Bubble {
21818 listener(action, window, cx)
21819 }
21820 })
21821 }),
21822 );
21823
21824 let editor_actions = self.editor_actions.clone();
21825 Subscription::new(move || {
21826 editor_actions.borrow_mut().remove(&id);
21827 })
21828 }
21829
21830 pub fn file_header_size(&self) -> u32 {
21831 FILE_HEADER_HEIGHT
21832 }
21833
21834 pub fn restore(
21835 &mut self,
21836 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21837 window: &mut Window,
21838 cx: &mut Context<Self>,
21839 ) {
21840 let workspace = self.workspace();
21841 let project = self.project();
21842 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21843 let mut tasks = Vec::new();
21844 for (buffer_id, changes) in revert_changes {
21845 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21846 buffer.update(cx, |buffer, cx| {
21847 buffer.edit(
21848 changes
21849 .into_iter()
21850 .map(|(range, text)| (range, text.to_string())),
21851 None,
21852 cx,
21853 );
21854 });
21855
21856 if let Some(project) =
21857 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21858 {
21859 project.update(cx, |project, cx| {
21860 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21861 })
21862 }
21863 }
21864 }
21865 tasks
21866 });
21867 cx.spawn_in(window, async move |_, cx| {
21868 for (buffer, task) in save_tasks {
21869 let result = task.await;
21870 if result.is_err() {
21871 let Some(path) = buffer
21872 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21873 .ok()
21874 else {
21875 continue;
21876 };
21877 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21878 let Some(task) = cx
21879 .update_window_entity(workspace, |workspace, window, cx| {
21880 workspace
21881 .open_path_preview(path, None, false, false, false, window, cx)
21882 })
21883 .ok()
21884 else {
21885 continue;
21886 };
21887 task.await.log_err();
21888 }
21889 }
21890 }
21891 })
21892 .detach();
21893 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21894 selections.refresh()
21895 });
21896 }
21897
21898 pub fn to_pixel_point(
21899 &self,
21900 source: multi_buffer::Anchor,
21901 editor_snapshot: &EditorSnapshot,
21902 window: &mut Window,
21903 ) -> Option<gpui::Point<Pixels>> {
21904 let source_point = source.to_display_point(editor_snapshot);
21905 self.display_to_pixel_point(source_point, editor_snapshot, window)
21906 }
21907
21908 pub fn display_to_pixel_point(
21909 &self,
21910 source: DisplayPoint,
21911 editor_snapshot: &EditorSnapshot,
21912 window: &mut Window,
21913 ) -> Option<gpui::Point<Pixels>> {
21914 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21915 let text_layout_details = self.text_layout_details(window);
21916 let scroll_top = text_layout_details
21917 .scroll_anchor
21918 .scroll_position(editor_snapshot)
21919 .y;
21920
21921 if source.row().as_f64() < scroll_top.floor() {
21922 return None;
21923 }
21924 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21925 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21926 Some(gpui::Point::new(source_x, source_y))
21927 }
21928
21929 pub fn has_visible_completions_menu(&self) -> bool {
21930 !self.edit_prediction_preview_is_active()
21931 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21932 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21933 })
21934 }
21935
21936 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21937 if self.mode.is_minimap() {
21938 return;
21939 }
21940 self.addons
21941 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21942 }
21943
21944 pub fn unregister_addon<T: Addon>(&mut self) {
21945 self.addons.remove(&std::any::TypeId::of::<T>());
21946 }
21947
21948 pub fn addon<T: Addon>(&self) -> Option<&T> {
21949 let type_id = std::any::TypeId::of::<T>();
21950 self.addons
21951 .get(&type_id)
21952 .and_then(|item| item.to_any().downcast_ref::<T>())
21953 }
21954
21955 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21956 let type_id = std::any::TypeId::of::<T>();
21957 self.addons
21958 .get_mut(&type_id)
21959 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21960 }
21961
21962 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21963 let text_layout_details = self.text_layout_details(window);
21964 let style = &text_layout_details.editor_style;
21965 let font_id = window.text_system().resolve_font(&style.text.font());
21966 let font_size = style.text.font_size.to_pixels(window.rem_size());
21967 let line_height = style.text.line_height_in_pixels(window.rem_size());
21968 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21969 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21970
21971 CharacterDimensions {
21972 em_width,
21973 em_advance,
21974 line_height,
21975 }
21976 }
21977
21978 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21979 self.load_diff_task.clone()
21980 }
21981
21982 fn read_metadata_from_db(
21983 &mut self,
21984 item_id: u64,
21985 workspace_id: WorkspaceId,
21986 window: &mut Window,
21987 cx: &mut Context<Editor>,
21988 ) {
21989 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21990 && !self.mode.is_minimap()
21991 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21992 {
21993 let buffer_snapshot = OnceCell::new();
21994
21995 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21996 && !folds.is_empty()
21997 {
21998 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21999 self.fold_ranges(
22000 folds
22001 .into_iter()
22002 .map(|(start, end)| {
22003 snapshot.clip_offset(start, Bias::Left)
22004 ..snapshot.clip_offset(end, Bias::Right)
22005 })
22006 .collect(),
22007 false,
22008 window,
22009 cx,
22010 );
22011 }
22012
22013 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22014 && !selections.is_empty()
22015 {
22016 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22017 // skip adding the initial selection to selection history
22018 self.selection_history.mode = SelectionHistoryMode::Skipping;
22019 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22020 s.select_ranges(selections.into_iter().map(|(start, end)| {
22021 snapshot.clip_offset(start, Bias::Left)
22022 ..snapshot.clip_offset(end, Bias::Right)
22023 }));
22024 });
22025 self.selection_history.mode = SelectionHistoryMode::Normal;
22026 };
22027 }
22028
22029 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22030 }
22031
22032 fn update_lsp_data(
22033 &mut self,
22034 for_buffer: Option<BufferId>,
22035 window: &mut Window,
22036 cx: &mut Context<'_, Self>,
22037 ) {
22038 self.pull_diagnostics(for_buffer, window, cx);
22039 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22040 }
22041
22042 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22043 if self.ignore_lsp_data() {
22044 return;
22045 }
22046 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22047 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22048 }
22049 }
22050
22051 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22052 if !self.registered_buffers.contains_key(&buffer_id)
22053 && let Some(project) = self.project.as_ref()
22054 {
22055 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22056 project.update(cx, |project, cx| {
22057 self.registered_buffers.insert(
22058 buffer_id,
22059 project.register_buffer_with_language_servers(&buffer, cx),
22060 );
22061 });
22062 } else {
22063 self.registered_buffers.remove(&buffer_id);
22064 }
22065 }
22066 }
22067
22068 fn ignore_lsp_data(&self) -> bool {
22069 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22070 // skip any LSP updates for it.
22071 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22072 }
22073}
22074
22075fn edit_for_markdown_paste<'a>(
22076 buffer: &MultiBufferSnapshot,
22077 range: Range<usize>,
22078 to_insert: &'a str,
22079 url: Option<url::Url>,
22080) -> (Range<usize>, Cow<'a, str>) {
22081 if url.is_none() {
22082 return (range, Cow::Borrowed(to_insert));
22083 };
22084
22085 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22086
22087 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22088 Cow::Borrowed(to_insert)
22089 } else {
22090 Cow::Owned(format!("[{old_text}]({to_insert})"))
22091 };
22092 (range, new_text)
22093}
22094
22095#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22096pub enum VimFlavor {
22097 Vim,
22098 Helix,
22099}
22100
22101pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22102 if vim_mode_setting::HelixModeSetting::try_get(cx)
22103 .map(|helix_mode| helix_mode.0)
22104 .unwrap_or(false)
22105 {
22106 Some(VimFlavor::Helix)
22107 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22108 .map(|vim_mode| vim_mode.0)
22109 .unwrap_or(false)
22110 {
22111 Some(VimFlavor::Vim)
22112 } else {
22113 None // neither vim nor helix mode
22114 }
22115}
22116
22117fn process_completion_for_edit(
22118 completion: &Completion,
22119 intent: CompletionIntent,
22120 buffer: &Entity<Buffer>,
22121 cursor_position: &text::Anchor,
22122 cx: &mut Context<Editor>,
22123) -> CompletionEdit {
22124 let buffer = buffer.read(cx);
22125 let buffer_snapshot = buffer.snapshot();
22126 let (snippet, new_text) = if completion.is_snippet() {
22127 let mut snippet_source = completion.new_text.clone();
22128 // Workaround for typescript language server issues so that methods don't expand within
22129 // strings and functions with type expressions. The previous point is used because the query
22130 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22131 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22132 let previous_point = if previous_point.column > 0 {
22133 cursor_position.to_previous_offset(&buffer_snapshot)
22134 } else {
22135 cursor_position.to_offset(&buffer_snapshot)
22136 };
22137 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22138 && scope.prefers_label_for_snippet_in_completion()
22139 && let Some(label) = completion.label()
22140 && matches!(
22141 completion.kind(),
22142 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22143 )
22144 {
22145 snippet_source = label;
22146 }
22147 match Snippet::parse(&snippet_source).log_err() {
22148 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22149 None => (None, completion.new_text.clone()),
22150 }
22151 } else {
22152 (None, completion.new_text.clone())
22153 };
22154
22155 let mut range_to_replace = {
22156 let replace_range = &completion.replace_range;
22157 if let CompletionSource::Lsp {
22158 insert_range: Some(insert_range),
22159 ..
22160 } = &completion.source
22161 {
22162 debug_assert_eq!(
22163 insert_range.start, replace_range.start,
22164 "insert_range and replace_range should start at the same position"
22165 );
22166 debug_assert!(
22167 insert_range
22168 .start
22169 .cmp(cursor_position, &buffer_snapshot)
22170 .is_le(),
22171 "insert_range should start before or at cursor position"
22172 );
22173 debug_assert!(
22174 replace_range
22175 .start
22176 .cmp(cursor_position, &buffer_snapshot)
22177 .is_le(),
22178 "replace_range should start before or at cursor position"
22179 );
22180
22181 let should_replace = match intent {
22182 CompletionIntent::CompleteWithInsert => false,
22183 CompletionIntent::CompleteWithReplace => true,
22184 CompletionIntent::Complete | CompletionIntent::Compose => {
22185 let insert_mode =
22186 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22187 .completions
22188 .lsp_insert_mode;
22189 match insert_mode {
22190 LspInsertMode::Insert => false,
22191 LspInsertMode::Replace => true,
22192 LspInsertMode::ReplaceSubsequence => {
22193 let mut text_to_replace = buffer.chars_for_range(
22194 buffer.anchor_before(replace_range.start)
22195 ..buffer.anchor_after(replace_range.end),
22196 );
22197 let mut current_needle = text_to_replace.next();
22198 for haystack_ch in completion.label.text.chars() {
22199 if let Some(needle_ch) = current_needle
22200 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22201 {
22202 current_needle = text_to_replace.next();
22203 }
22204 }
22205 current_needle.is_none()
22206 }
22207 LspInsertMode::ReplaceSuffix => {
22208 if replace_range
22209 .end
22210 .cmp(cursor_position, &buffer_snapshot)
22211 .is_gt()
22212 {
22213 let range_after_cursor = *cursor_position..replace_range.end;
22214 let text_after_cursor = buffer
22215 .text_for_range(
22216 buffer.anchor_before(range_after_cursor.start)
22217 ..buffer.anchor_after(range_after_cursor.end),
22218 )
22219 .collect::<String>()
22220 .to_ascii_lowercase();
22221 completion
22222 .label
22223 .text
22224 .to_ascii_lowercase()
22225 .ends_with(&text_after_cursor)
22226 } else {
22227 true
22228 }
22229 }
22230 }
22231 }
22232 };
22233
22234 if should_replace {
22235 replace_range.clone()
22236 } else {
22237 insert_range.clone()
22238 }
22239 } else {
22240 replace_range.clone()
22241 }
22242 };
22243
22244 if range_to_replace
22245 .end
22246 .cmp(cursor_position, &buffer_snapshot)
22247 .is_lt()
22248 {
22249 range_to_replace.end = *cursor_position;
22250 }
22251
22252 CompletionEdit {
22253 new_text,
22254 replace_range: range_to_replace.to_offset(buffer),
22255 snippet,
22256 }
22257}
22258
22259struct CompletionEdit {
22260 new_text: String,
22261 replace_range: Range<usize>,
22262 snippet: Option<Snippet>,
22263}
22264
22265fn insert_extra_newline_brackets(
22266 buffer: &MultiBufferSnapshot,
22267 range: Range<usize>,
22268 language: &language::LanguageScope,
22269) -> bool {
22270 let leading_whitespace_len = buffer
22271 .reversed_chars_at(range.start)
22272 .take_while(|c| c.is_whitespace() && *c != '\n')
22273 .map(|c| c.len_utf8())
22274 .sum::<usize>();
22275 let trailing_whitespace_len = buffer
22276 .chars_at(range.end)
22277 .take_while(|c| c.is_whitespace() && *c != '\n')
22278 .map(|c| c.len_utf8())
22279 .sum::<usize>();
22280 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22281
22282 language.brackets().any(|(pair, enabled)| {
22283 let pair_start = pair.start.trim_end();
22284 let pair_end = pair.end.trim_start();
22285
22286 enabled
22287 && pair.newline
22288 && buffer.contains_str_at(range.end, pair_end)
22289 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22290 })
22291}
22292
22293fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22294 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22295 [(buffer, range, _)] => (*buffer, range.clone()),
22296 _ => return false,
22297 };
22298 let pair = {
22299 let mut result: Option<BracketMatch> = None;
22300
22301 for pair in buffer
22302 .all_bracket_ranges(range.clone())
22303 .filter(move |pair| {
22304 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22305 })
22306 {
22307 let len = pair.close_range.end - pair.open_range.start;
22308
22309 if let Some(existing) = &result {
22310 let existing_len = existing.close_range.end - existing.open_range.start;
22311 if len > existing_len {
22312 continue;
22313 }
22314 }
22315
22316 result = Some(pair);
22317 }
22318
22319 result
22320 };
22321 let Some(pair) = pair else {
22322 return false;
22323 };
22324 pair.newline_only
22325 && buffer
22326 .chars_for_range(pair.open_range.end..range.start)
22327 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22328 .all(|c| c.is_whitespace() && c != '\n')
22329}
22330
22331fn update_uncommitted_diff_for_buffer(
22332 editor: Entity<Editor>,
22333 project: &Entity<Project>,
22334 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22335 buffer: Entity<MultiBuffer>,
22336 cx: &mut App,
22337) -> Task<()> {
22338 let mut tasks = Vec::new();
22339 project.update(cx, |project, cx| {
22340 for buffer in buffers {
22341 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22342 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22343 }
22344 }
22345 });
22346 cx.spawn(async move |cx| {
22347 let diffs = future::join_all(tasks).await;
22348 if editor
22349 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22350 .unwrap_or(false)
22351 {
22352 return;
22353 }
22354
22355 buffer
22356 .update(cx, |buffer, cx| {
22357 for diff in diffs.into_iter().flatten() {
22358 buffer.add_diff(diff, cx);
22359 }
22360 })
22361 .ok();
22362 })
22363}
22364
22365fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22366 let tab_size = tab_size.get() as usize;
22367 let mut width = offset;
22368
22369 for ch in text.chars() {
22370 width += if ch == '\t' {
22371 tab_size - (width % tab_size)
22372 } else {
22373 1
22374 };
22375 }
22376
22377 width - offset
22378}
22379
22380#[cfg(test)]
22381mod tests {
22382 use super::*;
22383
22384 #[test]
22385 fn test_string_size_with_expanded_tabs() {
22386 let nz = |val| NonZeroU32::new(val).unwrap();
22387 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22388 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22389 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22390 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22391 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22392 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22393 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22394 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22395 }
22396}
22397
22398/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22399struct WordBreakingTokenizer<'a> {
22400 input: &'a str,
22401}
22402
22403impl<'a> WordBreakingTokenizer<'a> {
22404 fn new(input: &'a str) -> Self {
22405 Self { input }
22406 }
22407}
22408
22409fn is_char_ideographic(ch: char) -> bool {
22410 use unicode_script::Script::*;
22411 use unicode_script::UnicodeScript;
22412 matches!(ch.script(), Han | Tangut | Yi)
22413}
22414
22415fn is_grapheme_ideographic(text: &str) -> bool {
22416 text.chars().any(is_char_ideographic)
22417}
22418
22419fn is_grapheme_whitespace(text: &str) -> bool {
22420 text.chars().any(|x| x.is_whitespace())
22421}
22422
22423fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22424 text.chars()
22425 .next()
22426 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22427}
22428
22429#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22430enum WordBreakToken<'a> {
22431 Word { token: &'a str, grapheme_len: usize },
22432 InlineWhitespace { token: &'a str, grapheme_len: usize },
22433 Newline,
22434}
22435
22436impl<'a> Iterator for WordBreakingTokenizer<'a> {
22437 /// Yields a span, the count of graphemes in the token, and whether it was
22438 /// whitespace. Note that it also breaks at word boundaries.
22439 type Item = WordBreakToken<'a>;
22440
22441 fn next(&mut self) -> Option<Self::Item> {
22442 use unicode_segmentation::UnicodeSegmentation;
22443 if self.input.is_empty() {
22444 return None;
22445 }
22446
22447 let mut iter = self.input.graphemes(true).peekable();
22448 let mut offset = 0;
22449 let mut grapheme_len = 0;
22450 if let Some(first_grapheme) = iter.next() {
22451 let is_newline = first_grapheme == "\n";
22452 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22453 offset += first_grapheme.len();
22454 grapheme_len += 1;
22455 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22456 if let Some(grapheme) = iter.peek().copied()
22457 && should_stay_with_preceding_ideograph(grapheme)
22458 {
22459 offset += grapheme.len();
22460 grapheme_len += 1;
22461 }
22462 } else {
22463 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22464 let mut next_word_bound = words.peek().copied();
22465 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22466 next_word_bound = words.next();
22467 }
22468 while let Some(grapheme) = iter.peek().copied() {
22469 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22470 break;
22471 };
22472 if is_grapheme_whitespace(grapheme) != is_whitespace
22473 || (grapheme == "\n") != is_newline
22474 {
22475 break;
22476 };
22477 offset += grapheme.len();
22478 grapheme_len += 1;
22479 iter.next();
22480 }
22481 }
22482 let token = &self.input[..offset];
22483 self.input = &self.input[offset..];
22484 if token == "\n" {
22485 Some(WordBreakToken::Newline)
22486 } else if is_whitespace {
22487 Some(WordBreakToken::InlineWhitespace {
22488 token,
22489 grapheme_len,
22490 })
22491 } else {
22492 Some(WordBreakToken::Word {
22493 token,
22494 grapheme_len,
22495 })
22496 }
22497 } else {
22498 None
22499 }
22500 }
22501}
22502
22503#[test]
22504fn test_word_breaking_tokenizer() {
22505 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22506 ("", &[]),
22507 (" ", &[whitespace(" ", 2)]),
22508 ("Ʒ", &[word("Ʒ", 1)]),
22509 ("Ǽ", &[word("Ǽ", 1)]),
22510 ("⋑", &[word("⋑", 1)]),
22511 ("⋑⋑", &[word("⋑⋑", 2)]),
22512 (
22513 "原理,进而",
22514 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22515 ),
22516 (
22517 "hello world",
22518 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22519 ),
22520 (
22521 "hello, world",
22522 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22523 ),
22524 (
22525 " hello world",
22526 &[
22527 whitespace(" ", 2),
22528 word("hello", 5),
22529 whitespace(" ", 1),
22530 word("world", 5),
22531 ],
22532 ),
22533 (
22534 "这是什么 \n 钢笔",
22535 &[
22536 word("这", 1),
22537 word("是", 1),
22538 word("什", 1),
22539 word("么", 1),
22540 whitespace(" ", 1),
22541 newline(),
22542 whitespace(" ", 1),
22543 word("钢", 1),
22544 word("笔", 1),
22545 ],
22546 ),
22547 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22548 ];
22549
22550 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22551 WordBreakToken::Word {
22552 token,
22553 grapheme_len,
22554 }
22555 }
22556
22557 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22558 WordBreakToken::InlineWhitespace {
22559 token,
22560 grapheme_len,
22561 }
22562 }
22563
22564 fn newline() -> WordBreakToken<'static> {
22565 WordBreakToken::Newline
22566 }
22567
22568 for (input, result) in tests {
22569 assert_eq!(
22570 WordBreakingTokenizer::new(input)
22571 .collect::<Vec<_>>()
22572 .as_slice(),
22573 *result,
22574 );
22575 }
22576}
22577
22578fn wrap_with_prefix(
22579 first_line_prefix: String,
22580 subsequent_lines_prefix: String,
22581 unwrapped_text: String,
22582 wrap_column: usize,
22583 tab_size: NonZeroU32,
22584 preserve_existing_whitespace: bool,
22585) -> String {
22586 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22587 let subsequent_lines_prefix_len =
22588 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22589 let mut wrapped_text = String::new();
22590 let mut current_line = first_line_prefix;
22591 let mut is_first_line = true;
22592
22593 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22594 let mut current_line_len = first_line_prefix_len;
22595 let mut in_whitespace = false;
22596 for token in tokenizer {
22597 let have_preceding_whitespace = in_whitespace;
22598 match token {
22599 WordBreakToken::Word {
22600 token,
22601 grapheme_len,
22602 } => {
22603 in_whitespace = false;
22604 let current_prefix_len = if is_first_line {
22605 first_line_prefix_len
22606 } else {
22607 subsequent_lines_prefix_len
22608 };
22609 if current_line_len + grapheme_len > wrap_column
22610 && current_line_len != current_prefix_len
22611 {
22612 wrapped_text.push_str(current_line.trim_end());
22613 wrapped_text.push('\n');
22614 is_first_line = false;
22615 current_line = subsequent_lines_prefix.clone();
22616 current_line_len = subsequent_lines_prefix_len;
22617 }
22618 current_line.push_str(token);
22619 current_line_len += grapheme_len;
22620 }
22621 WordBreakToken::InlineWhitespace {
22622 mut token,
22623 mut grapheme_len,
22624 } => {
22625 in_whitespace = true;
22626 if have_preceding_whitespace && !preserve_existing_whitespace {
22627 continue;
22628 }
22629 if !preserve_existing_whitespace {
22630 // Keep a single whitespace grapheme as-is
22631 if let Some(first) =
22632 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22633 {
22634 token = first;
22635 } else {
22636 token = " ";
22637 }
22638 grapheme_len = 1;
22639 }
22640 let current_prefix_len = if is_first_line {
22641 first_line_prefix_len
22642 } else {
22643 subsequent_lines_prefix_len
22644 };
22645 if current_line_len + grapheme_len > wrap_column {
22646 wrapped_text.push_str(current_line.trim_end());
22647 wrapped_text.push('\n');
22648 is_first_line = false;
22649 current_line = subsequent_lines_prefix.clone();
22650 current_line_len = subsequent_lines_prefix_len;
22651 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22652 current_line.push_str(token);
22653 current_line_len += grapheme_len;
22654 }
22655 }
22656 WordBreakToken::Newline => {
22657 in_whitespace = true;
22658 let current_prefix_len = if is_first_line {
22659 first_line_prefix_len
22660 } else {
22661 subsequent_lines_prefix_len
22662 };
22663 if preserve_existing_whitespace {
22664 wrapped_text.push_str(current_line.trim_end());
22665 wrapped_text.push('\n');
22666 is_first_line = false;
22667 current_line = subsequent_lines_prefix.clone();
22668 current_line_len = subsequent_lines_prefix_len;
22669 } else if have_preceding_whitespace {
22670 continue;
22671 } else if current_line_len + 1 > wrap_column
22672 && current_line_len != current_prefix_len
22673 {
22674 wrapped_text.push_str(current_line.trim_end());
22675 wrapped_text.push('\n');
22676 is_first_line = false;
22677 current_line = subsequent_lines_prefix.clone();
22678 current_line_len = subsequent_lines_prefix_len;
22679 } else if current_line_len != current_prefix_len {
22680 current_line.push(' ');
22681 current_line_len += 1;
22682 }
22683 }
22684 }
22685 }
22686
22687 if !current_line.is_empty() {
22688 wrapped_text.push_str(¤t_line);
22689 }
22690 wrapped_text
22691}
22692
22693#[test]
22694fn test_wrap_with_prefix() {
22695 assert_eq!(
22696 wrap_with_prefix(
22697 "# ".to_string(),
22698 "# ".to_string(),
22699 "abcdefg".to_string(),
22700 4,
22701 NonZeroU32::new(4).unwrap(),
22702 false,
22703 ),
22704 "# abcdefg"
22705 );
22706 assert_eq!(
22707 wrap_with_prefix(
22708 "".to_string(),
22709 "".to_string(),
22710 "\thello world".to_string(),
22711 8,
22712 NonZeroU32::new(4).unwrap(),
22713 false,
22714 ),
22715 "hello\nworld"
22716 );
22717 assert_eq!(
22718 wrap_with_prefix(
22719 "// ".to_string(),
22720 "// ".to_string(),
22721 "xx \nyy zz aa bb cc".to_string(),
22722 12,
22723 NonZeroU32::new(4).unwrap(),
22724 false,
22725 ),
22726 "// xx yy zz\n// aa bb cc"
22727 );
22728 assert_eq!(
22729 wrap_with_prefix(
22730 String::new(),
22731 String::new(),
22732 "这是什么 \n 钢笔".to_string(),
22733 3,
22734 NonZeroU32::new(4).unwrap(),
22735 false,
22736 ),
22737 "这是什\n么 钢\n笔"
22738 );
22739 assert_eq!(
22740 wrap_with_prefix(
22741 String::new(),
22742 String::new(),
22743 format!("foo{}bar", '\u{2009}'), // thin space
22744 80,
22745 NonZeroU32::new(4).unwrap(),
22746 false,
22747 ),
22748 format!("foo{}bar", '\u{2009}')
22749 );
22750}
22751
22752pub trait CollaborationHub {
22753 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22754 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22755 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22756}
22757
22758impl CollaborationHub for Entity<Project> {
22759 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22760 self.read(cx).collaborators()
22761 }
22762
22763 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22764 self.read(cx).user_store().read(cx).participant_indices()
22765 }
22766
22767 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22768 let this = self.read(cx);
22769 let user_ids = this.collaborators().values().map(|c| c.user_id);
22770 this.user_store().read(cx).participant_names(user_ids, cx)
22771 }
22772}
22773
22774pub trait SemanticsProvider {
22775 fn hover(
22776 &self,
22777 buffer: &Entity<Buffer>,
22778 position: text::Anchor,
22779 cx: &mut App,
22780 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22781
22782 fn inline_values(
22783 &self,
22784 buffer_handle: Entity<Buffer>,
22785 range: Range<text::Anchor>,
22786 cx: &mut App,
22787 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22788
22789 fn applicable_inlay_chunks(
22790 &self,
22791 buffer: &Entity<Buffer>,
22792 ranges: &[Range<text::Anchor>],
22793 cx: &mut App,
22794 ) -> Vec<Range<BufferRow>>;
22795
22796 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22797
22798 fn inlay_hints(
22799 &self,
22800 invalidate: InvalidationStrategy,
22801 buffer: Entity<Buffer>,
22802 ranges: Vec<Range<text::Anchor>>,
22803 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22804 cx: &mut App,
22805 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22806
22807 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22808
22809 fn document_highlights(
22810 &self,
22811 buffer: &Entity<Buffer>,
22812 position: text::Anchor,
22813 cx: &mut App,
22814 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22815
22816 fn definitions(
22817 &self,
22818 buffer: &Entity<Buffer>,
22819 position: text::Anchor,
22820 kind: GotoDefinitionKind,
22821 cx: &mut App,
22822 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22823
22824 fn range_for_rename(
22825 &self,
22826 buffer: &Entity<Buffer>,
22827 position: text::Anchor,
22828 cx: &mut App,
22829 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22830
22831 fn perform_rename(
22832 &self,
22833 buffer: &Entity<Buffer>,
22834 position: text::Anchor,
22835 new_name: String,
22836 cx: &mut App,
22837 ) -> Option<Task<Result<ProjectTransaction>>>;
22838}
22839
22840pub trait CompletionProvider {
22841 fn completions(
22842 &self,
22843 excerpt_id: ExcerptId,
22844 buffer: &Entity<Buffer>,
22845 buffer_position: text::Anchor,
22846 trigger: CompletionContext,
22847 window: &mut Window,
22848 cx: &mut Context<Editor>,
22849 ) -> Task<Result<Vec<CompletionResponse>>>;
22850
22851 fn resolve_completions(
22852 &self,
22853 _buffer: Entity<Buffer>,
22854 _completion_indices: Vec<usize>,
22855 _completions: Rc<RefCell<Box<[Completion]>>>,
22856 _cx: &mut Context<Editor>,
22857 ) -> Task<Result<bool>> {
22858 Task::ready(Ok(false))
22859 }
22860
22861 fn apply_additional_edits_for_completion(
22862 &self,
22863 _buffer: Entity<Buffer>,
22864 _completions: Rc<RefCell<Box<[Completion]>>>,
22865 _completion_index: usize,
22866 _push_to_history: bool,
22867 _cx: &mut Context<Editor>,
22868 ) -> Task<Result<Option<language::Transaction>>> {
22869 Task::ready(Ok(None))
22870 }
22871
22872 fn is_completion_trigger(
22873 &self,
22874 buffer: &Entity<Buffer>,
22875 position: language::Anchor,
22876 text: &str,
22877 trigger_in_words: bool,
22878 menu_is_open: bool,
22879 cx: &mut Context<Editor>,
22880 ) -> bool;
22881
22882 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22883
22884 fn sort_completions(&self) -> bool {
22885 true
22886 }
22887
22888 fn filter_completions(&self) -> bool {
22889 true
22890 }
22891}
22892
22893pub trait CodeActionProvider {
22894 fn id(&self) -> Arc<str>;
22895
22896 fn code_actions(
22897 &self,
22898 buffer: &Entity<Buffer>,
22899 range: Range<text::Anchor>,
22900 window: &mut Window,
22901 cx: &mut App,
22902 ) -> Task<Result<Vec<CodeAction>>>;
22903
22904 fn apply_code_action(
22905 &self,
22906 buffer_handle: Entity<Buffer>,
22907 action: CodeAction,
22908 excerpt_id: ExcerptId,
22909 push_to_history: bool,
22910 window: &mut Window,
22911 cx: &mut App,
22912 ) -> Task<Result<ProjectTransaction>>;
22913}
22914
22915impl CodeActionProvider for Entity<Project> {
22916 fn id(&self) -> Arc<str> {
22917 "project".into()
22918 }
22919
22920 fn code_actions(
22921 &self,
22922 buffer: &Entity<Buffer>,
22923 range: Range<text::Anchor>,
22924 _window: &mut Window,
22925 cx: &mut App,
22926 ) -> Task<Result<Vec<CodeAction>>> {
22927 self.update(cx, |project, cx| {
22928 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22929 let code_actions = project.code_actions(buffer, range, None, cx);
22930 cx.background_spawn(async move {
22931 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22932 Ok(code_lens_actions
22933 .context("code lens fetch")?
22934 .into_iter()
22935 .flatten()
22936 .chain(
22937 code_actions
22938 .context("code action fetch")?
22939 .into_iter()
22940 .flatten(),
22941 )
22942 .collect())
22943 })
22944 })
22945 }
22946
22947 fn apply_code_action(
22948 &self,
22949 buffer_handle: Entity<Buffer>,
22950 action: CodeAction,
22951 _excerpt_id: ExcerptId,
22952 push_to_history: bool,
22953 _window: &mut Window,
22954 cx: &mut App,
22955 ) -> Task<Result<ProjectTransaction>> {
22956 self.update(cx, |project, cx| {
22957 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22958 })
22959 }
22960}
22961
22962fn snippet_completions(
22963 project: &Project,
22964 buffer: &Entity<Buffer>,
22965 buffer_position: text::Anchor,
22966 cx: &mut App,
22967) -> Task<Result<CompletionResponse>> {
22968 let languages = buffer.read(cx).languages_at(buffer_position);
22969 let snippet_store = project.snippets().read(cx);
22970
22971 let scopes: Vec<_> = languages
22972 .iter()
22973 .filter_map(|language| {
22974 let language_name = language.lsp_id();
22975 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22976
22977 if snippets.is_empty() {
22978 None
22979 } else {
22980 Some((language.default_scope(), snippets))
22981 }
22982 })
22983 .collect();
22984
22985 if scopes.is_empty() {
22986 return Task::ready(Ok(CompletionResponse {
22987 completions: vec![],
22988 display_options: CompletionDisplayOptions::default(),
22989 is_incomplete: false,
22990 }));
22991 }
22992
22993 let snapshot = buffer.read(cx).text_snapshot();
22994 let executor = cx.background_executor().clone();
22995
22996 cx.background_spawn(async move {
22997 let mut is_incomplete = false;
22998 let mut completions: Vec<Completion> = Vec::new();
22999 for (scope, snippets) in scopes.into_iter() {
23000 let classifier =
23001 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23002
23003 const MAX_WORD_PREFIX_LEN: usize = 128;
23004 let last_word: String = snapshot
23005 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23006 .take(MAX_WORD_PREFIX_LEN)
23007 .take_while(|c| classifier.is_word(*c))
23008 .collect::<String>()
23009 .chars()
23010 .rev()
23011 .collect();
23012
23013 if last_word.is_empty() {
23014 return Ok(CompletionResponse {
23015 completions: vec![],
23016 display_options: CompletionDisplayOptions::default(),
23017 is_incomplete: true,
23018 });
23019 }
23020
23021 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23022 let to_lsp = |point: &text::Anchor| {
23023 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23024 point_to_lsp(end)
23025 };
23026 let lsp_end = to_lsp(&buffer_position);
23027
23028 let candidates = snippets
23029 .iter()
23030 .enumerate()
23031 .flat_map(|(ix, snippet)| {
23032 snippet
23033 .prefix
23034 .iter()
23035 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23036 })
23037 .collect::<Vec<StringMatchCandidate>>();
23038
23039 const MAX_RESULTS: usize = 100;
23040 let mut matches = fuzzy::match_strings(
23041 &candidates,
23042 &last_word,
23043 last_word.chars().any(|c| c.is_uppercase()),
23044 true,
23045 MAX_RESULTS,
23046 &Default::default(),
23047 executor.clone(),
23048 )
23049 .await;
23050
23051 if matches.len() >= MAX_RESULTS {
23052 is_incomplete = true;
23053 }
23054
23055 // Remove all candidates where the query's start does not match the start of any word in the candidate
23056 if let Some(query_start) = last_word.chars().next() {
23057 matches.retain(|string_match| {
23058 split_words(&string_match.string).any(|word| {
23059 // Check that the first codepoint of the word as lowercase matches the first
23060 // codepoint of the query as lowercase
23061 word.chars()
23062 .flat_map(|codepoint| codepoint.to_lowercase())
23063 .zip(query_start.to_lowercase())
23064 .all(|(word_cp, query_cp)| word_cp == query_cp)
23065 })
23066 });
23067 }
23068
23069 let matched_strings = matches
23070 .into_iter()
23071 .map(|m| m.string)
23072 .collect::<HashSet<_>>();
23073
23074 completions.extend(snippets.iter().filter_map(|snippet| {
23075 let matching_prefix = snippet
23076 .prefix
23077 .iter()
23078 .find(|prefix| matched_strings.contains(*prefix))?;
23079 let start = as_offset - last_word.len();
23080 let start = snapshot.anchor_before(start);
23081 let range = start..buffer_position;
23082 let lsp_start = to_lsp(&start);
23083 let lsp_range = lsp::Range {
23084 start: lsp_start,
23085 end: lsp_end,
23086 };
23087 Some(Completion {
23088 replace_range: range,
23089 new_text: snippet.body.clone(),
23090 source: CompletionSource::Lsp {
23091 insert_range: None,
23092 server_id: LanguageServerId(usize::MAX),
23093 resolved: true,
23094 lsp_completion: Box::new(lsp::CompletionItem {
23095 label: snippet.prefix.first().unwrap().clone(),
23096 kind: Some(CompletionItemKind::SNIPPET),
23097 label_details: snippet.description.as_ref().map(|description| {
23098 lsp::CompletionItemLabelDetails {
23099 detail: Some(description.clone()),
23100 description: None,
23101 }
23102 }),
23103 insert_text_format: Some(InsertTextFormat::SNIPPET),
23104 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23105 lsp::InsertReplaceEdit {
23106 new_text: snippet.body.clone(),
23107 insert: lsp_range,
23108 replace: lsp_range,
23109 },
23110 )),
23111 filter_text: Some(snippet.body.clone()),
23112 sort_text: Some(char::MAX.to_string()),
23113 ..lsp::CompletionItem::default()
23114 }),
23115 lsp_defaults: None,
23116 },
23117 label: CodeLabel::plain(matching_prefix.clone(), None),
23118 icon_path: None,
23119 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23120 single_line: snippet.name.clone().into(),
23121 plain_text: snippet
23122 .description
23123 .clone()
23124 .map(|description| description.into()),
23125 }),
23126 insert_text_mode: None,
23127 confirm: None,
23128 })
23129 }))
23130 }
23131
23132 Ok(CompletionResponse {
23133 completions,
23134 display_options: CompletionDisplayOptions::default(),
23135 is_incomplete,
23136 })
23137 })
23138}
23139
23140impl CompletionProvider for Entity<Project> {
23141 fn completions(
23142 &self,
23143 _excerpt_id: ExcerptId,
23144 buffer: &Entity<Buffer>,
23145 buffer_position: text::Anchor,
23146 options: CompletionContext,
23147 _window: &mut Window,
23148 cx: &mut Context<Editor>,
23149 ) -> Task<Result<Vec<CompletionResponse>>> {
23150 self.update(cx, |project, cx| {
23151 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23152 let project_completions = project.completions(buffer, buffer_position, options, cx);
23153 cx.background_spawn(async move {
23154 let mut responses = project_completions.await?;
23155 let snippets = snippets.await?;
23156 if !snippets.completions.is_empty() {
23157 responses.push(snippets);
23158 }
23159 Ok(responses)
23160 })
23161 })
23162 }
23163
23164 fn resolve_completions(
23165 &self,
23166 buffer: Entity<Buffer>,
23167 completion_indices: Vec<usize>,
23168 completions: Rc<RefCell<Box<[Completion]>>>,
23169 cx: &mut Context<Editor>,
23170 ) -> Task<Result<bool>> {
23171 self.update(cx, |project, cx| {
23172 project.lsp_store().update(cx, |lsp_store, cx| {
23173 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23174 })
23175 })
23176 }
23177
23178 fn apply_additional_edits_for_completion(
23179 &self,
23180 buffer: Entity<Buffer>,
23181 completions: Rc<RefCell<Box<[Completion]>>>,
23182 completion_index: usize,
23183 push_to_history: bool,
23184 cx: &mut Context<Editor>,
23185 ) -> Task<Result<Option<language::Transaction>>> {
23186 self.update(cx, |project, cx| {
23187 project.lsp_store().update(cx, |lsp_store, cx| {
23188 lsp_store.apply_additional_edits_for_completion(
23189 buffer,
23190 completions,
23191 completion_index,
23192 push_to_history,
23193 cx,
23194 )
23195 })
23196 })
23197 }
23198
23199 fn is_completion_trigger(
23200 &self,
23201 buffer: &Entity<Buffer>,
23202 position: language::Anchor,
23203 text: &str,
23204 trigger_in_words: bool,
23205 menu_is_open: bool,
23206 cx: &mut Context<Editor>,
23207 ) -> bool {
23208 let mut chars = text.chars();
23209 let char = if let Some(char) = chars.next() {
23210 char
23211 } else {
23212 return false;
23213 };
23214 if chars.next().is_some() {
23215 return false;
23216 }
23217
23218 let buffer = buffer.read(cx);
23219 let snapshot = buffer.snapshot();
23220 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23221 return false;
23222 }
23223 let classifier = snapshot
23224 .char_classifier_at(position)
23225 .scope_context(Some(CharScopeContext::Completion));
23226 if trigger_in_words && classifier.is_word(char) {
23227 return true;
23228 }
23229
23230 buffer.completion_triggers().contains(text)
23231 }
23232}
23233
23234impl SemanticsProvider for Entity<Project> {
23235 fn hover(
23236 &self,
23237 buffer: &Entity<Buffer>,
23238 position: text::Anchor,
23239 cx: &mut App,
23240 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23241 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23242 }
23243
23244 fn document_highlights(
23245 &self,
23246 buffer: &Entity<Buffer>,
23247 position: text::Anchor,
23248 cx: &mut App,
23249 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23250 Some(self.update(cx, |project, cx| {
23251 project.document_highlights(buffer, position, cx)
23252 }))
23253 }
23254
23255 fn definitions(
23256 &self,
23257 buffer: &Entity<Buffer>,
23258 position: text::Anchor,
23259 kind: GotoDefinitionKind,
23260 cx: &mut App,
23261 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23262 Some(self.update(cx, |project, cx| match kind {
23263 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23264 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23265 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23266 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23267 }))
23268 }
23269
23270 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23271 self.update(cx, |project, cx| {
23272 if project
23273 .active_debug_session(cx)
23274 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23275 {
23276 return true;
23277 }
23278
23279 buffer.update(cx, |buffer, cx| {
23280 project.any_language_server_supports_inlay_hints(buffer, cx)
23281 })
23282 })
23283 }
23284
23285 fn inline_values(
23286 &self,
23287 buffer_handle: Entity<Buffer>,
23288 range: Range<text::Anchor>,
23289 cx: &mut App,
23290 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23291 self.update(cx, |project, cx| {
23292 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23293
23294 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23295 })
23296 }
23297
23298 fn applicable_inlay_chunks(
23299 &self,
23300 buffer: &Entity<Buffer>,
23301 ranges: &[Range<text::Anchor>],
23302 cx: &mut App,
23303 ) -> Vec<Range<BufferRow>> {
23304 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23305 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23306 })
23307 }
23308
23309 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23310 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23311 lsp_store.invalidate_inlay_hints(for_buffers)
23312 });
23313 }
23314
23315 fn inlay_hints(
23316 &self,
23317 invalidate: InvalidationStrategy,
23318 buffer: Entity<Buffer>,
23319 ranges: Vec<Range<text::Anchor>>,
23320 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23321 cx: &mut App,
23322 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23323 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23324 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23325 }))
23326 }
23327
23328 fn range_for_rename(
23329 &self,
23330 buffer: &Entity<Buffer>,
23331 position: text::Anchor,
23332 cx: &mut App,
23333 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23334 Some(self.update(cx, |project, cx| {
23335 let buffer = buffer.clone();
23336 let task = project.prepare_rename(buffer.clone(), position, cx);
23337 cx.spawn(async move |_, cx| {
23338 Ok(match task.await? {
23339 PrepareRenameResponse::Success(range) => Some(range),
23340 PrepareRenameResponse::InvalidPosition => None,
23341 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23342 // Fallback on using TreeSitter info to determine identifier range
23343 buffer.read_with(cx, |buffer, _| {
23344 let snapshot = buffer.snapshot();
23345 let (range, kind) = snapshot.surrounding_word(position, None);
23346 if kind != Some(CharKind::Word) {
23347 return None;
23348 }
23349 Some(
23350 snapshot.anchor_before(range.start)
23351 ..snapshot.anchor_after(range.end),
23352 )
23353 })?
23354 }
23355 })
23356 })
23357 }))
23358 }
23359
23360 fn perform_rename(
23361 &self,
23362 buffer: &Entity<Buffer>,
23363 position: text::Anchor,
23364 new_name: String,
23365 cx: &mut App,
23366 ) -> Option<Task<Result<ProjectTransaction>>> {
23367 Some(self.update(cx, |project, cx| {
23368 project.perform_rename(buffer.clone(), position, new_name, cx)
23369 }))
23370 }
23371}
23372
23373fn consume_contiguous_rows(
23374 contiguous_row_selections: &mut Vec<Selection<Point>>,
23375 selection: &Selection<Point>,
23376 display_map: &DisplaySnapshot,
23377 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23378) -> (MultiBufferRow, MultiBufferRow) {
23379 contiguous_row_selections.push(selection.clone());
23380 let start_row = starting_row(selection, display_map);
23381 let mut end_row = ending_row(selection, display_map);
23382
23383 while let Some(next_selection) = selections.peek() {
23384 if next_selection.start.row <= end_row.0 {
23385 end_row = ending_row(next_selection, display_map);
23386 contiguous_row_selections.push(selections.next().unwrap().clone());
23387 } else {
23388 break;
23389 }
23390 }
23391 (start_row, end_row)
23392}
23393
23394fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23395 if selection.start.column > 0 {
23396 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23397 } else {
23398 MultiBufferRow(selection.start.row)
23399 }
23400}
23401
23402fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23403 if next_selection.end.column > 0 || next_selection.is_empty() {
23404 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23405 } else {
23406 MultiBufferRow(next_selection.end.row)
23407 }
23408}
23409
23410impl EditorSnapshot {
23411 pub fn remote_selections_in_range<'a>(
23412 &'a self,
23413 range: &'a Range<Anchor>,
23414 collaboration_hub: &dyn CollaborationHub,
23415 cx: &'a App,
23416 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23417 let participant_names = collaboration_hub.user_names(cx);
23418 let participant_indices = collaboration_hub.user_participant_indices(cx);
23419 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23420 let collaborators_by_replica_id = collaborators_by_peer_id
23421 .values()
23422 .map(|collaborator| (collaborator.replica_id, collaborator))
23423 .collect::<HashMap<_, _>>();
23424 self.buffer_snapshot()
23425 .selections_in_range(range, false)
23426 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23427 if replica_id == ReplicaId::AGENT {
23428 Some(RemoteSelection {
23429 replica_id,
23430 selection,
23431 cursor_shape,
23432 line_mode,
23433 collaborator_id: CollaboratorId::Agent,
23434 user_name: Some("Agent".into()),
23435 color: cx.theme().players().agent(),
23436 })
23437 } else {
23438 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23439 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23440 let user_name = participant_names.get(&collaborator.user_id).cloned();
23441 Some(RemoteSelection {
23442 replica_id,
23443 selection,
23444 cursor_shape,
23445 line_mode,
23446 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23447 user_name,
23448 color: if let Some(index) = participant_index {
23449 cx.theme().players().color_for_participant(index.0)
23450 } else {
23451 cx.theme().players().absent()
23452 },
23453 })
23454 }
23455 })
23456 }
23457
23458 pub fn hunks_for_ranges(
23459 &self,
23460 ranges: impl IntoIterator<Item = Range<Point>>,
23461 ) -> Vec<MultiBufferDiffHunk> {
23462 let mut hunks = Vec::new();
23463 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23464 HashMap::default();
23465 for query_range in ranges {
23466 let query_rows =
23467 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23468 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23469 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23470 ) {
23471 // Include deleted hunks that are adjacent to the query range, because
23472 // otherwise they would be missed.
23473 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23474 if hunk.status().is_deleted() {
23475 intersects_range |= hunk.row_range.start == query_rows.end;
23476 intersects_range |= hunk.row_range.end == query_rows.start;
23477 }
23478 if intersects_range {
23479 if !processed_buffer_rows
23480 .entry(hunk.buffer_id)
23481 .or_default()
23482 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23483 {
23484 continue;
23485 }
23486 hunks.push(hunk);
23487 }
23488 }
23489 }
23490
23491 hunks
23492 }
23493
23494 fn display_diff_hunks_for_rows<'a>(
23495 &'a self,
23496 display_rows: Range<DisplayRow>,
23497 folded_buffers: &'a HashSet<BufferId>,
23498 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23499 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23500 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23501
23502 self.buffer_snapshot()
23503 .diff_hunks_in_range(buffer_start..buffer_end)
23504 .filter_map(|hunk| {
23505 if folded_buffers.contains(&hunk.buffer_id) {
23506 return None;
23507 }
23508
23509 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23510 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23511
23512 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23513 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23514
23515 let display_hunk = if hunk_display_start.column() != 0 {
23516 DisplayDiffHunk::Folded {
23517 display_row: hunk_display_start.row(),
23518 }
23519 } else {
23520 let mut end_row = hunk_display_end.row();
23521 if hunk_display_end.column() > 0 {
23522 end_row.0 += 1;
23523 }
23524 let is_created_file = hunk.is_created_file();
23525 DisplayDiffHunk::Unfolded {
23526 status: hunk.status(),
23527 diff_base_byte_range: hunk.diff_base_byte_range,
23528 display_row_range: hunk_display_start.row()..end_row,
23529 multi_buffer_range: Anchor::range_in_buffer(
23530 hunk.excerpt_id,
23531 hunk.buffer_id,
23532 hunk.buffer_range,
23533 ),
23534 is_created_file,
23535 }
23536 };
23537
23538 Some(display_hunk)
23539 })
23540 }
23541
23542 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23543 self.display_snapshot
23544 .buffer_snapshot()
23545 .language_at(position)
23546 }
23547
23548 pub fn is_focused(&self) -> bool {
23549 self.is_focused
23550 }
23551
23552 pub fn placeholder_text(&self) -> Option<String> {
23553 self.placeholder_display_snapshot
23554 .as_ref()
23555 .map(|display_map| display_map.text())
23556 }
23557
23558 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23559 self.scroll_anchor.scroll_position(&self.display_snapshot)
23560 }
23561
23562 fn gutter_dimensions(
23563 &self,
23564 font_id: FontId,
23565 font_size: Pixels,
23566 max_line_number_width: Pixels,
23567 cx: &App,
23568 ) -> Option<GutterDimensions> {
23569 if !self.show_gutter {
23570 return None;
23571 }
23572
23573 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23574 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23575
23576 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23577 matches!(
23578 ProjectSettings::get_global(cx).git.git_gutter,
23579 GitGutterSetting::TrackedFiles
23580 )
23581 });
23582 let gutter_settings = EditorSettings::get_global(cx).gutter;
23583 let show_line_numbers = self
23584 .show_line_numbers
23585 .unwrap_or(gutter_settings.line_numbers);
23586 let line_gutter_width = if show_line_numbers {
23587 // Avoid flicker-like gutter resizes when the line number gains another digit by
23588 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23589 let min_width_for_number_on_gutter =
23590 ch_advance * gutter_settings.min_line_number_digits as f32;
23591 max_line_number_width.max(min_width_for_number_on_gutter)
23592 } else {
23593 0.0.into()
23594 };
23595
23596 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23597 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23598
23599 let git_blame_entries_width =
23600 self.git_blame_gutter_max_author_length
23601 .map(|max_author_length| {
23602 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23603 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23604
23605 /// The number of characters to dedicate to gaps and margins.
23606 const SPACING_WIDTH: usize = 4;
23607
23608 let max_char_count = max_author_length.min(renderer.max_author_length())
23609 + ::git::SHORT_SHA_LENGTH
23610 + MAX_RELATIVE_TIMESTAMP.len()
23611 + SPACING_WIDTH;
23612
23613 ch_advance * max_char_count
23614 });
23615
23616 let is_singleton = self.buffer_snapshot().is_singleton();
23617
23618 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23619 left_padding += if !is_singleton {
23620 ch_width * 4.0
23621 } else if show_runnables || show_breakpoints {
23622 ch_width * 3.0
23623 } else if show_git_gutter && show_line_numbers {
23624 ch_width * 2.0
23625 } else if show_git_gutter || show_line_numbers {
23626 ch_width
23627 } else {
23628 px(0.)
23629 };
23630
23631 let shows_folds = is_singleton && gutter_settings.folds;
23632
23633 let right_padding = if shows_folds && show_line_numbers {
23634 ch_width * 4.0
23635 } else if shows_folds || (!is_singleton && show_line_numbers) {
23636 ch_width * 3.0
23637 } else if show_line_numbers {
23638 ch_width
23639 } else {
23640 px(0.)
23641 };
23642
23643 Some(GutterDimensions {
23644 left_padding,
23645 right_padding,
23646 width: line_gutter_width + left_padding + right_padding,
23647 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23648 git_blame_entries_width,
23649 })
23650 }
23651
23652 pub fn render_crease_toggle(
23653 &self,
23654 buffer_row: MultiBufferRow,
23655 row_contains_cursor: bool,
23656 editor: Entity<Editor>,
23657 window: &mut Window,
23658 cx: &mut App,
23659 ) -> Option<AnyElement> {
23660 let folded = self.is_line_folded(buffer_row);
23661 let mut is_foldable = false;
23662
23663 if let Some(crease) = self
23664 .crease_snapshot
23665 .query_row(buffer_row, self.buffer_snapshot())
23666 {
23667 is_foldable = true;
23668 match crease {
23669 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23670 if let Some(render_toggle) = render_toggle {
23671 let toggle_callback =
23672 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23673 if folded {
23674 editor.update(cx, |editor, cx| {
23675 editor.fold_at(buffer_row, window, cx)
23676 });
23677 } else {
23678 editor.update(cx, |editor, cx| {
23679 editor.unfold_at(buffer_row, window, cx)
23680 });
23681 }
23682 });
23683 return Some((render_toggle)(
23684 buffer_row,
23685 folded,
23686 toggle_callback,
23687 window,
23688 cx,
23689 ));
23690 }
23691 }
23692 }
23693 }
23694
23695 is_foldable |= self.starts_indent(buffer_row);
23696
23697 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23698 Some(
23699 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23700 .toggle_state(folded)
23701 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23702 if folded {
23703 this.unfold_at(buffer_row, window, cx);
23704 } else {
23705 this.fold_at(buffer_row, window, cx);
23706 }
23707 }))
23708 .into_any_element(),
23709 )
23710 } else {
23711 None
23712 }
23713 }
23714
23715 pub fn render_crease_trailer(
23716 &self,
23717 buffer_row: MultiBufferRow,
23718 window: &mut Window,
23719 cx: &mut App,
23720 ) -> Option<AnyElement> {
23721 let folded = self.is_line_folded(buffer_row);
23722 if let Crease::Inline { render_trailer, .. } = self
23723 .crease_snapshot
23724 .query_row(buffer_row, self.buffer_snapshot())?
23725 {
23726 let render_trailer = render_trailer.as_ref()?;
23727 Some(render_trailer(buffer_row, folded, window, cx))
23728 } else {
23729 None
23730 }
23731 }
23732}
23733
23734impl Deref for EditorSnapshot {
23735 type Target = DisplaySnapshot;
23736
23737 fn deref(&self) -> &Self::Target {
23738 &self.display_snapshot
23739 }
23740}
23741
23742#[derive(Clone, Debug, PartialEq, Eq)]
23743pub enum EditorEvent {
23744 InputIgnored {
23745 text: Arc<str>,
23746 },
23747 InputHandled {
23748 utf16_range_to_replace: Option<Range<isize>>,
23749 text: Arc<str>,
23750 },
23751 ExcerptsAdded {
23752 buffer: Entity<Buffer>,
23753 predecessor: ExcerptId,
23754 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23755 },
23756 ExcerptsRemoved {
23757 ids: Vec<ExcerptId>,
23758 removed_buffer_ids: Vec<BufferId>,
23759 },
23760 BufferFoldToggled {
23761 ids: Vec<ExcerptId>,
23762 folded: bool,
23763 },
23764 ExcerptsEdited {
23765 ids: Vec<ExcerptId>,
23766 },
23767 ExcerptsExpanded {
23768 ids: Vec<ExcerptId>,
23769 },
23770 BufferEdited,
23771 Edited {
23772 transaction_id: clock::Lamport,
23773 },
23774 Reparsed(BufferId),
23775 Focused,
23776 FocusedIn,
23777 Blurred,
23778 DirtyChanged,
23779 Saved,
23780 TitleChanged,
23781 SelectionsChanged {
23782 local: bool,
23783 },
23784 ScrollPositionChanged {
23785 local: bool,
23786 autoscroll: bool,
23787 },
23788 TransactionUndone {
23789 transaction_id: clock::Lamport,
23790 },
23791 TransactionBegun {
23792 transaction_id: clock::Lamport,
23793 },
23794 CursorShapeChanged,
23795 BreadcrumbsChanged,
23796 PushedToNavHistory {
23797 anchor: Anchor,
23798 is_deactivate: bool,
23799 },
23800}
23801
23802impl EventEmitter<EditorEvent> for Editor {}
23803
23804impl Focusable for Editor {
23805 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23806 self.focus_handle.clone()
23807 }
23808}
23809
23810impl Render for Editor {
23811 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23812 let settings = ThemeSettings::get_global(cx);
23813
23814 let mut text_style = match self.mode {
23815 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23816 color: cx.theme().colors().editor_foreground,
23817 font_family: settings.ui_font.family.clone(),
23818 font_features: settings.ui_font.features.clone(),
23819 font_fallbacks: settings.ui_font.fallbacks.clone(),
23820 font_size: rems(0.875).into(),
23821 font_weight: settings.ui_font.weight,
23822 line_height: relative(settings.buffer_line_height.value()),
23823 ..Default::default()
23824 },
23825 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23826 color: cx.theme().colors().editor_foreground,
23827 font_family: settings.buffer_font.family.clone(),
23828 font_features: settings.buffer_font.features.clone(),
23829 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23830 font_size: settings.buffer_font_size(cx).into(),
23831 font_weight: settings.buffer_font.weight,
23832 line_height: relative(settings.buffer_line_height.value()),
23833 ..Default::default()
23834 },
23835 };
23836 if let Some(text_style_refinement) = &self.text_style_refinement {
23837 text_style.refine(text_style_refinement)
23838 }
23839
23840 let background = match self.mode {
23841 EditorMode::SingleLine => cx.theme().system().transparent,
23842 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23843 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23844 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23845 };
23846
23847 EditorElement::new(
23848 &cx.entity(),
23849 EditorStyle {
23850 background,
23851 border: cx.theme().colors().border,
23852 local_player: cx.theme().players().local(),
23853 text: text_style,
23854 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23855 syntax: cx.theme().syntax().clone(),
23856 status: cx.theme().status().clone(),
23857 inlay_hints_style: make_inlay_hints_style(cx),
23858 edit_prediction_styles: make_suggestion_styles(cx),
23859 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23860 show_underlines: self.diagnostics_enabled(),
23861 },
23862 )
23863 }
23864}
23865
23866impl EntityInputHandler for Editor {
23867 fn text_for_range(
23868 &mut self,
23869 range_utf16: Range<usize>,
23870 adjusted_range: &mut Option<Range<usize>>,
23871 _: &mut Window,
23872 cx: &mut Context<Self>,
23873 ) -> Option<String> {
23874 let snapshot = self.buffer.read(cx).read(cx);
23875 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23876 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23877 if (start.0..end.0) != range_utf16 {
23878 adjusted_range.replace(start.0..end.0);
23879 }
23880 Some(snapshot.text_for_range(start..end).collect())
23881 }
23882
23883 fn selected_text_range(
23884 &mut self,
23885 ignore_disabled_input: bool,
23886 _: &mut Window,
23887 cx: &mut Context<Self>,
23888 ) -> Option<UTF16Selection> {
23889 // Prevent the IME menu from appearing when holding down an alphabetic key
23890 // while input is disabled.
23891 if !ignore_disabled_input && !self.input_enabled {
23892 return None;
23893 }
23894
23895 let selection = self
23896 .selections
23897 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23898 let range = selection.range();
23899
23900 Some(UTF16Selection {
23901 range: range.start.0..range.end.0,
23902 reversed: selection.reversed,
23903 })
23904 }
23905
23906 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23907 let snapshot = self.buffer.read(cx).read(cx);
23908 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23909 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23910 }
23911
23912 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23913 self.clear_highlights::<InputComposition>(cx);
23914 self.ime_transaction.take();
23915 }
23916
23917 fn replace_text_in_range(
23918 &mut self,
23919 range_utf16: Option<Range<usize>>,
23920 text: &str,
23921 window: &mut Window,
23922 cx: &mut Context<Self>,
23923 ) {
23924 if !self.input_enabled {
23925 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23926 return;
23927 }
23928
23929 self.transact(window, cx, |this, window, cx| {
23930 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23931 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23932 Some(this.selection_replacement_ranges(range_utf16, cx))
23933 } else {
23934 this.marked_text_ranges(cx)
23935 };
23936
23937 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23938 let newest_selection_id = this.selections.newest_anchor().id;
23939 this.selections
23940 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23941 .iter()
23942 .zip(ranges_to_replace.iter())
23943 .find_map(|(selection, range)| {
23944 if selection.id == newest_selection_id {
23945 Some(
23946 (range.start.0 as isize - selection.head().0 as isize)
23947 ..(range.end.0 as isize - selection.head().0 as isize),
23948 )
23949 } else {
23950 None
23951 }
23952 })
23953 });
23954
23955 cx.emit(EditorEvent::InputHandled {
23956 utf16_range_to_replace: range_to_replace,
23957 text: text.into(),
23958 });
23959
23960 if let Some(new_selected_ranges) = new_selected_ranges {
23961 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23962 selections.select_ranges(new_selected_ranges)
23963 });
23964 this.backspace(&Default::default(), window, cx);
23965 }
23966
23967 this.handle_input(text, window, cx);
23968 });
23969
23970 if let Some(transaction) = self.ime_transaction {
23971 self.buffer.update(cx, |buffer, cx| {
23972 buffer.group_until_transaction(transaction, cx);
23973 });
23974 }
23975
23976 self.unmark_text(window, cx);
23977 }
23978
23979 fn replace_and_mark_text_in_range(
23980 &mut self,
23981 range_utf16: Option<Range<usize>>,
23982 text: &str,
23983 new_selected_range_utf16: Option<Range<usize>>,
23984 window: &mut Window,
23985 cx: &mut Context<Self>,
23986 ) {
23987 if !self.input_enabled {
23988 return;
23989 }
23990
23991 let transaction = self.transact(window, cx, |this, window, cx| {
23992 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23993 let snapshot = this.buffer.read(cx).read(cx);
23994 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23995 for marked_range in &mut marked_ranges {
23996 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23997 marked_range.start.0 += relative_range_utf16.start;
23998 marked_range.start =
23999 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24000 marked_range.end =
24001 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24002 }
24003 }
24004 Some(marked_ranges)
24005 } else if let Some(range_utf16) = range_utf16 {
24006 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24007 Some(this.selection_replacement_ranges(range_utf16, cx))
24008 } else {
24009 None
24010 };
24011
24012 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24013 let newest_selection_id = this.selections.newest_anchor().id;
24014 this.selections
24015 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24016 .iter()
24017 .zip(ranges_to_replace.iter())
24018 .find_map(|(selection, range)| {
24019 if selection.id == newest_selection_id {
24020 Some(
24021 (range.start.0 as isize - selection.head().0 as isize)
24022 ..(range.end.0 as isize - selection.head().0 as isize),
24023 )
24024 } else {
24025 None
24026 }
24027 })
24028 });
24029
24030 cx.emit(EditorEvent::InputHandled {
24031 utf16_range_to_replace: range_to_replace,
24032 text: text.into(),
24033 });
24034
24035 if let Some(ranges) = ranges_to_replace {
24036 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24037 s.select_ranges(ranges)
24038 });
24039 }
24040
24041 let marked_ranges = {
24042 let snapshot = this.buffer.read(cx).read(cx);
24043 this.selections
24044 .disjoint_anchors_arc()
24045 .iter()
24046 .map(|selection| {
24047 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24048 })
24049 .collect::<Vec<_>>()
24050 };
24051
24052 if text.is_empty() {
24053 this.unmark_text(window, cx);
24054 } else {
24055 this.highlight_text::<InputComposition>(
24056 marked_ranges.clone(),
24057 HighlightStyle {
24058 underline: Some(UnderlineStyle {
24059 thickness: px(1.),
24060 color: None,
24061 wavy: false,
24062 }),
24063 ..Default::default()
24064 },
24065 cx,
24066 );
24067 }
24068
24069 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24070 let use_autoclose = this.use_autoclose;
24071 let use_auto_surround = this.use_auto_surround;
24072 this.set_use_autoclose(false);
24073 this.set_use_auto_surround(false);
24074 this.handle_input(text, window, cx);
24075 this.set_use_autoclose(use_autoclose);
24076 this.set_use_auto_surround(use_auto_surround);
24077
24078 if let Some(new_selected_range) = new_selected_range_utf16 {
24079 let snapshot = this.buffer.read(cx).read(cx);
24080 let new_selected_ranges = marked_ranges
24081 .into_iter()
24082 .map(|marked_range| {
24083 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24084 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24085 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24086 snapshot.clip_offset_utf16(new_start, Bias::Left)
24087 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24088 })
24089 .collect::<Vec<_>>();
24090
24091 drop(snapshot);
24092 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24093 selections.select_ranges(new_selected_ranges)
24094 });
24095 }
24096 });
24097
24098 self.ime_transaction = self.ime_transaction.or(transaction);
24099 if let Some(transaction) = self.ime_transaction {
24100 self.buffer.update(cx, |buffer, cx| {
24101 buffer.group_until_transaction(transaction, cx);
24102 });
24103 }
24104
24105 if self.text_highlights::<InputComposition>(cx).is_none() {
24106 self.ime_transaction.take();
24107 }
24108 }
24109
24110 fn bounds_for_range(
24111 &mut self,
24112 range_utf16: Range<usize>,
24113 element_bounds: gpui::Bounds<Pixels>,
24114 window: &mut Window,
24115 cx: &mut Context<Self>,
24116 ) -> Option<gpui::Bounds<Pixels>> {
24117 let text_layout_details = self.text_layout_details(window);
24118 let CharacterDimensions {
24119 em_width,
24120 em_advance,
24121 line_height,
24122 } = self.character_dimensions(window);
24123
24124 let snapshot = self.snapshot(window, cx);
24125 let scroll_position = snapshot.scroll_position();
24126 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24127
24128 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24129 let x = Pixels::from(
24130 ScrollOffset::from(
24131 snapshot.x_for_display_point(start, &text_layout_details)
24132 + self.gutter_dimensions.full_width(),
24133 ) - scroll_left,
24134 );
24135 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24136
24137 Some(Bounds {
24138 origin: element_bounds.origin + point(x, y),
24139 size: size(em_width, line_height),
24140 })
24141 }
24142
24143 fn character_index_for_point(
24144 &mut self,
24145 point: gpui::Point<Pixels>,
24146 _window: &mut Window,
24147 _cx: &mut Context<Self>,
24148 ) -> Option<usize> {
24149 let position_map = self.last_position_map.as_ref()?;
24150 if !position_map.text_hitbox.contains(&point) {
24151 return None;
24152 }
24153 let display_point = position_map.point_for_position(point).previous_valid;
24154 let anchor = position_map
24155 .snapshot
24156 .display_point_to_anchor(display_point, Bias::Left);
24157 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24158 Some(utf16_offset.0)
24159 }
24160
24161 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24162 self.input_enabled
24163 }
24164}
24165
24166trait SelectionExt {
24167 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24168 fn spanned_rows(
24169 &self,
24170 include_end_if_at_line_start: bool,
24171 map: &DisplaySnapshot,
24172 ) -> Range<MultiBufferRow>;
24173}
24174
24175impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24176 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24177 let start = self
24178 .start
24179 .to_point(map.buffer_snapshot())
24180 .to_display_point(map);
24181 let end = self
24182 .end
24183 .to_point(map.buffer_snapshot())
24184 .to_display_point(map);
24185 if self.reversed {
24186 end..start
24187 } else {
24188 start..end
24189 }
24190 }
24191
24192 fn spanned_rows(
24193 &self,
24194 include_end_if_at_line_start: bool,
24195 map: &DisplaySnapshot,
24196 ) -> Range<MultiBufferRow> {
24197 let start = self.start.to_point(map.buffer_snapshot());
24198 let mut end = self.end.to_point(map.buffer_snapshot());
24199 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24200 end.row -= 1;
24201 }
24202
24203 let buffer_start = map.prev_line_boundary(start).0;
24204 let buffer_end = map.next_line_boundary(end).0;
24205 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24206 }
24207}
24208
24209impl<T: InvalidationRegion> InvalidationStack<T> {
24210 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24211 where
24212 S: Clone + ToOffset,
24213 {
24214 while let Some(region) = self.last() {
24215 let all_selections_inside_invalidation_ranges =
24216 if selections.len() == region.ranges().len() {
24217 selections
24218 .iter()
24219 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24220 .all(|(selection, invalidation_range)| {
24221 let head = selection.head().to_offset(buffer);
24222 invalidation_range.start <= head && invalidation_range.end >= head
24223 })
24224 } else {
24225 false
24226 };
24227
24228 if all_selections_inside_invalidation_ranges {
24229 break;
24230 } else {
24231 self.pop();
24232 }
24233 }
24234 }
24235}
24236
24237impl<T> Default for InvalidationStack<T> {
24238 fn default() -> Self {
24239 Self(Default::default())
24240 }
24241}
24242
24243impl<T> Deref for InvalidationStack<T> {
24244 type Target = Vec<T>;
24245
24246 fn deref(&self) -> &Self::Target {
24247 &self.0
24248 }
24249}
24250
24251impl<T> DerefMut for InvalidationStack<T> {
24252 fn deref_mut(&mut self) -> &mut Self::Target {
24253 &mut self.0
24254 }
24255}
24256
24257impl InvalidationRegion for SnippetState {
24258 fn ranges(&self) -> &[Range<Anchor>] {
24259 &self.ranges[self.active_index]
24260 }
24261}
24262
24263fn edit_prediction_edit_text(
24264 current_snapshot: &BufferSnapshot,
24265 edits: &[(Range<Anchor>, String)],
24266 edit_preview: &EditPreview,
24267 include_deletions: bool,
24268 cx: &App,
24269) -> HighlightedText {
24270 let edits = edits
24271 .iter()
24272 .map(|(anchor, text)| {
24273 (
24274 anchor.start.text_anchor..anchor.end.text_anchor,
24275 text.clone(),
24276 )
24277 })
24278 .collect::<Vec<_>>();
24279
24280 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24281}
24282
24283fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24284 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24285 // Just show the raw edit text with basic styling
24286 let mut text = String::new();
24287 let mut highlights = Vec::new();
24288
24289 let insertion_highlight_style = HighlightStyle {
24290 color: Some(cx.theme().colors().text),
24291 ..Default::default()
24292 };
24293
24294 for (_, edit_text) in edits {
24295 let start_offset = text.len();
24296 text.push_str(edit_text);
24297 let end_offset = text.len();
24298
24299 if start_offset < end_offset {
24300 highlights.push((start_offset..end_offset, insertion_highlight_style));
24301 }
24302 }
24303
24304 HighlightedText {
24305 text: text.into(),
24306 highlights,
24307 }
24308}
24309
24310pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24311 match severity {
24312 lsp::DiagnosticSeverity::ERROR => colors.error,
24313 lsp::DiagnosticSeverity::WARNING => colors.warning,
24314 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24315 lsp::DiagnosticSeverity::HINT => colors.info,
24316 _ => colors.ignored,
24317 }
24318}
24319
24320pub fn styled_runs_for_code_label<'a>(
24321 label: &'a CodeLabel,
24322 syntax_theme: &'a theme::SyntaxTheme,
24323) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24324 let fade_out = HighlightStyle {
24325 fade_out: Some(0.35),
24326 ..Default::default()
24327 };
24328
24329 let mut prev_end = label.filter_range.end;
24330 label
24331 .runs
24332 .iter()
24333 .enumerate()
24334 .flat_map(move |(ix, (range, highlight_id))| {
24335 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24336 style
24337 } else {
24338 return Default::default();
24339 };
24340 let muted_style = style.highlight(fade_out);
24341
24342 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24343 if range.start >= label.filter_range.end {
24344 if range.start > prev_end {
24345 runs.push((prev_end..range.start, fade_out));
24346 }
24347 runs.push((range.clone(), muted_style));
24348 } else if range.end <= label.filter_range.end {
24349 runs.push((range.clone(), style));
24350 } else {
24351 runs.push((range.start..label.filter_range.end, style));
24352 runs.push((label.filter_range.end..range.end, muted_style));
24353 }
24354 prev_end = cmp::max(prev_end, range.end);
24355
24356 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24357 runs.push((prev_end..label.text.len(), fade_out));
24358 }
24359
24360 runs
24361 })
24362}
24363
24364pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24365 let mut prev_index = 0;
24366 let mut prev_codepoint: Option<char> = None;
24367 text.char_indices()
24368 .chain([(text.len(), '\0')])
24369 .filter_map(move |(index, codepoint)| {
24370 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24371 let is_boundary = index == text.len()
24372 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24373 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24374 if is_boundary {
24375 let chunk = &text[prev_index..index];
24376 prev_index = index;
24377 Some(chunk)
24378 } else {
24379 None
24380 }
24381 })
24382}
24383
24384pub trait RangeToAnchorExt: Sized {
24385 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24386
24387 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24388 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24389 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24390 }
24391}
24392
24393impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24394 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24395 let start_offset = self.start.to_offset(snapshot);
24396 let end_offset = self.end.to_offset(snapshot);
24397 if start_offset == end_offset {
24398 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24399 } else {
24400 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24401 }
24402 }
24403}
24404
24405pub trait RowExt {
24406 fn as_f64(&self) -> f64;
24407
24408 fn next_row(&self) -> Self;
24409
24410 fn previous_row(&self) -> Self;
24411
24412 fn minus(&self, other: Self) -> u32;
24413}
24414
24415impl RowExt for DisplayRow {
24416 fn as_f64(&self) -> f64 {
24417 self.0 as _
24418 }
24419
24420 fn next_row(&self) -> Self {
24421 Self(self.0 + 1)
24422 }
24423
24424 fn previous_row(&self) -> Self {
24425 Self(self.0.saturating_sub(1))
24426 }
24427
24428 fn minus(&self, other: Self) -> u32 {
24429 self.0 - other.0
24430 }
24431}
24432
24433impl RowExt for MultiBufferRow {
24434 fn as_f64(&self) -> f64 {
24435 self.0 as _
24436 }
24437
24438 fn next_row(&self) -> Self {
24439 Self(self.0 + 1)
24440 }
24441
24442 fn previous_row(&self) -> Self {
24443 Self(self.0.saturating_sub(1))
24444 }
24445
24446 fn minus(&self, other: Self) -> u32 {
24447 self.0 - other.0
24448 }
24449}
24450
24451trait RowRangeExt {
24452 type Row;
24453
24454 fn len(&self) -> usize;
24455
24456 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24457}
24458
24459impl RowRangeExt for Range<MultiBufferRow> {
24460 type Row = MultiBufferRow;
24461
24462 fn len(&self) -> usize {
24463 (self.end.0 - self.start.0) as usize
24464 }
24465
24466 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24467 (self.start.0..self.end.0).map(MultiBufferRow)
24468 }
24469}
24470
24471impl RowRangeExt for Range<DisplayRow> {
24472 type Row = DisplayRow;
24473
24474 fn len(&self) -> usize {
24475 (self.end.0 - self.start.0) as usize
24476 }
24477
24478 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24479 (self.start.0..self.end.0).map(DisplayRow)
24480 }
24481}
24482
24483/// If select range has more than one line, we
24484/// just point the cursor to range.start.
24485fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24486 if range.start.row == range.end.row {
24487 range
24488 } else {
24489 range.start..range.start
24490 }
24491}
24492pub struct KillRing(ClipboardItem);
24493impl Global for KillRing {}
24494
24495const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24496
24497enum BreakpointPromptEditAction {
24498 Log,
24499 Condition,
24500 HitCondition,
24501}
24502
24503struct BreakpointPromptEditor {
24504 pub(crate) prompt: Entity<Editor>,
24505 editor: WeakEntity<Editor>,
24506 breakpoint_anchor: Anchor,
24507 breakpoint: Breakpoint,
24508 edit_action: BreakpointPromptEditAction,
24509 block_ids: HashSet<CustomBlockId>,
24510 editor_margins: Arc<Mutex<EditorMargins>>,
24511 _subscriptions: Vec<Subscription>,
24512}
24513
24514impl BreakpointPromptEditor {
24515 const MAX_LINES: u8 = 4;
24516
24517 fn new(
24518 editor: WeakEntity<Editor>,
24519 breakpoint_anchor: Anchor,
24520 breakpoint: Breakpoint,
24521 edit_action: BreakpointPromptEditAction,
24522 window: &mut Window,
24523 cx: &mut Context<Self>,
24524 ) -> Self {
24525 let base_text = match edit_action {
24526 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24527 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24528 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24529 }
24530 .map(|msg| msg.to_string())
24531 .unwrap_or_default();
24532
24533 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24534 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24535
24536 let prompt = cx.new(|cx| {
24537 let mut prompt = Editor::new(
24538 EditorMode::AutoHeight {
24539 min_lines: 1,
24540 max_lines: Some(Self::MAX_LINES as usize),
24541 },
24542 buffer,
24543 None,
24544 window,
24545 cx,
24546 );
24547 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24548 prompt.set_show_cursor_when_unfocused(false, cx);
24549 prompt.set_placeholder_text(
24550 match edit_action {
24551 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24552 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24553 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24554 },
24555 window,
24556 cx,
24557 );
24558
24559 prompt
24560 });
24561
24562 Self {
24563 prompt,
24564 editor,
24565 breakpoint_anchor,
24566 breakpoint,
24567 edit_action,
24568 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24569 block_ids: Default::default(),
24570 _subscriptions: vec![],
24571 }
24572 }
24573
24574 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24575 self.block_ids.extend(block_ids)
24576 }
24577
24578 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24579 if let Some(editor) = self.editor.upgrade() {
24580 let message = self
24581 .prompt
24582 .read(cx)
24583 .buffer
24584 .read(cx)
24585 .as_singleton()
24586 .expect("A multi buffer in breakpoint prompt isn't possible")
24587 .read(cx)
24588 .as_rope()
24589 .to_string();
24590
24591 editor.update(cx, |editor, cx| {
24592 editor.edit_breakpoint_at_anchor(
24593 self.breakpoint_anchor,
24594 self.breakpoint.clone(),
24595 match self.edit_action {
24596 BreakpointPromptEditAction::Log => {
24597 BreakpointEditAction::EditLogMessage(message.into())
24598 }
24599 BreakpointPromptEditAction::Condition => {
24600 BreakpointEditAction::EditCondition(message.into())
24601 }
24602 BreakpointPromptEditAction::HitCondition => {
24603 BreakpointEditAction::EditHitCondition(message.into())
24604 }
24605 },
24606 cx,
24607 );
24608
24609 editor.remove_blocks(self.block_ids.clone(), None, cx);
24610 cx.focus_self(window);
24611 });
24612 }
24613 }
24614
24615 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24616 self.editor
24617 .update(cx, |editor, cx| {
24618 editor.remove_blocks(self.block_ids.clone(), None, cx);
24619 window.focus(&editor.focus_handle);
24620 })
24621 .log_err();
24622 }
24623
24624 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24625 let settings = ThemeSettings::get_global(cx);
24626 let text_style = TextStyle {
24627 color: if self.prompt.read(cx).read_only(cx) {
24628 cx.theme().colors().text_disabled
24629 } else {
24630 cx.theme().colors().text
24631 },
24632 font_family: settings.buffer_font.family.clone(),
24633 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24634 font_size: settings.buffer_font_size(cx).into(),
24635 font_weight: settings.buffer_font.weight,
24636 line_height: relative(settings.buffer_line_height.value()),
24637 ..Default::default()
24638 };
24639 EditorElement::new(
24640 &self.prompt,
24641 EditorStyle {
24642 background: cx.theme().colors().editor_background,
24643 local_player: cx.theme().players().local(),
24644 text: text_style,
24645 ..Default::default()
24646 },
24647 )
24648 }
24649}
24650
24651impl Render for BreakpointPromptEditor {
24652 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24653 let editor_margins = *self.editor_margins.lock();
24654 let gutter_dimensions = editor_margins.gutter;
24655 h_flex()
24656 .key_context("Editor")
24657 .bg(cx.theme().colors().editor_background)
24658 .border_y_1()
24659 .border_color(cx.theme().status().info_border)
24660 .size_full()
24661 .py(window.line_height() / 2.5)
24662 .on_action(cx.listener(Self::confirm))
24663 .on_action(cx.listener(Self::cancel))
24664 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24665 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24666 }
24667}
24668
24669impl Focusable for BreakpointPromptEditor {
24670 fn focus_handle(&self, cx: &App) -> FocusHandle {
24671 self.prompt.focus_handle(cx)
24672 }
24673}
24674
24675fn all_edits_insertions_or_deletions(
24676 edits: &Vec<(Range<Anchor>, String)>,
24677 snapshot: &MultiBufferSnapshot,
24678) -> bool {
24679 let mut all_insertions = true;
24680 let mut all_deletions = true;
24681
24682 for (range, new_text) in edits.iter() {
24683 let range_is_empty = range.to_offset(snapshot).is_empty();
24684 let text_is_empty = new_text.is_empty();
24685
24686 if range_is_empty != text_is_empty {
24687 if range_is_empty {
24688 all_deletions = false;
24689 } else {
24690 all_insertions = false;
24691 }
24692 } else {
24693 return false;
24694 }
24695
24696 if !all_insertions && !all_deletions {
24697 return false;
24698 }
24699 }
24700 all_insertions || all_deletions
24701}
24702
24703struct MissingEditPredictionKeybindingTooltip;
24704
24705impl Render for MissingEditPredictionKeybindingTooltip {
24706 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24707 ui::tooltip_container(cx, |container, cx| {
24708 container
24709 .flex_shrink_0()
24710 .max_w_80()
24711 .min_h(rems_from_px(124.))
24712 .justify_between()
24713 .child(
24714 v_flex()
24715 .flex_1()
24716 .text_ui_sm(cx)
24717 .child(Label::new("Conflict with Accept Keybinding"))
24718 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24719 )
24720 .child(
24721 h_flex()
24722 .pb_1()
24723 .gap_1()
24724 .items_end()
24725 .w_full()
24726 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24727 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24728 }))
24729 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24730 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24731 })),
24732 )
24733 })
24734 }
24735}
24736
24737#[derive(Debug, Clone, Copy, PartialEq)]
24738pub struct LineHighlight {
24739 pub background: Background,
24740 pub border: Option<gpui::Hsla>,
24741 pub include_gutter: bool,
24742 pub type_id: Option<TypeId>,
24743}
24744
24745struct LineManipulationResult {
24746 pub new_text: String,
24747 pub line_count_before: usize,
24748 pub line_count_after: usize,
24749}
24750
24751fn render_diff_hunk_controls(
24752 row: u32,
24753 status: &DiffHunkStatus,
24754 hunk_range: Range<Anchor>,
24755 is_created_file: bool,
24756 line_height: Pixels,
24757 editor: &Entity<Editor>,
24758 _win: &mut Window,
24759 cx: &mut App,
24760) -> AnyElement {
24761 h_flex()
24762 .h(line_height)
24763 .mr_1()
24764 .gap_1()
24765 .px_0p5()
24766 .pb_1()
24767 .border_x_1()
24768 .border_b_1()
24769 .border_color(cx.theme().colors().border_variant)
24770 .rounded_b_lg()
24771 .bg(cx.theme().colors().editor_background)
24772 .gap_1()
24773 .block_mouse_except_scroll()
24774 .shadow_md()
24775 .child(if status.has_secondary_hunk() {
24776 Button::new(("stage", row as u64), "Stage")
24777 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24778 .tooltip({
24779 let focus_handle = editor.focus_handle(cx);
24780 move |_win, cx| {
24781 Tooltip::for_action_in(
24782 "Stage Hunk",
24783 &::git::ToggleStaged,
24784 &focus_handle,
24785 cx,
24786 )
24787 }
24788 })
24789 .on_click({
24790 let editor = editor.clone();
24791 move |_event, _window, cx| {
24792 editor.update(cx, |editor, cx| {
24793 editor.stage_or_unstage_diff_hunks(
24794 true,
24795 vec![hunk_range.start..hunk_range.start],
24796 cx,
24797 );
24798 });
24799 }
24800 })
24801 } else {
24802 Button::new(("unstage", row as u64), "Unstage")
24803 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24804 .tooltip({
24805 let focus_handle = editor.focus_handle(cx);
24806 move |_window, cx| {
24807 Tooltip::for_action_in(
24808 "Unstage Hunk",
24809 &::git::ToggleStaged,
24810 &focus_handle,
24811 cx,
24812 )
24813 }
24814 })
24815 .on_click({
24816 let editor = editor.clone();
24817 move |_event, _window, cx| {
24818 editor.update(cx, |editor, cx| {
24819 editor.stage_or_unstage_diff_hunks(
24820 false,
24821 vec![hunk_range.start..hunk_range.start],
24822 cx,
24823 );
24824 });
24825 }
24826 })
24827 })
24828 .child(
24829 Button::new(("restore", row as u64), "Restore")
24830 .tooltip({
24831 let focus_handle = editor.focus_handle(cx);
24832 move |_window, cx| {
24833 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24834 }
24835 })
24836 .on_click({
24837 let editor = editor.clone();
24838 move |_event, window, cx| {
24839 editor.update(cx, |editor, cx| {
24840 let snapshot = editor.snapshot(window, cx);
24841 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24842 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24843 });
24844 }
24845 })
24846 .disabled(is_created_file),
24847 )
24848 .when(
24849 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24850 |el| {
24851 el.child(
24852 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24853 .shape(IconButtonShape::Square)
24854 .icon_size(IconSize::Small)
24855 // .disabled(!has_multiple_hunks)
24856 .tooltip({
24857 let focus_handle = editor.focus_handle(cx);
24858 move |_window, cx| {
24859 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24860 }
24861 })
24862 .on_click({
24863 let editor = editor.clone();
24864 move |_event, window, cx| {
24865 editor.update(cx, |editor, cx| {
24866 let snapshot = editor.snapshot(window, cx);
24867 let position =
24868 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24869 editor.go_to_hunk_before_or_after_position(
24870 &snapshot,
24871 position,
24872 Direction::Next,
24873 window,
24874 cx,
24875 );
24876 editor.expand_selected_diff_hunks(cx);
24877 });
24878 }
24879 }),
24880 )
24881 .child(
24882 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24883 .shape(IconButtonShape::Square)
24884 .icon_size(IconSize::Small)
24885 // .disabled(!has_multiple_hunks)
24886 .tooltip({
24887 let focus_handle = editor.focus_handle(cx);
24888 move |_window, cx| {
24889 Tooltip::for_action_in(
24890 "Previous Hunk",
24891 &GoToPreviousHunk,
24892 &focus_handle,
24893 cx,
24894 )
24895 }
24896 })
24897 .on_click({
24898 let editor = editor.clone();
24899 move |_event, window, cx| {
24900 editor.update(cx, |editor, cx| {
24901 let snapshot = editor.snapshot(window, cx);
24902 let point =
24903 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24904 editor.go_to_hunk_before_or_after_position(
24905 &snapshot,
24906 point,
24907 Direction::Prev,
24908 window,
24909 cx,
24910 );
24911 editor.expand_selected_diff_hunks(cx);
24912 });
24913 }
24914 }),
24915 )
24916 },
24917 )
24918 .into_any_element()
24919}
24920
24921pub fn multibuffer_context_lines(cx: &App) -> u32 {
24922 EditorSettings::try_get(cx)
24923 .map(|settings| settings.excerpt_context_lines)
24924 .unwrap_or(2)
24925 .min(32)
24926}