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 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2285 editor.inline_blame_popover.take();
2286 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2287 cx.background_executor()
2288 .timer(Duration::from_millis(50))
2289 .await;
2290 editor
2291 .update_in(cx, |editor, window, cx| {
2292 editor.register_visible_buffers(cx);
2293 editor.refresh_colors_for_visible_range(None, window, cx);
2294 editor.refresh_inlay_hints(
2295 InlayHintRefreshReason::NewLinesShown,
2296 cx,
2297 );
2298
2299 let new_anchor = editor.scroll_manager.anchor();
2300 let snapshot = editor.snapshot(window, cx);
2301 editor.update_restoration_data(cx, move |data| {
2302 data.scroll_position = (
2303 new_anchor.top_row(snapshot.buffer_snapshot()),
2304 new_anchor.offset,
2305 );
2306 });
2307 })
2308 .ok();
2309 });
2310 }
2311 }
2312 EditorEvent::Edited { .. } => {
2313 if vim_flavor(cx).is_none() {
2314 let display_map = editor.display_snapshot(cx);
2315 let selections = editor.selections.all_adjusted_display(&display_map);
2316 let pop_state = editor
2317 .change_list
2318 .last()
2319 .map(|previous| {
2320 previous.len() == selections.len()
2321 && previous.iter().enumerate().all(|(ix, p)| {
2322 p.to_display_point(&display_map).row()
2323 == selections[ix].head().row()
2324 })
2325 })
2326 .unwrap_or(false);
2327 let new_positions = selections
2328 .into_iter()
2329 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2330 .collect();
2331 editor
2332 .change_list
2333 .push_to_change_list(pop_state, new_positions);
2334 }
2335 }
2336 _ => (),
2337 },
2338 ));
2339
2340 if let Some(dap_store) = editor
2341 .project
2342 .as_ref()
2343 .map(|project| project.read(cx).dap_store())
2344 {
2345 let weak_editor = cx.weak_entity();
2346
2347 editor
2348 ._subscriptions
2349 .push(
2350 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2351 let session_entity = cx.entity();
2352 weak_editor
2353 .update(cx, |editor, cx| {
2354 editor._subscriptions.push(
2355 cx.subscribe(&session_entity, Self::on_debug_session_event),
2356 );
2357 })
2358 .ok();
2359 }),
2360 );
2361
2362 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2363 editor
2364 ._subscriptions
2365 .push(cx.subscribe(&session, Self::on_debug_session_event));
2366 }
2367 }
2368
2369 // skip adding the initial selection to selection history
2370 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2371 editor.end_selection(window, cx);
2372 editor.selection_history.mode = SelectionHistoryMode::Normal;
2373
2374 editor.scroll_manager.show_scrollbars(window, cx);
2375 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2376
2377 if full_mode {
2378 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2379 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2380
2381 if editor.git_blame_inline_enabled {
2382 editor.start_git_blame_inline(false, window, cx);
2383 }
2384
2385 editor.go_to_active_debug_line(window, cx);
2386
2387 editor.minimap =
2388 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2389 editor.colors = Some(LspColorData::new(cx));
2390 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2391
2392 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2393 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2394 }
2395 editor.update_lsp_data(None, window, cx);
2396 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2397 }
2398
2399 editor
2400 }
2401
2402 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2403 self.selections.display_map(cx)
2404 }
2405
2406 pub fn deploy_mouse_context_menu(
2407 &mut self,
2408 position: gpui::Point<Pixels>,
2409 context_menu: Entity<ContextMenu>,
2410 window: &mut Window,
2411 cx: &mut Context<Self>,
2412 ) {
2413 self.mouse_context_menu = Some(MouseContextMenu::new(
2414 self,
2415 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2416 context_menu,
2417 window,
2418 cx,
2419 ));
2420 }
2421
2422 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2423 self.mouse_context_menu
2424 .as_ref()
2425 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2426 }
2427
2428 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2429 if self
2430 .selections
2431 .pending_anchor()
2432 .is_some_and(|pending_selection| {
2433 let snapshot = self.buffer().read(cx).snapshot(cx);
2434 pending_selection.range().includes(range, &snapshot)
2435 })
2436 {
2437 return true;
2438 }
2439
2440 self.selections
2441 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2442 .into_iter()
2443 .any(|selection| {
2444 // This is needed to cover a corner case, if we just check for an existing
2445 // selection in the fold range, having a cursor at the start of the fold
2446 // marks it as selected. Non-empty selections don't cause this.
2447 let length = selection.end - selection.start;
2448 length > 0
2449 })
2450 }
2451
2452 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2453 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2454 }
2455
2456 fn key_context_internal(
2457 &self,
2458 has_active_edit_prediction: bool,
2459 window: &mut Window,
2460 cx: &mut App,
2461 ) -> KeyContext {
2462 let mut key_context = KeyContext::new_with_defaults();
2463 key_context.add("Editor");
2464 let mode = match self.mode {
2465 EditorMode::SingleLine => "single_line",
2466 EditorMode::AutoHeight { .. } => "auto_height",
2467 EditorMode::Minimap { .. } => "minimap",
2468 EditorMode::Full { .. } => "full",
2469 };
2470
2471 if EditorSettings::jupyter_enabled(cx) {
2472 key_context.add("jupyter");
2473 }
2474
2475 key_context.set("mode", mode);
2476 if self.pending_rename.is_some() {
2477 key_context.add("renaming");
2478 }
2479
2480 match self.context_menu.borrow().as_ref() {
2481 Some(CodeContextMenu::Completions(menu)) => {
2482 if menu.visible() {
2483 key_context.add("menu");
2484 key_context.add("showing_completions");
2485 }
2486 }
2487 Some(CodeContextMenu::CodeActions(menu)) => {
2488 if menu.visible() {
2489 key_context.add("menu");
2490 key_context.add("showing_code_actions")
2491 }
2492 }
2493 None => {}
2494 }
2495
2496 if self.signature_help_state.has_multiple_signatures() {
2497 key_context.add("showing_signature_help");
2498 }
2499
2500 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2501 if !self.focus_handle(cx).contains_focused(window, cx)
2502 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2503 {
2504 for addon in self.addons.values() {
2505 addon.extend_key_context(&mut key_context, cx)
2506 }
2507 }
2508
2509 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2510 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2511 Some(
2512 file.full_path(cx)
2513 .extension()?
2514 .to_string_lossy()
2515 .into_owned(),
2516 )
2517 }) {
2518 key_context.set("extension", extension);
2519 }
2520 } else {
2521 key_context.add("multibuffer");
2522 }
2523
2524 if has_active_edit_prediction {
2525 if self.edit_prediction_in_conflict() {
2526 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2527 } else {
2528 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2529 key_context.add("copilot_suggestion");
2530 }
2531 }
2532
2533 if self.selection_mark_mode {
2534 key_context.add("selection_mode");
2535 }
2536
2537 let disjoint = self.selections.disjoint_anchors();
2538 let snapshot = self.snapshot(window, cx);
2539 let snapshot = snapshot.buffer_snapshot();
2540 if self.mode == EditorMode::SingleLine
2541 && let [selection] = disjoint
2542 && selection.start == selection.end
2543 && selection.end.to_offset(snapshot) == snapshot.len()
2544 {
2545 key_context.add("end_of_input");
2546 }
2547
2548 key_context
2549 }
2550
2551 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2552 self.last_bounds.as_ref()
2553 }
2554
2555 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2556 if self.mouse_cursor_hidden {
2557 self.mouse_cursor_hidden = false;
2558 cx.notify();
2559 }
2560 }
2561
2562 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2563 let hide_mouse_cursor = match origin {
2564 HideMouseCursorOrigin::TypingAction => {
2565 matches!(
2566 self.hide_mouse_mode,
2567 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2568 )
2569 }
2570 HideMouseCursorOrigin::MovementAction => {
2571 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2572 }
2573 };
2574 if self.mouse_cursor_hidden != hide_mouse_cursor {
2575 self.mouse_cursor_hidden = hide_mouse_cursor;
2576 cx.notify();
2577 }
2578 }
2579
2580 pub fn edit_prediction_in_conflict(&self) -> bool {
2581 if !self.show_edit_predictions_in_menu() {
2582 return false;
2583 }
2584
2585 let showing_completions = self
2586 .context_menu
2587 .borrow()
2588 .as_ref()
2589 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2590
2591 showing_completions
2592 || self.edit_prediction_requires_modifier()
2593 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2594 // bindings to insert tab characters.
2595 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2596 }
2597
2598 pub fn accept_edit_prediction_keybind(
2599 &self,
2600 accept_partial: bool,
2601 window: &mut Window,
2602 cx: &mut App,
2603 ) -> AcceptEditPredictionBinding {
2604 let key_context = self.key_context_internal(true, window, cx);
2605 let in_conflict = self.edit_prediction_in_conflict();
2606
2607 let bindings = if accept_partial {
2608 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2609 } else {
2610 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2611 };
2612
2613 // TODO: if the binding contains multiple keystrokes, display all of them, not
2614 // just the first one.
2615 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2616 !in_conflict
2617 || binding
2618 .keystrokes()
2619 .first()
2620 .is_some_and(|keystroke| keystroke.modifiers().modified())
2621 }))
2622 }
2623
2624 pub fn new_file(
2625 workspace: &mut Workspace,
2626 _: &workspace::NewFile,
2627 window: &mut Window,
2628 cx: &mut Context<Workspace>,
2629 ) {
2630 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2631 "Failed to create buffer",
2632 window,
2633 cx,
2634 |e, _, _| match e.error_code() {
2635 ErrorCode::RemoteUpgradeRequired => Some(format!(
2636 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2637 e.error_tag("required").unwrap_or("the latest version")
2638 )),
2639 _ => None,
2640 },
2641 );
2642 }
2643
2644 pub fn new_in_workspace(
2645 workspace: &mut Workspace,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) -> Task<Result<Entity<Editor>>> {
2649 let project = workspace.project().clone();
2650 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2651
2652 cx.spawn_in(window, async move |workspace, cx| {
2653 let buffer = create.await?;
2654 workspace.update_in(cx, |workspace, window, cx| {
2655 let editor =
2656 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2657 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2658 editor
2659 })
2660 })
2661 }
2662
2663 fn new_file_vertical(
2664 workspace: &mut Workspace,
2665 _: &workspace::NewFileSplitVertical,
2666 window: &mut Window,
2667 cx: &mut Context<Workspace>,
2668 ) {
2669 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2670 }
2671
2672 fn new_file_horizontal(
2673 workspace: &mut Workspace,
2674 _: &workspace::NewFileSplitHorizontal,
2675 window: &mut Window,
2676 cx: &mut Context<Workspace>,
2677 ) {
2678 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2679 }
2680
2681 fn new_file_split(
2682 workspace: &mut Workspace,
2683 action: &workspace::NewFileSplit,
2684 window: &mut Window,
2685 cx: &mut Context<Workspace>,
2686 ) {
2687 Self::new_file_in_direction(workspace, action.0, window, cx)
2688 }
2689
2690 fn new_file_in_direction(
2691 workspace: &mut Workspace,
2692 direction: SplitDirection,
2693 window: &mut Window,
2694 cx: &mut Context<Workspace>,
2695 ) {
2696 let project = workspace.project().clone();
2697 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2698
2699 cx.spawn_in(window, async move |workspace, cx| {
2700 let buffer = create.await?;
2701 workspace.update_in(cx, move |workspace, window, cx| {
2702 workspace.split_item(
2703 direction,
2704 Box::new(
2705 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2706 ),
2707 window,
2708 cx,
2709 )
2710 })?;
2711 anyhow::Ok(())
2712 })
2713 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2714 match e.error_code() {
2715 ErrorCode::RemoteUpgradeRequired => Some(format!(
2716 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2717 e.error_tag("required").unwrap_or("the latest version")
2718 )),
2719 _ => None,
2720 }
2721 });
2722 }
2723
2724 pub fn leader_id(&self) -> Option<CollaboratorId> {
2725 self.leader_id
2726 }
2727
2728 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2729 &self.buffer
2730 }
2731
2732 pub fn project(&self) -> Option<&Entity<Project>> {
2733 self.project.as_ref()
2734 }
2735
2736 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2737 self.workspace.as_ref()?.0.upgrade()
2738 }
2739
2740 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2741 self.buffer().read(cx).title(cx)
2742 }
2743
2744 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2745 let git_blame_gutter_max_author_length = self
2746 .render_git_blame_gutter(cx)
2747 .then(|| {
2748 if let Some(blame) = self.blame.as_ref() {
2749 let max_author_length =
2750 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2751 Some(max_author_length)
2752 } else {
2753 None
2754 }
2755 })
2756 .flatten();
2757
2758 EditorSnapshot {
2759 mode: self.mode.clone(),
2760 show_gutter: self.show_gutter,
2761 show_line_numbers: self.show_line_numbers,
2762 show_git_diff_gutter: self.show_git_diff_gutter,
2763 show_code_actions: self.show_code_actions,
2764 show_runnables: self.show_runnables,
2765 show_breakpoints: self.show_breakpoints,
2766 git_blame_gutter_max_author_length,
2767 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2768 placeholder_display_snapshot: self
2769 .placeholder_display_map
2770 .as_ref()
2771 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2772 scroll_anchor: self.scroll_manager.anchor(),
2773 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2774 is_focused: self.focus_handle.is_focused(window),
2775 current_line_highlight: self
2776 .current_line_highlight
2777 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2778 gutter_hovered: self.gutter_hovered,
2779 }
2780 }
2781
2782 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2783 self.buffer.read(cx).language_at(point, cx)
2784 }
2785
2786 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2787 self.buffer.read(cx).read(cx).file_at(point).cloned()
2788 }
2789
2790 pub fn active_excerpt(
2791 &self,
2792 cx: &App,
2793 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2794 self.buffer
2795 .read(cx)
2796 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2797 }
2798
2799 pub fn mode(&self) -> &EditorMode {
2800 &self.mode
2801 }
2802
2803 pub fn set_mode(&mut self, mode: EditorMode) {
2804 self.mode = mode;
2805 }
2806
2807 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2808 self.collaboration_hub.as_deref()
2809 }
2810
2811 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2812 self.collaboration_hub = Some(hub);
2813 }
2814
2815 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2816 self.in_project_search = in_project_search;
2817 }
2818
2819 pub fn set_custom_context_menu(
2820 &mut self,
2821 f: impl 'static
2822 + Fn(
2823 &mut Self,
2824 DisplayPoint,
2825 &mut Window,
2826 &mut Context<Self>,
2827 ) -> Option<Entity<ui::ContextMenu>>,
2828 ) {
2829 self.custom_context_menu = Some(Box::new(f))
2830 }
2831
2832 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2833 self.completion_provider = provider;
2834 }
2835
2836 #[cfg(any(test, feature = "test-support"))]
2837 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2838 self.completion_provider.clone()
2839 }
2840
2841 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2842 self.semantics_provider.clone()
2843 }
2844
2845 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2846 self.semantics_provider = provider;
2847 }
2848
2849 pub fn set_edit_prediction_provider<T>(
2850 &mut self,
2851 provider: Option<Entity<T>>,
2852 window: &mut Window,
2853 cx: &mut Context<Self>,
2854 ) where
2855 T: EditPredictionProvider,
2856 {
2857 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2858 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2859 if this.focus_handle.is_focused(window) {
2860 this.update_visible_edit_prediction(window, cx);
2861 }
2862 }),
2863 provider: Arc::new(provider),
2864 });
2865 self.update_edit_prediction_settings(cx);
2866 self.refresh_edit_prediction(false, false, window, cx);
2867 }
2868
2869 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2870 self.placeholder_display_map
2871 .as_ref()
2872 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2873 }
2874
2875 pub fn set_placeholder_text(
2876 &mut self,
2877 placeholder_text: &str,
2878 window: &mut Window,
2879 cx: &mut Context<Self>,
2880 ) {
2881 let multibuffer = cx
2882 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2883
2884 let style = window.text_style();
2885
2886 self.placeholder_display_map = Some(cx.new(|cx| {
2887 DisplayMap::new(
2888 multibuffer,
2889 style.font(),
2890 style.font_size.to_pixels(window.rem_size()),
2891 None,
2892 FILE_HEADER_HEIGHT,
2893 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2894 Default::default(),
2895 DiagnosticSeverity::Off,
2896 cx,
2897 )
2898 }));
2899 cx.notify();
2900 }
2901
2902 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2903 self.cursor_shape = cursor_shape;
2904
2905 // Disrupt blink for immediate user feedback that the cursor shape has changed
2906 self.blink_manager.update(cx, BlinkManager::show_cursor);
2907
2908 cx.notify();
2909 }
2910
2911 pub fn set_current_line_highlight(
2912 &mut self,
2913 current_line_highlight: Option<CurrentLineHighlight>,
2914 ) {
2915 self.current_line_highlight = current_line_highlight;
2916 }
2917
2918 pub fn range_for_match<T: std::marker::Copy>(
2919 &self,
2920 range: &Range<T>,
2921 collapse: bool,
2922 ) -> Range<T> {
2923 if collapse {
2924 return range.start..range.start;
2925 }
2926 range.clone()
2927 }
2928
2929 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2930 if self.display_map.read(cx).clip_at_line_ends != clip {
2931 self.display_map
2932 .update(cx, |map, _| map.clip_at_line_ends = clip);
2933 }
2934 }
2935
2936 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2937 self.input_enabled = input_enabled;
2938 }
2939
2940 pub fn set_edit_predictions_hidden_for_vim_mode(
2941 &mut self,
2942 hidden: bool,
2943 window: &mut Window,
2944 cx: &mut Context<Self>,
2945 ) {
2946 if hidden != self.edit_predictions_hidden_for_vim_mode {
2947 self.edit_predictions_hidden_for_vim_mode = hidden;
2948 if hidden {
2949 self.update_visible_edit_prediction(window, cx);
2950 } else {
2951 self.refresh_edit_prediction(true, false, window, cx);
2952 }
2953 }
2954 }
2955
2956 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2957 self.menu_edit_predictions_policy = value;
2958 }
2959
2960 pub fn set_autoindent(&mut self, autoindent: bool) {
2961 if autoindent {
2962 self.autoindent_mode = Some(AutoindentMode::EachLine);
2963 } else {
2964 self.autoindent_mode = None;
2965 }
2966 }
2967
2968 pub fn read_only(&self, cx: &App) -> bool {
2969 self.read_only || self.buffer.read(cx).read_only()
2970 }
2971
2972 pub fn set_read_only(&mut self, read_only: bool) {
2973 self.read_only = read_only;
2974 }
2975
2976 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2977 self.use_autoclose = autoclose;
2978 }
2979
2980 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2981 self.use_auto_surround = auto_surround;
2982 }
2983
2984 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2985 self.auto_replace_emoji_shortcode = auto_replace;
2986 }
2987
2988 pub fn toggle_edit_predictions(
2989 &mut self,
2990 _: &ToggleEditPrediction,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 ) {
2994 if self.show_edit_predictions_override.is_some() {
2995 self.set_show_edit_predictions(None, window, cx);
2996 } else {
2997 let show_edit_predictions = !self.edit_predictions_enabled();
2998 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2999 }
3000 }
3001
3002 pub fn set_show_edit_predictions(
3003 &mut self,
3004 show_edit_predictions: Option<bool>,
3005 window: &mut Window,
3006 cx: &mut Context<Self>,
3007 ) {
3008 self.show_edit_predictions_override = show_edit_predictions;
3009 self.update_edit_prediction_settings(cx);
3010
3011 if let Some(false) = show_edit_predictions {
3012 self.discard_edit_prediction(false, cx);
3013 } else {
3014 self.refresh_edit_prediction(false, true, window, cx);
3015 }
3016 }
3017
3018 fn edit_predictions_disabled_in_scope(
3019 &self,
3020 buffer: &Entity<Buffer>,
3021 buffer_position: language::Anchor,
3022 cx: &App,
3023 ) -> bool {
3024 let snapshot = buffer.read(cx).snapshot();
3025 let settings = snapshot.settings_at(buffer_position, cx);
3026
3027 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3028 return false;
3029 };
3030
3031 scope.override_name().is_some_and(|scope_name| {
3032 settings
3033 .edit_predictions_disabled_in
3034 .iter()
3035 .any(|s| s == scope_name)
3036 })
3037 }
3038
3039 pub fn set_use_modal_editing(&mut self, to: bool) {
3040 self.use_modal_editing = to;
3041 }
3042
3043 pub fn use_modal_editing(&self) -> bool {
3044 self.use_modal_editing
3045 }
3046
3047 fn selections_did_change(
3048 &mut self,
3049 local: bool,
3050 old_cursor_position: &Anchor,
3051 effects: SelectionEffects,
3052 window: &mut Window,
3053 cx: &mut Context<Self>,
3054 ) {
3055 window.invalidate_character_coordinates();
3056
3057 // Copy selections to primary selection buffer
3058 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3059 if local {
3060 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3061 let buffer_handle = self.buffer.read(cx).read(cx);
3062
3063 let mut text = String::new();
3064 for (index, selection) in selections.iter().enumerate() {
3065 let text_for_selection = buffer_handle
3066 .text_for_range(selection.start..selection.end)
3067 .collect::<String>();
3068
3069 text.push_str(&text_for_selection);
3070 if index != selections.len() - 1 {
3071 text.push('\n');
3072 }
3073 }
3074
3075 if !text.is_empty() {
3076 cx.write_to_primary(ClipboardItem::new_string(text));
3077 }
3078 }
3079
3080 let selection_anchors = self.selections.disjoint_anchors_arc();
3081
3082 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3083 self.buffer.update(cx, |buffer, cx| {
3084 buffer.set_active_selections(
3085 &selection_anchors,
3086 self.selections.line_mode(),
3087 self.cursor_shape,
3088 cx,
3089 )
3090 });
3091 }
3092 let display_map = self
3093 .display_map
3094 .update(cx, |display_map, cx| display_map.snapshot(cx));
3095 let buffer = display_map.buffer_snapshot();
3096 if self.selections.count() == 1 {
3097 self.add_selections_state = None;
3098 }
3099 self.select_next_state = None;
3100 self.select_prev_state = None;
3101 self.select_syntax_node_history.try_clear();
3102 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3103 self.snippet_stack.invalidate(&selection_anchors, buffer);
3104 self.take_rename(false, window, cx);
3105
3106 let newest_selection = self.selections.newest_anchor();
3107 let new_cursor_position = newest_selection.head();
3108 let selection_start = newest_selection.start;
3109
3110 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3111 self.push_to_nav_history(
3112 *old_cursor_position,
3113 Some(new_cursor_position.to_point(buffer)),
3114 false,
3115 effects.nav_history == Some(true),
3116 cx,
3117 );
3118 }
3119
3120 if local {
3121 if let Some(buffer_id) = new_cursor_position.buffer_id {
3122 self.register_buffer(buffer_id, cx);
3123 }
3124
3125 let mut context_menu = self.context_menu.borrow_mut();
3126 let completion_menu = match context_menu.as_ref() {
3127 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3128 Some(CodeContextMenu::CodeActions(_)) => {
3129 *context_menu = None;
3130 None
3131 }
3132 None => None,
3133 };
3134 let completion_position = completion_menu.map(|menu| menu.initial_position);
3135 drop(context_menu);
3136
3137 if effects.completions
3138 && let Some(completion_position) = completion_position
3139 {
3140 let start_offset = selection_start.to_offset(buffer);
3141 let position_matches = start_offset == completion_position.to_offset(buffer);
3142 let continue_showing = if position_matches {
3143 if self.snippet_stack.is_empty() {
3144 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3145 == Some(CharKind::Word)
3146 } else {
3147 // Snippet choices can be shown even when the cursor is in whitespace.
3148 // Dismissing the menu with actions like backspace is handled by
3149 // invalidation regions.
3150 true
3151 }
3152 } else {
3153 false
3154 };
3155
3156 if continue_showing {
3157 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3158 } else {
3159 self.hide_context_menu(window, cx);
3160 }
3161 }
3162
3163 hide_hover(self, cx);
3164
3165 if old_cursor_position.to_display_point(&display_map).row()
3166 != new_cursor_position.to_display_point(&display_map).row()
3167 {
3168 self.available_code_actions.take();
3169 }
3170 self.refresh_code_actions(window, cx);
3171 self.refresh_document_highlights(cx);
3172 refresh_linked_ranges(self, window, cx);
3173
3174 self.refresh_selected_text_highlights(false, window, cx);
3175 self.refresh_matching_bracket_highlights(window, cx);
3176 self.update_visible_edit_prediction(window, cx);
3177 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3178 self.inline_blame_popover.take();
3179 if self.git_blame_inline_enabled {
3180 self.start_inline_blame_timer(window, cx);
3181 }
3182 }
3183
3184 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3185 cx.emit(EditorEvent::SelectionsChanged { local });
3186
3187 let selections = &self.selections.disjoint_anchors_arc();
3188 if selections.len() == 1 {
3189 cx.emit(SearchEvent::ActiveMatchChanged)
3190 }
3191 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3192 let inmemory_selections = selections
3193 .iter()
3194 .map(|s| {
3195 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3196 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3197 })
3198 .collect();
3199 self.update_restoration_data(cx, |data| {
3200 data.selections = inmemory_selections;
3201 });
3202
3203 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3204 && let Some(workspace_id) =
3205 self.workspace.as_ref().and_then(|workspace| workspace.1)
3206 {
3207 let snapshot = self.buffer().read(cx).snapshot(cx);
3208 let selections = selections.clone();
3209 let background_executor = cx.background_executor().clone();
3210 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3211 self.serialize_selections = cx.background_spawn(async move {
3212 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3213 let db_selections = selections
3214 .iter()
3215 .map(|selection| {
3216 (
3217 selection.start.to_offset(&snapshot),
3218 selection.end.to_offset(&snapshot),
3219 )
3220 })
3221 .collect();
3222
3223 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3224 .await
3225 .with_context(|| {
3226 format!(
3227 "persisting editor selections for editor {editor_id}, \
3228 workspace {workspace_id:?}"
3229 )
3230 })
3231 .log_err();
3232 });
3233 }
3234 }
3235
3236 cx.notify();
3237 }
3238
3239 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3240 use text::ToOffset as _;
3241 use text::ToPoint as _;
3242
3243 if self.mode.is_minimap()
3244 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3245 {
3246 return;
3247 }
3248
3249 if !self.buffer().read(cx).is_singleton() {
3250 return;
3251 }
3252
3253 let display_snapshot = self
3254 .display_map
3255 .update(cx, |display_map, cx| display_map.snapshot(cx));
3256 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3257 return;
3258 };
3259 let inmemory_folds = display_snapshot
3260 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3261 .map(|fold| {
3262 fold.range.start.text_anchor.to_point(&snapshot)
3263 ..fold.range.end.text_anchor.to_point(&snapshot)
3264 })
3265 .collect();
3266 self.update_restoration_data(cx, |data| {
3267 data.folds = inmemory_folds;
3268 });
3269
3270 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3271 return;
3272 };
3273 let background_executor = cx.background_executor().clone();
3274 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3275 let db_folds = display_snapshot
3276 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3277 .map(|fold| {
3278 (
3279 fold.range.start.text_anchor.to_offset(&snapshot),
3280 fold.range.end.text_anchor.to_offset(&snapshot),
3281 )
3282 })
3283 .collect();
3284 self.serialize_folds = cx.background_spawn(async move {
3285 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3286 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3287 .await
3288 .with_context(|| {
3289 format!(
3290 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3291 )
3292 })
3293 .log_err();
3294 });
3295 }
3296
3297 pub fn sync_selections(
3298 &mut self,
3299 other: Entity<Editor>,
3300 cx: &mut Context<Self>,
3301 ) -> gpui::Subscription {
3302 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3303 if !other_selections.is_empty() {
3304 self.selections.change_with(cx, |selections| {
3305 selections.select_anchors(other_selections);
3306 });
3307 }
3308
3309 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3310 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3311 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3312 if other_selections.is_empty() {
3313 return;
3314 }
3315 this.selections.change_with(cx, |selections| {
3316 selections.select_anchors(other_selections);
3317 });
3318 }
3319 });
3320
3321 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3322 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3323 let these_selections = this.selections.disjoint_anchors().to_vec();
3324 if these_selections.is_empty() {
3325 return;
3326 }
3327 other.update(cx, |other_editor, cx| {
3328 other_editor.selections.change_with(cx, |selections| {
3329 selections.select_anchors(these_selections);
3330 })
3331 });
3332 }
3333 });
3334
3335 Subscription::join(other_subscription, this_subscription)
3336 }
3337
3338 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3339 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3340 /// effects of selection change occur at the end of the transaction.
3341 pub fn change_selections<R>(
3342 &mut self,
3343 effects: SelectionEffects,
3344 window: &mut Window,
3345 cx: &mut Context<Self>,
3346 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3347 ) -> R {
3348 if let Some(state) = &mut self.deferred_selection_effects_state {
3349 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3350 state.effects.completions = effects.completions;
3351 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3352 let (changed, result) = self.selections.change_with(cx, change);
3353 state.changed |= changed;
3354 return result;
3355 }
3356 let mut state = DeferredSelectionEffectsState {
3357 changed: false,
3358 effects,
3359 old_cursor_position: self.selections.newest_anchor().head(),
3360 history_entry: SelectionHistoryEntry {
3361 selections: self.selections.disjoint_anchors_arc(),
3362 select_next_state: self.select_next_state.clone(),
3363 select_prev_state: self.select_prev_state.clone(),
3364 add_selections_state: self.add_selections_state.clone(),
3365 },
3366 };
3367 let (changed, result) = self.selections.change_with(cx, change);
3368 state.changed = state.changed || changed;
3369 if self.defer_selection_effects {
3370 self.deferred_selection_effects_state = Some(state);
3371 } else {
3372 self.apply_selection_effects(state, window, cx);
3373 }
3374 result
3375 }
3376
3377 /// Defers the effects of selection change, so that the effects of multiple calls to
3378 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3379 /// to selection history and the state of popovers based on selection position aren't
3380 /// erroneously updated.
3381 pub fn with_selection_effects_deferred<R>(
3382 &mut self,
3383 window: &mut Window,
3384 cx: &mut Context<Self>,
3385 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3386 ) -> R {
3387 let already_deferred = self.defer_selection_effects;
3388 self.defer_selection_effects = true;
3389 let result = update(self, window, cx);
3390 if !already_deferred {
3391 self.defer_selection_effects = false;
3392 if let Some(state) = self.deferred_selection_effects_state.take() {
3393 self.apply_selection_effects(state, window, cx);
3394 }
3395 }
3396 result
3397 }
3398
3399 fn apply_selection_effects(
3400 &mut self,
3401 state: DeferredSelectionEffectsState,
3402 window: &mut Window,
3403 cx: &mut Context<Self>,
3404 ) {
3405 if state.changed {
3406 self.selection_history.push(state.history_entry);
3407
3408 if let Some(autoscroll) = state.effects.scroll {
3409 self.request_autoscroll(autoscroll, cx);
3410 }
3411
3412 let old_cursor_position = &state.old_cursor_position;
3413
3414 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3415
3416 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3417 self.show_signature_help(&ShowSignatureHelp, window, cx);
3418 }
3419 }
3420 }
3421
3422 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3423 where
3424 I: IntoIterator<Item = (Range<S>, T)>,
3425 S: ToOffset,
3426 T: Into<Arc<str>>,
3427 {
3428 if self.read_only(cx) {
3429 return;
3430 }
3431
3432 self.buffer
3433 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3434 }
3435
3436 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3437 where
3438 I: IntoIterator<Item = (Range<S>, T)>,
3439 S: ToOffset,
3440 T: Into<Arc<str>>,
3441 {
3442 if self.read_only(cx) {
3443 return;
3444 }
3445
3446 self.buffer.update(cx, |buffer, cx| {
3447 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3448 });
3449 }
3450
3451 pub fn edit_with_block_indent<I, S, T>(
3452 &mut self,
3453 edits: I,
3454 original_indent_columns: Vec<Option<u32>>,
3455 cx: &mut Context<Self>,
3456 ) where
3457 I: IntoIterator<Item = (Range<S>, T)>,
3458 S: ToOffset,
3459 T: Into<Arc<str>>,
3460 {
3461 if self.read_only(cx) {
3462 return;
3463 }
3464
3465 self.buffer.update(cx, |buffer, cx| {
3466 buffer.edit(
3467 edits,
3468 Some(AutoindentMode::Block {
3469 original_indent_columns,
3470 }),
3471 cx,
3472 )
3473 });
3474 }
3475
3476 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3477 self.hide_context_menu(window, cx);
3478
3479 match phase {
3480 SelectPhase::Begin {
3481 position,
3482 add,
3483 click_count,
3484 } => self.begin_selection(position, add, click_count, window, cx),
3485 SelectPhase::BeginColumnar {
3486 position,
3487 goal_column,
3488 reset,
3489 mode,
3490 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3491 SelectPhase::Extend {
3492 position,
3493 click_count,
3494 } => self.extend_selection(position, click_count, window, cx),
3495 SelectPhase::Update {
3496 position,
3497 goal_column,
3498 scroll_delta,
3499 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3500 SelectPhase::End => self.end_selection(window, cx),
3501 }
3502 }
3503
3504 fn extend_selection(
3505 &mut self,
3506 position: DisplayPoint,
3507 click_count: usize,
3508 window: &mut Window,
3509 cx: &mut Context<Self>,
3510 ) {
3511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3512 let tail = self.selections.newest::<usize>(&display_map).tail();
3513 let click_count = click_count.max(match self.selections.select_mode() {
3514 SelectMode::Character => 1,
3515 SelectMode::Word(_) => 2,
3516 SelectMode::Line(_) => 3,
3517 SelectMode::All => 4,
3518 });
3519 self.begin_selection(position, false, click_count, window, cx);
3520
3521 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3522
3523 let current_selection = match self.selections.select_mode() {
3524 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3525 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3526 };
3527
3528 let mut pending_selection = self
3529 .selections
3530 .pending_anchor()
3531 .cloned()
3532 .expect("extend_selection not called with pending selection");
3533
3534 if pending_selection
3535 .start
3536 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3537 == Ordering::Greater
3538 {
3539 pending_selection.start = current_selection.start;
3540 }
3541 if pending_selection
3542 .end
3543 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3544 == Ordering::Less
3545 {
3546 pending_selection.end = current_selection.end;
3547 pending_selection.reversed = true;
3548 }
3549
3550 let mut pending_mode = self.selections.pending_mode().unwrap();
3551 match &mut pending_mode {
3552 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3553 _ => {}
3554 }
3555
3556 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3557 SelectionEffects::scroll(Autoscroll::fit())
3558 } else {
3559 SelectionEffects::no_scroll()
3560 };
3561
3562 self.change_selections(effects, window, cx, |s| {
3563 s.set_pending(pending_selection.clone(), pending_mode);
3564 s.set_is_extending(true);
3565 });
3566 }
3567
3568 fn begin_selection(
3569 &mut self,
3570 position: DisplayPoint,
3571 add: bool,
3572 click_count: usize,
3573 window: &mut Window,
3574 cx: &mut Context<Self>,
3575 ) {
3576 if !self.focus_handle.is_focused(window) {
3577 self.last_focused_descendant = None;
3578 window.focus(&self.focus_handle);
3579 }
3580
3581 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3582 let buffer = display_map.buffer_snapshot();
3583 let position = display_map.clip_point(position, Bias::Left);
3584
3585 let start;
3586 let end;
3587 let mode;
3588 let mut auto_scroll;
3589 match click_count {
3590 1 => {
3591 start = buffer.anchor_before(position.to_point(&display_map));
3592 end = start;
3593 mode = SelectMode::Character;
3594 auto_scroll = true;
3595 }
3596 2 => {
3597 let position = display_map
3598 .clip_point(position, Bias::Left)
3599 .to_offset(&display_map, Bias::Left);
3600 let (range, _) = buffer.surrounding_word(position, None);
3601 start = buffer.anchor_before(range.start);
3602 end = buffer.anchor_before(range.end);
3603 mode = SelectMode::Word(start..end);
3604 auto_scroll = true;
3605 }
3606 3 => {
3607 let position = display_map
3608 .clip_point(position, Bias::Left)
3609 .to_point(&display_map);
3610 let line_start = display_map.prev_line_boundary(position).0;
3611 let next_line_start = buffer.clip_point(
3612 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3613 Bias::Left,
3614 );
3615 start = buffer.anchor_before(line_start);
3616 end = buffer.anchor_before(next_line_start);
3617 mode = SelectMode::Line(start..end);
3618 auto_scroll = true;
3619 }
3620 _ => {
3621 start = buffer.anchor_before(0);
3622 end = buffer.anchor_before(buffer.len());
3623 mode = SelectMode::All;
3624 auto_scroll = false;
3625 }
3626 }
3627 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3628
3629 let point_to_delete: Option<usize> = {
3630 let selected_points: Vec<Selection<Point>> =
3631 self.selections.disjoint_in_range(start..end, &display_map);
3632
3633 if !add || click_count > 1 {
3634 None
3635 } else if !selected_points.is_empty() {
3636 Some(selected_points[0].id)
3637 } else {
3638 let clicked_point_already_selected =
3639 self.selections.disjoint_anchors().iter().find(|selection| {
3640 selection.start.to_point(buffer) == start.to_point(buffer)
3641 || selection.end.to_point(buffer) == end.to_point(buffer)
3642 });
3643
3644 clicked_point_already_selected.map(|selection| selection.id)
3645 }
3646 };
3647
3648 let selections_count = self.selections.count();
3649 let effects = if auto_scroll {
3650 SelectionEffects::default()
3651 } else {
3652 SelectionEffects::no_scroll()
3653 };
3654
3655 self.change_selections(effects, window, cx, |s| {
3656 if let Some(point_to_delete) = point_to_delete {
3657 s.delete(point_to_delete);
3658
3659 if selections_count == 1 {
3660 s.set_pending_anchor_range(start..end, mode);
3661 }
3662 } else {
3663 if !add {
3664 s.clear_disjoint();
3665 }
3666
3667 s.set_pending_anchor_range(start..end, mode);
3668 }
3669 });
3670 }
3671
3672 fn begin_columnar_selection(
3673 &mut self,
3674 position: DisplayPoint,
3675 goal_column: u32,
3676 reset: bool,
3677 mode: ColumnarMode,
3678 window: &mut Window,
3679 cx: &mut Context<Self>,
3680 ) {
3681 if !self.focus_handle.is_focused(window) {
3682 self.last_focused_descendant = None;
3683 window.focus(&self.focus_handle);
3684 }
3685
3686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3687
3688 if reset {
3689 let pointer_position = display_map
3690 .buffer_snapshot()
3691 .anchor_before(position.to_point(&display_map));
3692
3693 self.change_selections(
3694 SelectionEffects::scroll(Autoscroll::newest()),
3695 window,
3696 cx,
3697 |s| {
3698 s.clear_disjoint();
3699 s.set_pending_anchor_range(
3700 pointer_position..pointer_position,
3701 SelectMode::Character,
3702 );
3703 },
3704 );
3705 };
3706
3707 let tail = self.selections.newest::<Point>(&display_map).tail();
3708 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3709 self.columnar_selection_state = match mode {
3710 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3711 selection_tail: selection_anchor,
3712 display_point: if reset {
3713 if position.column() != goal_column {
3714 Some(DisplayPoint::new(position.row(), goal_column))
3715 } else {
3716 None
3717 }
3718 } else {
3719 None
3720 },
3721 }),
3722 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3723 selection_tail: selection_anchor,
3724 }),
3725 };
3726
3727 if !reset {
3728 self.select_columns(position, goal_column, &display_map, window, cx);
3729 }
3730 }
3731
3732 fn update_selection(
3733 &mut self,
3734 position: DisplayPoint,
3735 goal_column: u32,
3736 scroll_delta: gpui::Point<f32>,
3737 window: &mut Window,
3738 cx: &mut Context<Self>,
3739 ) {
3740 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3741
3742 if self.columnar_selection_state.is_some() {
3743 self.select_columns(position, goal_column, &display_map, window, cx);
3744 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3745 let buffer = display_map.buffer_snapshot();
3746 let head;
3747 let tail;
3748 let mode = self.selections.pending_mode().unwrap();
3749 match &mode {
3750 SelectMode::Character => {
3751 head = position.to_point(&display_map);
3752 tail = pending.tail().to_point(buffer);
3753 }
3754 SelectMode::Word(original_range) => {
3755 let offset = display_map
3756 .clip_point(position, Bias::Left)
3757 .to_offset(&display_map, Bias::Left);
3758 let original_range = original_range.to_offset(buffer);
3759
3760 let head_offset = if buffer.is_inside_word(offset, None)
3761 || original_range.contains(&offset)
3762 {
3763 let (word_range, _) = buffer.surrounding_word(offset, None);
3764 if word_range.start < original_range.start {
3765 word_range.start
3766 } else {
3767 word_range.end
3768 }
3769 } else {
3770 offset
3771 };
3772
3773 head = head_offset.to_point(buffer);
3774 if head_offset <= original_range.start {
3775 tail = original_range.end.to_point(buffer);
3776 } else {
3777 tail = original_range.start.to_point(buffer);
3778 }
3779 }
3780 SelectMode::Line(original_range) => {
3781 let original_range = original_range.to_point(display_map.buffer_snapshot());
3782
3783 let position = display_map
3784 .clip_point(position, Bias::Left)
3785 .to_point(&display_map);
3786 let line_start = display_map.prev_line_boundary(position).0;
3787 let next_line_start = buffer.clip_point(
3788 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3789 Bias::Left,
3790 );
3791
3792 if line_start < original_range.start {
3793 head = line_start
3794 } else {
3795 head = next_line_start
3796 }
3797
3798 if head <= original_range.start {
3799 tail = original_range.end;
3800 } else {
3801 tail = original_range.start;
3802 }
3803 }
3804 SelectMode::All => {
3805 return;
3806 }
3807 };
3808
3809 if head < tail {
3810 pending.start = buffer.anchor_before(head);
3811 pending.end = buffer.anchor_before(tail);
3812 pending.reversed = true;
3813 } else {
3814 pending.start = buffer.anchor_before(tail);
3815 pending.end = buffer.anchor_before(head);
3816 pending.reversed = false;
3817 }
3818
3819 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3820 s.set_pending(pending.clone(), mode);
3821 });
3822 } else {
3823 log::error!("update_selection dispatched with no pending selection");
3824 return;
3825 }
3826
3827 self.apply_scroll_delta(scroll_delta, window, cx);
3828 cx.notify();
3829 }
3830
3831 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3832 self.columnar_selection_state.take();
3833 if let Some(pending_mode) = self.selections.pending_mode() {
3834 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3835 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3836 s.select(selections);
3837 s.clear_pending();
3838 if s.is_extending() {
3839 s.set_is_extending(false);
3840 } else {
3841 s.set_select_mode(pending_mode);
3842 }
3843 });
3844 }
3845 }
3846
3847 fn select_columns(
3848 &mut self,
3849 head: DisplayPoint,
3850 goal_column: u32,
3851 display_map: &DisplaySnapshot,
3852 window: &mut Window,
3853 cx: &mut Context<Self>,
3854 ) {
3855 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3856 return;
3857 };
3858
3859 let tail = match columnar_state {
3860 ColumnarSelectionState::FromMouse {
3861 selection_tail,
3862 display_point,
3863 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3864 ColumnarSelectionState::FromSelection { selection_tail } => {
3865 selection_tail.to_display_point(display_map)
3866 }
3867 };
3868
3869 let start_row = cmp::min(tail.row(), head.row());
3870 let end_row = cmp::max(tail.row(), head.row());
3871 let start_column = cmp::min(tail.column(), goal_column);
3872 let end_column = cmp::max(tail.column(), goal_column);
3873 let reversed = start_column < tail.column();
3874
3875 let selection_ranges = (start_row.0..=end_row.0)
3876 .map(DisplayRow)
3877 .filter_map(|row| {
3878 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3879 || start_column <= display_map.line_len(row))
3880 && !display_map.is_block_line(row)
3881 {
3882 let start = display_map
3883 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3884 .to_point(display_map);
3885 let end = display_map
3886 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3887 .to_point(display_map);
3888 if reversed {
3889 Some(end..start)
3890 } else {
3891 Some(start..end)
3892 }
3893 } else {
3894 None
3895 }
3896 })
3897 .collect::<Vec<_>>();
3898 if selection_ranges.is_empty() {
3899 return;
3900 }
3901
3902 let ranges = match columnar_state {
3903 ColumnarSelectionState::FromMouse { .. } => {
3904 let mut non_empty_ranges = selection_ranges
3905 .iter()
3906 .filter(|selection_range| selection_range.start != selection_range.end)
3907 .peekable();
3908 if non_empty_ranges.peek().is_some() {
3909 non_empty_ranges.cloned().collect()
3910 } else {
3911 selection_ranges
3912 }
3913 }
3914 _ => selection_ranges,
3915 };
3916
3917 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3918 s.select_ranges(ranges);
3919 });
3920 cx.notify();
3921 }
3922
3923 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3924 self.selections
3925 .all_adjusted(snapshot)
3926 .iter()
3927 .any(|selection| !selection.is_empty())
3928 }
3929
3930 pub fn has_pending_nonempty_selection(&self) -> bool {
3931 let pending_nonempty_selection = match self.selections.pending_anchor() {
3932 Some(Selection { start, end, .. }) => start != end,
3933 None => false,
3934 };
3935
3936 pending_nonempty_selection
3937 || (self.columnar_selection_state.is_some()
3938 && self.selections.disjoint_anchors().len() > 1)
3939 }
3940
3941 pub fn has_pending_selection(&self) -> bool {
3942 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3943 }
3944
3945 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3946 self.selection_mark_mode = false;
3947 self.selection_drag_state = SelectionDragState::None;
3948
3949 if self.clear_expanded_diff_hunks(cx) {
3950 cx.notify();
3951 return;
3952 }
3953 if self.dismiss_menus_and_popups(true, window, cx) {
3954 return;
3955 }
3956
3957 if self.mode.is_full()
3958 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3959 {
3960 return;
3961 }
3962
3963 cx.propagate();
3964 }
3965
3966 pub fn dismiss_menus_and_popups(
3967 &mut self,
3968 is_user_requested: bool,
3969 window: &mut Window,
3970 cx: &mut Context<Self>,
3971 ) -> bool {
3972 if self.take_rename(false, window, cx).is_some() {
3973 return true;
3974 }
3975
3976 if self.hide_blame_popover(true, cx) {
3977 return true;
3978 }
3979
3980 if hide_hover(self, cx) {
3981 return true;
3982 }
3983
3984 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3985 return true;
3986 }
3987
3988 if self.hide_context_menu(window, cx).is_some() {
3989 return true;
3990 }
3991
3992 if self.mouse_context_menu.take().is_some() {
3993 return true;
3994 }
3995
3996 if is_user_requested && self.discard_edit_prediction(true, cx) {
3997 return true;
3998 }
3999
4000 if self.snippet_stack.pop().is_some() {
4001 return true;
4002 }
4003
4004 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4005 self.dismiss_diagnostics(cx);
4006 return true;
4007 }
4008
4009 false
4010 }
4011
4012 fn linked_editing_ranges_for(
4013 &self,
4014 selection: Range<text::Anchor>,
4015 cx: &App,
4016 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4017 if self.linked_edit_ranges.is_empty() {
4018 return None;
4019 }
4020 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4021 selection.end.buffer_id.and_then(|end_buffer_id| {
4022 if selection.start.buffer_id != Some(end_buffer_id) {
4023 return None;
4024 }
4025 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4026 let snapshot = buffer.read(cx).snapshot();
4027 self.linked_edit_ranges
4028 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4029 .map(|ranges| (ranges, snapshot, buffer))
4030 })?;
4031 use text::ToOffset as TO;
4032 // find offset from the start of current range to current cursor position
4033 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4034
4035 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4036 let start_difference = start_offset - start_byte_offset;
4037 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4038 let end_difference = end_offset - start_byte_offset;
4039 // Current range has associated linked ranges.
4040 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4041 for range in linked_ranges.iter() {
4042 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4043 let end_offset = start_offset + end_difference;
4044 let start_offset = start_offset + start_difference;
4045 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4046 continue;
4047 }
4048 if self.selections.disjoint_anchor_ranges().any(|s| {
4049 if s.start.buffer_id != selection.start.buffer_id
4050 || s.end.buffer_id != selection.end.buffer_id
4051 {
4052 return false;
4053 }
4054 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4055 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4056 }) {
4057 continue;
4058 }
4059 let start = buffer_snapshot.anchor_after(start_offset);
4060 let end = buffer_snapshot.anchor_after(end_offset);
4061 linked_edits
4062 .entry(buffer.clone())
4063 .or_default()
4064 .push(start..end);
4065 }
4066 Some(linked_edits)
4067 }
4068
4069 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4070 let text: Arc<str> = text.into();
4071
4072 if self.read_only(cx) {
4073 return;
4074 }
4075
4076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4077
4078 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4079 let mut bracket_inserted = false;
4080 let mut edits = Vec::new();
4081 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4082 let mut new_selections = Vec::with_capacity(selections.len());
4083 let mut new_autoclose_regions = Vec::new();
4084 let snapshot = self.buffer.read(cx).read(cx);
4085 let mut clear_linked_edit_ranges = false;
4086
4087 for (selection, autoclose_region) in
4088 self.selections_with_autoclose_regions(selections, &snapshot)
4089 {
4090 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4091 // Determine if the inserted text matches the opening or closing
4092 // bracket of any of this language's bracket pairs.
4093 let mut bracket_pair = None;
4094 let mut is_bracket_pair_start = false;
4095 let mut is_bracket_pair_end = false;
4096 if !text.is_empty() {
4097 let mut bracket_pair_matching_end = None;
4098 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4099 // and they are removing the character that triggered IME popup.
4100 for (pair, enabled) in scope.brackets() {
4101 if !pair.close && !pair.surround {
4102 continue;
4103 }
4104
4105 if enabled && pair.start.ends_with(text.as_ref()) {
4106 let prefix_len = pair.start.len() - text.len();
4107 let preceding_text_matches_prefix = prefix_len == 0
4108 || (selection.start.column >= (prefix_len as u32)
4109 && snapshot.contains_str_at(
4110 Point::new(
4111 selection.start.row,
4112 selection.start.column - (prefix_len as u32),
4113 ),
4114 &pair.start[..prefix_len],
4115 ));
4116 if preceding_text_matches_prefix {
4117 bracket_pair = Some(pair.clone());
4118 is_bracket_pair_start = true;
4119 break;
4120 }
4121 }
4122 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4123 {
4124 // take first bracket pair matching end, but don't break in case a later bracket
4125 // pair matches start
4126 bracket_pair_matching_end = Some(pair.clone());
4127 }
4128 }
4129 if let Some(end) = bracket_pair_matching_end
4130 && bracket_pair.is_none()
4131 {
4132 bracket_pair = Some(end);
4133 is_bracket_pair_end = true;
4134 }
4135 }
4136
4137 if let Some(bracket_pair) = bracket_pair {
4138 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4139 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4140 let auto_surround =
4141 self.use_auto_surround && snapshot_settings.use_auto_surround;
4142 if selection.is_empty() {
4143 if is_bracket_pair_start {
4144 // If the inserted text is a suffix of an opening bracket and the
4145 // selection is preceded by the rest of the opening bracket, then
4146 // insert the closing bracket.
4147 let following_text_allows_autoclose = snapshot
4148 .chars_at(selection.start)
4149 .next()
4150 .is_none_or(|c| scope.should_autoclose_before(c));
4151
4152 let preceding_text_allows_autoclose = selection.start.column == 0
4153 || snapshot
4154 .reversed_chars_at(selection.start)
4155 .next()
4156 .is_none_or(|c| {
4157 bracket_pair.start != bracket_pair.end
4158 || !snapshot
4159 .char_classifier_at(selection.start)
4160 .is_word(c)
4161 });
4162
4163 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4164 && bracket_pair.start.len() == 1
4165 {
4166 let target = bracket_pair.start.chars().next().unwrap();
4167 let current_line_count = snapshot
4168 .reversed_chars_at(selection.start)
4169 .take_while(|&c| c != '\n')
4170 .filter(|&c| c == target)
4171 .count();
4172 current_line_count % 2 == 1
4173 } else {
4174 false
4175 };
4176
4177 if autoclose
4178 && bracket_pair.close
4179 && following_text_allows_autoclose
4180 && preceding_text_allows_autoclose
4181 && !is_closing_quote
4182 {
4183 let anchor = snapshot.anchor_before(selection.end);
4184 new_selections.push((selection.map(|_| anchor), text.len()));
4185 new_autoclose_regions.push((
4186 anchor,
4187 text.len(),
4188 selection.id,
4189 bracket_pair.clone(),
4190 ));
4191 edits.push((
4192 selection.range(),
4193 format!("{}{}", text, bracket_pair.end).into(),
4194 ));
4195 bracket_inserted = true;
4196 continue;
4197 }
4198 }
4199
4200 if let Some(region) = autoclose_region {
4201 // If the selection is followed by an auto-inserted closing bracket,
4202 // then don't insert that closing bracket again; just move the selection
4203 // past the closing bracket.
4204 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4205 && text.as_ref() == region.pair.end.as_str()
4206 && snapshot.contains_str_at(region.range.end, text.as_ref());
4207 if should_skip {
4208 let anchor = snapshot.anchor_after(selection.end);
4209 new_selections
4210 .push((selection.map(|_| anchor), region.pair.end.len()));
4211 continue;
4212 }
4213 }
4214
4215 let always_treat_brackets_as_autoclosed = snapshot
4216 .language_settings_at(selection.start, cx)
4217 .always_treat_brackets_as_autoclosed;
4218 if always_treat_brackets_as_autoclosed
4219 && is_bracket_pair_end
4220 && snapshot.contains_str_at(selection.end, text.as_ref())
4221 {
4222 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4223 // and the inserted text is a closing bracket and the selection is followed
4224 // by the closing bracket then move the selection past the closing bracket.
4225 let anchor = snapshot.anchor_after(selection.end);
4226 new_selections.push((selection.map(|_| anchor), text.len()));
4227 continue;
4228 }
4229 }
4230 // If an opening bracket is 1 character long and is typed while
4231 // text is selected, then surround that text with the bracket pair.
4232 else if auto_surround
4233 && bracket_pair.surround
4234 && is_bracket_pair_start
4235 && bracket_pair.start.chars().count() == 1
4236 {
4237 edits.push((selection.start..selection.start, text.clone()));
4238 edits.push((
4239 selection.end..selection.end,
4240 bracket_pair.end.as_str().into(),
4241 ));
4242 bracket_inserted = true;
4243 new_selections.push((
4244 Selection {
4245 id: selection.id,
4246 start: snapshot.anchor_after(selection.start),
4247 end: snapshot.anchor_before(selection.end),
4248 reversed: selection.reversed,
4249 goal: selection.goal,
4250 },
4251 0,
4252 ));
4253 continue;
4254 }
4255 }
4256 }
4257
4258 if self.auto_replace_emoji_shortcode
4259 && selection.is_empty()
4260 && text.as_ref().ends_with(':')
4261 && let Some(possible_emoji_short_code) =
4262 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4263 && !possible_emoji_short_code.is_empty()
4264 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4265 {
4266 let emoji_shortcode_start = Point::new(
4267 selection.start.row,
4268 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4269 );
4270
4271 // Remove shortcode from buffer
4272 edits.push((
4273 emoji_shortcode_start..selection.start,
4274 "".to_string().into(),
4275 ));
4276 new_selections.push((
4277 Selection {
4278 id: selection.id,
4279 start: snapshot.anchor_after(emoji_shortcode_start),
4280 end: snapshot.anchor_before(selection.start),
4281 reversed: selection.reversed,
4282 goal: selection.goal,
4283 },
4284 0,
4285 ));
4286
4287 // Insert emoji
4288 let selection_start_anchor = snapshot.anchor_after(selection.start);
4289 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4290 edits.push((selection.start..selection.end, emoji.to_string().into()));
4291
4292 continue;
4293 }
4294
4295 // If not handling any auto-close operation, then just replace the selected
4296 // text with the given input and move the selection to the end of the
4297 // newly inserted text.
4298 let anchor = snapshot.anchor_after(selection.end);
4299 if !self.linked_edit_ranges.is_empty() {
4300 let start_anchor = snapshot.anchor_before(selection.start);
4301
4302 let is_word_char = text.chars().next().is_none_or(|char| {
4303 let classifier = snapshot
4304 .char_classifier_at(start_anchor.to_offset(&snapshot))
4305 .scope_context(Some(CharScopeContext::LinkedEdit));
4306 classifier.is_word(char)
4307 });
4308
4309 if is_word_char {
4310 if let Some(ranges) = self
4311 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4312 {
4313 for (buffer, edits) in ranges {
4314 linked_edits
4315 .entry(buffer.clone())
4316 .or_default()
4317 .extend(edits.into_iter().map(|range| (range, text.clone())));
4318 }
4319 }
4320 } else {
4321 clear_linked_edit_ranges = true;
4322 }
4323 }
4324
4325 new_selections.push((selection.map(|_| anchor), 0));
4326 edits.push((selection.start..selection.end, text.clone()));
4327 }
4328
4329 drop(snapshot);
4330
4331 self.transact(window, cx, |this, window, cx| {
4332 if clear_linked_edit_ranges {
4333 this.linked_edit_ranges.clear();
4334 }
4335 let initial_buffer_versions =
4336 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4337
4338 this.buffer.update(cx, |buffer, cx| {
4339 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4340 });
4341 for (buffer, edits) in linked_edits {
4342 buffer.update(cx, |buffer, cx| {
4343 let snapshot = buffer.snapshot();
4344 let edits = edits
4345 .into_iter()
4346 .map(|(range, text)| {
4347 use text::ToPoint as TP;
4348 let end_point = TP::to_point(&range.end, &snapshot);
4349 let start_point = TP::to_point(&range.start, &snapshot);
4350 (start_point..end_point, text)
4351 })
4352 .sorted_by_key(|(range, _)| range.start);
4353 buffer.edit(edits, None, cx);
4354 })
4355 }
4356 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4357 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4358 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4359 let new_selections =
4360 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4361 .zip(new_selection_deltas)
4362 .map(|(selection, delta)| Selection {
4363 id: selection.id,
4364 start: selection.start + delta,
4365 end: selection.end + delta,
4366 reversed: selection.reversed,
4367 goal: SelectionGoal::None,
4368 })
4369 .collect::<Vec<_>>();
4370
4371 let mut i = 0;
4372 for (position, delta, selection_id, pair) in new_autoclose_regions {
4373 let position = position.to_offset(map.buffer_snapshot()) + delta;
4374 let start = map.buffer_snapshot().anchor_before(position);
4375 let end = map.buffer_snapshot().anchor_after(position);
4376 while let Some(existing_state) = this.autoclose_regions.get(i) {
4377 match existing_state
4378 .range
4379 .start
4380 .cmp(&start, map.buffer_snapshot())
4381 {
4382 Ordering::Less => i += 1,
4383 Ordering::Greater => break,
4384 Ordering::Equal => {
4385 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4386 Ordering::Less => i += 1,
4387 Ordering::Equal => break,
4388 Ordering::Greater => break,
4389 }
4390 }
4391 }
4392 }
4393 this.autoclose_regions.insert(
4394 i,
4395 AutocloseRegion {
4396 selection_id,
4397 range: start..end,
4398 pair,
4399 },
4400 );
4401 }
4402
4403 let had_active_edit_prediction = this.has_active_edit_prediction();
4404 this.change_selections(
4405 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4406 window,
4407 cx,
4408 |s| s.select(new_selections),
4409 );
4410
4411 if !bracket_inserted
4412 && let Some(on_type_format_task) =
4413 this.trigger_on_type_formatting(text.to_string(), window, cx)
4414 {
4415 on_type_format_task.detach_and_log_err(cx);
4416 }
4417
4418 let editor_settings = EditorSettings::get_global(cx);
4419 if bracket_inserted
4420 && (editor_settings.auto_signature_help
4421 || editor_settings.show_signature_help_after_edits)
4422 {
4423 this.show_signature_help(&ShowSignatureHelp, window, cx);
4424 }
4425
4426 let trigger_in_words =
4427 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4428 if this.hard_wrap.is_some() {
4429 let latest: Range<Point> = this.selections.newest(&map).range();
4430 if latest.is_empty()
4431 && this
4432 .buffer()
4433 .read(cx)
4434 .snapshot(cx)
4435 .line_len(MultiBufferRow(latest.start.row))
4436 == latest.start.column
4437 {
4438 this.rewrap_impl(
4439 RewrapOptions {
4440 override_language_settings: true,
4441 preserve_existing_whitespace: true,
4442 },
4443 cx,
4444 )
4445 }
4446 }
4447 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4448 refresh_linked_ranges(this, window, cx);
4449 this.refresh_edit_prediction(true, false, window, cx);
4450 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4451 });
4452 }
4453
4454 fn find_possible_emoji_shortcode_at_position(
4455 snapshot: &MultiBufferSnapshot,
4456 position: Point,
4457 ) -> Option<String> {
4458 let mut chars = Vec::new();
4459 let mut found_colon = false;
4460 for char in snapshot.reversed_chars_at(position).take(100) {
4461 // Found a possible emoji shortcode in the middle of the buffer
4462 if found_colon {
4463 if char.is_whitespace() {
4464 chars.reverse();
4465 return Some(chars.iter().collect());
4466 }
4467 // If the previous character is not a whitespace, we are in the middle of a word
4468 // and we only want to complete the shortcode if the word is made up of other emojis
4469 let mut containing_word = String::new();
4470 for ch in snapshot
4471 .reversed_chars_at(position)
4472 .skip(chars.len() + 1)
4473 .take(100)
4474 {
4475 if ch.is_whitespace() {
4476 break;
4477 }
4478 containing_word.push(ch);
4479 }
4480 let containing_word = containing_word.chars().rev().collect::<String>();
4481 if util::word_consists_of_emojis(containing_word.as_str()) {
4482 chars.reverse();
4483 return Some(chars.iter().collect());
4484 }
4485 }
4486
4487 if char.is_whitespace() || !char.is_ascii() {
4488 return None;
4489 }
4490 if char == ':' {
4491 found_colon = true;
4492 } else {
4493 chars.push(char);
4494 }
4495 }
4496 // Found a possible emoji shortcode at the beginning of the buffer
4497 chars.reverse();
4498 Some(chars.iter().collect())
4499 }
4500
4501 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4503 self.transact(window, cx, |this, window, cx| {
4504 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4505 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4506 let multi_buffer = this.buffer.read(cx);
4507 let buffer = multi_buffer.snapshot(cx);
4508 selections
4509 .iter()
4510 .map(|selection| {
4511 let start_point = selection.start.to_point(&buffer);
4512 let mut existing_indent =
4513 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4514 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4515 let start = selection.start;
4516 let end = selection.end;
4517 let selection_is_empty = start == end;
4518 let language_scope = buffer.language_scope_at(start);
4519 let (
4520 comment_delimiter,
4521 doc_delimiter,
4522 insert_extra_newline,
4523 indent_on_newline,
4524 indent_on_extra_newline,
4525 ) = if let Some(language) = &language_scope {
4526 let mut insert_extra_newline =
4527 insert_extra_newline_brackets(&buffer, start..end, language)
4528 || insert_extra_newline_tree_sitter(&buffer, start..end);
4529
4530 // Comment extension on newline is allowed only for cursor selections
4531 let comment_delimiter = maybe!({
4532 if !selection_is_empty {
4533 return None;
4534 }
4535
4536 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4537 return None;
4538 }
4539
4540 let delimiters = language.line_comment_prefixes();
4541 let max_len_of_delimiter =
4542 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4543 let (snapshot, range) =
4544 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4545
4546 let num_of_whitespaces = snapshot
4547 .chars_for_range(range.clone())
4548 .take_while(|c| c.is_whitespace())
4549 .count();
4550 let comment_candidate = snapshot
4551 .chars_for_range(range.clone())
4552 .skip(num_of_whitespaces)
4553 .take(max_len_of_delimiter)
4554 .collect::<String>();
4555 let (delimiter, trimmed_len) = delimiters
4556 .iter()
4557 .filter_map(|delimiter| {
4558 let prefix = delimiter.trim_end();
4559 if comment_candidate.starts_with(prefix) {
4560 Some((delimiter, prefix.len()))
4561 } else {
4562 None
4563 }
4564 })
4565 .max_by_key(|(_, len)| *len)?;
4566
4567 if let Some(BlockCommentConfig {
4568 start: block_start, ..
4569 }) = language.block_comment()
4570 {
4571 let block_start_trimmed = block_start.trim_end();
4572 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4573 let line_content = snapshot
4574 .chars_for_range(range)
4575 .skip(num_of_whitespaces)
4576 .take(block_start_trimmed.len())
4577 .collect::<String>();
4578
4579 if line_content.starts_with(block_start_trimmed) {
4580 return None;
4581 }
4582 }
4583 }
4584
4585 let cursor_is_placed_after_comment_marker =
4586 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4587 if cursor_is_placed_after_comment_marker {
4588 Some(delimiter.clone())
4589 } else {
4590 None
4591 }
4592 });
4593
4594 let mut indent_on_newline = IndentSize::spaces(0);
4595 let mut indent_on_extra_newline = IndentSize::spaces(0);
4596
4597 let doc_delimiter = maybe!({
4598 if !selection_is_empty {
4599 return None;
4600 }
4601
4602 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4603 return None;
4604 }
4605
4606 let BlockCommentConfig {
4607 start: start_tag,
4608 end: end_tag,
4609 prefix: delimiter,
4610 tab_size: len,
4611 } = language.documentation_comment()?;
4612 let is_within_block_comment = buffer
4613 .language_scope_at(start_point)
4614 .is_some_and(|scope| scope.override_name() == Some("comment"));
4615 if !is_within_block_comment {
4616 return None;
4617 }
4618
4619 let (snapshot, range) =
4620 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4621
4622 let num_of_whitespaces = snapshot
4623 .chars_for_range(range.clone())
4624 .take_while(|c| c.is_whitespace())
4625 .count();
4626
4627 // 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.
4628 let column = start_point.column;
4629 let cursor_is_after_start_tag = {
4630 let start_tag_len = start_tag.len();
4631 let start_tag_line = snapshot
4632 .chars_for_range(range.clone())
4633 .skip(num_of_whitespaces)
4634 .take(start_tag_len)
4635 .collect::<String>();
4636 if start_tag_line.starts_with(start_tag.as_ref()) {
4637 num_of_whitespaces + start_tag_len <= column as usize
4638 } else {
4639 false
4640 }
4641 };
4642
4643 let cursor_is_after_delimiter = {
4644 let delimiter_trim = delimiter.trim_end();
4645 let delimiter_line = snapshot
4646 .chars_for_range(range.clone())
4647 .skip(num_of_whitespaces)
4648 .take(delimiter_trim.len())
4649 .collect::<String>();
4650 if delimiter_line.starts_with(delimiter_trim) {
4651 num_of_whitespaces + delimiter_trim.len() <= column as usize
4652 } else {
4653 false
4654 }
4655 };
4656
4657 let cursor_is_before_end_tag_if_exists = {
4658 let mut char_position = 0u32;
4659 let mut end_tag_offset = None;
4660
4661 'outer: for chunk in snapshot.text_for_range(range) {
4662 if let Some(byte_pos) = chunk.find(&**end_tag) {
4663 let chars_before_match =
4664 chunk[..byte_pos].chars().count() as u32;
4665 end_tag_offset =
4666 Some(char_position + chars_before_match);
4667 break 'outer;
4668 }
4669 char_position += chunk.chars().count() as u32;
4670 }
4671
4672 if let Some(end_tag_offset) = end_tag_offset {
4673 let cursor_is_before_end_tag = column <= end_tag_offset;
4674 if cursor_is_after_start_tag {
4675 if cursor_is_before_end_tag {
4676 insert_extra_newline = true;
4677 }
4678 let cursor_is_at_start_of_end_tag =
4679 column == end_tag_offset;
4680 if cursor_is_at_start_of_end_tag {
4681 indent_on_extra_newline.len = *len;
4682 }
4683 }
4684 cursor_is_before_end_tag
4685 } else {
4686 true
4687 }
4688 };
4689
4690 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4691 && cursor_is_before_end_tag_if_exists
4692 {
4693 if cursor_is_after_start_tag {
4694 indent_on_newline.len = *len;
4695 }
4696 Some(delimiter.clone())
4697 } else {
4698 None
4699 }
4700 });
4701
4702 (
4703 comment_delimiter,
4704 doc_delimiter,
4705 insert_extra_newline,
4706 indent_on_newline,
4707 indent_on_extra_newline,
4708 )
4709 } else {
4710 (
4711 None,
4712 None,
4713 false,
4714 IndentSize::default(),
4715 IndentSize::default(),
4716 )
4717 };
4718
4719 let prevent_auto_indent = doc_delimiter.is_some();
4720 let delimiter = comment_delimiter.or(doc_delimiter);
4721
4722 let capacity_for_delimiter =
4723 delimiter.as_deref().map(str::len).unwrap_or_default();
4724 let mut new_text = String::with_capacity(
4725 1 + capacity_for_delimiter
4726 + existing_indent.len as usize
4727 + indent_on_newline.len as usize
4728 + indent_on_extra_newline.len as usize,
4729 );
4730 new_text.push('\n');
4731 new_text.extend(existing_indent.chars());
4732 new_text.extend(indent_on_newline.chars());
4733
4734 if let Some(delimiter) = &delimiter {
4735 new_text.push_str(delimiter);
4736 }
4737
4738 if insert_extra_newline {
4739 new_text.push('\n');
4740 new_text.extend(existing_indent.chars());
4741 new_text.extend(indent_on_extra_newline.chars());
4742 }
4743
4744 let anchor = buffer.anchor_after(end);
4745 let new_selection = selection.map(|_| anchor);
4746 (
4747 ((start..end, new_text), prevent_auto_indent),
4748 (insert_extra_newline, new_selection),
4749 )
4750 })
4751 .unzip()
4752 };
4753
4754 let mut auto_indent_edits = Vec::new();
4755 let mut edits = Vec::new();
4756 for (edit, prevent_auto_indent) in edits_with_flags {
4757 if prevent_auto_indent {
4758 edits.push(edit);
4759 } else {
4760 auto_indent_edits.push(edit);
4761 }
4762 }
4763 if !edits.is_empty() {
4764 this.edit(edits, cx);
4765 }
4766 if !auto_indent_edits.is_empty() {
4767 this.edit_with_autoindent(auto_indent_edits, cx);
4768 }
4769
4770 let buffer = this.buffer.read(cx).snapshot(cx);
4771 let new_selections = selection_info
4772 .into_iter()
4773 .map(|(extra_newline_inserted, new_selection)| {
4774 let mut cursor = new_selection.end.to_point(&buffer);
4775 if extra_newline_inserted {
4776 cursor.row -= 1;
4777 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4778 }
4779 new_selection.map(|_| cursor)
4780 })
4781 .collect();
4782
4783 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4784 this.refresh_edit_prediction(true, false, window, cx);
4785 });
4786 }
4787
4788 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4790
4791 let buffer = self.buffer.read(cx);
4792 let snapshot = buffer.snapshot(cx);
4793
4794 let mut edits = Vec::new();
4795 let mut rows = Vec::new();
4796
4797 for (rows_inserted, selection) in self
4798 .selections
4799 .all_adjusted(&self.display_snapshot(cx))
4800 .into_iter()
4801 .enumerate()
4802 {
4803 let cursor = selection.head();
4804 let row = cursor.row;
4805
4806 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4807
4808 let newline = "\n".to_string();
4809 edits.push((start_of_line..start_of_line, newline));
4810
4811 rows.push(row + rows_inserted as u32);
4812 }
4813
4814 self.transact(window, cx, |editor, window, cx| {
4815 editor.edit(edits, cx);
4816
4817 editor.change_selections(Default::default(), window, cx, |s| {
4818 let mut index = 0;
4819 s.move_cursors_with(|map, _, _| {
4820 let row = rows[index];
4821 index += 1;
4822
4823 let point = Point::new(row, 0);
4824 let boundary = map.next_line_boundary(point).1;
4825 let clipped = map.clip_point(boundary, Bias::Left);
4826
4827 (clipped, SelectionGoal::None)
4828 });
4829 });
4830
4831 let mut indent_edits = Vec::new();
4832 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4833 for row in rows {
4834 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4835 for (row, indent) in indents {
4836 if indent.len == 0 {
4837 continue;
4838 }
4839
4840 let text = match indent.kind {
4841 IndentKind::Space => " ".repeat(indent.len as usize),
4842 IndentKind::Tab => "\t".repeat(indent.len as usize),
4843 };
4844 let point = Point::new(row.0, 0);
4845 indent_edits.push((point..point, text));
4846 }
4847 }
4848 editor.edit(indent_edits, cx);
4849 });
4850 }
4851
4852 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4854
4855 let buffer = self.buffer.read(cx);
4856 let snapshot = buffer.snapshot(cx);
4857
4858 let mut edits = Vec::new();
4859 let mut rows = Vec::new();
4860 let mut rows_inserted = 0;
4861
4862 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4863 let cursor = selection.head();
4864 let row = cursor.row;
4865
4866 let point = Point::new(row + 1, 0);
4867 let start_of_line = snapshot.clip_point(point, Bias::Left);
4868
4869 let newline = "\n".to_string();
4870 edits.push((start_of_line..start_of_line, newline));
4871
4872 rows_inserted += 1;
4873 rows.push(row + rows_inserted);
4874 }
4875
4876 self.transact(window, cx, |editor, window, cx| {
4877 editor.edit(edits, cx);
4878
4879 editor.change_selections(Default::default(), window, cx, |s| {
4880 let mut index = 0;
4881 s.move_cursors_with(|map, _, _| {
4882 let row = rows[index];
4883 index += 1;
4884
4885 let point = Point::new(row, 0);
4886 let boundary = map.next_line_boundary(point).1;
4887 let clipped = map.clip_point(boundary, Bias::Left);
4888
4889 (clipped, SelectionGoal::None)
4890 });
4891 });
4892
4893 let mut indent_edits = Vec::new();
4894 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4895 for row in rows {
4896 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4897 for (row, indent) in indents {
4898 if indent.len == 0 {
4899 continue;
4900 }
4901
4902 let text = match indent.kind {
4903 IndentKind::Space => " ".repeat(indent.len as usize),
4904 IndentKind::Tab => "\t".repeat(indent.len as usize),
4905 };
4906 let point = Point::new(row.0, 0);
4907 indent_edits.push((point..point, text));
4908 }
4909 }
4910 editor.edit(indent_edits, cx);
4911 });
4912 }
4913
4914 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4915 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4916 original_indent_columns: Vec::new(),
4917 });
4918 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4919 }
4920
4921 fn insert_with_autoindent_mode(
4922 &mut self,
4923 text: &str,
4924 autoindent_mode: Option<AutoindentMode>,
4925 window: &mut Window,
4926 cx: &mut Context<Self>,
4927 ) {
4928 if self.read_only(cx) {
4929 return;
4930 }
4931
4932 let text: Arc<str> = text.into();
4933 self.transact(window, cx, |this, window, cx| {
4934 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4935 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4936 let anchors = {
4937 let snapshot = buffer.read(cx);
4938 old_selections
4939 .iter()
4940 .map(|s| {
4941 let anchor = snapshot.anchor_after(s.head());
4942 s.map(|_| anchor)
4943 })
4944 .collect::<Vec<_>>()
4945 };
4946 buffer.edit(
4947 old_selections
4948 .iter()
4949 .map(|s| (s.start..s.end, text.clone())),
4950 autoindent_mode,
4951 cx,
4952 );
4953 anchors
4954 });
4955
4956 this.change_selections(Default::default(), window, cx, |s| {
4957 s.select_anchors(selection_anchors);
4958 });
4959
4960 cx.notify();
4961 });
4962 }
4963
4964 fn trigger_completion_on_input(
4965 &mut self,
4966 text: &str,
4967 trigger_in_words: bool,
4968 window: &mut Window,
4969 cx: &mut Context<Self>,
4970 ) {
4971 let completions_source = self
4972 .context_menu
4973 .borrow()
4974 .as_ref()
4975 .and_then(|menu| match menu {
4976 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4977 CodeContextMenu::CodeActions(_) => None,
4978 });
4979
4980 match completions_source {
4981 Some(CompletionsMenuSource::Words { .. }) => {
4982 self.open_or_update_completions_menu(
4983 Some(CompletionsMenuSource::Words {
4984 ignore_threshold: false,
4985 }),
4986 None,
4987 window,
4988 cx,
4989 );
4990 }
4991 Some(CompletionsMenuSource::Normal)
4992 | Some(CompletionsMenuSource::SnippetChoices)
4993 | None
4994 if self.is_completion_trigger(
4995 text,
4996 trigger_in_words,
4997 completions_source.is_some(),
4998 cx,
4999 ) =>
5000 {
5001 self.show_completions(
5002 &ShowCompletions {
5003 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5004 },
5005 window,
5006 cx,
5007 )
5008 }
5009 _ => {
5010 self.hide_context_menu(window, cx);
5011 }
5012 }
5013 }
5014
5015 fn is_completion_trigger(
5016 &self,
5017 text: &str,
5018 trigger_in_words: bool,
5019 menu_is_open: bool,
5020 cx: &mut Context<Self>,
5021 ) -> bool {
5022 let position = self.selections.newest_anchor().head();
5023 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5024 return false;
5025 };
5026
5027 if let Some(completion_provider) = &self.completion_provider {
5028 completion_provider.is_completion_trigger(
5029 &buffer,
5030 position.text_anchor,
5031 text,
5032 trigger_in_words,
5033 menu_is_open,
5034 cx,
5035 )
5036 } else {
5037 false
5038 }
5039 }
5040
5041 /// If any empty selections is touching the start of its innermost containing autoclose
5042 /// region, expand it to select the brackets.
5043 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5044 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5045 let buffer = self.buffer.read(cx).read(cx);
5046 let new_selections = self
5047 .selections_with_autoclose_regions(selections, &buffer)
5048 .map(|(mut selection, region)| {
5049 if !selection.is_empty() {
5050 return selection;
5051 }
5052
5053 if let Some(region) = region {
5054 let mut range = region.range.to_offset(&buffer);
5055 if selection.start == range.start && range.start >= region.pair.start.len() {
5056 range.start -= region.pair.start.len();
5057 if buffer.contains_str_at(range.start, ®ion.pair.start)
5058 && buffer.contains_str_at(range.end, ®ion.pair.end)
5059 {
5060 range.end += region.pair.end.len();
5061 selection.start = range.start;
5062 selection.end = range.end;
5063
5064 return selection;
5065 }
5066 }
5067 }
5068
5069 let always_treat_brackets_as_autoclosed = buffer
5070 .language_settings_at(selection.start, cx)
5071 .always_treat_brackets_as_autoclosed;
5072
5073 if !always_treat_brackets_as_autoclosed {
5074 return selection;
5075 }
5076
5077 if let Some(scope) = buffer.language_scope_at(selection.start) {
5078 for (pair, enabled) in scope.brackets() {
5079 if !enabled || !pair.close {
5080 continue;
5081 }
5082
5083 if buffer.contains_str_at(selection.start, &pair.end) {
5084 let pair_start_len = pair.start.len();
5085 if buffer.contains_str_at(
5086 selection.start.saturating_sub(pair_start_len),
5087 &pair.start,
5088 ) {
5089 selection.start -= pair_start_len;
5090 selection.end += pair.end.len();
5091
5092 return selection;
5093 }
5094 }
5095 }
5096 }
5097
5098 selection
5099 })
5100 .collect();
5101
5102 drop(buffer);
5103 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5104 selections.select(new_selections)
5105 });
5106 }
5107
5108 /// Iterate the given selections, and for each one, find the smallest surrounding
5109 /// autoclose region. This uses the ordering of the selections and the autoclose
5110 /// regions to avoid repeated comparisons.
5111 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5112 &'a self,
5113 selections: impl IntoIterator<Item = Selection<D>>,
5114 buffer: &'a MultiBufferSnapshot,
5115 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5116 let mut i = 0;
5117 let mut regions = self.autoclose_regions.as_slice();
5118 selections.into_iter().map(move |selection| {
5119 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5120
5121 let mut enclosing = None;
5122 while let Some(pair_state) = regions.get(i) {
5123 if pair_state.range.end.to_offset(buffer) < range.start {
5124 regions = ®ions[i + 1..];
5125 i = 0;
5126 } else if pair_state.range.start.to_offset(buffer) > range.end {
5127 break;
5128 } else {
5129 if pair_state.selection_id == selection.id {
5130 enclosing = Some(pair_state);
5131 }
5132 i += 1;
5133 }
5134 }
5135
5136 (selection, enclosing)
5137 })
5138 }
5139
5140 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5141 fn invalidate_autoclose_regions(
5142 &mut self,
5143 mut selections: &[Selection<Anchor>],
5144 buffer: &MultiBufferSnapshot,
5145 ) {
5146 self.autoclose_regions.retain(|state| {
5147 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5148 return false;
5149 }
5150
5151 let mut i = 0;
5152 while let Some(selection) = selections.get(i) {
5153 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5154 selections = &selections[1..];
5155 continue;
5156 }
5157 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5158 break;
5159 }
5160 if selection.id == state.selection_id {
5161 return true;
5162 } else {
5163 i += 1;
5164 }
5165 }
5166 false
5167 });
5168 }
5169
5170 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5171 let offset = position.to_offset(buffer);
5172 let (word_range, kind) =
5173 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5174 if offset > word_range.start && kind == Some(CharKind::Word) {
5175 Some(
5176 buffer
5177 .text_for_range(word_range.start..offset)
5178 .collect::<String>(),
5179 )
5180 } else {
5181 None
5182 }
5183 }
5184
5185 pub fn visible_excerpts(
5186 &self,
5187 cx: &mut Context<Editor>,
5188 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5189 let Some(project) = self.project() else {
5190 return HashMap::default();
5191 };
5192 let project = project.read(cx);
5193 let multi_buffer = self.buffer().read(cx);
5194 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5195 let multi_buffer_visible_start = self
5196 .scroll_manager
5197 .anchor()
5198 .anchor
5199 .to_point(&multi_buffer_snapshot);
5200 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5201 multi_buffer_visible_start
5202 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5203 Bias::Left,
5204 );
5205 multi_buffer_snapshot
5206 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5207 .into_iter()
5208 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5209 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5210 let buffer_file = project::File::from_dyn(buffer.file())?;
5211 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5212 let worktree_entry = buffer_worktree
5213 .read(cx)
5214 .entry_for_id(buffer_file.project_entry_id()?)?;
5215 if worktree_entry.is_ignored {
5216 None
5217 } else {
5218 Some((
5219 excerpt_id,
5220 (
5221 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5222 buffer.version().clone(),
5223 excerpt_visible_range,
5224 ),
5225 ))
5226 }
5227 })
5228 .collect()
5229 }
5230
5231 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5232 TextLayoutDetails {
5233 text_system: window.text_system().clone(),
5234 editor_style: self.style.clone().unwrap(),
5235 rem_size: window.rem_size(),
5236 scroll_anchor: self.scroll_manager.anchor(),
5237 visible_rows: self.visible_line_count(),
5238 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5239 }
5240 }
5241
5242 fn trigger_on_type_formatting(
5243 &self,
5244 input: String,
5245 window: &mut Window,
5246 cx: &mut Context<Self>,
5247 ) -> Option<Task<Result<()>>> {
5248 if input.len() != 1 {
5249 return None;
5250 }
5251
5252 let project = self.project()?;
5253 let position = self.selections.newest_anchor().head();
5254 let (buffer, buffer_position) = self
5255 .buffer
5256 .read(cx)
5257 .text_anchor_for_position(position, cx)?;
5258
5259 let settings = language_settings::language_settings(
5260 buffer
5261 .read(cx)
5262 .language_at(buffer_position)
5263 .map(|l| l.name()),
5264 buffer.read(cx).file(),
5265 cx,
5266 );
5267 if !settings.use_on_type_format {
5268 return None;
5269 }
5270
5271 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5272 // hence we do LSP request & edit on host side only — add formats to host's history.
5273 let push_to_lsp_host_history = true;
5274 // If this is not the host, append its history with new edits.
5275 let push_to_client_history = project.read(cx).is_via_collab();
5276
5277 let on_type_formatting = project.update(cx, |project, cx| {
5278 project.on_type_format(
5279 buffer.clone(),
5280 buffer_position,
5281 input,
5282 push_to_lsp_host_history,
5283 cx,
5284 )
5285 });
5286 Some(cx.spawn_in(window, async move |editor, cx| {
5287 if let Some(transaction) = on_type_formatting.await? {
5288 if push_to_client_history {
5289 buffer
5290 .update(cx, |buffer, _| {
5291 buffer.push_transaction(transaction, Instant::now());
5292 buffer.finalize_last_transaction();
5293 })
5294 .ok();
5295 }
5296 editor.update(cx, |editor, cx| {
5297 editor.refresh_document_highlights(cx);
5298 })?;
5299 }
5300 Ok(())
5301 }))
5302 }
5303
5304 pub fn show_word_completions(
5305 &mut self,
5306 _: &ShowWordCompletions,
5307 window: &mut Window,
5308 cx: &mut Context<Self>,
5309 ) {
5310 self.open_or_update_completions_menu(
5311 Some(CompletionsMenuSource::Words {
5312 ignore_threshold: true,
5313 }),
5314 None,
5315 window,
5316 cx,
5317 );
5318 }
5319
5320 pub fn show_completions(
5321 &mut self,
5322 options: &ShowCompletions,
5323 window: &mut Window,
5324 cx: &mut Context<Self>,
5325 ) {
5326 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5327 }
5328
5329 fn open_or_update_completions_menu(
5330 &mut self,
5331 requested_source: Option<CompletionsMenuSource>,
5332 trigger: Option<&str>,
5333 window: &mut Window,
5334 cx: &mut Context<Self>,
5335 ) {
5336 if self.pending_rename.is_some() {
5337 return;
5338 }
5339
5340 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5341
5342 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5343 // inserted and selected. To handle that case, the start of the selection is used so that
5344 // the menu starts with all choices.
5345 let position = self
5346 .selections
5347 .newest_anchor()
5348 .start
5349 .bias_right(&multibuffer_snapshot);
5350 if position.diff_base_anchor.is_some() {
5351 return;
5352 }
5353 let buffer_position = multibuffer_snapshot.anchor_before(position);
5354 let Some(buffer) = buffer_position
5355 .buffer_id
5356 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5357 else {
5358 return;
5359 };
5360 let buffer_snapshot = buffer.read(cx).snapshot();
5361
5362 let query: Option<Arc<String>> =
5363 Self::completion_query(&multibuffer_snapshot, buffer_position)
5364 .map(|query| query.into());
5365
5366 drop(multibuffer_snapshot);
5367
5368 // Hide the current completions menu when query is empty. Without this, cached
5369 // completions from before the trigger char may be reused (#32774).
5370 if query.is_none() {
5371 let menu_is_open = matches!(
5372 self.context_menu.borrow().as_ref(),
5373 Some(CodeContextMenu::Completions(_))
5374 );
5375 if menu_is_open {
5376 self.hide_context_menu(window, cx);
5377 }
5378 }
5379
5380 let mut ignore_word_threshold = false;
5381 let provider = match requested_source {
5382 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5383 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5384 ignore_word_threshold = ignore_threshold;
5385 None
5386 }
5387 Some(CompletionsMenuSource::SnippetChoices) => {
5388 log::error!("bug: SnippetChoices requested_source is not handled");
5389 None
5390 }
5391 };
5392
5393 let sort_completions = provider
5394 .as_ref()
5395 .is_some_and(|provider| provider.sort_completions());
5396
5397 let filter_completions = provider
5398 .as_ref()
5399 .is_none_or(|provider| provider.filter_completions());
5400
5401 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5402 if filter_completions {
5403 menu.filter(query.clone(), provider.clone(), window, cx);
5404 }
5405 // When `is_incomplete` is false, no need to re-query completions when the current query
5406 // is a suffix of the initial query.
5407 if !menu.is_incomplete {
5408 // If the new query is a suffix of the old query (typing more characters) and
5409 // the previous result was complete, the existing completions can be filtered.
5410 //
5411 // Note that this is always true for snippet completions.
5412 let query_matches = match (&menu.initial_query, &query) {
5413 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5414 (None, _) => true,
5415 _ => false,
5416 };
5417 if query_matches {
5418 let position_matches = if menu.initial_position == position {
5419 true
5420 } else {
5421 let snapshot = self.buffer.read(cx).read(cx);
5422 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5423 };
5424 if position_matches {
5425 return;
5426 }
5427 }
5428 }
5429 };
5430
5431 let trigger_kind = match trigger {
5432 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5433 CompletionTriggerKind::TRIGGER_CHARACTER
5434 }
5435 _ => CompletionTriggerKind::INVOKED,
5436 };
5437 let completion_context = CompletionContext {
5438 trigger_character: trigger.and_then(|trigger| {
5439 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5440 Some(String::from(trigger))
5441 } else {
5442 None
5443 }
5444 }),
5445 trigger_kind,
5446 };
5447
5448 let Anchor {
5449 excerpt_id: buffer_excerpt_id,
5450 text_anchor: buffer_position,
5451 ..
5452 } = buffer_position;
5453
5454 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5455 buffer_snapshot.surrounding_word(buffer_position, None)
5456 {
5457 let word_to_exclude = buffer_snapshot
5458 .text_for_range(word_range.clone())
5459 .collect::<String>();
5460 (
5461 buffer_snapshot.anchor_before(word_range.start)
5462 ..buffer_snapshot.anchor_after(buffer_position),
5463 Some(word_to_exclude),
5464 )
5465 } else {
5466 (buffer_position..buffer_position, None)
5467 };
5468
5469 let language = buffer_snapshot
5470 .language_at(buffer_position)
5471 .map(|language| language.name());
5472
5473 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5474 .completions
5475 .clone();
5476
5477 let show_completion_documentation = buffer_snapshot
5478 .settings_at(buffer_position, cx)
5479 .show_completion_documentation;
5480
5481 // The document can be large, so stay in reasonable bounds when searching for words,
5482 // otherwise completion pop-up might be slow to appear.
5483 const WORD_LOOKUP_ROWS: u32 = 5_000;
5484 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5485 let min_word_search = buffer_snapshot.clip_point(
5486 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5487 Bias::Left,
5488 );
5489 let max_word_search = buffer_snapshot.clip_point(
5490 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5491 Bias::Right,
5492 );
5493 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5494 ..buffer_snapshot.point_to_offset(max_word_search);
5495
5496 let skip_digits = query
5497 .as_ref()
5498 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5499
5500 let omit_word_completions = !self.word_completions_enabled
5501 || (!ignore_word_threshold
5502 && match &query {
5503 Some(query) => query.chars().count() < completion_settings.words_min_length,
5504 None => completion_settings.words_min_length != 0,
5505 });
5506
5507 let (mut words, provider_responses) = match &provider {
5508 Some(provider) => {
5509 let provider_responses = provider.completions(
5510 buffer_excerpt_id,
5511 &buffer,
5512 buffer_position,
5513 completion_context,
5514 window,
5515 cx,
5516 );
5517
5518 let words = match (omit_word_completions, completion_settings.words) {
5519 (true, _) | (_, WordsCompletionMode::Disabled) => {
5520 Task::ready(BTreeMap::default())
5521 }
5522 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5523 .background_spawn(async move {
5524 buffer_snapshot.words_in_range(WordsQuery {
5525 fuzzy_contents: None,
5526 range: word_search_range,
5527 skip_digits,
5528 })
5529 }),
5530 };
5531
5532 (words, provider_responses)
5533 }
5534 None => {
5535 let words = if omit_word_completions {
5536 Task::ready(BTreeMap::default())
5537 } else {
5538 cx.background_spawn(async move {
5539 buffer_snapshot.words_in_range(WordsQuery {
5540 fuzzy_contents: None,
5541 range: word_search_range,
5542 skip_digits,
5543 })
5544 })
5545 };
5546 (words, Task::ready(Ok(Vec::new())))
5547 }
5548 };
5549
5550 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5551
5552 let id = post_inc(&mut self.next_completion_id);
5553 let task = cx.spawn_in(window, async move |editor, cx| {
5554 let Ok(()) = editor.update(cx, |this, _| {
5555 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5556 }) else {
5557 return;
5558 };
5559
5560 // TODO: Ideally completions from different sources would be selectively re-queried, so
5561 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5562 let mut completions = Vec::new();
5563 let mut is_incomplete = false;
5564 let mut display_options: Option<CompletionDisplayOptions> = None;
5565 if let Some(provider_responses) = provider_responses.await.log_err()
5566 && !provider_responses.is_empty()
5567 {
5568 for response in provider_responses {
5569 completions.extend(response.completions);
5570 is_incomplete = is_incomplete || response.is_incomplete;
5571 match display_options.as_mut() {
5572 None => {
5573 display_options = Some(response.display_options);
5574 }
5575 Some(options) => options.merge(&response.display_options),
5576 }
5577 }
5578 if completion_settings.words == WordsCompletionMode::Fallback {
5579 words = Task::ready(BTreeMap::default());
5580 }
5581 }
5582 let display_options = display_options.unwrap_or_default();
5583
5584 let mut words = words.await;
5585 if let Some(word_to_exclude) = &word_to_exclude {
5586 words.remove(word_to_exclude);
5587 }
5588 for lsp_completion in &completions {
5589 words.remove(&lsp_completion.new_text);
5590 }
5591 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5592 replace_range: word_replace_range.clone(),
5593 new_text: word.clone(),
5594 label: CodeLabel::plain(word, None),
5595 icon_path: None,
5596 documentation: None,
5597 source: CompletionSource::BufferWord {
5598 word_range,
5599 resolved: false,
5600 },
5601 insert_text_mode: Some(InsertTextMode::AS_IS),
5602 confirm: None,
5603 }));
5604
5605 let menu = if completions.is_empty() {
5606 None
5607 } else {
5608 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5609 let languages = editor
5610 .workspace
5611 .as_ref()
5612 .and_then(|(workspace, _)| workspace.upgrade())
5613 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5614 let menu = CompletionsMenu::new(
5615 id,
5616 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5617 sort_completions,
5618 show_completion_documentation,
5619 position,
5620 query.clone(),
5621 is_incomplete,
5622 buffer.clone(),
5623 completions.into(),
5624 display_options,
5625 snippet_sort_order,
5626 languages,
5627 language,
5628 cx,
5629 );
5630
5631 let query = if filter_completions { query } else { None };
5632 let matches_task = if let Some(query) = query {
5633 menu.do_async_filtering(query, cx)
5634 } else {
5635 Task::ready(menu.unfiltered_matches())
5636 };
5637 (menu, matches_task)
5638 }) else {
5639 return;
5640 };
5641
5642 let matches = matches_task.await;
5643
5644 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5645 // Newer menu already set, so exit.
5646 if let Some(CodeContextMenu::Completions(prev_menu)) =
5647 editor.context_menu.borrow().as_ref()
5648 && prev_menu.id > id
5649 {
5650 return;
5651 };
5652
5653 // Only valid to take prev_menu because it the new menu is immediately set
5654 // below, or the menu is hidden.
5655 if let Some(CodeContextMenu::Completions(prev_menu)) =
5656 editor.context_menu.borrow_mut().take()
5657 {
5658 let position_matches =
5659 if prev_menu.initial_position == menu.initial_position {
5660 true
5661 } else {
5662 let snapshot = editor.buffer.read(cx).read(cx);
5663 prev_menu.initial_position.to_offset(&snapshot)
5664 == menu.initial_position.to_offset(&snapshot)
5665 };
5666 if position_matches {
5667 // Preserve markdown cache before `set_filter_results` because it will
5668 // try to populate the documentation cache.
5669 menu.preserve_markdown_cache(prev_menu);
5670 }
5671 };
5672
5673 menu.set_filter_results(matches, provider, window, cx);
5674 }) else {
5675 return;
5676 };
5677
5678 menu.visible().then_some(menu)
5679 };
5680
5681 editor
5682 .update_in(cx, |editor, window, cx| {
5683 if editor.focus_handle.is_focused(window)
5684 && let Some(menu) = menu
5685 {
5686 *editor.context_menu.borrow_mut() =
5687 Some(CodeContextMenu::Completions(menu));
5688
5689 crate::hover_popover::hide_hover(editor, cx);
5690 if editor.show_edit_predictions_in_menu() {
5691 editor.update_visible_edit_prediction(window, cx);
5692 } else {
5693 editor.discard_edit_prediction(false, cx);
5694 }
5695
5696 cx.notify();
5697 return;
5698 }
5699
5700 if editor.completion_tasks.len() <= 1 {
5701 // If there are no more completion tasks and the last menu was empty, we should hide it.
5702 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5703 // If it was already hidden and we don't show edit predictions in the menu,
5704 // we should also show the edit prediction when available.
5705 if was_hidden && editor.show_edit_predictions_in_menu() {
5706 editor.update_visible_edit_prediction(window, cx);
5707 }
5708 }
5709 })
5710 .ok();
5711 });
5712
5713 self.completion_tasks.push((id, task));
5714 }
5715
5716 #[cfg(feature = "test-support")]
5717 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5718 let menu = self.context_menu.borrow();
5719 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5720 let completions = menu.completions.borrow();
5721 Some(completions.to_vec())
5722 } else {
5723 None
5724 }
5725 }
5726
5727 pub fn with_completions_menu_matching_id<R>(
5728 &self,
5729 id: CompletionId,
5730 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5731 ) -> R {
5732 let mut context_menu = self.context_menu.borrow_mut();
5733 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5734 return f(None);
5735 };
5736 if completions_menu.id != id {
5737 return f(None);
5738 }
5739 f(Some(completions_menu))
5740 }
5741
5742 pub fn confirm_completion(
5743 &mut self,
5744 action: &ConfirmCompletion,
5745 window: &mut Window,
5746 cx: &mut Context<Self>,
5747 ) -> Option<Task<Result<()>>> {
5748 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5749 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5750 }
5751
5752 pub fn confirm_completion_insert(
5753 &mut self,
5754 _: &ConfirmCompletionInsert,
5755 window: &mut Window,
5756 cx: &mut Context<Self>,
5757 ) -> Option<Task<Result<()>>> {
5758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5759 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5760 }
5761
5762 pub fn confirm_completion_replace(
5763 &mut self,
5764 _: &ConfirmCompletionReplace,
5765 window: &mut Window,
5766 cx: &mut Context<Self>,
5767 ) -> Option<Task<Result<()>>> {
5768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5769 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5770 }
5771
5772 pub fn compose_completion(
5773 &mut self,
5774 action: &ComposeCompletion,
5775 window: &mut Window,
5776 cx: &mut Context<Self>,
5777 ) -> Option<Task<Result<()>>> {
5778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5779 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5780 }
5781
5782 fn do_completion(
5783 &mut self,
5784 item_ix: Option<usize>,
5785 intent: CompletionIntent,
5786 window: &mut Window,
5787 cx: &mut Context<Editor>,
5788 ) -> Option<Task<Result<()>>> {
5789 use language::ToOffset as _;
5790
5791 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5792 else {
5793 return None;
5794 };
5795
5796 let candidate_id = {
5797 let entries = completions_menu.entries.borrow();
5798 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5799 if self.show_edit_predictions_in_menu() {
5800 self.discard_edit_prediction(true, cx);
5801 }
5802 mat.candidate_id
5803 };
5804
5805 let completion = completions_menu
5806 .completions
5807 .borrow()
5808 .get(candidate_id)?
5809 .clone();
5810 cx.stop_propagation();
5811
5812 let buffer_handle = completions_menu.buffer.clone();
5813
5814 let CompletionEdit {
5815 new_text,
5816 snippet,
5817 replace_range,
5818 } = process_completion_for_edit(
5819 &completion,
5820 intent,
5821 &buffer_handle,
5822 &completions_menu.initial_position.text_anchor,
5823 cx,
5824 );
5825
5826 let buffer = buffer_handle.read(cx);
5827 let snapshot = self.buffer.read(cx).snapshot(cx);
5828 let newest_anchor = self.selections.newest_anchor();
5829 let replace_range_multibuffer = {
5830 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5831 excerpt.map_range_from_buffer(replace_range.clone())
5832 };
5833 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5834 return None;
5835 }
5836
5837 let old_text = buffer
5838 .text_for_range(replace_range.clone())
5839 .collect::<String>();
5840 let lookbehind = newest_anchor
5841 .start
5842 .text_anchor
5843 .to_offset(buffer)
5844 .saturating_sub(replace_range.start);
5845 let lookahead = replace_range
5846 .end
5847 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5848 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5849 let suffix = &old_text[lookbehind.min(old_text.len())..];
5850
5851 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5852 let mut ranges = Vec::new();
5853 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5854
5855 for selection in &selections {
5856 let range = if selection.id == newest_anchor.id {
5857 replace_range_multibuffer.clone()
5858 } else {
5859 let mut range = selection.range();
5860
5861 // if prefix is present, don't duplicate it
5862 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5863 range.start = range.start.saturating_sub(lookbehind);
5864
5865 // if suffix is also present, mimic the newest cursor and replace it
5866 if selection.id != newest_anchor.id
5867 && snapshot.contains_str_at(range.end, suffix)
5868 {
5869 range.end += lookahead;
5870 }
5871 }
5872 range
5873 };
5874
5875 ranges.push(range.clone());
5876
5877 if !self.linked_edit_ranges.is_empty() {
5878 let start_anchor = snapshot.anchor_before(range.start);
5879 let end_anchor = snapshot.anchor_after(range.end);
5880 if let Some(ranges) = self
5881 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5882 {
5883 for (buffer, edits) in ranges {
5884 linked_edits
5885 .entry(buffer.clone())
5886 .or_default()
5887 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5888 }
5889 }
5890 }
5891 }
5892
5893 let common_prefix_len = old_text
5894 .chars()
5895 .zip(new_text.chars())
5896 .take_while(|(a, b)| a == b)
5897 .map(|(a, _)| a.len_utf8())
5898 .sum::<usize>();
5899
5900 cx.emit(EditorEvent::InputHandled {
5901 utf16_range_to_replace: None,
5902 text: new_text[common_prefix_len..].into(),
5903 });
5904
5905 self.transact(window, cx, |editor, window, cx| {
5906 if let Some(mut snippet) = snippet {
5907 snippet.text = new_text.to_string();
5908 editor
5909 .insert_snippet(&ranges, snippet, window, cx)
5910 .log_err();
5911 } else {
5912 editor.buffer.update(cx, |multi_buffer, cx| {
5913 let auto_indent = match completion.insert_text_mode {
5914 Some(InsertTextMode::AS_IS) => None,
5915 _ => editor.autoindent_mode.clone(),
5916 };
5917 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5918 multi_buffer.edit(edits, auto_indent, cx);
5919 });
5920 }
5921 for (buffer, edits) in linked_edits {
5922 buffer.update(cx, |buffer, cx| {
5923 let snapshot = buffer.snapshot();
5924 let edits = edits
5925 .into_iter()
5926 .map(|(range, text)| {
5927 use text::ToPoint as TP;
5928 let end_point = TP::to_point(&range.end, &snapshot);
5929 let start_point = TP::to_point(&range.start, &snapshot);
5930 (start_point..end_point, text)
5931 })
5932 .sorted_by_key(|(range, _)| range.start);
5933 buffer.edit(edits, None, cx);
5934 })
5935 }
5936
5937 editor.refresh_edit_prediction(true, false, window, cx);
5938 });
5939 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5940
5941 let show_new_completions_on_confirm = completion
5942 .confirm
5943 .as_ref()
5944 .is_some_and(|confirm| confirm(intent, window, cx));
5945 if show_new_completions_on_confirm {
5946 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5947 }
5948
5949 let provider = self.completion_provider.as_ref()?;
5950 drop(completion);
5951 let apply_edits = provider.apply_additional_edits_for_completion(
5952 buffer_handle,
5953 completions_menu.completions.clone(),
5954 candidate_id,
5955 true,
5956 cx,
5957 );
5958
5959 let editor_settings = EditorSettings::get_global(cx);
5960 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5961 // After the code completion is finished, users often want to know what signatures are needed.
5962 // so we should automatically call signature_help
5963 self.show_signature_help(&ShowSignatureHelp, window, cx);
5964 }
5965
5966 Some(cx.foreground_executor().spawn(async move {
5967 apply_edits.await?;
5968 Ok(())
5969 }))
5970 }
5971
5972 pub fn toggle_code_actions(
5973 &mut self,
5974 action: &ToggleCodeActions,
5975 window: &mut Window,
5976 cx: &mut Context<Self>,
5977 ) {
5978 let quick_launch = action.quick_launch;
5979 let mut context_menu = self.context_menu.borrow_mut();
5980 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5981 if code_actions.deployed_from == action.deployed_from {
5982 // Toggle if we're selecting the same one
5983 *context_menu = None;
5984 cx.notify();
5985 return;
5986 } else {
5987 // Otherwise, clear it and start a new one
5988 *context_menu = None;
5989 cx.notify();
5990 }
5991 }
5992 drop(context_menu);
5993 let snapshot = self.snapshot(window, cx);
5994 let deployed_from = action.deployed_from.clone();
5995 let action = action.clone();
5996 self.completion_tasks.clear();
5997 self.discard_edit_prediction(false, cx);
5998
5999 let multibuffer_point = match &action.deployed_from {
6000 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6001 DisplayPoint::new(*row, 0).to_point(&snapshot)
6002 }
6003 _ => self
6004 .selections
6005 .newest::<Point>(&snapshot.display_snapshot)
6006 .head(),
6007 };
6008 let Some((buffer, buffer_row)) = snapshot
6009 .buffer_snapshot()
6010 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6011 .and_then(|(buffer_snapshot, range)| {
6012 self.buffer()
6013 .read(cx)
6014 .buffer(buffer_snapshot.remote_id())
6015 .map(|buffer| (buffer, range.start.row))
6016 })
6017 else {
6018 return;
6019 };
6020 let buffer_id = buffer.read(cx).remote_id();
6021 let tasks = self
6022 .tasks
6023 .get(&(buffer_id, buffer_row))
6024 .map(|t| Arc::new(t.to_owned()));
6025
6026 if !self.focus_handle.is_focused(window) {
6027 return;
6028 }
6029 let project = self.project.clone();
6030
6031 let code_actions_task = match deployed_from {
6032 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6033 _ => self.code_actions(buffer_row, window, cx),
6034 };
6035
6036 let runnable_task = match deployed_from {
6037 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6038 _ => {
6039 let mut task_context_task = Task::ready(None);
6040 if let Some(tasks) = &tasks
6041 && let Some(project) = project
6042 {
6043 task_context_task =
6044 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6045 }
6046
6047 cx.spawn_in(window, {
6048 let buffer = buffer.clone();
6049 async move |editor, cx| {
6050 let task_context = task_context_task.await;
6051
6052 let resolved_tasks =
6053 tasks
6054 .zip(task_context.clone())
6055 .map(|(tasks, task_context)| ResolvedTasks {
6056 templates: tasks.resolve(&task_context).collect(),
6057 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6058 multibuffer_point.row,
6059 tasks.column,
6060 )),
6061 });
6062 let debug_scenarios = editor
6063 .update(cx, |editor, cx| {
6064 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6065 })?
6066 .await;
6067 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6068 }
6069 })
6070 }
6071 };
6072
6073 cx.spawn_in(window, async move |editor, cx| {
6074 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6075 let code_actions = code_actions_task.await;
6076 let spawn_straight_away = quick_launch
6077 && resolved_tasks
6078 .as_ref()
6079 .is_some_and(|tasks| tasks.templates.len() == 1)
6080 && code_actions
6081 .as_ref()
6082 .is_none_or(|actions| actions.is_empty())
6083 && debug_scenarios.is_empty();
6084
6085 editor.update_in(cx, |editor, window, cx| {
6086 crate::hover_popover::hide_hover(editor, cx);
6087 let actions = CodeActionContents::new(
6088 resolved_tasks,
6089 code_actions,
6090 debug_scenarios,
6091 task_context.unwrap_or_default(),
6092 );
6093
6094 // Don't show the menu if there are no actions available
6095 if actions.is_empty() {
6096 cx.notify();
6097 return Task::ready(Ok(()));
6098 }
6099
6100 *editor.context_menu.borrow_mut() =
6101 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6102 buffer,
6103 actions,
6104 selected_item: Default::default(),
6105 scroll_handle: UniformListScrollHandle::default(),
6106 deployed_from,
6107 }));
6108 cx.notify();
6109 if spawn_straight_away
6110 && let Some(task) = editor.confirm_code_action(
6111 &ConfirmCodeAction { item_ix: Some(0) },
6112 window,
6113 cx,
6114 )
6115 {
6116 return task;
6117 }
6118
6119 Task::ready(Ok(()))
6120 })
6121 })
6122 .detach_and_log_err(cx);
6123 }
6124
6125 fn debug_scenarios(
6126 &mut self,
6127 resolved_tasks: &Option<ResolvedTasks>,
6128 buffer: &Entity<Buffer>,
6129 cx: &mut App,
6130 ) -> Task<Vec<task::DebugScenario>> {
6131 maybe!({
6132 let project = self.project()?;
6133 let dap_store = project.read(cx).dap_store();
6134 let mut scenarios = vec![];
6135 let resolved_tasks = resolved_tasks.as_ref()?;
6136 let buffer = buffer.read(cx);
6137 let language = buffer.language()?;
6138 let file = buffer.file();
6139 let debug_adapter = language_settings(language.name().into(), file, cx)
6140 .debuggers
6141 .first()
6142 .map(SharedString::from)
6143 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6144
6145 dap_store.update(cx, |dap_store, cx| {
6146 for (_, task) in &resolved_tasks.templates {
6147 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6148 task.original_task().clone(),
6149 debug_adapter.clone().into(),
6150 task.display_label().to_owned().into(),
6151 cx,
6152 );
6153 scenarios.push(maybe_scenario);
6154 }
6155 });
6156 Some(cx.background_spawn(async move {
6157 futures::future::join_all(scenarios)
6158 .await
6159 .into_iter()
6160 .flatten()
6161 .collect::<Vec<_>>()
6162 }))
6163 })
6164 .unwrap_or_else(|| Task::ready(vec![]))
6165 }
6166
6167 fn code_actions(
6168 &mut self,
6169 buffer_row: u32,
6170 window: &mut Window,
6171 cx: &mut Context<Self>,
6172 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6173 let mut task = self.code_actions_task.take();
6174 cx.spawn_in(window, async move |editor, cx| {
6175 while let Some(prev_task) = task {
6176 prev_task.await.log_err();
6177 task = editor
6178 .update(cx, |this, _| this.code_actions_task.take())
6179 .ok()?;
6180 }
6181
6182 editor
6183 .update(cx, |editor, cx| {
6184 editor
6185 .available_code_actions
6186 .clone()
6187 .and_then(|(location, code_actions)| {
6188 let snapshot = location.buffer.read(cx).snapshot();
6189 let point_range = location.range.to_point(&snapshot);
6190 let point_range = point_range.start.row..=point_range.end.row;
6191 if point_range.contains(&buffer_row) {
6192 Some(code_actions)
6193 } else {
6194 None
6195 }
6196 })
6197 })
6198 .ok()
6199 .flatten()
6200 })
6201 }
6202
6203 pub fn confirm_code_action(
6204 &mut self,
6205 action: &ConfirmCodeAction,
6206 window: &mut Window,
6207 cx: &mut Context<Self>,
6208 ) -> Option<Task<Result<()>>> {
6209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6210
6211 let actions_menu =
6212 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6213 menu
6214 } else {
6215 return None;
6216 };
6217
6218 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6219 let action = actions_menu.actions.get(action_ix)?;
6220 let title = action.label();
6221 let buffer = actions_menu.buffer;
6222 let workspace = self.workspace()?;
6223
6224 match action {
6225 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6226 workspace.update(cx, |workspace, cx| {
6227 workspace.schedule_resolved_task(
6228 task_source_kind,
6229 resolved_task,
6230 false,
6231 window,
6232 cx,
6233 );
6234
6235 Some(Task::ready(Ok(())))
6236 })
6237 }
6238 CodeActionsItem::CodeAction {
6239 excerpt_id,
6240 action,
6241 provider,
6242 } => {
6243 let apply_code_action =
6244 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6245 let workspace = workspace.downgrade();
6246 Some(cx.spawn_in(window, async move |editor, cx| {
6247 let project_transaction = apply_code_action.await?;
6248 Self::open_project_transaction(
6249 &editor,
6250 workspace,
6251 project_transaction,
6252 title,
6253 cx,
6254 )
6255 .await
6256 }))
6257 }
6258 CodeActionsItem::DebugScenario(scenario) => {
6259 let context = actions_menu.actions.context;
6260
6261 workspace.update(cx, |workspace, cx| {
6262 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6263 workspace.start_debug_session(
6264 scenario,
6265 context,
6266 Some(buffer),
6267 None,
6268 window,
6269 cx,
6270 );
6271 });
6272 Some(Task::ready(Ok(())))
6273 }
6274 }
6275 }
6276
6277 pub async fn open_project_transaction(
6278 editor: &WeakEntity<Editor>,
6279 workspace: WeakEntity<Workspace>,
6280 transaction: ProjectTransaction,
6281 title: String,
6282 cx: &mut AsyncWindowContext,
6283 ) -> Result<()> {
6284 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6285 cx.update(|_, cx| {
6286 entries.sort_unstable_by_key(|(buffer, _)| {
6287 buffer.read(cx).file().map(|f| f.path().clone())
6288 });
6289 })?;
6290 if entries.is_empty() {
6291 return Ok(());
6292 }
6293
6294 // If the project transaction's edits are all contained within this editor, then
6295 // avoid opening a new editor to display them.
6296
6297 if let [(buffer, transaction)] = &*entries {
6298 let excerpt = editor.update(cx, |editor, cx| {
6299 editor
6300 .buffer()
6301 .read(cx)
6302 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6303 })?;
6304 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6305 && excerpted_buffer == *buffer
6306 {
6307 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6308 let excerpt_range = excerpt_range.to_offset(buffer);
6309 buffer
6310 .edited_ranges_for_transaction::<usize>(transaction)
6311 .all(|range| {
6312 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6313 })
6314 })?;
6315
6316 if all_edits_within_excerpt {
6317 return Ok(());
6318 }
6319 }
6320 }
6321
6322 let mut ranges_to_highlight = Vec::new();
6323 let excerpt_buffer = cx.new(|cx| {
6324 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6325 for (buffer_handle, transaction) in &entries {
6326 let edited_ranges = buffer_handle
6327 .read(cx)
6328 .edited_ranges_for_transaction::<Point>(transaction)
6329 .collect::<Vec<_>>();
6330 let (ranges, _) = multibuffer.set_excerpts_for_path(
6331 PathKey::for_buffer(buffer_handle, cx),
6332 buffer_handle.clone(),
6333 edited_ranges,
6334 multibuffer_context_lines(cx),
6335 cx,
6336 );
6337
6338 ranges_to_highlight.extend(ranges);
6339 }
6340 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6341 multibuffer
6342 })?;
6343
6344 workspace.update_in(cx, |workspace, window, cx| {
6345 let project = workspace.project().clone();
6346 let editor =
6347 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6348 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6349 editor.update(cx, |editor, cx| {
6350 editor.highlight_background::<Self>(
6351 &ranges_to_highlight,
6352 |theme| theme.colors().editor_highlighted_line_background,
6353 cx,
6354 );
6355 });
6356 })?;
6357
6358 Ok(())
6359 }
6360
6361 pub fn clear_code_action_providers(&mut self) {
6362 self.code_action_providers.clear();
6363 self.available_code_actions.take();
6364 }
6365
6366 pub fn add_code_action_provider(
6367 &mut self,
6368 provider: Rc<dyn CodeActionProvider>,
6369 window: &mut Window,
6370 cx: &mut Context<Self>,
6371 ) {
6372 if self
6373 .code_action_providers
6374 .iter()
6375 .any(|existing_provider| existing_provider.id() == provider.id())
6376 {
6377 return;
6378 }
6379
6380 self.code_action_providers.push(provider);
6381 self.refresh_code_actions(window, cx);
6382 }
6383
6384 pub fn remove_code_action_provider(
6385 &mut self,
6386 id: Arc<str>,
6387 window: &mut Window,
6388 cx: &mut Context<Self>,
6389 ) {
6390 self.code_action_providers
6391 .retain(|provider| provider.id() != id);
6392 self.refresh_code_actions(window, cx);
6393 }
6394
6395 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6396 !self.code_action_providers.is_empty()
6397 && EditorSettings::get_global(cx).toolbar.code_actions
6398 }
6399
6400 pub fn has_available_code_actions(&self) -> bool {
6401 self.available_code_actions
6402 .as_ref()
6403 .is_some_and(|(_, actions)| !actions.is_empty())
6404 }
6405
6406 fn render_inline_code_actions(
6407 &self,
6408 icon_size: ui::IconSize,
6409 display_row: DisplayRow,
6410 is_active: bool,
6411 cx: &mut Context<Self>,
6412 ) -> AnyElement {
6413 let show_tooltip = !self.context_menu_visible();
6414 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6415 .icon_size(icon_size)
6416 .shape(ui::IconButtonShape::Square)
6417 .icon_color(ui::Color::Hidden)
6418 .toggle_state(is_active)
6419 .when(show_tooltip, |this| {
6420 this.tooltip({
6421 let focus_handle = self.focus_handle.clone();
6422 move |_window, cx| {
6423 Tooltip::for_action_in(
6424 "Toggle Code Actions",
6425 &ToggleCodeActions {
6426 deployed_from: None,
6427 quick_launch: false,
6428 },
6429 &focus_handle,
6430 cx,
6431 )
6432 }
6433 })
6434 })
6435 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6436 window.focus(&editor.focus_handle(cx));
6437 editor.toggle_code_actions(
6438 &crate::actions::ToggleCodeActions {
6439 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6440 display_row,
6441 )),
6442 quick_launch: false,
6443 },
6444 window,
6445 cx,
6446 );
6447 }))
6448 .into_any_element()
6449 }
6450
6451 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6452 &self.context_menu
6453 }
6454
6455 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6456 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6457 cx.background_executor()
6458 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6459 .await;
6460
6461 let (start_buffer, start, _, end, newest_selection) = this
6462 .update(cx, |this, cx| {
6463 let newest_selection = this.selections.newest_anchor().clone();
6464 if newest_selection.head().diff_base_anchor.is_some() {
6465 return None;
6466 }
6467 let display_snapshot = this.display_snapshot(cx);
6468 let newest_selection_adjusted =
6469 this.selections.newest_adjusted(&display_snapshot);
6470 let buffer = this.buffer.read(cx);
6471
6472 let (start_buffer, start) =
6473 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6474 let (end_buffer, end) =
6475 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6476
6477 Some((start_buffer, start, end_buffer, end, newest_selection))
6478 })?
6479 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6480 .context(
6481 "Expected selection to lie in a single buffer when refreshing code actions",
6482 )?;
6483 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6484 let providers = this.code_action_providers.clone();
6485 let tasks = this
6486 .code_action_providers
6487 .iter()
6488 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6489 .collect::<Vec<_>>();
6490 (providers, tasks)
6491 })?;
6492
6493 let mut actions = Vec::new();
6494 for (provider, provider_actions) in
6495 providers.into_iter().zip(future::join_all(tasks).await)
6496 {
6497 if let Some(provider_actions) = provider_actions.log_err() {
6498 actions.extend(provider_actions.into_iter().map(|action| {
6499 AvailableCodeAction {
6500 excerpt_id: newest_selection.start.excerpt_id,
6501 action,
6502 provider: provider.clone(),
6503 }
6504 }));
6505 }
6506 }
6507
6508 this.update(cx, |this, cx| {
6509 this.available_code_actions = if actions.is_empty() {
6510 None
6511 } else {
6512 Some((
6513 Location {
6514 buffer: start_buffer,
6515 range: start..end,
6516 },
6517 actions.into(),
6518 ))
6519 };
6520 cx.notify();
6521 })
6522 }));
6523 }
6524
6525 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6526 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6527 self.show_git_blame_inline = false;
6528
6529 self.show_git_blame_inline_delay_task =
6530 Some(cx.spawn_in(window, async move |this, cx| {
6531 cx.background_executor().timer(delay).await;
6532
6533 this.update(cx, |this, cx| {
6534 this.show_git_blame_inline = true;
6535 cx.notify();
6536 })
6537 .log_err();
6538 }));
6539 }
6540 }
6541
6542 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6543 let snapshot = self.snapshot(window, cx);
6544 let cursor = self
6545 .selections
6546 .newest::<Point>(&snapshot.display_snapshot)
6547 .head();
6548 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6549 else {
6550 return;
6551 };
6552
6553 let Some(blame) = self.blame.as_ref() else {
6554 return;
6555 };
6556
6557 let row_info = RowInfo {
6558 buffer_id: Some(buffer.remote_id()),
6559 buffer_row: Some(point.row),
6560 ..Default::default()
6561 };
6562 let Some((buffer, blame_entry)) = blame
6563 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6564 .flatten()
6565 else {
6566 return;
6567 };
6568
6569 let anchor = self.selections.newest_anchor().head();
6570 let position = self.to_pixel_point(anchor, &snapshot, window);
6571 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6572 self.show_blame_popover(
6573 buffer,
6574 &blame_entry,
6575 position + last_bounds.origin,
6576 true,
6577 cx,
6578 );
6579 };
6580 }
6581
6582 fn show_blame_popover(
6583 &mut self,
6584 buffer: BufferId,
6585 blame_entry: &BlameEntry,
6586 position: gpui::Point<Pixels>,
6587 ignore_timeout: bool,
6588 cx: &mut Context<Self>,
6589 ) {
6590 if let Some(state) = &mut self.inline_blame_popover {
6591 state.hide_task.take();
6592 } else {
6593 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6594 let blame_entry = blame_entry.clone();
6595 let show_task = cx.spawn(async move |editor, cx| {
6596 if !ignore_timeout {
6597 cx.background_executor()
6598 .timer(std::time::Duration::from_millis(blame_popover_delay))
6599 .await;
6600 }
6601 editor
6602 .update(cx, |editor, cx| {
6603 editor.inline_blame_popover_show_task.take();
6604 let Some(blame) = editor.blame.as_ref() else {
6605 return;
6606 };
6607 let blame = blame.read(cx);
6608 let details = blame.details_for_entry(buffer, &blame_entry);
6609 let markdown = cx.new(|cx| {
6610 Markdown::new(
6611 details
6612 .as_ref()
6613 .map(|message| message.message.clone())
6614 .unwrap_or_default(),
6615 None,
6616 None,
6617 cx,
6618 )
6619 });
6620 editor.inline_blame_popover = Some(InlineBlamePopover {
6621 position,
6622 hide_task: None,
6623 popover_bounds: None,
6624 popover_state: InlineBlamePopoverState {
6625 scroll_handle: ScrollHandle::new(),
6626 commit_message: details,
6627 markdown,
6628 },
6629 keyboard_grace: ignore_timeout,
6630 });
6631 cx.notify();
6632 })
6633 .ok();
6634 });
6635 self.inline_blame_popover_show_task = Some(show_task);
6636 }
6637 }
6638
6639 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6640 self.inline_blame_popover_show_task.take();
6641 if let Some(state) = &mut self.inline_blame_popover {
6642 let hide_task = cx.spawn(async move |editor, cx| {
6643 if !ignore_timeout {
6644 cx.background_executor()
6645 .timer(std::time::Duration::from_millis(100))
6646 .await;
6647 }
6648 editor
6649 .update(cx, |editor, cx| {
6650 editor.inline_blame_popover.take();
6651 cx.notify();
6652 })
6653 .ok();
6654 });
6655 state.hide_task = Some(hide_task);
6656 true
6657 } else {
6658 false
6659 }
6660 }
6661
6662 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6663 if self.pending_rename.is_some() {
6664 return None;
6665 }
6666
6667 let provider = self.semantics_provider.clone()?;
6668 let buffer = self.buffer.read(cx);
6669 let newest_selection = self.selections.newest_anchor().clone();
6670 let cursor_position = newest_selection.head();
6671 let (cursor_buffer, cursor_buffer_position) =
6672 buffer.text_anchor_for_position(cursor_position, cx)?;
6673 let (tail_buffer, tail_buffer_position) =
6674 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6675 if cursor_buffer != tail_buffer {
6676 return None;
6677 }
6678
6679 let snapshot = cursor_buffer.read(cx).snapshot();
6680 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6681 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6682 if start_word_range != end_word_range {
6683 self.document_highlights_task.take();
6684 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6685 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6686 return None;
6687 }
6688
6689 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6690 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6691 cx.background_executor()
6692 .timer(Duration::from_millis(debounce))
6693 .await;
6694
6695 let highlights = if let Some(highlights) = cx
6696 .update(|cx| {
6697 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6698 })
6699 .ok()
6700 .flatten()
6701 {
6702 highlights.await.log_err()
6703 } else {
6704 None
6705 };
6706
6707 if let Some(highlights) = highlights {
6708 this.update(cx, |this, cx| {
6709 if this.pending_rename.is_some() {
6710 return;
6711 }
6712
6713 let buffer = this.buffer.read(cx);
6714 if buffer
6715 .text_anchor_for_position(cursor_position, cx)
6716 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6717 {
6718 return;
6719 }
6720
6721 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6722 let mut write_ranges = Vec::new();
6723 let mut read_ranges = Vec::new();
6724 for highlight in highlights {
6725 let buffer_id = cursor_buffer.read(cx).remote_id();
6726 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6727 {
6728 let start = highlight
6729 .range
6730 .start
6731 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6732 let end = highlight
6733 .range
6734 .end
6735 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6736 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6737 continue;
6738 }
6739
6740 let range =
6741 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6742 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6743 write_ranges.push(range);
6744 } else {
6745 read_ranges.push(range);
6746 }
6747 }
6748 }
6749
6750 this.highlight_background::<DocumentHighlightRead>(
6751 &read_ranges,
6752 |theme| theme.colors().editor_document_highlight_read_background,
6753 cx,
6754 );
6755 this.highlight_background::<DocumentHighlightWrite>(
6756 &write_ranges,
6757 |theme| theme.colors().editor_document_highlight_write_background,
6758 cx,
6759 );
6760 cx.notify();
6761 })
6762 .log_err();
6763 }
6764 }));
6765 None
6766 }
6767
6768 fn prepare_highlight_query_from_selection(
6769 &mut self,
6770 window: &Window,
6771 cx: &mut Context<Editor>,
6772 ) -> Option<(String, Range<Anchor>)> {
6773 if matches!(self.mode, EditorMode::SingleLine) {
6774 return None;
6775 }
6776 if !EditorSettings::get_global(cx).selection_highlight {
6777 return None;
6778 }
6779 if self.selections.count() != 1 || self.selections.line_mode() {
6780 return None;
6781 }
6782 let snapshot = self.snapshot(window, cx);
6783 let selection = self.selections.newest::<Point>(&snapshot);
6784 // If the selection spans multiple rows OR it is empty
6785 if selection.start.row != selection.end.row
6786 || selection.start.column == selection.end.column
6787 {
6788 return None;
6789 }
6790 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6791 let query = snapshot
6792 .buffer_snapshot()
6793 .text_for_range(selection_anchor_range.clone())
6794 .collect::<String>();
6795 if query.trim().is_empty() {
6796 return None;
6797 }
6798 Some((query, selection_anchor_range))
6799 }
6800
6801 fn update_selection_occurrence_highlights(
6802 &mut self,
6803 query_text: String,
6804 query_range: Range<Anchor>,
6805 multi_buffer_range_to_query: Range<Point>,
6806 use_debounce: bool,
6807 window: &mut Window,
6808 cx: &mut Context<Editor>,
6809 ) -> Task<()> {
6810 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6811 cx.spawn_in(window, async move |editor, cx| {
6812 if use_debounce {
6813 cx.background_executor()
6814 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6815 .await;
6816 }
6817 let match_task = cx.background_spawn(async move {
6818 let buffer_ranges = multi_buffer_snapshot
6819 .range_to_buffer_ranges(multi_buffer_range_to_query)
6820 .into_iter()
6821 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6822 let mut match_ranges = Vec::new();
6823 let Ok(regex) = project::search::SearchQuery::text(
6824 query_text.clone(),
6825 false,
6826 false,
6827 false,
6828 Default::default(),
6829 Default::default(),
6830 false,
6831 None,
6832 ) else {
6833 return Vec::default();
6834 };
6835 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6836 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6837 match_ranges.extend(
6838 regex
6839 .search(buffer_snapshot, Some(search_range.clone()))
6840 .await
6841 .into_iter()
6842 .filter_map(|match_range| {
6843 let match_start = buffer_snapshot
6844 .anchor_after(search_range.start + match_range.start);
6845 let match_end = buffer_snapshot
6846 .anchor_before(search_range.start + match_range.end);
6847 let match_anchor_range = Anchor::range_in_buffer(
6848 excerpt_id,
6849 buffer_snapshot.remote_id(),
6850 match_start..match_end,
6851 );
6852 (match_anchor_range != query_range).then_some(match_anchor_range)
6853 }),
6854 );
6855 }
6856 match_ranges
6857 });
6858 let match_ranges = match_task.await;
6859 editor
6860 .update_in(cx, |editor, _, cx| {
6861 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6862 if !match_ranges.is_empty() {
6863 editor.highlight_background::<SelectedTextHighlight>(
6864 &match_ranges,
6865 |theme| theme.colors().editor_document_highlight_bracket_background,
6866 cx,
6867 )
6868 }
6869 })
6870 .log_err();
6871 })
6872 }
6873
6874 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6875 struct NewlineFold;
6876 let type_id = std::any::TypeId::of::<NewlineFold>();
6877 if !self.mode.is_single_line() {
6878 return;
6879 }
6880 let snapshot = self.snapshot(window, cx);
6881 if snapshot.buffer_snapshot().max_point().row == 0 {
6882 return;
6883 }
6884 let task = cx.background_spawn(async move {
6885 let new_newlines = snapshot
6886 .buffer_chars_at(0)
6887 .filter_map(|(c, i)| {
6888 if c == '\n' {
6889 Some(
6890 snapshot.buffer_snapshot().anchor_after(i)
6891 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6892 )
6893 } else {
6894 None
6895 }
6896 })
6897 .collect::<Vec<_>>();
6898 let existing_newlines = snapshot
6899 .folds_in_range(0..snapshot.buffer_snapshot().len())
6900 .filter_map(|fold| {
6901 if fold.placeholder.type_tag == Some(type_id) {
6902 Some(fold.range.start..fold.range.end)
6903 } else {
6904 None
6905 }
6906 })
6907 .collect::<Vec<_>>();
6908
6909 (new_newlines, existing_newlines)
6910 });
6911 self.folding_newlines = cx.spawn(async move |this, cx| {
6912 let (new_newlines, existing_newlines) = task.await;
6913 if new_newlines == existing_newlines {
6914 return;
6915 }
6916 let placeholder = FoldPlaceholder {
6917 render: Arc::new(move |_, _, cx| {
6918 div()
6919 .bg(cx.theme().status().hint_background)
6920 .border_b_1()
6921 .size_full()
6922 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6923 .border_color(cx.theme().status().hint)
6924 .child("\\n")
6925 .into_any()
6926 }),
6927 constrain_width: false,
6928 merge_adjacent: false,
6929 type_tag: Some(type_id),
6930 };
6931 let creases = new_newlines
6932 .into_iter()
6933 .map(|range| Crease::simple(range, placeholder.clone()))
6934 .collect();
6935 this.update(cx, |this, cx| {
6936 this.display_map.update(cx, |display_map, cx| {
6937 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6938 display_map.fold(creases, cx);
6939 });
6940 })
6941 .ok();
6942 });
6943 }
6944
6945 fn refresh_selected_text_highlights(
6946 &mut self,
6947 on_buffer_edit: bool,
6948 window: &mut Window,
6949 cx: &mut Context<Editor>,
6950 ) {
6951 let Some((query_text, query_range)) =
6952 self.prepare_highlight_query_from_selection(window, cx)
6953 else {
6954 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6955 self.quick_selection_highlight_task.take();
6956 self.debounced_selection_highlight_task.take();
6957 return;
6958 };
6959 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6960 if on_buffer_edit
6961 || self
6962 .quick_selection_highlight_task
6963 .as_ref()
6964 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6965 {
6966 let multi_buffer_visible_start = self
6967 .scroll_manager
6968 .anchor()
6969 .anchor
6970 .to_point(&multi_buffer_snapshot);
6971 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6972 multi_buffer_visible_start
6973 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6974 Bias::Left,
6975 );
6976 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6977 self.quick_selection_highlight_task = Some((
6978 query_range.clone(),
6979 self.update_selection_occurrence_highlights(
6980 query_text.clone(),
6981 query_range.clone(),
6982 multi_buffer_visible_range,
6983 false,
6984 window,
6985 cx,
6986 ),
6987 ));
6988 }
6989 if on_buffer_edit
6990 || self
6991 .debounced_selection_highlight_task
6992 .as_ref()
6993 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6994 {
6995 let multi_buffer_start = multi_buffer_snapshot
6996 .anchor_before(0)
6997 .to_point(&multi_buffer_snapshot);
6998 let multi_buffer_end = multi_buffer_snapshot
6999 .anchor_after(multi_buffer_snapshot.len())
7000 .to_point(&multi_buffer_snapshot);
7001 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7002 self.debounced_selection_highlight_task = Some((
7003 query_range.clone(),
7004 self.update_selection_occurrence_highlights(
7005 query_text,
7006 query_range,
7007 multi_buffer_full_range,
7008 true,
7009 window,
7010 cx,
7011 ),
7012 ));
7013 }
7014 }
7015
7016 pub fn refresh_edit_prediction(
7017 &mut self,
7018 debounce: bool,
7019 user_requested: bool,
7020 window: &mut Window,
7021 cx: &mut Context<Self>,
7022 ) -> Option<()> {
7023 if DisableAiSettings::get_global(cx).disable_ai {
7024 return None;
7025 }
7026
7027 let provider = self.edit_prediction_provider()?;
7028 let cursor = self.selections.newest_anchor().head();
7029 let (buffer, cursor_buffer_position) =
7030 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7031
7032 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7033 self.discard_edit_prediction(false, cx);
7034 return None;
7035 }
7036
7037 self.update_visible_edit_prediction(window, cx);
7038
7039 if !user_requested
7040 && (!self.should_show_edit_predictions()
7041 || !self.is_focused(window)
7042 || buffer.read(cx).is_empty())
7043 {
7044 self.discard_edit_prediction(false, cx);
7045 return None;
7046 }
7047
7048 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7049 Some(())
7050 }
7051
7052 fn show_edit_predictions_in_menu(&self) -> bool {
7053 match self.edit_prediction_settings {
7054 EditPredictionSettings::Disabled => false,
7055 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7056 }
7057 }
7058
7059 pub fn edit_predictions_enabled(&self) -> bool {
7060 match self.edit_prediction_settings {
7061 EditPredictionSettings::Disabled => false,
7062 EditPredictionSettings::Enabled { .. } => true,
7063 }
7064 }
7065
7066 fn edit_prediction_requires_modifier(&self) -> bool {
7067 match self.edit_prediction_settings {
7068 EditPredictionSettings::Disabled => false,
7069 EditPredictionSettings::Enabled {
7070 preview_requires_modifier,
7071 ..
7072 } => preview_requires_modifier,
7073 }
7074 }
7075
7076 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7077 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7078 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7079 self.discard_edit_prediction(false, cx);
7080 } else {
7081 let selection = self.selections.newest_anchor();
7082 let cursor = selection.head();
7083
7084 if let Some((buffer, cursor_buffer_position)) =
7085 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7086 {
7087 self.edit_prediction_settings =
7088 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7089 }
7090 }
7091 }
7092
7093 fn edit_prediction_settings_at_position(
7094 &self,
7095 buffer: &Entity<Buffer>,
7096 buffer_position: language::Anchor,
7097 cx: &App,
7098 ) -> EditPredictionSettings {
7099 if !self.mode.is_full()
7100 || !self.show_edit_predictions_override.unwrap_or(true)
7101 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7102 {
7103 return EditPredictionSettings::Disabled;
7104 }
7105
7106 let buffer = buffer.read(cx);
7107
7108 let file = buffer.file();
7109
7110 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7111 return EditPredictionSettings::Disabled;
7112 };
7113
7114 let by_provider = matches!(
7115 self.menu_edit_predictions_policy,
7116 MenuEditPredictionsPolicy::ByProvider
7117 );
7118
7119 let show_in_menu = by_provider
7120 && self
7121 .edit_prediction_provider
7122 .as_ref()
7123 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7124
7125 let preview_requires_modifier =
7126 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7127
7128 EditPredictionSettings::Enabled {
7129 show_in_menu,
7130 preview_requires_modifier,
7131 }
7132 }
7133
7134 fn should_show_edit_predictions(&self) -> bool {
7135 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7136 }
7137
7138 pub fn edit_prediction_preview_is_active(&self) -> bool {
7139 matches!(
7140 self.edit_prediction_preview,
7141 EditPredictionPreview::Active { .. }
7142 )
7143 }
7144
7145 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7146 let cursor = self.selections.newest_anchor().head();
7147 if let Some((buffer, cursor_position)) =
7148 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7149 {
7150 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7151 } else {
7152 false
7153 }
7154 }
7155
7156 pub fn supports_minimap(&self, cx: &App) -> bool {
7157 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7158 }
7159
7160 fn edit_predictions_enabled_in_buffer(
7161 &self,
7162 buffer: &Entity<Buffer>,
7163 buffer_position: language::Anchor,
7164 cx: &App,
7165 ) -> bool {
7166 maybe!({
7167 if self.read_only(cx) {
7168 return Some(false);
7169 }
7170 let provider = self.edit_prediction_provider()?;
7171 if !provider.is_enabled(buffer, buffer_position, cx) {
7172 return Some(false);
7173 }
7174 let buffer = buffer.read(cx);
7175 let Some(file) = buffer.file() else {
7176 return Some(true);
7177 };
7178 let settings = all_language_settings(Some(file), cx);
7179 Some(settings.edit_predictions_enabled_for_file(file, cx))
7180 })
7181 .unwrap_or(false)
7182 }
7183
7184 fn cycle_edit_prediction(
7185 &mut self,
7186 direction: Direction,
7187 window: &mut Window,
7188 cx: &mut Context<Self>,
7189 ) -> Option<()> {
7190 let provider = self.edit_prediction_provider()?;
7191 let cursor = self.selections.newest_anchor().head();
7192 let (buffer, cursor_buffer_position) =
7193 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7194 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7195 return None;
7196 }
7197
7198 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7199 self.update_visible_edit_prediction(window, cx);
7200
7201 Some(())
7202 }
7203
7204 pub fn show_edit_prediction(
7205 &mut self,
7206 _: &ShowEditPrediction,
7207 window: &mut Window,
7208 cx: &mut Context<Self>,
7209 ) {
7210 if !self.has_active_edit_prediction() {
7211 self.refresh_edit_prediction(false, true, window, cx);
7212 return;
7213 }
7214
7215 self.update_visible_edit_prediction(window, cx);
7216 }
7217
7218 pub fn display_cursor_names(
7219 &mut self,
7220 _: &DisplayCursorNames,
7221 window: &mut Window,
7222 cx: &mut Context<Self>,
7223 ) {
7224 self.show_cursor_names(window, cx);
7225 }
7226
7227 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7228 self.show_cursor_names = true;
7229 cx.notify();
7230 cx.spawn_in(window, async move |this, cx| {
7231 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7232 this.update(cx, |this, cx| {
7233 this.show_cursor_names = false;
7234 cx.notify()
7235 })
7236 .ok()
7237 })
7238 .detach();
7239 }
7240
7241 pub fn next_edit_prediction(
7242 &mut self,
7243 _: &NextEditPrediction,
7244 window: &mut Window,
7245 cx: &mut Context<Self>,
7246 ) {
7247 if self.has_active_edit_prediction() {
7248 self.cycle_edit_prediction(Direction::Next, window, cx);
7249 } else {
7250 let is_copilot_disabled = self
7251 .refresh_edit_prediction(false, true, window, cx)
7252 .is_none();
7253 if is_copilot_disabled {
7254 cx.propagate();
7255 }
7256 }
7257 }
7258
7259 pub fn previous_edit_prediction(
7260 &mut self,
7261 _: &PreviousEditPrediction,
7262 window: &mut Window,
7263 cx: &mut Context<Self>,
7264 ) {
7265 if self.has_active_edit_prediction() {
7266 self.cycle_edit_prediction(Direction::Prev, window, cx);
7267 } else {
7268 let is_copilot_disabled = self
7269 .refresh_edit_prediction(false, true, window, cx)
7270 .is_none();
7271 if is_copilot_disabled {
7272 cx.propagate();
7273 }
7274 }
7275 }
7276
7277 pub fn accept_edit_prediction(
7278 &mut self,
7279 _: &AcceptEditPrediction,
7280 window: &mut Window,
7281 cx: &mut Context<Self>,
7282 ) {
7283 if self.show_edit_predictions_in_menu() {
7284 self.hide_context_menu(window, cx);
7285 }
7286
7287 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7288 return;
7289 };
7290
7291 match &active_edit_prediction.completion {
7292 EditPrediction::MoveWithin { target, .. } => {
7293 let target = *target;
7294
7295 if let Some(position_map) = &self.last_position_map {
7296 if position_map
7297 .visible_row_range
7298 .contains(&target.to_display_point(&position_map.snapshot).row())
7299 || !self.edit_prediction_requires_modifier()
7300 {
7301 self.unfold_ranges(&[target..target], true, false, cx);
7302 // Note that this is also done in vim's handler of the Tab action.
7303 self.change_selections(
7304 SelectionEffects::scroll(Autoscroll::newest()),
7305 window,
7306 cx,
7307 |selections| {
7308 selections.select_anchor_ranges([target..target]);
7309 },
7310 );
7311 self.clear_row_highlights::<EditPredictionPreview>();
7312
7313 self.edit_prediction_preview
7314 .set_previous_scroll_position(None);
7315 } else {
7316 self.edit_prediction_preview
7317 .set_previous_scroll_position(Some(
7318 position_map.snapshot.scroll_anchor,
7319 ));
7320
7321 self.highlight_rows::<EditPredictionPreview>(
7322 target..target,
7323 cx.theme().colors().editor_highlighted_line_background,
7324 RowHighlightOptions {
7325 autoscroll: true,
7326 ..Default::default()
7327 },
7328 cx,
7329 );
7330 self.request_autoscroll(Autoscroll::fit(), cx);
7331 }
7332 }
7333 }
7334 EditPrediction::MoveOutside { snapshot, target } => {
7335 if let Some(workspace) = self.workspace() {
7336 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7337 .detach_and_log_err(cx);
7338 }
7339 }
7340 EditPrediction::Edit { edits, .. } => {
7341 self.report_edit_prediction_event(
7342 active_edit_prediction.completion_id.clone(),
7343 true,
7344 cx,
7345 );
7346
7347 if let Some(provider) = self.edit_prediction_provider() {
7348 provider.accept(cx);
7349 }
7350
7351 // Store the transaction ID and selections before applying the edit
7352 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7353
7354 let snapshot = self.buffer.read(cx).snapshot(cx);
7355 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7356
7357 self.buffer.update(cx, |buffer, cx| {
7358 buffer.edit(edits.iter().cloned(), None, cx)
7359 });
7360
7361 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7362 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7363 });
7364
7365 let selections = self.selections.disjoint_anchors_arc();
7366 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7367 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7368 if has_new_transaction {
7369 self.selection_history
7370 .insert_transaction(transaction_id_now, selections);
7371 }
7372 }
7373
7374 self.update_visible_edit_prediction(window, cx);
7375 if self.active_edit_prediction.is_none() {
7376 self.refresh_edit_prediction(true, true, window, cx);
7377 }
7378
7379 cx.notify();
7380 }
7381 }
7382
7383 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7384 }
7385
7386 pub fn accept_partial_edit_prediction(
7387 &mut self,
7388 _: &AcceptPartialEditPrediction,
7389 window: &mut Window,
7390 cx: &mut Context<Self>,
7391 ) {
7392 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7393 return;
7394 };
7395 if self.selections.count() != 1 {
7396 return;
7397 }
7398
7399 match &active_edit_prediction.completion {
7400 EditPrediction::MoveWithin { target, .. } => {
7401 let target = *target;
7402 self.change_selections(
7403 SelectionEffects::scroll(Autoscroll::newest()),
7404 window,
7405 cx,
7406 |selections| {
7407 selections.select_anchor_ranges([target..target]);
7408 },
7409 );
7410 }
7411 EditPrediction::MoveOutside { snapshot, target } => {
7412 if let Some(workspace) = self.workspace() {
7413 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7414 .detach_and_log_err(cx);
7415 }
7416 }
7417 EditPrediction::Edit { edits, .. } => {
7418 self.report_edit_prediction_event(
7419 active_edit_prediction.completion_id.clone(),
7420 true,
7421 cx,
7422 );
7423
7424 // Find an insertion that starts at the cursor position.
7425 let snapshot = self.buffer.read(cx).snapshot(cx);
7426 let cursor_offset = self
7427 .selections
7428 .newest::<usize>(&self.display_snapshot(cx))
7429 .head();
7430 let insertion = edits.iter().find_map(|(range, text)| {
7431 let range = range.to_offset(&snapshot);
7432 if range.is_empty() && range.start == cursor_offset {
7433 Some(text)
7434 } else {
7435 None
7436 }
7437 });
7438
7439 if let Some(text) = insertion {
7440 let mut partial_completion = text
7441 .chars()
7442 .by_ref()
7443 .take_while(|c| c.is_alphabetic())
7444 .collect::<String>();
7445 if partial_completion.is_empty() {
7446 partial_completion = text
7447 .chars()
7448 .by_ref()
7449 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7450 .collect::<String>();
7451 }
7452
7453 cx.emit(EditorEvent::InputHandled {
7454 utf16_range_to_replace: None,
7455 text: partial_completion.clone().into(),
7456 });
7457
7458 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7459
7460 self.refresh_edit_prediction(true, true, window, cx);
7461 cx.notify();
7462 } else {
7463 self.accept_edit_prediction(&Default::default(), window, cx);
7464 }
7465 }
7466 }
7467 }
7468
7469 fn discard_edit_prediction(
7470 &mut self,
7471 should_report_edit_prediction_event: bool,
7472 cx: &mut Context<Self>,
7473 ) -> bool {
7474 if should_report_edit_prediction_event {
7475 let completion_id = self
7476 .active_edit_prediction
7477 .as_ref()
7478 .and_then(|active_completion| active_completion.completion_id.clone());
7479
7480 self.report_edit_prediction_event(completion_id, false, cx);
7481 }
7482
7483 if let Some(provider) = self.edit_prediction_provider() {
7484 provider.discard(cx);
7485 }
7486
7487 self.take_active_edit_prediction(cx)
7488 }
7489
7490 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7491 let Some(provider) = self.edit_prediction_provider() else {
7492 return;
7493 };
7494
7495 let Some((_, buffer, _)) = self
7496 .buffer
7497 .read(cx)
7498 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7499 else {
7500 return;
7501 };
7502
7503 let extension = buffer
7504 .read(cx)
7505 .file()
7506 .and_then(|file| Some(file.path().extension()?.to_string()));
7507
7508 let event_type = match accepted {
7509 true => "Edit Prediction Accepted",
7510 false => "Edit Prediction Discarded",
7511 };
7512 telemetry::event!(
7513 event_type,
7514 provider = provider.name(),
7515 prediction_id = id,
7516 suggestion_accepted = accepted,
7517 file_extension = extension,
7518 );
7519 }
7520
7521 fn open_editor_at_anchor(
7522 snapshot: &language::BufferSnapshot,
7523 target: language::Anchor,
7524 workspace: &Entity<Workspace>,
7525 window: &mut Window,
7526 cx: &mut App,
7527 ) -> Task<Result<()>> {
7528 workspace.update(cx, |workspace, cx| {
7529 let path = snapshot.file().map(|file| file.full_path(cx));
7530 let Some(path) =
7531 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7532 else {
7533 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7534 };
7535 let target = text::ToPoint::to_point(&target, snapshot);
7536 let item = workspace.open_path(path, None, true, window, cx);
7537 window.spawn(cx, async move |cx| {
7538 let Some(editor) = item.await?.downcast::<Editor>() else {
7539 return Ok(());
7540 };
7541 editor
7542 .update_in(cx, |editor, window, cx| {
7543 editor.go_to_singleton_buffer_point(target, window, cx);
7544 })
7545 .ok();
7546 anyhow::Ok(())
7547 })
7548 })
7549 }
7550
7551 pub fn has_active_edit_prediction(&self) -> bool {
7552 self.active_edit_prediction.is_some()
7553 }
7554
7555 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7556 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7557 return false;
7558 };
7559
7560 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7561 self.clear_highlights::<EditPredictionHighlight>(cx);
7562 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7563 true
7564 }
7565
7566 /// Returns true when we're displaying the edit prediction popover below the cursor
7567 /// like we are not previewing and the LSP autocomplete menu is visible
7568 /// or we are in `when_holding_modifier` mode.
7569 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7570 if self.edit_prediction_preview_is_active()
7571 || !self.show_edit_predictions_in_menu()
7572 || !self.edit_predictions_enabled()
7573 {
7574 return false;
7575 }
7576
7577 if self.has_visible_completions_menu() {
7578 return true;
7579 }
7580
7581 has_completion && self.edit_prediction_requires_modifier()
7582 }
7583
7584 fn handle_modifiers_changed(
7585 &mut self,
7586 modifiers: Modifiers,
7587 position_map: &PositionMap,
7588 window: &mut Window,
7589 cx: &mut Context<Self>,
7590 ) {
7591 // Ensure that the edit prediction preview is updated, even when not
7592 // enabled, if there's an active edit prediction preview.
7593 if self.show_edit_predictions_in_menu()
7594 || matches!(
7595 self.edit_prediction_preview,
7596 EditPredictionPreview::Active { .. }
7597 )
7598 {
7599 self.update_edit_prediction_preview(&modifiers, window, cx);
7600 }
7601
7602 self.update_selection_mode(&modifiers, position_map, window, cx);
7603
7604 let mouse_position = window.mouse_position();
7605 if !position_map.text_hitbox.is_hovered(window) {
7606 return;
7607 }
7608
7609 self.update_hovered_link(
7610 position_map.point_for_position(mouse_position),
7611 &position_map.snapshot,
7612 modifiers,
7613 window,
7614 cx,
7615 )
7616 }
7617
7618 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7619 match EditorSettings::get_global(cx).multi_cursor_modifier {
7620 MultiCursorModifier::Alt => modifiers.secondary(),
7621 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7622 }
7623 }
7624
7625 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7626 match EditorSettings::get_global(cx).multi_cursor_modifier {
7627 MultiCursorModifier::Alt => modifiers.alt,
7628 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7629 }
7630 }
7631
7632 fn columnar_selection_mode(
7633 modifiers: &Modifiers,
7634 cx: &mut Context<Self>,
7635 ) -> Option<ColumnarMode> {
7636 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7637 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7638 Some(ColumnarMode::FromMouse)
7639 } else if Self::is_alt_pressed(modifiers, cx) {
7640 Some(ColumnarMode::FromSelection)
7641 } else {
7642 None
7643 }
7644 } else {
7645 None
7646 }
7647 }
7648
7649 fn update_selection_mode(
7650 &mut self,
7651 modifiers: &Modifiers,
7652 position_map: &PositionMap,
7653 window: &mut Window,
7654 cx: &mut Context<Self>,
7655 ) {
7656 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7657 return;
7658 };
7659 if self.selections.pending_anchor().is_none() {
7660 return;
7661 }
7662
7663 let mouse_position = window.mouse_position();
7664 let point_for_position = position_map.point_for_position(mouse_position);
7665 let position = point_for_position.previous_valid;
7666
7667 self.select(
7668 SelectPhase::BeginColumnar {
7669 position,
7670 reset: false,
7671 mode,
7672 goal_column: point_for_position.exact_unclipped.column(),
7673 },
7674 window,
7675 cx,
7676 );
7677 }
7678
7679 fn update_edit_prediction_preview(
7680 &mut self,
7681 modifiers: &Modifiers,
7682 window: &mut Window,
7683 cx: &mut Context<Self>,
7684 ) {
7685 let mut modifiers_held = false;
7686 if let Some(accept_keystroke) = self
7687 .accept_edit_prediction_keybind(false, window, cx)
7688 .keystroke()
7689 {
7690 modifiers_held = modifiers_held
7691 || (accept_keystroke.modifiers() == modifiers
7692 && accept_keystroke.modifiers().modified());
7693 };
7694 if let Some(accept_partial_keystroke) = self
7695 .accept_edit_prediction_keybind(true, window, cx)
7696 .keystroke()
7697 {
7698 modifiers_held = modifiers_held
7699 || (accept_partial_keystroke.modifiers() == modifiers
7700 && accept_partial_keystroke.modifiers().modified());
7701 }
7702
7703 if modifiers_held {
7704 if matches!(
7705 self.edit_prediction_preview,
7706 EditPredictionPreview::Inactive { .. }
7707 ) {
7708 self.edit_prediction_preview = EditPredictionPreview::Active {
7709 previous_scroll_position: None,
7710 since: Instant::now(),
7711 };
7712
7713 self.update_visible_edit_prediction(window, cx);
7714 cx.notify();
7715 }
7716 } else if let EditPredictionPreview::Active {
7717 previous_scroll_position,
7718 since,
7719 } = self.edit_prediction_preview
7720 {
7721 if let (Some(previous_scroll_position), Some(position_map)) =
7722 (previous_scroll_position, self.last_position_map.as_ref())
7723 {
7724 self.set_scroll_position(
7725 previous_scroll_position
7726 .scroll_position(&position_map.snapshot.display_snapshot),
7727 window,
7728 cx,
7729 );
7730 }
7731
7732 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7733 released_too_fast: since.elapsed() < Duration::from_millis(200),
7734 };
7735 self.clear_row_highlights::<EditPredictionPreview>();
7736 self.update_visible_edit_prediction(window, cx);
7737 cx.notify();
7738 }
7739 }
7740
7741 fn update_visible_edit_prediction(
7742 &mut self,
7743 _window: &mut Window,
7744 cx: &mut Context<Self>,
7745 ) -> Option<()> {
7746 if DisableAiSettings::get_global(cx).disable_ai {
7747 return None;
7748 }
7749
7750 if self.ime_transaction.is_some() {
7751 self.discard_edit_prediction(false, cx);
7752 return None;
7753 }
7754
7755 let selection = self.selections.newest_anchor();
7756 let cursor = selection.head();
7757 let multibuffer = self.buffer.read(cx).snapshot(cx);
7758 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7759 let excerpt_id = cursor.excerpt_id;
7760
7761 let show_in_menu = self.show_edit_predictions_in_menu();
7762 let completions_menu_has_precedence = !show_in_menu
7763 && (self.context_menu.borrow().is_some()
7764 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7765
7766 if completions_menu_has_precedence
7767 || !offset_selection.is_empty()
7768 || self
7769 .active_edit_prediction
7770 .as_ref()
7771 .is_some_and(|completion| {
7772 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7773 return false;
7774 };
7775 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7776 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7777 !invalidation_range.contains(&offset_selection.head())
7778 })
7779 {
7780 self.discard_edit_prediction(false, cx);
7781 return None;
7782 }
7783
7784 self.take_active_edit_prediction(cx);
7785 let Some(provider) = self.edit_prediction_provider() else {
7786 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7787 return None;
7788 };
7789
7790 let (buffer, cursor_buffer_position) =
7791 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7792
7793 self.edit_prediction_settings =
7794 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7795
7796 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7797
7798 if self.edit_prediction_indent_conflict {
7799 let cursor_point = cursor.to_point(&multibuffer);
7800
7801 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7802
7803 if let Some((_, indent)) = indents.iter().next()
7804 && indent.len == cursor_point.column
7805 {
7806 self.edit_prediction_indent_conflict = false;
7807 }
7808 }
7809
7810 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7811
7812 let (completion_id, edits, edit_preview) = match edit_prediction {
7813 edit_prediction::EditPrediction::Local {
7814 id,
7815 edits,
7816 edit_preview,
7817 } => (id, edits, edit_preview),
7818 edit_prediction::EditPrediction::Jump {
7819 id,
7820 snapshot,
7821 target,
7822 } => {
7823 self.stale_edit_prediction_in_menu = None;
7824 self.active_edit_prediction = Some(EditPredictionState {
7825 inlay_ids: vec![],
7826 completion: EditPrediction::MoveOutside { snapshot, target },
7827 completion_id: id,
7828 invalidation_range: None,
7829 });
7830 cx.notify();
7831 return Some(());
7832 }
7833 };
7834
7835 let edits = edits
7836 .into_iter()
7837 .flat_map(|(range, new_text)| {
7838 Some((
7839 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7840 new_text,
7841 ))
7842 })
7843 .collect::<Vec<_>>();
7844 if edits.is_empty() {
7845 return None;
7846 }
7847
7848 let first_edit_start = edits.first().unwrap().0.start;
7849 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7850 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7851
7852 let last_edit_end = edits.last().unwrap().0.end;
7853 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7854 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7855
7856 let cursor_row = cursor.to_point(&multibuffer).row;
7857
7858 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7859
7860 let mut inlay_ids = Vec::new();
7861 let invalidation_row_range;
7862 let move_invalidation_row_range = if cursor_row < edit_start_row {
7863 Some(cursor_row..edit_end_row)
7864 } else if cursor_row > edit_end_row {
7865 Some(edit_start_row..cursor_row)
7866 } else {
7867 None
7868 };
7869 let supports_jump = self
7870 .edit_prediction_provider
7871 .as_ref()
7872 .map(|provider| provider.provider.supports_jump_to_edit())
7873 .unwrap_or(true);
7874
7875 let is_move = supports_jump
7876 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7877 let completion = if is_move {
7878 invalidation_row_range =
7879 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7880 let target = first_edit_start;
7881 EditPrediction::MoveWithin { target, snapshot }
7882 } else {
7883 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7884 && !self.edit_predictions_hidden_for_vim_mode;
7885
7886 if show_completions_in_buffer {
7887 if edits
7888 .iter()
7889 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7890 {
7891 let mut inlays = Vec::new();
7892 for (range, new_text) in &edits {
7893 let inlay = Inlay::edit_prediction(
7894 post_inc(&mut self.next_inlay_id),
7895 range.start,
7896 new_text.as_str(),
7897 );
7898 inlay_ids.push(inlay.id);
7899 inlays.push(inlay);
7900 }
7901
7902 self.splice_inlays(&[], inlays, cx);
7903 } else {
7904 let background_color = cx.theme().status().deleted_background;
7905 self.highlight_text::<EditPredictionHighlight>(
7906 edits.iter().map(|(range, _)| range.clone()).collect(),
7907 HighlightStyle {
7908 background_color: Some(background_color),
7909 ..Default::default()
7910 },
7911 cx,
7912 );
7913 }
7914 }
7915
7916 invalidation_row_range = edit_start_row..edit_end_row;
7917
7918 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7919 if provider.show_tab_accept_marker() {
7920 EditDisplayMode::TabAccept
7921 } else {
7922 EditDisplayMode::Inline
7923 }
7924 } else {
7925 EditDisplayMode::DiffPopover
7926 };
7927
7928 EditPrediction::Edit {
7929 edits,
7930 edit_preview,
7931 display_mode,
7932 snapshot,
7933 }
7934 };
7935
7936 let invalidation_range = multibuffer
7937 .anchor_before(Point::new(invalidation_row_range.start, 0))
7938 ..multibuffer.anchor_after(Point::new(
7939 invalidation_row_range.end,
7940 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7941 ));
7942
7943 self.stale_edit_prediction_in_menu = None;
7944 self.active_edit_prediction = Some(EditPredictionState {
7945 inlay_ids,
7946 completion,
7947 completion_id,
7948 invalidation_range: Some(invalidation_range),
7949 });
7950
7951 cx.notify();
7952
7953 Some(())
7954 }
7955
7956 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7957 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7958 }
7959
7960 fn clear_tasks(&mut self) {
7961 self.tasks.clear()
7962 }
7963
7964 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7965 if self.tasks.insert(key, value).is_some() {
7966 // This case should hopefully be rare, but just in case...
7967 log::error!(
7968 "multiple different run targets found on a single line, only the last target will be rendered"
7969 )
7970 }
7971 }
7972
7973 /// Get all display points of breakpoints that will be rendered within editor
7974 ///
7975 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7976 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7977 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7978 fn active_breakpoints(
7979 &self,
7980 range: Range<DisplayRow>,
7981 window: &mut Window,
7982 cx: &mut Context<Self>,
7983 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7984 let mut breakpoint_display_points = HashMap::default();
7985
7986 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7987 return breakpoint_display_points;
7988 };
7989
7990 let snapshot = self.snapshot(window, cx);
7991
7992 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7993 let Some(project) = self.project() else {
7994 return breakpoint_display_points;
7995 };
7996
7997 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7998 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7999
8000 for (buffer_snapshot, range, excerpt_id) in
8001 multi_buffer_snapshot.range_to_buffer_ranges(range)
8002 {
8003 let Some(buffer) = project
8004 .read(cx)
8005 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8006 else {
8007 continue;
8008 };
8009 let breakpoints = breakpoint_store.read(cx).breakpoints(
8010 &buffer,
8011 Some(
8012 buffer_snapshot.anchor_before(range.start)
8013 ..buffer_snapshot.anchor_after(range.end),
8014 ),
8015 buffer_snapshot,
8016 cx,
8017 );
8018 for (breakpoint, state) in breakpoints {
8019 let multi_buffer_anchor =
8020 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8021 let position = multi_buffer_anchor
8022 .to_point(&multi_buffer_snapshot)
8023 .to_display_point(&snapshot);
8024
8025 breakpoint_display_points.insert(
8026 position.row(),
8027 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8028 );
8029 }
8030 }
8031
8032 breakpoint_display_points
8033 }
8034
8035 fn breakpoint_context_menu(
8036 &self,
8037 anchor: Anchor,
8038 window: &mut Window,
8039 cx: &mut Context<Self>,
8040 ) -> Entity<ui::ContextMenu> {
8041 let weak_editor = cx.weak_entity();
8042 let focus_handle = self.focus_handle(cx);
8043
8044 let row = self
8045 .buffer
8046 .read(cx)
8047 .snapshot(cx)
8048 .summary_for_anchor::<Point>(&anchor)
8049 .row;
8050
8051 let breakpoint = self
8052 .breakpoint_at_row(row, window, cx)
8053 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8054
8055 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8056 "Edit Log Breakpoint"
8057 } else {
8058 "Set Log Breakpoint"
8059 };
8060
8061 let condition_breakpoint_msg = if breakpoint
8062 .as_ref()
8063 .is_some_and(|bp| bp.1.condition.is_some())
8064 {
8065 "Edit Condition Breakpoint"
8066 } else {
8067 "Set Condition Breakpoint"
8068 };
8069
8070 let hit_condition_breakpoint_msg = if breakpoint
8071 .as_ref()
8072 .is_some_and(|bp| bp.1.hit_condition.is_some())
8073 {
8074 "Edit Hit Condition Breakpoint"
8075 } else {
8076 "Set Hit Condition Breakpoint"
8077 };
8078
8079 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8080 "Unset Breakpoint"
8081 } else {
8082 "Set Breakpoint"
8083 };
8084
8085 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8086
8087 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8088 BreakpointState::Enabled => Some("Disable"),
8089 BreakpointState::Disabled => Some("Enable"),
8090 });
8091
8092 let (anchor, breakpoint) =
8093 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8094
8095 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8096 menu.on_blur_subscription(Subscription::new(|| {}))
8097 .context(focus_handle)
8098 .when(run_to_cursor, |this| {
8099 let weak_editor = weak_editor.clone();
8100 this.entry("Run to cursor", None, move |window, cx| {
8101 weak_editor
8102 .update(cx, |editor, cx| {
8103 editor.change_selections(
8104 SelectionEffects::no_scroll(),
8105 window,
8106 cx,
8107 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8108 );
8109 })
8110 .ok();
8111
8112 window.dispatch_action(Box::new(RunToCursor), cx);
8113 })
8114 .separator()
8115 })
8116 .when_some(toggle_state_msg, |this, msg| {
8117 this.entry(msg, None, {
8118 let weak_editor = weak_editor.clone();
8119 let breakpoint = breakpoint.clone();
8120 move |_window, cx| {
8121 weak_editor
8122 .update(cx, |this, cx| {
8123 this.edit_breakpoint_at_anchor(
8124 anchor,
8125 breakpoint.as_ref().clone(),
8126 BreakpointEditAction::InvertState,
8127 cx,
8128 );
8129 })
8130 .log_err();
8131 }
8132 })
8133 })
8134 .entry(set_breakpoint_msg, None, {
8135 let weak_editor = weak_editor.clone();
8136 let breakpoint = breakpoint.clone();
8137 move |_window, cx| {
8138 weak_editor
8139 .update(cx, |this, cx| {
8140 this.edit_breakpoint_at_anchor(
8141 anchor,
8142 breakpoint.as_ref().clone(),
8143 BreakpointEditAction::Toggle,
8144 cx,
8145 );
8146 })
8147 .log_err();
8148 }
8149 })
8150 .entry(log_breakpoint_msg, None, {
8151 let breakpoint = breakpoint.clone();
8152 let weak_editor = weak_editor.clone();
8153 move |window, cx| {
8154 weak_editor
8155 .update(cx, |this, cx| {
8156 this.add_edit_breakpoint_block(
8157 anchor,
8158 breakpoint.as_ref(),
8159 BreakpointPromptEditAction::Log,
8160 window,
8161 cx,
8162 );
8163 })
8164 .log_err();
8165 }
8166 })
8167 .entry(condition_breakpoint_msg, None, {
8168 let breakpoint = breakpoint.clone();
8169 let weak_editor = weak_editor.clone();
8170 move |window, cx| {
8171 weak_editor
8172 .update(cx, |this, cx| {
8173 this.add_edit_breakpoint_block(
8174 anchor,
8175 breakpoint.as_ref(),
8176 BreakpointPromptEditAction::Condition,
8177 window,
8178 cx,
8179 );
8180 })
8181 .log_err();
8182 }
8183 })
8184 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8185 weak_editor
8186 .update(cx, |this, cx| {
8187 this.add_edit_breakpoint_block(
8188 anchor,
8189 breakpoint.as_ref(),
8190 BreakpointPromptEditAction::HitCondition,
8191 window,
8192 cx,
8193 );
8194 })
8195 .log_err();
8196 })
8197 })
8198 }
8199
8200 fn render_breakpoint(
8201 &self,
8202 position: Anchor,
8203 row: DisplayRow,
8204 breakpoint: &Breakpoint,
8205 state: Option<BreakpointSessionState>,
8206 cx: &mut Context<Self>,
8207 ) -> IconButton {
8208 let is_rejected = state.is_some_and(|s| !s.verified);
8209 // Is it a breakpoint that shows up when hovering over gutter?
8210 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8211 (false, false),
8212 |PhantomBreakpointIndicator {
8213 is_active,
8214 display_row,
8215 collides_with_existing_breakpoint,
8216 }| {
8217 (
8218 is_active && display_row == row,
8219 collides_with_existing_breakpoint,
8220 )
8221 },
8222 );
8223
8224 let (color, icon) = {
8225 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8226 (false, false) => ui::IconName::DebugBreakpoint,
8227 (true, false) => ui::IconName::DebugLogBreakpoint,
8228 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8229 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8230 };
8231
8232 let color = if is_phantom {
8233 Color::Hint
8234 } else if is_rejected {
8235 Color::Disabled
8236 } else {
8237 Color::Debugger
8238 };
8239
8240 (color, icon)
8241 };
8242
8243 let breakpoint = Arc::from(breakpoint.clone());
8244
8245 let alt_as_text = gpui::Keystroke {
8246 modifiers: Modifiers::secondary_key(),
8247 ..Default::default()
8248 };
8249 let primary_action_text = if breakpoint.is_disabled() {
8250 "Enable breakpoint"
8251 } else if is_phantom && !collides_with_existing {
8252 "Set breakpoint"
8253 } else {
8254 "Unset breakpoint"
8255 };
8256 let focus_handle = self.focus_handle.clone();
8257
8258 let meta = if is_rejected {
8259 SharedString::from("No executable code is associated with this line.")
8260 } else if collides_with_existing && !breakpoint.is_disabled() {
8261 SharedString::from(format!(
8262 "{alt_as_text}-click to disable,\nright-click for more options."
8263 ))
8264 } else {
8265 SharedString::from("Right-click for more options.")
8266 };
8267 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8268 .icon_size(IconSize::XSmall)
8269 .size(ui::ButtonSize::None)
8270 .when(is_rejected, |this| {
8271 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8272 })
8273 .icon_color(color)
8274 .style(ButtonStyle::Transparent)
8275 .on_click(cx.listener({
8276 move |editor, event: &ClickEvent, window, cx| {
8277 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8278 BreakpointEditAction::InvertState
8279 } else {
8280 BreakpointEditAction::Toggle
8281 };
8282
8283 window.focus(&editor.focus_handle(cx));
8284 editor.edit_breakpoint_at_anchor(
8285 position,
8286 breakpoint.as_ref().clone(),
8287 edit_action,
8288 cx,
8289 );
8290 }
8291 }))
8292 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8293 editor.set_breakpoint_context_menu(
8294 row,
8295 Some(position),
8296 event.position(),
8297 window,
8298 cx,
8299 );
8300 }))
8301 .tooltip(move |_window, cx| {
8302 Tooltip::with_meta_in(
8303 primary_action_text,
8304 Some(&ToggleBreakpoint),
8305 meta.clone(),
8306 &focus_handle,
8307 cx,
8308 )
8309 })
8310 }
8311
8312 fn build_tasks_context(
8313 project: &Entity<Project>,
8314 buffer: &Entity<Buffer>,
8315 buffer_row: u32,
8316 tasks: &Arc<RunnableTasks>,
8317 cx: &mut Context<Self>,
8318 ) -> Task<Option<task::TaskContext>> {
8319 let position = Point::new(buffer_row, tasks.column);
8320 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8321 let location = Location {
8322 buffer: buffer.clone(),
8323 range: range_start..range_start,
8324 };
8325 // Fill in the environmental variables from the tree-sitter captures
8326 let mut captured_task_variables = TaskVariables::default();
8327 for (capture_name, value) in tasks.extra_variables.clone() {
8328 captured_task_variables.insert(
8329 task::VariableName::Custom(capture_name.into()),
8330 value.clone(),
8331 );
8332 }
8333 project.update(cx, |project, cx| {
8334 project.task_store().update(cx, |task_store, cx| {
8335 task_store.task_context_for_location(captured_task_variables, location, cx)
8336 })
8337 })
8338 }
8339
8340 pub fn spawn_nearest_task(
8341 &mut self,
8342 action: &SpawnNearestTask,
8343 window: &mut Window,
8344 cx: &mut Context<Self>,
8345 ) {
8346 let Some((workspace, _)) = self.workspace.clone() else {
8347 return;
8348 };
8349 let Some(project) = self.project.clone() else {
8350 return;
8351 };
8352
8353 // Try to find a closest, enclosing node using tree-sitter that has a task
8354 let Some((buffer, buffer_row, tasks)) = self
8355 .find_enclosing_node_task(cx)
8356 // Or find the task that's closest in row-distance.
8357 .or_else(|| self.find_closest_task(cx))
8358 else {
8359 return;
8360 };
8361
8362 let reveal_strategy = action.reveal;
8363 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8364 cx.spawn_in(window, async move |_, cx| {
8365 let context = task_context.await?;
8366 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8367
8368 let resolved = &mut resolved_task.resolved;
8369 resolved.reveal = reveal_strategy;
8370
8371 workspace
8372 .update_in(cx, |workspace, window, cx| {
8373 workspace.schedule_resolved_task(
8374 task_source_kind,
8375 resolved_task,
8376 false,
8377 window,
8378 cx,
8379 );
8380 })
8381 .ok()
8382 })
8383 .detach();
8384 }
8385
8386 fn find_closest_task(
8387 &mut self,
8388 cx: &mut Context<Self>,
8389 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8390 let cursor_row = self
8391 .selections
8392 .newest_adjusted(&self.display_snapshot(cx))
8393 .head()
8394 .row;
8395
8396 let ((buffer_id, row), tasks) = self
8397 .tasks
8398 .iter()
8399 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8400
8401 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8402 let tasks = Arc::new(tasks.to_owned());
8403 Some((buffer, *row, tasks))
8404 }
8405
8406 fn find_enclosing_node_task(
8407 &mut self,
8408 cx: &mut Context<Self>,
8409 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8410 let snapshot = self.buffer.read(cx).snapshot(cx);
8411 let offset = self
8412 .selections
8413 .newest::<usize>(&self.display_snapshot(cx))
8414 .head();
8415 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8416 let buffer_id = excerpt.buffer().remote_id();
8417
8418 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8419 let mut cursor = layer.node().walk();
8420
8421 while cursor.goto_first_child_for_byte(offset).is_some() {
8422 if cursor.node().end_byte() == offset {
8423 cursor.goto_next_sibling();
8424 }
8425 }
8426
8427 // Ascend to the smallest ancestor that contains the range and has a task.
8428 loop {
8429 let node = cursor.node();
8430 let node_range = node.byte_range();
8431 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8432
8433 // Check if this node contains our offset
8434 if node_range.start <= offset && node_range.end >= offset {
8435 // If it contains offset, check for task
8436 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8437 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8438 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8439 }
8440 }
8441
8442 if !cursor.goto_parent() {
8443 break;
8444 }
8445 }
8446 None
8447 }
8448
8449 fn render_run_indicator(
8450 &self,
8451 _style: &EditorStyle,
8452 is_active: bool,
8453 row: DisplayRow,
8454 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8455 cx: &mut Context<Self>,
8456 ) -> IconButton {
8457 let color = Color::Muted;
8458 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8459
8460 IconButton::new(
8461 ("run_indicator", row.0 as usize),
8462 ui::IconName::PlayOutlined,
8463 )
8464 .shape(ui::IconButtonShape::Square)
8465 .icon_size(IconSize::XSmall)
8466 .icon_color(color)
8467 .toggle_state(is_active)
8468 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8469 let quick_launch = match e {
8470 ClickEvent::Keyboard(_) => true,
8471 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8472 };
8473
8474 window.focus(&editor.focus_handle(cx));
8475 editor.toggle_code_actions(
8476 &ToggleCodeActions {
8477 deployed_from: Some(CodeActionSource::RunMenu(row)),
8478 quick_launch,
8479 },
8480 window,
8481 cx,
8482 );
8483 }))
8484 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8485 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8486 }))
8487 }
8488
8489 pub fn context_menu_visible(&self) -> bool {
8490 !self.edit_prediction_preview_is_active()
8491 && self
8492 .context_menu
8493 .borrow()
8494 .as_ref()
8495 .is_some_and(|menu| menu.visible())
8496 }
8497
8498 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8499 self.context_menu
8500 .borrow()
8501 .as_ref()
8502 .map(|menu| menu.origin())
8503 }
8504
8505 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8506 self.context_menu_options = Some(options);
8507 }
8508
8509 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8510 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8511
8512 fn render_edit_prediction_popover(
8513 &mut self,
8514 text_bounds: &Bounds<Pixels>,
8515 content_origin: gpui::Point<Pixels>,
8516 right_margin: Pixels,
8517 editor_snapshot: &EditorSnapshot,
8518 visible_row_range: Range<DisplayRow>,
8519 scroll_top: ScrollOffset,
8520 scroll_bottom: ScrollOffset,
8521 line_layouts: &[LineWithInvisibles],
8522 line_height: Pixels,
8523 scroll_position: gpui::Point<ScrollOffset>,
8524 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8525 newest_selection_head: Option<DisplayPoint>,
8526 editor_width: Pixels,
8527 style: &EditorStyle,
8528 window: &mut Window,
8529 cx: &mut App,
8530 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8531 if self.mode().is_minimap() {
8532 return None;
8533 }
8534 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8535
8536 if self.edit_prediction_visible_in_cursor_popover(true) {
8537 return None;
8538 }
8539
8540 match &active_edit_prediction.completion {
8541 EditPrediction::MoveWithin { target, .. } => {
8542 let target_display_point = target.to_display_point(editor_snapshot);
8543
8544 if self.edit_prediction_requires_modifier() {
8545 if !self.edit_prediction_preview_is_active() {
8546 return None;
8547 }
8548
8549 self.render_edit_prediction_modifier_jump_popover(
8550 text_bounds,
8551 content_origin,
8552 visible_row_range,
8553 line_layouts,
8554 line_height,
8555 scroll_pixel_position,
8556 newest_selection_head,
8557 target_display_point,
8558 window,
8559 cx,
8560 )
8561 } else {
8562 self.render_edit_prediction_eager_jump_popover(
8563 text_bounds,
8564 content_origin,
8565 editor_snapshot,
8566 visible_row_range,
8567 scroll_top,
8568 scroll_bottom,
8569 line_height,
8570 scroll_pixel_position,
8571 target_display_point,
8572 editor_width,
8573 window,
8574 cx,
8575 )
8576 }
8577 }
8578 EditPrediction::Edit {
8579 display_mode: EditDisplayMode::Inline,
8580 ..
8581 } => None,
8582 EditPrediction::Edit {
8583 display_mode: EditDisplayMode::TabAccept,
8584 edits,
8585 ..
8586 } => {
8587 let range = &edits.first()?.0;
8588 let target_display_point = range.end.to_display_point(editor_snapshot);
8589
8590 self.render_edit_prediction_end_of_line_popover(
8591 "Accept",
8592 editor_snapshot,
8593 visible_row_range,
8594 target_display_point,
8595 line_height,
8596 scroll_pixel_position,
8597 content_origin,
8598 editor_width,
8599 window,
8600 cx,
8601 )
8602 }
8603 EditPrediction::Edit {
8604 edits,
8605 edit_preview,
8606 display_mode: EditDisplayMode::DiffPopover,
8607 snapshot,
8608 } => self.render_edit_prediction_diff_popover(
8609 text_bounds,
8610 content_origin,
8611 right_margin,
8612 editor_snapshot,
8613 visible_row_range,
8614 line_layouts,
8615 line_height,
8616 scroll_position,
8617 scroll_pixel_position,
8618 newest_selection_head,
8619 editor_width,
8620 style,
8621 edits,
8622 edit_preview,
8623 snapshot,
8624 window,
8625 cx,
8626 ),
8627 EditPrediction::MoveOutside { snapshot, .. } => {
8628 let file_name = snapshot
8629 .file()
8630 .map(|file| file.file_name(cx))
8631 .unwrap_or("untitled");
8632 let mut element = self
8633 .render_edit_prediction_line_popover(
8634 format!("Jump to {file_name}"),
8635 Some(IconName::ZedPredict),
8636 window,
8637 cx,
8638 )
8639 .into_any();
8640
8641 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8642 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8643 let origin_y = text_bounds.size.height - size.height - px(30.);
8644 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8645 element.prepaint_at(origin, window, cx);
8646
8647 Some((element, origin))
8648 }
8649 }
8650 }
8651
8652 fn render_edit_prediction_modifier_jump_popover(
8653 &mut self,
8654 text_bounds: &Bounds<Pixels>,
8655 content_origin: gpui::Point<Pixels>,
8656 visible_row_range: Range<DisplayRow>,
8657 line_layouts: &[LineWithInvisibles],
8658 line_height: Pixels,
8659 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8660 newest_selection_head: Option<DisplayPoint>,
8661 target_display_point: DisplayPoint,
8662 window: &mut Window,
8663 cx: &mut App,
8664 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8665 let scrolled_content_origin =
8666 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8667
8668 const SCROLL_PADDING_Y: Pixels = px(12.);
8669
8670 if target_display_point.row() < visible_row_range.start {
8671 return self.render_edit_prediction_scroll_popover(
8672 |_| SCROLL_PADDING_Y,
8673 IconName::ArrowUp,
8674 visible_row_range,
8675 line_layouts,
8676 newest_selection_head,
8677 scrolled_content_origin,
8678 window,
8679 cx,
8680 );
8681 } else if target_display_point.row() >= visible_row_range.end {
8682 return self.render_edit_prediction_scroll_popover(
8683 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8684 IconName::ArrowDown,
8685 visible_row_range,
8686 line_layouts,
8687 newest_selection_head,
8688 scrolled_content_origin,
8689 window,
8690 cx,
8691 );
8692 }
8693
8694 const POLE_WIDTH: Pixels = px(2.);
8695
8696 let line_layout =
8697 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8698 let target_column = target_display_point.column() as usize;
8699
8700 let target_x = line_layout.x_for_index(target_column);
8701 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8702 - scroll_pixel_position.y;
8703
8704 let flag_on_right = target_x < text_bounds.size.width / 2.;
8705
8706 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8707 border_color.l += 0.001;
8708
8709 let mut element = v_flex()
8710 .items_end()
8711 .when(flag_on_right, |el| el.items_start())
8712 .child(if flag_on_right {
8713 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8714 .rounded_bl(px(0.))
8715 .rounded_tl(px(0.))
8716 .border_l_2()
8717 .border_color(border_color)
8718 } else {
8719 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8720 .rounded_br(px(0.))
8721 .rounded_tr(px(0.))
8722 .border_r_2()
8723 .border_color(border_color)
8724 })
8725 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8726 .into_any();
8727
8728 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8729
8730 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8731 - point(
8732 if flag_on_right {
8733 POLE_WIDTH
8734 } else {
8735 size.width - POLE_WIDTH
8736 },
8737 size.height - line_height,
8738 );
8739
8740 origin.x = origin.x.max(content_origin.x);
8741
8742 element.prepaint_at(origin, window, cx);
8743
8744 Some((element, origin))
8745 }
8746
8747 fn render_edit_prediction_scroll_popover(
8748 &mut self,
8749 to_y: impl Fn(Size<Pixels>) -> Pixels,
8750 scroll_icon: IconName,
8751 visible_row_range: Range<DisplayRow>,
8752 line_layouts: &[LineWithInvisibles],
8753 newest_selection_head: Option<DisplayPoint>,
8754 scrolled_content_origin: gpui::Point<Pixels>,
8755 window: &mut Window,
8756 cx: &mut App,
8757 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8758 let mut element = self
8759 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8760 .into_any();
8761
8762 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8763
8764 let cursor = newest_selection_head?;
8765 let cursor_row_layout =
8766 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8767 let cursor_column = cursor.column() as usize;
8768
8769 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8770
8771 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8772
8773 element.prepaint_at(origin, window, cx);
8774 Some((element, origin))
8775 }
8776
8777 fn render_edit_prediction_eager_jump_popover(
8778 &mut self,
8779 text_bounds: &Bounds<Pixels>,
8780 content_origin: gpui::Point<Pixels>,
8781 editor_snapshot: &EditorSnapshot,
8782 visible_row_range: Range<DisplayRow>,
8783 scroll_top: ScrollOffset,
8784 scroll_bottom: ScrollOffset,
8785 line_height: Pixels,
8786 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8787 target_display_point: DisplayPoint,
8788 editor_width: Pixels,
8789 window: &mut Window,
8790 cx: &mut App,
8791 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8792 if target_display_point.row().as_f64() < scroll_top {
8793 let mut element = self
8794 .render_edit_prediction_line_popover(
8795 "Jump to Edit",
8796 Some(IconName::ArrowUp),
8797 window,
8798 cx,
8799 )
8800 .into_any();
8801
8802 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8803 let offset = point(
8804 (text_bounds.size.width - size.width) / 2.,
8805 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8806 );
8807
8808 let origin = text_bounds.origin + offset;
8809 element.prepaint_at(origin, window, cx);
8810 Some((element, origin))
8811 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8812 let mut element = self
8813 .render_edit_prediction_line_popover(
8814 "Jump to Edit",
8815 Some(IconName::ArrowDown),
8816 window,
8817 cx,
8818 )
8819 .into_any();
8820
8821 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8822 let offset = point(
8823 (text_bounds.size.width - size.width) / 2.,
8824 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8825 );
8826
8827 let origin = text_bounds.origin + offset;
8828 element.prepaint_at(origin, window, cx);
8829 Some((element, origin))
8830 } else {
8831 self.render_edit_prediction_end_of_line_popover(
8832 "Jump to Edit",
8833 editor_snapshot,
8834 visible_row_range,
8835 target_display_point,
8836 line_height,
8837 scroll_pixel_position,
8838 content_origin,
8839 editor_width,
8840 window,
8841 cx,
8842 )
8843 }
8844 }
8845
8846 fn render_edit_prediction_end_of_line_popover(
8847 self: &mut Editor,
8848 label: &'static str,
8849 editor_snapshot: &EditorSnapshot,
8850 visible_row_range: Range<DisplayRow>,
8851 target_display_point: DisplayPoint,
8852 line_height: Pixels,
8853 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8854 content_origin: gpui::Point<Pixels>,
8855 editor_width: Pixels,
8856 window: &mut Window,
8857 cx: &mut App,
8858 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8859 let target_line_end = DisplayPoint::new(
8860 target_display_point.row(),
8861 editor_snapshot.line_len(target_display_point.row()),
8862 );
8863
8864 let mut element = self
8865 .render_edit_prediction_line_popover(label, None, window, cx)
8866 .into_any();
8867
8868 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8869
8870 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8871
8872 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8873 let mut origin = start_point
8874 + line_origin
8875 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8876 origin.x = origin.x.max(content_origin.x);
8877
8878 let max_x = content_origin.x + editor_width - size.width;
8879
8880 if origin.x > max_x {
8881 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8882
8883 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8884 origin.y += offset;
8885 IconName::ArrowUp
8886 } else {
8887 origin.y -= offset;
8888 IconName::ArrowDown
8889 };
8890
8891 element = self
8892 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8893 .into_any();
8894
8895 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8896
8897 origin.x = content_origin.x + editor_width - size.width - px(2.);
8898 }
8899
8900 element.prepaint_at(origin, window, cx);
8901 Some((element, origin))
8902 }
8903
8904 fn render_edit_prediction_diff_popover(
8905 self: &Editor,
8906 text_bounds: &Bounds<Pixels>,
8907 content_origin: gpui::Point<Pixels>,
8908 right_margin: Pixels,
8909 editor_snapshot: &EditorSnapshot,
8910 visible_row_range: Range<DisplayRow>,
8911 line_layouts: &[LineWithInvisibles],
8912 line_height: Pixels,
8913 scroll_position: gpui::Point<ScrollOffset>,
8914 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8915 newest_selection_head: Option<DisplayPoint>,
8916 editor_width: Pixels,
8917 style: &EditorStyle,
8918 edits: &Vec<(Range<Anchor>, String)>,
8919 edit_preview: &Option<language::EditPreview>,
8920 snapshot: &language::BufferSnapshot,
8921 window: &mut Window,
8922 cx: &mut App,
8923 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8924 let edit_start = edits
8925 .first()
8926 .unwrap()
8927 .0
8928 .start
8929 .to_display_point(editor_snapshot);
8930 let edit_end = edits
8931 .last()
8932 .unwrap()
8933 .0
8934 .end
8935 .to_display_point(editor_snapshot);
8936
8937 let is_visible = visible_row_range.contains(&edit_start.row())
8938 || visible_row_range.contains(&edit_end.row());
8939 if !is_visible {
8940 return None;
8941 }
8942
8943 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8944 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8945 } else {
8946 // Fallback for providers without edit_preview
8947 crate::edit_prediction_fallback_text(edits, cx)
8948 };
8949
8950 let styled_text = highlighted_edits.to_styled_text(&style.text);
8951 let line_count = highlighted_edits.text.lines().count();
8952
8953 const BORDER_WIDTH: Pixels = px(1.);
8954
8955 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8956 let has_keybind = keybind.is_some();
8957
8958 let mut element = h_flex()
8959 .items_start()
8960 .child(
8961 h_flex()
8962 .bg(cx.theme().colors().editor_background)
8963 .border(BORDER_WIDTH)
8964 .shadow_xs()
8965 .border_color(cx.theme().colors().border)
8966 .rounded_l_lg()
8967 .when(line_count > 1, |el| el.rounded_br_lg())
8968 .pr_1()
8969 .child(styled_text),
8970 )
8971 .child(
8972 h_flex()
8973 .h(line_height + BORDER_WIDTH * 2.)
8974 .px_1p5()
8975 .gap_1()
8976 // Workaround: For some reason, there's a gap if we don't do this
8977 .ml(-BORDER_WIDTH)
8978 .shadow(vec![gpui::BoxShadow {
8979 color: gpui::black().opacity(0.05),
8980 offset: point(px(1.), px(1.)),
8981 blur_radius: px(2.),
8982 spread_radius: px(0.),
8983 }])
8984 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8985 .border(BORDER_WIDTH)
8986 .border_color(cx.theme().colors().border)
8987 .rounded_r_lg()
8988 .id("edit_prediction_diff_popover_keybind")
8989 .when(!has_keybind, |el| {
8990 let status_colors = cx.theme().status();
8991
8992 el.bg(status_colors.error_background)
8993 .border_color(status_colors.error.opacity(0.6))
8994 .child(Icon::new(IconName::Info).color(Color::Error))
8995 .cursor_default()
8996 .hoverable_tooltip(move |_window, cx| {
8997 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8998 })
8999 })
9000 .children(keybind),
9001 )
9002 .into_any();
9003
9004 let longest_row =
9005 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9006 let longest_line_width = if visible_row_range.contains(&longest_row) {
9007 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9008 } else {
9009 layout_line(
9010 longest_row,
9011 editor_snapshot,
9012 style,
9013 editor_width,
9014 |_| false,
9015 window,
9016 cx,
9017 )
9018 .width
9019 };
9020
9021 let viewport_bounds =
9022 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9023 right: -right_margin,
9024 ..Default::default()
9025 });
9026
9027 let x_after_longest = Pixels::from(
9028 ScrollPixelOffset::from(
9029 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9030 ) - scroll_pixel_position.x,
9031 );
9032
9033 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9034
9035 // Fully visible if it can be displayed within the window (allow overlapping other
9036 // panes). However, this is only allowed if the popover starts within text_bounds.
9037 let can_position_to_the_right = x_after_longest < text_bounds.right()
9038 && x_after_longest + element_bounds.width < viewport_bounds.right();
9039
9040 let mut origin = if can_position_to_the_right {
9041 point(
9042 x_after_longest,
9043 text_bounds.origin.y
9044 + Pixels::from(
9045 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9046 - scroll_pixel_position.y,
9047 ),
9048 )
9049 } else {
9050 let cursor_row = newest_selection_head.map(|head| head.row());
9051 let above_edit = edit_start
9052 .row()
9053 .0
9054 .checked_sub(line_count as u32)
9055 .map(DisplayRow);
9056 let below_edit = Some(edit_end.row() + 1);
9057 let above_cursor =
9058 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9059 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9060
9061 // Place the edit popover adjacent to the edit if there is a location
9062 // available that is onscreen and does not obscure the cursor. Otherwise,
9063 // place it adjacent to the cursor.
9064 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9065 .into_iter()
9066 .flatten()
9067 .find(|&start_row| {
9068 let end_row = start_row + line_count as u32;
9069 visible_row_range.contains(&start_row)
9070 && visible_row_range.contains(&end_row)
9071 && cursor_row
9072 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9073 })?;
9074
9075 content_origin
9076 + point(
9077 Pixels::from(-scroll_pixel_position.x),
9078 Pixels::from(
9079 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9080 ),
9081 )
9082 };
9083
9084 origin.x -= BORDER_WIDTH;
9085
9086 window.defer_draw(element, origin, 1);
9087
9088 // Do not return an element, since it will already be drawn due to defer_draw.
9089 None
9090 }
9091
9092 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9093 px(30.)
9094 }
9095
9096 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9097 if self.read_only(cx) {
9098 cx.theme().players().read_only()
9099 } else {
9100 self.style.as_ref().unwrap().local_player
9101 }
9102 }
9103
9104 fn render_edit_prediction_accept_keybind(
9105 &self,
9106 window: &mut Window,
9107 cx: &mut App,
9108 ) -> Option<AnyElement> {
9109 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9110 let accept_keystroke = accept_binding.keystroke()?;
9111
9112 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9113
9114 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9115 Color::Accent
9116 } else {
9117 Color::Muted
9118 };
9119
9120 h_flex()
9121 .px_0p5()
9122 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9123 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9124 .text_size(TextSize::XSmall.rems(cx))
9125 .child(h_flex().children(ui::render_modifiers(
9126 accept_keystroke.modifiers(),
9127 PlatformStyle::platform(),
9128 Some(modifiers_color),
9129 Some(IconSize::XSmall.rems().into()),
9130 true,
9131 )))
9132 .when(is_platform_style_mac, |parent| {
9133 parent.child(accept_keystroke.key().to_string())
9134 })
9135 .when(!is_platform_style_mac, |parent| {
9136 parent.child(
9137 Key::new(
9138 util::capitalize(accept_keystroke.key()),
9139 Some(Color::Default),
9140 )
9141 .size(Some(IconSize::XSmall.rems().into())),
9142 )
9143 })
9144 .into_any()
9145 .into()
9146 }
9147
9148 fn render_edit_prediction_line_popover(
9149 &self,
9150 label: impl Into<SharedString>,
9151 icon: Option<IconName>,
9152 window: &mut Window,
9153 cx: &mut App,
9154 ) -> Stateful<Div> {
9155 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9156
9157 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9158 let has_keybind = keybind.is_some();
9159
9160 h_flex()
9161 .id("ep-line-popover")
9162 .py_0p5()
9163 .pl_1()
9164 .pr(padding_right)
9165 .gap_1()
9166 .rounded_md()
9167 .border_1()
9168 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9169 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9170 .shadow_xs()
9171 .when(!has_keybind, |el| {
9172 let status_colors = cx.theme().status();
9173
9174 el.bg(status_colors.error_background)
9175 .border_color(status_colors.error.opacity(0.6))
9176 .pl_2()
9177 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9178 .cursor_default()
9179 .hoverable_tooltip(move |_window, cx| {
9180 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9181 })
9182 })
9183 .children(keybind)
9184 .child(
9185 Label::new(label)
9186 .size(LabelSize::Small)
9187 .when(!has_keybind, |el| {
9188 el.color(cx.theme().status().error.into()).strikethrough()
9189 }),
9190 )
9191 .when(!has_keybind, |el| {
9192 el.child(
9193 h_flex().ml_1().child(
9194 Icon::new(IconName::Info)
9195 .size(IconSize::Small)
9196 .color(cx.theme().status().error.into()),
9197 ),
9198 )
9199 })
9200 .when_some(icon, |element, icon| {
9201 element.child(
9202 div()
9203 .mt(px(1.5))
9204 .child(Icon::new(icon).size(IconSize::Small)),
9205 )
9206 })
9207 }
9208
9209 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9210 let accent_color = cx.theme().colors().text_accent;
9211 let editor_bg_color = cx.theme().colors().editor_background;
9212 editor_bg_color.blend(accent_color.opacity(0.1))
9213 }
9214
9215 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9216 let accent_color = cx.theme().colors().text_accent;
9217 let editor_bg_color = cx.theme().colors().editor_background;
9218 editor_bg_color.blend(accent_color.opacity(0.6))
9219 }
9220 fn get_prediction_provider_icon_name(
9221 provider: &Option<RegisteredEditPredictionProvider>,
9222 ) -> IconName {
9223 match provider {
9224 Some(provider) => match provider.provider.name() {
9225 "copilot" => IconName::Copilot,
9226 "supermaven" => IconName::Supermaven,
9227 _ => IconName::ZedPredict,
9228 },
9229 None => IconName::ZedPredict,
9230 }
9231 }
9232
9233 fn render_edit_prediction_cursor_popover(
9234 &self,
9235 min_width: Pixels,
9236 max_width: Pixels,
9237 cursor_point: Point,
9238 style: &EditorStyle,
9239 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9240 _window: &Window,
9241 cx: &mut Context<Editor>,
9242 ) -> Option<AnyElement> {
9243 let provider = self.edit_prediction_provider.as_ref()?;
9244 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9245
9246 let is_refreshing = provider.provider.is_refreshing(cx);
9247
9248 fn pending_completion_container(icon: IconName) -> Div {
9249 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9250 }
9251
9252 let completion = match &self.active_edit_prediction {
9253 Some(prediction) => {
9254 if !self.has_visible_completions_menu() {
9255 const RADIUS: Pixels = px(6.);
9256 const BORDER_WIDTH: Pixels = px(1.);
9257
9258 return Some(
9259 h_flex()
9260 .elevation_2(cx)
9261 .border(BORDER_WIDTH)
9262 .border_color(cx.theme().colors().border)
9263 .when(accept_keystroke.is_none(), |el| {
9264 el.border_color(cx.theme().status().error)
9265 })
9266 .rounded(RADIUS)
9267 .rounded_tl(px(0.))
9268 .overflow_hidden()
9269 .child(div().px_1p5().child(match &prediction.completion {
9270 EditPrediction::MoveWithin { target, snapshot } => {
9271 use text::ToPoint as _;
9272 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9273 {
9274 Icon::new(IconName::ZedPredictDown)
9275 } else {
9276 Icon::new(IconName::ZedPredictUp)
9277 }
9278 }
9279 EditPrediction::MoveOutside { .. } => {
9280 // TODO [zeta2] custom icon for external jump?
9281 Icon::new(provider_icon)
9282 }
9283 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9284 }))
9285 .child(
9286 h_flex()
9287 .gap_1()
9288 .py_1()
9289 .px_2()
9290 .rounded_r(RADIUS - BORDER_WIDTH)
9291 .border_l_1()
9292 .border_color(cx.theme().colors().border)
9293 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9294 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9295 el.child(
9296 Label::new("Hold")
9297 .size(LabelSize::Small)
9298 .when(accept_keystroke.is_none(), |el| {
9299 el.strikethrough()
9300 })
9301 .line_height_style(LineHeightStyle::UiLabel),
9302 )
9303 })
9304 .id("edit_prediction_cursor_popover_keybind")
9305 .when(accept_keystroke.is_none(), |el| {
9306 let status_colors = cx.theme().status();
9307
9308 el.bg(status_colors.error_background)
9309 .border_color(status_colors.error.opacity(0.6))
9310 .child(Icon::new(IconName::Info).color(Color::Error))
9311 .cursor_default()
9312 .hoverable_tooltip(move |_window, cx| {
9313 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9314 .into()
9315 })
9316 })
9317 .when_some(
9318 accept_keystroke.as_ref(),
9319 |el, accept_keystroke| {
9320 el.child(h_flex().children(ui::render_modifiers(
9321 accept_keystroke.modifiers(),
9322 PlatformStyle::platform(),
9323 Some(Color::Default),
9324 Some(IconSize::XSmall.rems().into()),
9325 false,
9326 )))
9327 },
9328 ),
9329 )
9330 .into_any(),
9331 );
9332 }
9333
9334 self.render_edit_prediction_cursor_popover_preview(
9335 prediction,
9336 cursor_point,
9337 style,
9338 cx,
9339 )?
9340 }
9341
9342 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9343 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9344 stale_completion,
9345 cursor_point,
9346 style,
9347 cx,
9348 )?,
9349
9350 None => pending_completion_container(provider_icon)
9351 .child(Label::new("...").size(LabelSize::Small)),
9352 },
9353
9354 None => pending_completion_container(provider_icon)
9355 .child(Label::new("...").size(LabelSize::Small)),
9356 };
9357
9358 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9359 completion
9360 .with_animation(
9361 "loading-completion",
9362 Animation::new(Duration::from_secs(2))
9363 .repeat()
9364 .with_easing(pulsating_between(0.4, 0.8)),
9365 |label, delta| label.opacity(delta),
9366 )
9367 .into_any_element()
9368 } else {
9369 completion.into_any_element()
9370 };
9371
9372 let has_completion = self.active_edit_prediction.is_some();
9373
9374 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9375 Some(
9376 h_flex()
9377 .min_w(min_width)
9378 .max_w(max_width)
9379 .flex_1()
9380 .elevation_2(cx)
9381 .border_color(cx.theme().colors().border)
9382 .child(
9383 div()
9384 .flex_1()
9385 .py_1()
9386 .px_2()
9387 .overflow_hidden()
9388 .child(completion),
9389 )
9390 .when_some(accept_keystroke, |el, accept_keystroke| {
9391 if !accept_keystroke.modifiers().modified() {
9392 return el;
9393 }
9394
9395 el.child(
9396 h_flex()
9397 .h_full()
9398 .border_l_1()
9399 .rounded_r_lg()
9400 .border_color(cx.theme().colors().border)
9401 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9402 .gap_1()
9403 .py_1()
9404 .px_2()
9405 .child(
9406 h_flex()
9407 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9408 .when(is_platform_style_mac, |parent| parent.gap_1())
9409 .child(h_flex().children(ui::render_modifiers(
9410 accept_keystroke.modifiers(),
9411 PlatformStyle::platform(),
9412 Some(if !has_completion {
9413 Color::Muted
9414 } else {
9415 Color::Default
9416 }),
9417 None,
9418 false,
9419 ))),
9420 )
9421 .child(Label::new("Preview").into_any_element())
9422 .opacity(if has_completion { 1.0 } else { 0.4 }),
9423 )
9424 })
9425 .into_any(),
9426 )
9427 }
9428
9429 fn render_edit_prediction_cursor_popover_preview(
9430 &self,
9431 completion: &EditPredictionState,
9432 cursor_point: Point,
9433 style: &EditorStyle,
9434 cx: &mut Context<Editor>,
9435 ) -> Option<Div> {
9436 use text::ToPoint as _;
9437
9438 fn render_relative_row_jump(
9439 prefix: impl Into<String>,
9440 current_row: u32,
9441 target_row: u32,
9442 ) -> Div {
9443 let (row_diff, arrow) = if target_row < current_row {
9444 (current_row - target_row, IconName::ArrowUp)
9445 } else {
9446 (target_row - current_row, IconName::ArrowDown)
9447 };
9448
9449 h_flex()
9450 .child(
9451 Label::new(format!("{}{}", prefix.into(), row_diff))
9452 .color(Color::Muted)
9453 .size(LabelSize::Small),
9454 )
9455 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9456 }
9457
9458 let supports_jump = self
9459 .edit_prediction_provider
9460 .as_ref()
9461 .map(|provider| provider.provider.supports_jump_to_edit())
9462 .unwrap_or(true);
9463
9464 match &completion.completion {
9465 EditPrediction::MoveWithin {
9466 target, snapshot, ..
9467 } => {
9468 if !supports_jump {
9469 return None;
9470 }
9471
9472 Some(
9473 h_flex()
9474 .px_2()
9475 .gap_2()
9476 .flex_1()
9477 .child(
9478 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9479 Icon::new(IconName::ZedPredictDown)
9480 } else {
9481 Icon::new(IconName::ZedPredictUp)
9482 },
9483 )
9484 .child(Label::new("Jump to Edit")),
9485 )
9486 }
9487 EditPrediction::MoveOutside { snapshot, .. } => {
9488 let file_name = snapshot
9489 .file()
9490 .map(|file| file.file_name(cx))
9491 .unwrap_or("untitled");
9492 Some(
9493 h_flex()
9494 .px_2()
9495 .gap_2()
9496 .flex_1()
9497 .child(Icon::new(IconName::ZedPredict))
9498 .child(Label::new(format!("Jump to {file_name}"))),
9499 )
9500 }
9501 EditPrediction::Edit {
9502 edits,
9503 edit_preview,
9504 snapshot,
9505 display_mode: _,
9506 } => {
9507 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9508
9509 let (highlighted_edits, has_more_lines) =
9510 if let Some(edit_preview) = edit_preview.as_ref() {
9511 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9512 .first_line_preview()
9513 } else {
9514 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9515 };
9516
9517 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9518 .with_default_highlights(&style.text, highlighted_edits.highlights);
9519
9520 let preview = h_flex()
9521 .gap_1()
9522 .min_w_16()
9523 .child(styled_text)
9524 .when(has_more_lines, |parent| parent.child("…"));
9525
9526 let left = if supports_jump && first_edit_row != cursor_point.row {
9527 render_relative_row_jump("", cursor_point.row, first_edit_row)
9528 .into_any_element()
9529 } else {
9530 let icon_name =
9531 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9532 Icon::new(icon_name).into_any_element()
9533 };
9534
9535 Some(
9536 h_flex()
9537 .h_full()
9538 .flex_1()
9539 .gap_2()
9540 .pr_1()
9541 .overflow_x_hidden()
9542 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9543 .child(left)
9544 .child(preview),
9545 )
9546 }
9547 }
9548 }
9549
9550 pub fn render_context_menu(
9551 &self,
9552 style: &EditorStyle,
9553 max_height_in_lines: u32,
9554 window: &mut Window,
9555 cx: &mut Context<Editor>,
9556 ) -> Option<AnyElement> {
9557 let menu = self.context_menu.borrow();
9558 let menu = menu.as_ref()?;
9559 if !menu.visible() {
9560 return None;
9561 };
9562 Some(menu.render(style, max_height_in_lines, window, cx))
9563 }
9564
9565 fn render_context_menu_aside(
9566 &mut self,
9567 max_size: Size<Pixels>,
9568 window: &mut Window,
9569 cx: &mut Context<Editor>,
9570 ) -> Option<AnyElement> {
9571 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9572 if menu.visible() {
9573 menu.render_aside(max_size, window, cx)
9574 } else {
9575 None
9576 }
9577 })
9578 }
9579
9580 fn hide_context_menu(
9581 &mut self,
9582 window: &mut Window,
9583 cx: &mut Context<Self>,
9584 ) -> Option<CodeContextMenu> {
9585 cx.notify();
9586 self.completion_tasks.clear();
9587 let context_menu = self.context_menu.borrow_mut().take();
9588 self.stale_edit_prediction_in_menu.take();
9589 self.update_visible_edit_prediction(window, cx);
9590 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9591 && let Some(completion_provider) = &self.completion_provider
9592 {
9593 completion_provider.selection_changed(None, window, cx);
9594 }
9595 context_menu
9596 }
9597
9598 fn show_snippet_choices(
9599 &mut self,
9600 choices: &Vec<String>,
9601 selection: Range<Anchor>,
9602 cx: &mut Context<Self>,
9603 ) {
9604 let Some((_, buffer, _)) = self
9605 .buffer()
9606 .read(cx)
9607 .excerpt_containing(selection.start, cx)
9608 else {
9609 return;
9610 };
9611 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9612 else {
9613 return;
9614 };
9615 if buffer != end_buffer {
9616 log::error!("expected anchor range to have matching buffer IDs");
9617 return;
9618 }
9619
9620 let id = post_inc(&mut self.next_completion_id);
9621 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9622 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9623 CompletionsMenu::new_snippet_choices(
9624 id,
9625 true,
9626 choices,
9627 selection,
9628 buffer,
9629 snippet_sort_order,
9630 ),
9631 ));
9632 }
9633
9634 pub fn insert_snippet(
9635 &mut self,
9636 insertion_ranges: &[Range<usize>],
9637 snippet: Snippet,
9638 window: &mut Window,
9639 cx: &mut Context<Self>,
9640 ) -> Result<()> {
9641 struct Tabstop<T> {
9642 is_end_tabstop: bool,
9643 ranges: Vec<Range<T>>,
9644 choices: Option<Vec<String>>,
9645 }
9646
9647 let tabstops = self.buffer.update(cx, |buffer, cx| {
9648 let snippet_text: Arc<str> = snippet.text.clone().into();
9649 let edits = insertion_ranges
9650 .iter()
9651 .cloned()
9652 .map(|range| (range, snippet_text.clone()));
9653 let autoindent_mode = AutoindentMode::Block {
9654 original_indent_columns: Vec::new(),
9655 };
9656 buffer.edit(edits, Some(autoindent_mode), cx);
9657
9658 let snapshot = &*buffer.read(cx);
9659 let snippet = &snippet;
9660 snippet
9661 .tabstops
9662 .iter()
9663 .map(|tabstop| {
9664 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9665 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9666 });
9667 let mut tabstop_ranges = tabstop
9668 .ranges
9669 .iter()
9670 .flat_map(|tabstop_range| {
9671 let mut delta = 0_isize;
9672 insertion_ranges.iter().map(move |insertion_range| {
9673 let insertion_start = insertion_range.start as isize + delta;
9674 delta +=
9675 snippet.text.len() as isize - insertion_range.len() as isize;
9676
9677 let start = ((insertion_start + tabstop_range.start) as usize)
9678 .min(snapshot.len());
9679 let end = ((insertion_start + tabstop_range.end) as usize)
9680 .min(snapshot.len());
9681 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9682 })
9683 })
9684 .collect::<Vec<_>>();
9685 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9686
9687 Tabstop {
9688 is_end_tabstop,
9689 ranges: tabstop_ranges,
9690 choices: tabstop.choices.clone(),
9691 }
9692 })
9693 .collect::<Vec<_>>()
9694 });
9695 if let Some(tabstop) = tabstops.first() {
9696 self.change_selections(Default::default(), window, cx, |s| {
9697 // Reverse order so that the first range is the newest created selection.
9698 // Completions will use it and autoscroll will prioritize it.
9699 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9700 });
9701
9702 if let Some(choices) = &tabstop.choices
9703 && let Some(selection) = tabstop.ranges.first()
9704 {
9705 self.show_snippet_choices(choices, selection.clone(), cx)
9706 }
9707
9708 // If we're already at the last tabstop and it's at the end of the snippet,
9709 // we're done, we don't need to keep the state around.
9710 if !tabstop.is_end_tabstop {
9711 let choices = tabstops
9712 .iter()
9713 .map(|tabstop| tabstop.choices.clone())
9714 .collect();
9715
9716 let ranges = tabstops
9717 .into_iter()
9718 .map(|tabstop| tabstop.ranges)
9719 .collect::<Vec<_>>();
9720
9721 self.snippet_stack.push(SnippetState {
9722 active_index: 0,
9723 ranges,
9724 choices,
9725 });
9726 }
9727
9728 // Check whether the just-entered snippet ends with an auto-closable bracket.
9729 if self.autoclose_regions.is_empty() {
9730 let snapshot = self.buffer.read(cx).snapshot(cx);
9731 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9732 let selection_head = selection.head();
9733 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9734 continue;
9735 };
9736
9737 let mut bracket_pair = None;
9738 let max_lookup_length = scope
9739 .brackets()
9740 .map(|(pair, _)| {
9741 pair.start
9742 .as_str()
9743 .chars()
9744 .count()
9745 .max(pair.end.as_str().chars().count())
9746 })
9747 .max();
9748 if let Some(max_lookup_length) = max_lookup_length {
9749 let next_text = snapshot
9750 .chars_at(selection_head)
9751 .take(max_lookup_length)
9752 .collect::<String>();
9753 let prev_text = snapshot
9754 .reversed_chars_at(selection_head)
9755 .take(max_lookup_length)
9756 .collect::<String>();
9757
9758 for (pair, enabled) in scope.brackets() {
9759 if enabled
9760 && pair.close
9761 && prev_text.starts_with(pair.start.as_str())
9762 && next_text.starts_with(pair.end.as_str())
9763 {
9764 bracket_pair = Some(pair.clone());
9765 break;
9766 }
9767 }
9768 }
9769
9770 if let Some(pair) = bracket_pair {
9771 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9772 let autoclose_enabled =
9773 self.use_autoclose && snapshot_settings.use_autoclose;
9774 if autoclose_enabled {
9775 let start = snapshot.anchor_after(selection_head);
9776 let end = snapshot.anchor_after(selection_head);
9777 self.autoclose_regions.push(AutocloseRegion {
9778 selection_id: selection.id,
9779 range: start..end,
9780 pair,
9781 });
9782 }
9783 }
9784 }
9785 }
9786 }
9787 Ok(())
9788 }
9789
9790 pub fn move_to_next_snippet_tabstop(
9791 &mut self,
9792 window: &mut Window,
9793 cx: &mut Context<Self>,
9794 ) -> bool {
9795 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9796 }
9797
9798 pub fn move_to_prev_snippet_tabstop(
9799 &mut self,
9800 window: &mut Window,
9801 cx: &mut Context<Self>,
9802 ) -> bool {
9803 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9804 }
9805
9806 pub fn move_to_snippet_tabstop(
9807 &mut self,
9808 bias: Bias,
9809 window: &mut Window,
9810 cx: &mut Context<Self>,
9811 ) -> bool {
9812 if let Some(mut snippet) = self.snippet_stack.pop() {
9813 match bias {
9814 Bias::Left => {
9815 if snippet.active_index > 0 {
9816 snippet.active_index -= 1;
9817 } else {
9818 self.snippet_stack.push(snippet);
9819 return false;
9820 }
9821 }
9822 Bias::Right => {
9823 if snippet.active_index + 1 < snippet.ranges.len() {
9824 snippet.active_index += 1;
9825 } else {
9826 self.snippet_stack.push(snippet);
9827 return false;
9828 }
9829 }
9830 }
9831 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9832 self.change_selections(Default::default(), window, cx, |s| {
9833 // Reverse order so that the first range is the newest created selection.
9834 // Completions will use it and autoscroll will prioritize it.
9835 s.select_ranges(current_ranges.iter().rev().cloned())
9836 });
9837
9838 if let Some(choices) = &snippet.choices[snippet.active_index]
9839 && let Some(selection) = current_ranges.first()
9840 {
9841 self.show_snippet_choices(choices, selection.clone(), cx);
9842 }
9843
9844 // If snippet state is not at the last tabstop, push it back on the stack
9845 if snippet.active_index + 1 < snippet.ranges.len() {
9846 self.snippet_stack.push(snippet);
9847 }
9848 return true;
9849 }
9850 }
9851
9852 false
9853 }
9854
9855 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9856 self.transact(window, cx, |this, window, cx| {
9857 this.select_all(&SelectAll, window, cx);
9858 this.insert("", window, cx);
9859 });
9860 }
9861
9862 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9863 if self.read_only(cx) {
9864 return;
9865 }
9866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9867 self.transact(window, cx, |this, window, cx| {
9868 this.select_autoclose_pair(window, cx);
9869
9870 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9871
9872 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9873 if !this.linked_edit_ranges.is_empty() {
9874 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9875 let snapshot = this.buffer.read(cx).snapshot(cx);
9876
9877 for selection in selections.iter() {
9878 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9879 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9880 if selection_start.buffer_id != selection_end.buffer_id {
9881 continue;
9882 }
9883 if let Some(ranges) =
9884 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9885 {
9886 for (buffer, entries) in ranges {
9887 linked_ranges.entry(buffer).or_default().extend(entries);
9888 }
9889 }
9890 }
9891 }
9892
9893 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9894 for selection in &mut selections {
9895 if selection.is_empty() {
9896 let old_head = selection.head();
9897 let mut new_head =
9898 movement::left(&display_map, old_head.to_display_point(&display_map))
9899 .to_point(&display_map);
9900 if let Some((buffer, line_buffer_range)) = display_map
9901 .buffer_snapshot()
9902 .buffer_line_for_row(MultiBufferRow(old_head.row))
9903 {
9904 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9905 let indent_len = match indent_size.kind {
9906 IndentKind::Space => {
9907 buffer.settings_at(line_buffer_range.start, cx).tab_size
9908 }
9909 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9910 };
9911 if old_head.column <= indent_size.len && old_head.column > 0 {
9912 let indent_len = indent_len.get();
9913 new_head = cmp::min(
9914 new_head,
9915 MultiBufferPoint::new(
9916 old_head.row,
9917 ((old_head.column - 1) / indent_len) * indent_len,
9918 ),
9919 );
9920 }
9921 }
9922
9923 selection.set_head(new_head, SelectionGoal::None);
9924 }
9925 }
9926
9927 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9928 this.insert("", window, cx);
9929 let empty_str: Arc<str> = Arc::from("");
9930 for (buffer, edits) in linked_ranges {
9931 let snapshot = buffer.read(cx).snapshot();
9932 use text::ToPoint as TP;
9933
9934 let edits = edits
9935 .into_iter()
9936 .map(|range| {
9937 let end_point = TP::to_point(&range.end, &snapshot);
9938 let mut start_point = TP::to_point(&range.start, &snapshot);
9939
9940 if end_point == start_point {
9941 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9942 .saturating_sub(1);
9943 start_point =
9944 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9945 };
9946
9947 (start_point..end_point, empty_str.clone())
9948 })
9949 .sorted_by_key(|(range, _)| range.start)
9950 .collect::<Vec<_>>();
9951 buffer.update(cx, |this, cx| {
9952 this.edit(edits, None, cx);
9953 })
9954 }
9955 this.refresh_edit_prediction(true, false, window, cx);
9956 refresh_linked_ranges(this, window, cx);
9957 });
9958 }
9959
9960 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9961 if self.read_only(cx) {
9962 return;
9963 }
9964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9965 self.transact(window, cx, |this, window, cx| {
9966 this.change_selections(Default::default(), window, cx, |s| {
9967 s.move_with(|map, selection| {
9968 if selection.is_empty() {
9969 let cursor = movement::right(map, selection.head());
9970 selection.end = cursor;
9971 selection.reversed = true;
9972 selection.goal = SelectionGoal::None;
9973 }
9974 })
9975 });
9976 this.insert("", window, cx);
9977 this.refresh_edit_prediction(true, false, window, cx);
9978 });
9979 }
9980
9981 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9982 if self.mode.is_single_line() {
9983 cx.propagate();
9984 return;
9985 }
9986
9987 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9988 if self.move_to_prev_snippet_tabstop(window, cx) {
9989 return;
9990 }
9991 self.outdent(&Outdent, window, cx);
9992 }
9993
9994 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9995 if self.mode.is_single_line() {
9996 cx.propagate();
9997 return;
9998 }
9999
10000 if self.move_to_next_snippet_tabstop(window, cx) {
10001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10002 return;
10003 }
10004 if self.read_only(cx) {
10005 return;
10006 }
10007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10008 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10009 let buffer = self.buffer.read(cx);
10010 let snapshot = buffer.snapshot(cx);
10011 let rows_iter = selections.iter().map(|s| s.head().row);
10012 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10013
10014 let has_some_cursor_in_whitespace = selections
10015 .iter()
10016 .filter(|selection| selection.is_empty())
10017 .any(|selection| {
10018 let cursor = selection.head();
10019 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10020 cursor.column < current_indent.len
10021 });
10022
10023 let mut edits = Vec::new();
10024 let mut prev_edited_row = 0;
10025 let mut row_delta = 0;
10026 for selection in &mut selections {
10027 if selection.start.row != prev_edited_row {
10028 row_delta = 0;
10029 }
10030 prev_edited_row = selection.end.row;
10031
10032 // If the selection is non-empty, then increase the indentation of the selected lines.
10033 if !selection.is_empty() {
10034 row_delta =
10035 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10036 continue;
10037 }
10038
10039 let cursor = selection.head();
10040 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10041 if let Some(suggested_indent) =
10042 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10043 {
10044 // Don't do anything if already at suggested indent
10045 // and there is any other cursor which is not
10046 if has_some_cursor_in_whitespace
10047 && cursor.column == current_indent.len
10048 && current_indent.len == suggested_indent.len
10049 {
10050 continue;
10051 }
10052
10053 // Adjust line and move cursor to suggested indent
10054 // if cursor is not at suggested indent
10055 if cursor.column < suggested_indent.len
10056 && cursor.column <= current_indent.len
10057 && current_indent.len <= suggested_indent.len
10058 {
10059 selection.start = Point::new(cursor.row, suggested_indent.len);
10060 selection.end = selection.start;
10061 if row_delta == 0 {
10062 edits.extend(Buffer::edit_for_indent_size_adjustment(
10063 cursor.row,
10064 current_indent,
10065 suggested_indent,
10066 ));
10067 row_delta = suggested_indent.len - current_indent.len;
10068 }
10069 continue;
10070 }
10071
10072 // If current indent is more than suggested indent
10073 // only move cursor to current indent and skip indent
10074 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10075 selection.start = Point::new(cursor.row, current_indent.len);
10076 selection.end = selection.start;
10077 continue;
10078 }
10079 }
10080
10081 // Otherwise, insert a hard or soft tab.
10082 let settings = buffer.language_settings_at(cursor, cx);
10083 let tab_size = if settings.hard_tabs {
10084 IndentSize::tab()
10085 } else {
10086 let tab_size = settings.tab_size.get();
10087 let indent_remainder = snapshot
10088 .text_for_range(Point::new(cursor.row, 0)..cursor)
10089 .flat_map(str::chars)
10090 .fold(row_delta % tab_size, |counter: u32, c| {
10091 if c == '\t' {
10092 0
10093 } else {
10094 (counter + 1) % tab_size
10095 }
10096 });
10097
10098 let chars_to_next_tab_stop = tab_size - indent_remainder;
10099 IndentSize::spaces(chars_to_next_tab_stop)
10100 };
10101 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10102 selection.end = selection.start;
10103 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10104 row_delta += tab_size.len;
10105 }
10106
10107 self.transact(window, cx, |this, window, cx| {
10108 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10109 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10110 this.refresh_edit_prediction(true, false, window, cx);
10111 });
10112 }
10113
10114 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10115 if self.read_only(cx) {
10116 return;
10117 }
10118 if self.mode.is_single_line() {
10119 cx.propagate();
10120 return;
10121 }
10122
10123 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10124 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10125 let mut prev_edited_row = 0;
10126 let mut row_delta = 0;
10127 let mut edits = Vec::new();
10128 let buffer = self.buffer.read(cx);
10129 let snapshot = buffer.snapshot(cx);
10130 for selection in &mut selections {
10131 if selection.start.row != prev_edited_row {
10132 row_delta = 0;
10133 }
10134 prev_edited_row = selection.end.row;
10135
10136 row_delta =
10137 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10138 }
10139
10140 self.transact(window, cx, |this, window, cx| {
10141 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10142 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10143 });
10144 }
10145
10146 fn indent_selection(
10147 buffer: &MultiBuffer,
10148 snapshot: &MultiBufferSnapshot,
10149 selection: &mut Selection<Point>,
10150 edits: &mut Vec<(Range<Point>, String)>,
10151 delta_for_start_row: u32,
10152 cx: &App,
10153 ) -> u32 {
10154 let settings = buffer.language_settings_at(selection.start, cx);
10155 let tab_size = settings.tab_size.get();
10156 let indent_kind = if settings.hard_tabs {
10157 IndentKind::Tab
10158 } else {
10159 IndentKind::Space
10160 };
10161 let mut start_row = selection.start.row;
10162 let mut end_row = selection.end.row + 1;
10163
10164 // If a selection ends at the beginning of a line, don't indent
10165 // that last line.
10166 if selection.end.column == 0 && selection.end.row > selection.start.row {
10167 end_row -= 1;
10168 }
10169
10170 // Avoid re-indenting a row that has already been indented by a
10171 // previous selection, but still update this selection's column
10172 // to reflect that indentation.
10173 if delta_for_start_row > 0 {
10174 start_row += 1;
10175 selection.start.column += delta_for_start_row;
10176 if selection.end.row == selection.start.row {
10177 selection.end.column += delta_for_start_row;
10178 }
10179 }
10180
10181 let mut delta_for_end_row = 0;
10182 let has_multiple_rows = start_row + 1 != end_row;
10183 for row in start_row..end_row {
10184 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10185 let indent_delta = match (current_indent.kind, indent_kind) {
10186 (IndentKind::Space, IndentKind::Space) => {
10187 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10188 IndentSize::spaces(columns_to_next_tab_stop)
10189 }
10190 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10191 (_, IndentKind::Tab) => IndentSize::tab(),
10192 };
10193
10194 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10195 0
10196 } else {
10197 selection.start.column
10198 };
10199 let row_start = Point::new(row, start);
10200 edits.push((
10201 row_start..row_start,
10202 indent_delta.chars().collect::<String>(),
10203 ));
10204
10205 // Update this selection's endpoints to reflect the indentation.
10206 if row == selection.start.row {
10207 selection.start.column += indent_delta.len;
10208 }
10209 if row == selection.end.row {
10210 selection.end.column += indent_delta.len;
10211 delta_for_end_row = indent_delta.len;
10212 }
10213 }
10214
10215 if selection.start.row == selection.end.row {
10216 delta_for_start_row + delta_for_end_row
10217 } else {
10218 delta_for_end_row
10219 }
10220 }
10221
10222 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10223 if self.read_only(cx) {
10224 return;
10225 }
10226 if self.mode.is_single_line() {
10227 cx.propagate();
10228 return;
10229 }
10230
10231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10232 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10233 let selections = self.selections.all::<Point>(&display_map);
10234 let mut deletion_ranges = Vec::new();
10235 let mut last_outdent = None;
10236 {
10237 let buffer = self.buffer.read(cx);
10238 let snapshot = buffer.snapshot(cx);
10239 for selection in &selections {
10240 let settings = buffer.language_settings_at(selection.start, cx);
10241 let tab_size = settings.tab_size.get();
10242 let mut rows = selection.spanned_rows(false, &display_map);
10243
10244 // Avoid re-outdenting a row that has already been outdented by a
10245 // previous selection.
10246 if let Some(last_row) = last_outdent
10247 && last_row == rows.start
10248 {
10249 rows.start = rows.start.next_row();
10250 }
10251 let has_multiple_rows = rows.len() > 1;
10252 for row in rows.iter_rows() {
10253 let indent_size = snapshot.indent_size_for_line(row);
10254 if indent_size.len > 0 {
10255 let deletion_len = match indent_size.kind {
10256 IndentKind::Space => {
10257 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10258 if columns_to_prev_tab_stop == 0 {
10259 tab_size
10260 } else {
10261 columns_to_prev_tab_stop
10262 }
10263 }
10264 IndentKind::Tab => 1,
10265 };
10266 let start = if has_multiple_rows
10267 || deletion_len > selection.start.column
10268 || indent_size.len < selection.start.column
10269 {
10270 0
10271 } else {
10272 selection.start.column - deletion_len
10273 };
10274 deletion_ranges.push(
10275 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10276 );
10277 last_outdent = Some(row);
10278 }
10279 }
10280 }
10281 }
10282
10283 self.transact(window, cx, |this, window, cx| {
10284 this.buffer.update(cx, |buffer, cx| {
10285 let empty_str: Arc<str> = Arc::default();
10286 buffer.edit(
10287 deletion_ranges
10288 .into_iter()
10289 .map(|range| (range, empty_str.clone())),
10290 None,
10291 cx,
10292 );
10293 });
10294 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10295 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10296 });
10297 }
10298
10299 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10300 if self.read_only(cx) {
10301 return;
10302 }
10303 if self.mode.is_single_line() {
10304 cx.propagate();
10305 return;
10306 }
10307
10308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10309 let selections = self
10310 .selections
10311 .all::<usize>(&self.display_snapshot(cx))
10312 .into_iter()
10313 .map(|s| s.range());
10314
10315 self.transact(window, cx, |this, window, cx| {
10316 this.buffer.update(cx, |buffer, cx| {
10317 buffer.autoindent_ranges(selections, cx);
10318 });
10319 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10320 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10321 });
10322 }
10323
10324 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10326 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10327 let selections = self.selections.all::<Point>(&display_map);
10328
10329 let mut new_cursors = Vec::new();
10330 let mut edit_ranges = Vec::new();
10331 let mut selections = selections.iter().peekable();
10332 while let Some(selection) = selections.next() {
10333 let mut rows = selection.spanned_rows(false, &display_map);
10334
10335 // Accumulate contiguous regions of rows that we want to delete.
10336 while let Some(next_selection) = selections.peek() {
10337 let next_rows = next_selection.spanned_rows(false, &display_map);
10338 if next_rows.start <= rows.end {
10339 rows.end = next_rows.end;
10340 selections.next().unwrap();
10341 } else {
10342 break;
10343 }
10344 }
10345
10346 let buffer = display_map.buffer_snapshot();
10347 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10348 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10349 // If there's a line after the range, delete the \n from the end of the row range
10350 (
10351 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10352 rows.end,
10353 )
10354 } else {
10355 // If there isn't a line after the range, delete the \n from the line before the
10356 // start of the row range
10357 edit_start = edit_start.saturating_sub(1);
10358 (buffer.len(), rows.start.previous_row())
10359 };
10360
10361 let text_layout_details = self.text_layout_details(window);
10362 let x = display_map.x_for_display_point(
10363 selection.head().to_display_point(&display_map),
10364 &text_layout_details,
10365 );
10366 let row = Point::new(target_row.0, 0)
10367 .to_display_point(&display_map)
10368 .row();
10369 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10370
10371 new_cursors.push((
10372 selection.id,
10373 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10374 SelectionGoal::None,
10375 ));
10376 edit_ranges.push(edit_start..edit_end);
10377 }
10378
10379 self.transact(window, cx, |this, window, cx| {
10380 let buffer = this.buffer.update(cx, |buffer, cx| {
10381 let empty_str: Arc<str> = Arc::default();
10382 buffer.edit(
10383 edit_ranges
10384 .into_iter()
10385 .map(|range| (range, empty_str.clone())),
10386 None,
10387 cx,
10388 );
10389 buffer.snapshot(cx)
10390 });
10391 let new_selections = new_cursors
10392 .into_iter()
10393 .map(|(id, cursor, goal)| {
10394 let cursor = cursor.to_point(&buffer);
10395 Selection {
10396 id,
10397 start: cursor,
10398 end: cursor,
10399 reversed: false,
10400 goal,
10401 }
10402 })
10403 .collect();
10404
10405 this.change_selections(Default::default(), window, cx, |s| {
10406 s.select(new_selections);
10407 });
10408 });
10409 }
10410
10411 pub fn join_lines_impl(
10412 &mut self,
10413 insert_whitespace: bool,
10414 window: &mut Window,
10415 cx: &mut Context<Self>,
10416 ) {
10417 if self.read_only(cx) {
10418 return;
10419 }
10420 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10421 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10422 let start = MultiBufferRow(selection.start.row);
10423 // Treat single line selections as if they include the next line. Otherwise this action
10424 // would do nothing for single line selections individual cursors.
10425 let end = if selection.start.row == selection.end.row {
10426 MultiBufferRow(selection.start.row + 1)
10427 } else {
10428 MultiBufferRow(selection.end.row)
10429 };
10430
10431 if let Some(last_row_range) = row_ranges.last_mut()
10432 && start <= last_row_range.end
10433 {
10434 last_row_range.end = end;
10435 continue;
10436 }
10437 row_ranges.push(start..end);
10438 }
10439
10440 let snapshot = self.buffer.read(cx).snapshot(cx);
10441 let mut cursor_positions = Vec::new();
10442 for row_range in &row_ranges {
10443 let anchor = snapshot.anchor_before(Point::new(
10444 row_range.end.previous_row().0,
10445 snapshot.line_len(row_range.end.previous_row()),
10446 ));
10447 cursor_positions.push(anchor..anchor);
10448 }
10449
10450 self.transact(window, cx, |this, window, cx| {
10451 for row_range in row_ranges.into_iter().rev() {
10452 for row in row_range.iter_rows().rev() {
10453 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10454 let next_line_row = row.next_row();
10455 let indent = snapshot.indent_size_for_line(next_line_row);
10456 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10457
10458 let replace =
10459 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10460 " "
10461 } else {
10462 ""
10463 };
10464
10465 this.buffer.update(cx, |buffer, cx| {
10466 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10467 });
10468 }
10469 }
10470
10471 this.change_selections(Default::default(), window, cx, |s| {
10472 s.select_anchor_ranges(cursor_positions)
10473 });
10474 });
10475 }
10476
10477 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10478 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10479 self.join_lines_impl(true, window, cx);
10480 }
10481
10482 pub fn sort_lines_case_sensitive(
10483 &mut self,
10484 _: &SortLinesCaseSensitive,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10489 }
10490
10491 pub fn sort_lines_by_length(
10492 &mut self,
10493 _: &SortLinesByLength,
10494 window: &mut Window,
10495 cx: &mut Context<Self>,
10496 ) {
10497 self.manipulate_immutable_lines(window, cx, |lines| {
10498 lines.sort_by_key(|&line| line.chars().count())
10499 })
10500 }
10501
10502 pub fn sort_lines_case_insensitive(
10503 &mut self,
10504 _: &SortLinesCaseInsensitive,
10505 window: &mut Window,
10506 cx: &mut Context<Self>,
10507 ) {
10508 self.manipulate_immutable_lines(window, cx, |lines| {
10509 lines.sort_by_key(|line| line.to_lowercase())
10510 })
10511 }
10512
10513 pub fn unique_lines_case_insensitive(
10514 &mut self,
10515 _: &UniqueLinesCaseInsensitive,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 self.manipulate_immutable_lines(window, cx, |lines| {
10520 let mut seen = HashSet::default();
10521 lines.retain(|line| seen.insert(line.to_lowercase()));
10522 })
10523 }
10524
10525 pub fn unique_lines_case_sensitive(
10526 &mut self,
10527 _: &UniqueLinesCaseSensitive,
10528 window: &mut Window,
10529 cx: &mut Context<Self>,
10530 ) {
10531 self.manipulate_immutable_lines(window, cx, |lines| {
10532 let mut seen = HashSet::default();
10533 lines.retain(|line| seen.insert(*line));
10534 })
10535 }
10536
10537 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10538 let snapshot = self.buffer.read(cx).snapshot(cx);
10539 for selection in self.selections.disjoint_anchors_arc().iter() {
10540 if snapshot
10541 .language_at(selection.start)
10542 .and_then(|lang| lang.config().wrap_characters.as_ref())
10543 .is_some()
10544 {
10545 return true;
10546 }
10547 }
10548 false
10549 }
10550
10551 fn wrap_selections_in_tag(
10552 &mut self,
10553 _: &WrapSelectionsInTag,
10554 window: &mut Window,
10555 cx: &mut Context<Self>,
10556 ) {
10557 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10558
10559 let snapshot = self.buffer.read(cx).snapshot(cx);
10560
10561 let mut edits = Vec::new();
10562 let mut boundaries = Vec::new();
10563
10564 for selection in self
10565 .selections
10566 .all_adjusted(&self.display_snapshot(cx))
10567 .iter()
10568 {
10569 let Some(wrap_config) = snapshot
10570 .language_at(selection.start)
10571 .and_then(|lang| lang.config().wrap_characters.clone())
10572 else {
10573 continue;
10574 };
10575
10576 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10577 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10578
10579 let start_before = snapshot.anchor_before(selection.start);
10580 let end_after = snapshot.anchor_after(selection.end);
10581
10582 edits.push((start_before..start_before, open_tag));
10583 edits.push((end_after..end_after, close_tag));
10584
10585 boundaries.push((
10586 start_before,
10587 end_after,
10588 wrap_config.start_prefix.len(),
10589 wrap_config.end_suffix.len(),
10590 ));
10591 }
10592
10593 if edits.is_empty() {
10594 return;
10595 }
10596
10597 self.transact(window, cx, |this, window, cx| {
10598 let buffer = this.buffer.update(cx, |buffer, cx| {
10599 buffer.edit(edits, None, cx);
10600 buffer.snapshot(cx)
10601 });
10602
10603 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10604 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10605 boundaries.into_iter()
10606 {
10607 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10608 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10609 new_selections.push(open_offset..open_offset);
10610 new_selections.push(close_offset..close_offset);
10611 }
10612
10613 this.change_selections(Default::default(), window, cx, |s| {
10614 s.select_ranges(new_selections);
10615 });
10616
10617 this.request_autoscroll(Autoscroll::fit(), cx);
10618 });
10619 }
10620
10621 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10622 let Some(project) = self.project.clone() else {
10623 return;
10624 };
10625 self.reload(project, window, cx)
10626 .detach_and_notify_err(window, cx);
10627 }
10628
10629 pub fn restore_file(
10630 &mut self,
10631 _: &::git::RestoreFile,
10632 window: &mut Window,
10633 cx: &mut Context<Self>,
10634 ) {
10635 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10636 let mut buffer_ids = HashSet::default();
10637 let snapshot = self.buffer().read(cx).snapshot(cx);
10638 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10639 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10640 }
10641
10642 let buffer = self.buffer().read(cx);
10643 let ranges = buffer_ids
10644 .into_iter()
10645 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10646 .collect::<Vec<_>>();
10647
10648 self.restore_hunks_in_ranges(ranges, window, cx);
10649 }
10650
10651 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10652 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10653 let selections = self
10654 .selections
10655 .all(&self.display_snapshot(cx))
10656 .into_iter()
10657 .map(|s| s.range())
10658 .collect();
10659 self.restore_hunks_in_ranges(selections, window, cx);
10660 }
10661
10662 pub fn restore_hunks_in_ranges(
10663 &mut self,
10664 ranges: Vec<Range<Point>>,
10665 window: &mut Window,
10666 cx: &mut Context<Editor>,
10667 ) {
10668 let mut revert_changes = HashMap::default();
10669 let chunk_by = self
10670 .snapshot(window, cx)
10671 .hunks_for_ranges(ranges)
10672 .into_iter()
10673 .chunk_by(|hunk| hunk.buffer_id);
10674 for (buffer_id, hunks) in &chunk_by {
10675 let hunks = hunks.collect::<Vec<_>>();
10676 for hunk in &hunks {
10677 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10678 }
10679 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10680 }
10681 drop(chunk_by);
10682 if !revert_changes.is_empty() {
10683 self.transact(window, cx, |editor, window, cx| {
10684 editor.restore(revert_changes, window, cx);
10685 });
10686 }
10687 }
10688
10689 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10690 if let Some(status) = self
10691 .addons
10692 .iter()
10693 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10694 {
10695 return Some(status);
10696 }
10697 self.project
10698 .as_ref()?
10699 .read(cx)
10700 .status_for_buffer_id(buffer_id, cx)
10701 }
10702
10703 pub fn open_active_item_in_terminal(
10704 &mut self,
10705 _: &OpenInTerminal,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10710 let project_path = buffer.read(cx).project_path(cx)?;
10711 let project = self.project()?.read(cx);
10712 let entry = project.entry_for_path(&project_path, cx)?;
10713 let parent = match &entry.canonical_path {
10714 Some(canonical_path) => canonical_path.to_path_buf(),
10715 None => project.absolute_path(&project_path, cx)?,
10716 }
10717 .parent()?
10718 .to_path_buf();
10719 Some(parent)
10720 }) {
10721 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10722 }
10723 }
10724
10725 fn set_breakpoint_context_menu(
10726 &mut self,
10727 display_row: DisplayRow,
10728 position: Option<Anchor>,
10729 clicked_point: gpui::Point<Pixels>,
10730 window: &mut Window,
10731 cx: &mut Context<Self>,
10732 ) {
10733 let source = self
10734 .buffer
10735 .read(cx)
10736 .snapshot(cx)
10737 .anchor_before(Point::new(display_row.0, 0u32));
10738
10739 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10740
10741 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10742 self,
10743 source,
10744 clicked_point,
10745 context_menu,
10746 window,
10747 cx,
10748 );
10749 }
10750
10751 fn add_edit_breakpoint_block(
10752 &mut self,
10753 anchor: Anchor,
10754 breakpoint: &Breakpoint,
10755 edit_action: BreakpointPromptEditAction,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 let weak_editor = cx.weak_entity();
10760 let bp_prompt = cx.new(|cx| {
10761 BreakpointPromptEditor::new(
10762 weak_editor,
10763 anchor,
10764 breakpoint.clone(),
10765 edit_action,
10766 window,
10767 cx,
10768 )
10769 });
10770
10771 let height = bp_prompt.update(cx, |this, cx| {
10772 this.prompt
10773 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10774 });
10775 let cloned_prompt = bp_prompt.clone();
10776 let blocks = vec![BlockProperties {
10777 style: BlockStyle::Sticky,
10778 placement: BlockPlacement::Above(anchor),
10779 height: Some(height),
10780 render: Arc::new(move |cx| {
10781 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10782 cloned_prompt.clone().into_any_element()
10783 }),
10784 priority: 0,
10785 }];
10786
10787 let focus_handle = bp_prompt.focus_handle(cx);
10788 window.focus(&focus_handle);
10789
10790 let block_ids = self.insert_blocks(blocks, None, cx);
10791 bp_prompt.update(cx, |prompt, _| {
10792 prompt.add_block_ids(block_ids);
10793 });
10794 }
10795
10796 pub(crate) fn breakpoint_at_row(
10797 &self,
10798 row: u32,
10799 window: &mut Window,
10800 cx: &mut Context<Self>,
10801 ) -> Option<(Anchor, Breakpoint)> {
10802 let snapshot = self.snapshot(window, cx);
10803 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10804
10805 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10806 }
10807
10808 pub(crate) fn breakpoint_at_anchor(
10809 &self,
10810 breakpoint_position: Anchor,
10811 snapshot: &EditorSnapshot,
10812 cx: &mut Context<Self>,
10813 ) -> Option<(Anchor, Breakpoint)> {
10814 let buffer = self
10815 .buffer
10816 .read(cx)
10817 .buffer_for_anchor(breakpoint_position, cx)?;
10818
10819 let enclosing_excerpt = breakpoint_position.excerpt_id;
10820 let buffer_snapshot = buffer.read(cx).snapshot();
10821
10822 let row = buffer_snapshot
10823 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10824 .row;
10825
10826 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10827 let anchor_end = snapshot
10828 .buffer_snapshot()
10829 .anchor_after(Point::new(row, line_len));
10830
10831 self.breakpoint_store
10832 .as_ref()?
10833 .read_with(cx, |breakpoint_store, cx| {
10834 breakpoint_store
10835 .breakpoints(
10836 &buffer,
10837 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10838 &buffer_snapshot,
10839 cx,
10840 )
10841 .next()
10842 .and_then(|(bp, _)| {
10843 let breakpoint_row = buffer_snapshot
10844 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10845 .row;
10846
10847 if breakpoint_row == row {
10848 snapshot
10849 .buffer_snapshot()
10850 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10851 .map(|position| (position, bp.bp.clone()))
10852 } else {
10853 None
10854 }
10855 })
10856 })
10857 }
10858
10859 pub fn edit_log_breakpoint(
10860 &mut self,
10861 _: &EditLogBreakpoint,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10866 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10867 message: None,
10868 state: BreakpointState::Enabled,
10869 condition: None,
10870 hit_condition: None,
10871 });
10872
10873 self.add_edit_breakpoint_block(
10874 anchor,
10875 &breakpoint,
10876 BreakpointPromptEditAction::Log,
10877 window,
10878 cx,
10879 );
10880 }
10881 }
10882
10883 fn breakpoints_at_cursors(
10884 &self,
10885 window: &mut Window,
10886 cx: &mut Context<Self>,
10887 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10888 let snapshot = self.snapshot(window, cx);
10889 let cursors = self
10890 .selections
10891 .disjoint_anchors_arc()
10892 .iter()
10893 .map(|selection| {
10894 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10895
10896 let breakpoint_position = self
10897 .breakpoint_at_row(cursor_position.row, window, cx)
10898 .map(|bp| bp.0)
10899 .unwrap_or_else(|| {
10900 snapshot
10901 .display_snapshot
10902 .buffer_snapshot()
10903 .anchor_after(Point::new(cursor_position.row, 0))
10904 });
10905
10906 let breakpoint = self
10907 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10908 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10909
10910 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10911 })
10912 // 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.
10913 .collect::<HashMap<Anchor, _>>();
10914
10915 cursors.into_iter().collect()
10916 }
10917
10918 pub fn enable_breakpoint(
10919 &mut self,
10920 _: &crate::actions::EnableBreakpoint,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10925 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10926 continue;
10927 };
10928 self.edit_breakpoint_at_anchor(
10929 anchor,
10930 breakpoint,
10931 BreakpointEditAction::InvertState,
10932 cx,
10933 );
10934 }
10935 }
10936
10937 pub fn disable_breakpoint(
10938 &mut self,
10939 _: &crate::actions::DisableBreakpoint,
10940 window: &mut Window,
10941 cx: &mut Context<Self>,
10942 ) {
10943 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10944 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10945 continue;
10946 };
10947 self.edit_breakpoint_at_anchor(
10948 anchor,
10949 breakpoint,
10950 BreakpointEditAction::InvertState,
10951 cx,
10952 );
10953 }
10954 }
10955
10956 pub fn toggle_breakpoint(
10957 &mut self,
10958 _: &crate::actions::ToggleBreakpoint,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10963 if let Some(breakpoint) = breakpoint {
10964 self.edit_breakpoint_at_anchor(
10965 anchor,
10966 breakpoint,
10967 BreakpointEditAction::Toggle,
10968 cx,
10969 );
10970 } else {
10971 self.edit_breakpoint_at_anchor(
10972 anchor,
10973 Breakpoint::new_standard(),
10974 BreakpointEditAction::Toggle,
10975 cx,
10976 );
10977 }
10978 }
10979 }
10980
10981 pub fn edit_breakpoint_at_anchor(
10982 &mut self,
10983 breakpoint_position: Anchor,
10984 breakpoint: Breakpoint,
10985 edit_action: BreakpointEditAction,
10986 cx: &mut Context<Self>,
10987 ) {
10988 let Some(breakpoint_store) = &self.breakpoint_store else {
10989 return;
10990 };
10991
10992 let Some(buffer) = self
10993 .buffer
10994 .read(cx)
10995 .buffer_for_anchor(breakpoint_position, cx)
10996 else {
10997 return;
10998 };
10999
11000 breakpoint_store.update(cx, |breakpoint_store, cx| {
11001 breakpoint_store.toggle_breakpoint(
11002 buffer,
11003 BreakpointWithPosition {
11004 position: breakpoint_position.text_anchor,
11005 bp: breakpoint,
11006 },
11007 edit_action,
11008 cx,
11009 );
11010 });
11011
11012 cx.notify();
11013 }
11014
11015 #[cfg(any(test, feature = "test-support"))]
11016 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11017 self.breakpoint_store.clone()
11018 }
11019
11020 pub fn prepare_restore_change(
11021 &self,
11022 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11023 hunk: &MultiBufferDiffHunk,
11024 cx: &mut App,
11025 ) -> Option<()> {
11026 if hunk.is_created_file() {
11027 return None;
11028 }
11029 let buffer = self.buffer.read(cx);
11030 let diff = buffer.diff_for(hunk.buffer_id)?;
11031 let buffer = buffer.buffer(hunk.buffer_id)?;
11032 let buffer = buffer.read(cx);
11033 let original_text = diff
11034 .read(cx)
11035 .base_text()
11036 .as_rope()
11037 .slice(hunk.diff_base_byte_range.clone());
11038 let buffer_snapshot = buffer.snapshot();
11039 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11040 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11041 probe
11042 .0
11043 .start
11044 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11045 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11046 }) {
11047 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11048 Some(())
11049 } else {
11050 None
11051 }
11052 }
11053
11054 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11055 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11056 }
11057
11058 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11059 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11060 }
11061
11062 fn manipulate_lines<M>(
11063 &mut self,
11064 window: &mut Window,
11065 cx: &mut Context<Self>,
11066 mut manipulate: M,
11067 ) where
11068 M: FnMut(&str) -> LineManipulationResult,
11069 {
11070 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11071
11072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11073 let buffer = self.buffer.read(cx).snapshot(cx);
11074
11075 let mut edits = Vec::new();
11076
11077 let selections = self.selections.all::<Point>(&display_map);
11078 let mut selections = selections.iter().peekable();
11079 let mut contiguous_row_selections = Vec::new();
11080 let mut new_selections = Vec::new();
11081 let mut added_lines = 0;
11082 let mut removed_lines = 0;
11083
11084 while let Some(selection) = selections.next() {
11085 let (start_row, end_row) = consume_contiguous_rows(
11086 &mut contiguous_row_selections,
11087 selection,
11088 &display_map,
11089 &mut selections,
11090 );
11091
11092 let start_point = Point::new(start_row.0, 0);
11093 let end_point = Point::new(
11094 end_row.previous_row().0,
11095 buffer.line_len(end_row.previous_row()),
11096 );
11097 let text = buffer
11098 .text_for_range(start_point..end_point)
11099 .collect::<String>();
11100
11101 let LineManipulationResult {
11102 new_text,
11103 line_count_before,
11104 line_count_after,
11105 } = manipulate(&text);
11106
11107 edits.push((start_point..end_point, new_text));
11108
11109 // Selections must change based on added and removed line count
11110 let start_row =
11111 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11112 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11113 new_selections.push(Selection {
11114 id: selection.id,
11115 start: start_row,
11116 end: end_row,
11117 goal: SelectionGoal::None,
11118 reversed: selection.reversed,
11119 });
11120
11121 if line_count_after > line_count_before {
11122 added_lines += line_count_after - line_count_before;
11123 } else if line_count_before > line_count_after {
11124 removed_lines += line_count_before - line_count_after;
11125 }
11126 }
11127
11128 self.transact(window, cx, |this, window, cx| {
11129 let buffer = this.buffer.update(cx, |buffer, cx| {
11130 buffer.edit(edits, None, cx);
11131 buffer.snapshot(cx)
11132 });
11133
11134 // Recalculate offsets on newly edited buffer
11135 let new_selections = new_selections
11136 .iter()
11137 .map(|s| {
11138 let start_point = Point::new(s.start.0, 0);
11139 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11140 Selection {
11141 id: s.id,
11142 start: buffer.point_to_offset(start_point),
11143 end: buffer.point_to_offset(end_point),
11144 goal: s.goal,
11145 reversed: s.reversed,
11146 }
11147 })
11148 .collect();
11149
11150 this.change_selections(Default::default(), window, cx, |s| {
11151 s.select(new_selections);
11152 });
11153
11154 this.request_autoscroll(Autoscroll::fit(), cx);
11155 });
11156 }
11157
11158 fn manipulate_immutable_lines<Fn>(
11159 &mut self,
11160 window: &mut Window,
11161 cx: &mut Context<Self>,
11162 mut callback: Fn,
11163 ) where
11164 Fn: FnMut(&mut Vec<&str>),
11165 {
11166 self.manipulate_lines(window, cx, |text| {
11167 let mut lines: Vec<&str> = text.split('\n').collect();
11168 let line_count_before = lines.len();
11169
11170 callback(&mut lines);
11171
11172 LineManipulationResult {
11173 new_text: lines.join("\n"),
11174 line_count_before,
11175 line_count_after: lines.len(),
11176 }
11177 });
11178 }
11179
11180 fn manipulate_mutable_lines<Fn>(
11181 &mut self,
11182 window: &mut Window,
11183 cx: &mut Context<Self>,
11184 mut callback: Fn,
11185 ) where
11186 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11187 {
11188 self.manipulate_lines(window, cx, |text| {
11189 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11190 let line_count_before = lines.len();
11191
11192 callback(&mut lines);
11193
11194 LineManipulationResult {
11195 new_text: lines.join("\n"),
11196 line_count_before,
11197 line_count_after: lines.len(),
11198 }
11199 });
11200 }
11201
11202 pub fn convert_indentation_to_spaces(
11203 &mut self,
11204 _: &ConvertIndentationToSpaces,
11205 window: &mut Window,
11206 cx: &mut Context<Self>,
11207 ) {
11208 let settings = self.buffer.read(cx).language_settings(cx);
11209 let tab_size = settings.tab_size.get() as usize;
11210
11211 self.manipulate_mutable_lines(window, cx, |lines| {
11212 // Allocates a reasonably sized scratch buffer once for the whole loop
11213 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11214 // Avoids recomputing spaces that could be inserted many times
11215 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11216 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11217 .collect();
11218
11219 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11220 let mut chars = line.as_ref().chars();
11221 let mut col = 0;
11222 let mut changed = false;
11223
11224 for ch in chars.by_ref() {
11225 match ch {
11226 ' ' => {
11227 reindented_line.push(' ');
11228 col += 1;
11229 }
11230 '\t' => {
11231 // \t are converted to spaces depending on the current column
11232 let spaces_len = tab_size - (col % tab_size);
11233 reindented_line.extend(&space_cache[spaces_len - 1]);
11234 col += spaces_len;
11235 changed = true;
11236 }
11237 _ => {
11238 // If we dont append before break, the character is consumed
11239 reindented_line.push(ch);
11240 break;
11241 }
11242 }
11243 }
11244
11245 if !changed {
11246 reindented_line.clear();
11247 continue;
11248 }
11249 // Append the rest of the line and replace old reference with new one
11250 reindented_line.extend(chars);
11251 *line = Cow::Owned(reindented_line.clone());
11252 reindented_line.clear();
11253 }
11254 });
11255 }
11256
11257 pub fn convert_indentation_to_tabs(
11258 &mut self,
11259 _: &ConvertIndentationToTabs,
11260 window: &mut Window,
11261 cx: &mut Context<Self>,
11262 ) {
11263 let settings = self.buffer.read(cx).language_settings(cx);
11264 let tab_size = settings.tab_size.get() as usize;
11265
11266 self.manipulate_mutable_lines(window, cx, |lines| {
11267 // Allocates a reasonably sized buffer once for the whole loop
11268 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11269 // Avoids recomputing spaces that could be inserted many times
11270 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11271 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11272 .collect();
11273
11274 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11275 let mut chars = line.chars();
11276 let mut spaces_count = 0;
11277 let mut first_non_indent_char = None;
11278 let mut changed = false;
11279
11280 for ch in chars.by_ref() {
11281 match ch {
11282 ' ' => {
11283 // Keep track of spaces. Append \t when we reach tab_size
11284 spaces_count += 1;
11285 changed = true;
11286 if spaces_count == tab_size {
11287 reindented_line.push('\t');
11288 spaces_count = 0;
11289 }
11290 }
11291 '\t' => {
11292 reindented_line.push('\t');
11293 spaces_count = 0;
11294 }
11295 _ => {
11296 // Dont append it yet, we might have remaining spaces
11297 first_non_indent_char = Some(ch);
11298 break;
11299 }
11300 }
11301 }
11302
11303 if !changed {
11304 reindented_line.clear();
11305 continue;
11306 }
11307 // Remaining spaces that didn't make a full tab stop
11308 if spaces_count > 0 {
11309 reindented_line.extend(&space_cache[spaces_count - 1]);
11310 }
11311 // If we consume an extra character that was not indentation, add it back
11312 if let Some(extra_char) = first_non_indent_char {
11313 reindented_line.push(extra_char);
11314 }
11315 // Append the rest of the line and replace old reference with new one
11316 reindented_line.extend(chars);
11317 *line = Cow::Owned(reindented_line.clone());
11318 reindented_line.clear();
11319 }
11320 });
11321 }
11322
11323 pub fn convert_to_upper_case(
11324 &mut self,
11325 _: &ConvertToUpperCase,
11326 window: &mut Window,
11327 cx: &mut Context<Self>,
11328 ) {
11329 self.manipulate_text(window, cx, |text| text.to_uppercase())
11330 }
11331
11332 pub fn convert_to_lower_case(
11333 &mut self,
11334 _: &ConvertToLowerCase,
11335 window: &mut Window,
11336 cx: &mut Context<Self>,
11337 ) {
11338 self.manipulate_text(window, cx, |text| text.to_lowercase())
11339 }
11340
11341 pub fn convert_to_title_case(
11342 &mut self,
11343 _: &ConvertToTitleCase,
11344 window: &mut Window,
11345 cx: &mut Context<Self>,
11346 ) {
11347 self.manipulate_text(window, cx, |text| {
11348 text.split('\n')
11349 .map(|line| line.to_case(Case::Title))
11350 .join("\n")
11351 })
11352 }
11353
11354 pub fn convert_to_snake_case(
11355 &mut self,
11356 _: &ConvertToSnakeCase,
11357 window: &mut Window,
11358 cx: &mut Context<Self>,
11359 ) {
11360 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11361 }
11362
11363 pub fn convert_to_kebab_case(
11364 &mut self,
11365 _: &ConvertToKebabCase,
11366 window: &mut Window,
11367 cx: &mut Context<Self>,
11368 ) {
11369 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11370 }
11371
11372 pub fn convert_to_upper_camel_case(
11373 &mut self,
11374 _: &ConvertToUpperCamelCase,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 self.manipulate_text(window, cx, |text| {
11379 text.split('\n')
11380 .map(|line| line.to_case(Case::UpperCamel))
11381 .join("\n")
11382 })
11383 }
11384
11385 pub fn convert_to_lower_camel_case(
11386 &mut self,
11387 _: &ConvertToLowerCamelCase,
11388 window: &mut Window,
11389 cx: &mut Context<Self>,
11390 ) {
11391 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11392 }
11393
11394 pub fn convert_to_opposite_case(
11395 &mut self,
11396 _: &ConvertToOppositeCase,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 self.manipulate_text(window, cx, |text| {
11401 text.chars()
11402 .fold(String::with_capacity(text.len()), |mut t, c| {
11403 if c.is_uppercase() {
11404 t.extend(c.to_lowercase());
11405 } else {
11406 t.extend(c.to_uppercase());
11407 }
11408 t
11409 })
11410 })
11411 }
11412
11413 pub fn convert_to_sentence_case(
11414 &mut self,
11415 _: &ConvertToSentenceCase,
11416 window: &mut Window,
11417 cx: &mut Context<Self>,
11418 ) {
11419 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11420 }
11421
11422 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11423 self.manipulate_text(window, cx, |text| {
11424 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11425 if has_upper_case_characters {
11426 text.to_lowercase()
11427 } else {
11428 text.to_uppercase()
11429 }
11430 })
11431 }
11432
11433 pub fn convert_to_rot13(
11434 &mut self,
11435 _: &ConvertToRot13,
11436 window: &mut Window,
11437 cx: &mut Context<Self>,
11438 ) {
11439 self.manipulate_text(window, cx, |text| {
11440 text.chars()
11441 .map(|c| match c {
11442 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11443 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11444 _ => c,
11445 })
11446 .collect()
11447 })
11448 }
11449
11450 pub fn convert_to_rot47(
11451 &mut self,
11452 _: &ConvertToRot47,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 self.manipulate_text(window, cx, |text| {
11457 text.chars()
11458 .map(|c| {
11459 let code_point = c as u32;
11460 if code_point >= 33 && code_point <= 126 {
11461 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11462 }
11463 c
11464 })
11465 .collect()
11466 })
11467 }
11468
11469 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11470 where
11471 Fn: FnMut(&str) -> String,
11472 {
11473 let buffer = self.buffer.read(cx).snapshot(cx);
11474
11475 let mut new_selections = Vec::new();
11476 let mut edits = Vec::new();
11477 let mut selection_adjustment = 0i32;
11478
11479 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11480 let selection_is_empty = selection.is_empty();
11481
11482 let (start, end) = if selection_is_empty {
11483 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11484 (word_range.start, word_range.end)
11485 } else {
11486 (
11487 buffer.point_to_offset(selection.start),
11488 buffer.point_to_offset(selection.end),
11489 )
11490 };
11491
11492 let text = buffer.text_for_range(start..end).collect::<String>();
11493 let old_length = text.len() as i32;
11494 let text = callback(&text);
11495
11496 new_selections.push(Selection {
11497 start: (start as i32 - selection_adjustment) as usize,
11498 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11499 goal: SelectionGoal::None,
11500 id: selection.id,
11501 reversed: selection.reversed,
11502 });
11503
11504 selection_adjustment += old_length - text.len() as i32;
11505
11506 edits.push((start..end, text));
11507 }
11508
11509 self.transact(window, cx, |this, window, cx| {
11510 this.buffer.update(cx, |buffer, cx| {
11511 buffer.edit(edits, None, cx);
11512 });
11513
11514 this.change_selections(Default::default(), window, cx, |s| {
11515 s.select(new_selections);
11516 });
11517
11518 this.request_autoscroll(Autoscroll::fit(), cx);
11519 });
11520 }
11521
11522 pub fn move_selection_on_drop(
11523 &mut self,
11524 selection: &Selection<Anchor>,
11525 target: DisplayPoint,
11526 is_cut: bool,
11527 window: &mut Window,
11528 cx: &mut Context<Self>,
11529 ) {
11530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11531 let buffer = display_map.buffer_snapshot();
11532 let mut edits = Vec::new();
11533 let insert_point = display_map
11534 .clip_point(target, Bias::Left)
11535 .to_point(&display_map);
11536 let text = buffer
11537 .text_for_range(selection.start..selection.end)
11538 .collect::<String>();
11539 if is_cut {
11540 edits.push(((selection.start..selection.end), String::new()));
11541 }
11542 let insert_anchor = buffer.anchor_before(insert_point);
11543 edits.push(((insert_anchor..insert_anchor), text));
11544 let last_edit_start = insert_anchor.bias_left(buffer);
11545 let last_edit_end = insert_anchor.bias_right(buffer);
11546 self.transact(window, cx, |this, window, cx| {
11547 this.buffer.update(cx, |buffer, cx| {
11548 buffer.edit(edits, None, cx);
11549 });
11550 this.change_selections(Default::default(), window, cx, |s| {
11551 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11552 });
11553 });
11554 }
11555
11556 pub fn clear_selection_drag_state(&mut self) {
11557 self.selection_drag_state = SelectionDragState::None;
11558 }
11559
11560 pub fn duplicate(
11561 &mut self,
11562 upwards: bool,
11563 whole_lines: bool,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) {
11567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11568
11569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11570 let buffer = display_map.buffer_snapshot();
11571 let selections = self.selections.all::<Point>(&display_map);
11572
11573 let mut edits = Vec::new();
11574 let mut selections_iter = selections.iter().peekable();
11575 while let Some(selection) = selections_iter.next() {
11576 let mut rows = selection.spanned_rows(false, &display_map);
11577 // duplicate line-wise
11578 if whole_lines || selection.start == selection.end {
11579 // Avoid duplicating the same lines twice.
11580 while let Some(next_selection) = selections_iter.peek() {
11581 let next_rows = next_selection.spanned_rows(false, &display_map);
11582 if next_rows.start < rows.end {
11583 rows.end = next_rows.end;
11584 selections_iter.next().unwrap();
11585 } else {
11586 break;
11587 }
11588 }
11589
11590 // Copy the text from the selected row region and splice it either at the start
11591 // or end of the region.
11592 let start = Point::new(rows.start.0, 0);
11593 let end = Point::new(
11594 rows.end.previous_row().0,
11595 buffer.line_len(rows.end.previous_row()),
11596 );
11597
11598 let mut text = buffer.text_for_range(start..end).collect::<String>();
11599
11600 let insert_location = if upwards {
11601 // When duplicating upward, we need to insert before the current line.
11602 // If we're on the last line and it doesn't end with a newline,
11603 // we need to add a newline before the duplicated content.
11604 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11605 && buffer.max_point().column > 0
11606 && !text.ends_with('\n');
11607
11608 if needs_leading_newline {
11609 text.insert(0, '\n');
11610 end
11611 } else {
11612 text.push('\n');
11613 Point::new(rows.start.0, 0)
11614 }
11615 } else {
11616 text.push('\n');
11617 start
11618 };
11619 edits.push((insert_location..insert_location, text));
11620 } else {
11621 // duplicate character-wise
11622 let start = selection.start;
11623 let end = selection.end;
11624 let text = buffer.text_for_range(start..end).collect::<String>();
11625 edits.push((selection.end..selection.end, text));
11626 }
11627 }
11628
11629 self.transact(window, cx, |this, window, cx| {
11630 this.buffer.update(cx, |buffer, cx| {
11631 buffer.edit(edits, None, cx);
11632 });
11633
11634 // When duplicating upward with whole lines, move the cursor to the duplicated line
11635 if upwards && whole_lines {
11636 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11637
11638 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11639 let mut new_ranges = Vec::new();
11640 let selections = s.all::<Point>(&display_map);
11641 let mut selections_iter = selections.iter().peekable();
11642
11643 while let Some(first_selection) = selections_iter.next() {
11644 // Group contiguous selections together to find the total row span
11645 let mut group_selections = vec![first_selection];
11646 let mut rows = first_selection.spanned_rows(false, &display_map);
11647
11648 while let Some(next_selection) = selections_iter.peek() {
11649 let next_rows = next_selection.spanned_rows(false, &display_map);
11650 if next_rows.start < rows.end {
11651 rows.end = next_rows.end;
11652 group_selections.push(selections_iter.next().unwrap());
11653 } else {
11654 break;
11655 }
11656 }
11657
11658 let row_count = rows.end.0 - rows.start.0;
11659
11660 // Move all selections in this group up by the total number of duplicated rows
11661 for selection in group_selections {
11662 let new_start = Point::new(
11663 selection.start.row.saturating_sub(row_count),
11664 selection.start.column,
11665 );
11666
11667 let new_end = Point::new(
11668 selection.end.row.saturating_sub(row_count),
11669 selection.end.column,
11670 );
11671
11672 new_ranges.push(new_start..new_end);
11673 }
11674 }
11675
11676 s.select_ranges(new_ranges);
11677 });
11678 }
11679
11680 this.request_autoscroll(Autoscroll::fit(), cx);
11681 });
11682 }
11683
11684 pub fn duplicate_line_up(
11685 &mut self,
11686 _: &DuplicateLineUp,
11687 window: &mut Window,
11688 cx: &mut Context<Self>,
11689 ) {
11690 self.duplicate(true, true, window, cx);
11691 }
11692
11693 pub fn duplicate_line_down(
11694 &mut self,
11695 _: &DuplicateLineDown,
11696 window: &mut Window,
11697 cx: &mut Context<Self>,
11698 ) {
11699 self.duplicate(false, true, window, cx);
11700 }
11701
11702 pub fn duplicate_selection(
11703 &mut self,
11704 _: &DuplicateSelection,
11705 window: &mut Window,
11706 cx: &mut Context<Self>,
11707 ) {
11708 self.duplicate(false, false, window, cx);
11709 }
11710
11711 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11712 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11713 if self.mode.is_single_line() {
11714 cx.propagate();
11715 return;
11716 }
11717
11718 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11719 let buffer = self.buffer.read(cx).snapshot(cx);
11720
11721 let mut edits = Vec::new();
11722 let mut unfold_ranges = Vec::new();
11723 let mut refold_creases = Vec::new();
11724
11725 let selections = self.selections.all::<Point>(&display_map);
11726 let mut selections = selections.iter().peekable();
11727 let mut contiguous_row_selections = Vec::new();
11728 let mut new_selections = Vec::new();
11729
11730 while let Some(selection) = selections.next() {
11731 // Find all the selections that span a contiguous row range
11732 let (start_row, end_row) = consume_contiguous_rows(
11733 &mut contiguous_row_selections,
11734 selection,
11735 &display_map,
11736 &mut selections,
11737 );
11738
11739 // Move the text spanned by the row range to be before the line preceding the row range
11740 if start_row.0 > 0 {
11741 let range_to_move = Point::new(
11742 start_row.previous_row().0,
11743 buffer.line_len(start_row.previous_row()),
11744 )
11745 ..Point::new(
11746 end_row.previous_row().0,
11747 buffer.line_len(end_row.previous_row()),
11748 );
11749 let insertion_point = display_map
11750 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11751 .0;
11752
11753 // Don't move lines across excerpts
11754 if buffer
11755 .excerpt_containing(insertion_point..range_to_move.end)
11756 .is_some()
11757 {
11758 let text = buffer
11759 .text_for_range(range_to_move.clone())
11760 .flat_map(|s| s.chars())
11761 .skip(1)
11762 .chain(['\n'])
11763 .collect::<String>();
11764
11765 edits.push((
11766 buffer.anchor_after(range_to_move.start)
11767 ..buffer.anchor_before(range_to_move.end),
11768 String::new(),
11769 ));
11770 let insertion_anchor = buffer.anchor_after(insertion_point);
11771 edits.push((insertion_anchor..insertion_anchor, text));
11772
11773 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11774
11775 // Move selections up
11776 new_selections.extend(contiguous_row_selections.drain(..).map(
11777 |mut selection| {
11778 selection.start.row -= row_delta;
11779 selection.end.row -= row_delta;
11780 selection
11781 },
11782 ));
11783
11784 // Move folds up
11785 unfold_ranges.push(range_to_move.clone());
11786 for fold in display_map.folds_in_range(
11787 buffer.anchor_before(range_to_move.start)
11788 ..buffer.anchor_after(range_to_move.end),
11789 ) {
11790 let mut start = fold.range.start.to_point(&buffer);
11791 let mut end = fold.range.end.to_point(&buffer);
11792 start.row -= row_delta;
11793 end.row -= row_delta;
11794 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11795 }
11796 }
11797 }
11798
11799 // If we didn't move line(s), preserve the existing selections
11800 new_selections.append(&mut contiguous_row_selections);
11801 }
11802
11803 self.transact(window, cx, |this, window, cx| {
11804 this.unfold_ranges(&unfold_ranges, true, true, cx);
11805 this.buffer.update(cx, |buffer, cx| {
11806 for (range, text) in edits {
11807 buffer.edit([(range, text)], None, cx);
11808 }
11809 });
11810 this.fold_creases(refold_creases, true, window, cx);
11811 this.change_selections(Default::default(), window, cx, |s| {
11812 s.select(new_selections);
11813 })
11814 });
11815 }
11816
11817 pub fn move_line_down(
11818 &mut self,
11819 _: &MoveLineDown,
11820 window: &mut Window,
11821 cx: &mut Context<Self>,
11822 ) {
11823 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11824 if self.mode.is_single_line() {
11825 cx.propagate();
11826 return;
11827 }
11828
11829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11830 let buffer = self.buffer.read(cx).snapshot(cx);
11831
11832 let mut edits = Vec::new();
11833 let mut unfold_ranges = Vec::new();
11834 let mut refold_creases = Vec::new();
11835
11836 let selections = self.selections.all::<Point>(&display_map);
11837 let mut selections = selections.iter().peekable();
11838 let mut contiguous_row_selections = Vec::new();
11839 let mut new_selections = Vec::new();
11840
11841 while let Some(selection) = selections.next() {
11842 // Find all the selections that span a contiguous row range
11843 let (start_row, end_row) = consume_contiguous_rows(
11844 &mut contiguous_row_selections,
11845 selection,
11846 &display_map,
11847 &mut selections,
11848 );
11849
11850 // Move the text spanned by the row range to be after the last line of the row range
11851 if end_row.0 <= buffer.max_point().row {
11852 let range_to_move =
11853 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11854 let insertion_point = display_map
11855 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11856 .0;
11857
11858 // Don't move lines across excerpt boundaries
11859 if buffer
11860 .excerpt_containing(range_to_move.start..insertion_point)
11861 .is_some()
11862 {
11863 let mut text = String::from("\n");
11864 text.extend(buffer.text_for_range(range_to_move.clone()));
11865 text.pop(); // Drop trailing newline
11866 edits.push((
11867 buffer.anchor_after(range_to_move.start)
11868 ..buffer.anchor_before(range_to_move.end),
11869 String::new(),
11870 ));
11871 let insertion_anchor = buffer.anchor_after(insertion_point);
11872 edits.push((insertion_anchor..insertion_anchor, text));
11873
11874 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11875
11876 // Move selections down
11877 new_selections.extend(contiguous_row_selections.drain(..).map(
11878 |mut selection| {
11879 selection.start.row += row_delta;
11880 selection.end.row += row_delta;
11881 selection
11882 },
11883 ));
11884
11885 // Move folds down
11886 unfold_ranges.push(range_to_move.clone());
11887 for fold in display_map.folds_in_range(
11888 buffer.anchor_before(range_to_move.start)
11889 ..buffer.anchor_after(range_to_move.end),
11890 ) {
11891 let mut start = fold.range.start.to_point(&buffer);
11892 let mut end = fold.range.end.to_point(&buffer);
11893 start.row += row_delta;
11894 end.row += row_delta;
11895 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11896 }
11897 }
11898 }
11899
11900 // If we didn't move line(s), preserve the existing selections
11901 new_selections.append(&mut contiguous_row_selections);
11902 }
11903
11904 self.transact(window, cx, |this, window, cx| {
11905 this.unfold_ranges(&unfold_ranges, true, true, cx);
11906 this.buffer.update(cx, |buffer, cx| {
11907 for (range, text) in edits {
11908 buffer.edit([(range, text)], None, cx);
11909 }
11910 });
11911 this.fold_creases(refold_creases, true, window, cx);
11912 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11913 });
11914 }
11915
11916 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11917 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11918 let text_layout_details = &self.text_layout_details(window);
11919 self.transact(window, cx, |this, window, cx| {
11920 let edits = this.change_selections(Default::default(), window, cx, |s| {
11921 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11922 s.move_with(|display_map, selection| {
11923 if !selection.is_empty() {
11924 return;
11925 }
11926
11927 let mut head = selection.head();
11928 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11929 if head.column() == display_map.line_len(head.row()) {
11930 transpose_offset = display_map
11931 .buffer_snapshot()
11932 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11933 }
11934
11935 if transpose_offset == 0 {
11936 return;
11937 }
11938
11939 *head.column_mut() += 1;
11940 head = display_map.clip_point(head, Bias::Right);
11941 let goal = SelectionGoal::HorizontalPosition(
11942 display_map
11943 .x_for_display_point(head, text_layout_details)
11944 .into(),
11945 );
11946 selection.collapse_to(head, goal);
11947
11948 let transpose_start = display_map
11949 .buffer_snapshot()
11950 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11951 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11952 let transpose_end = display_map
11953 .buffer_snapshot()
11954 .clip_offset(transpose_offset + 1, Bias::Right);
11955 if let Some(ch) = display_map
11956 .buffer_snapshot()
11957 .chars_at(transpose_start)
11958 .next()
11959 {
11960 edits.push((transpose_start..transpose_offset, String::new()));
11961 edits.push((transpose_end..transpose_end, ch.to_string()));
11962 }
11963 }
11964 });
11965 edits
11966 });
11967 this.buffer
11968 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11969 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11970 this.change_selections(Default::default(), window, cx, |s| {
11971 s.select(selections);
11972 });
11973 });
11974 }
11975
11976 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11978 if self.mode.is_single_line() {
11979 cx.propagate();
11980 return;
11981 }
11982
11983 self.rewrap_impl(RewrapOptions::default(), cx)
11984 }
11985
11986 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11987 let buffer = self.buffer.read(cx).snapshot(cx);
11988 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11989
11990 #[derive(Clone, Debug, PartialEq)]
11991 enum CommentFormat {
11992 /// single line comment, with prefix for line
11993 Line(String),
11994 /// single line within a block comment, with prefix for line
11995 BlockLine(String),
11996 /// a single line of a block comment that includes the initial delimiter
11997 BlockCommentWithStart(BlockCommentConfig),
11998 /// a single line of a block comment that includes the ending delimiter
11999 BlockCommentWithEnd(BlockCommentConfig),
12000 }
12001
12002 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12003 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12004 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12005 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12006 .peekable();
12007
12008 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12009 row
12010 } else {
12011 return Vec::new();
12012 };
12013
12014 let language_settings = buffer.language_settings_at(selection.head(), cx);
12015 let language_scope = buffer.language_scope_at(selection.head());
12016
12017 let indent_and_prefix_for_row =
12018 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12019 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12020 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12021 &language_scope
12022 {
12023 let indent_end = Point::new(row, indent.len);
12024 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12025 let line_text_after_indent = buffer
12026 .text_for_range(indent_end..line_end)
12027 .collect::<String>();
12028
12029 let is_within_comment_override = buffer
12030 .language_scope_at(indent_end)
12031 .is_some_and(|scope| scope.override_name() == Some("comment"));
12032 let comment_delimiters = if is_within_comment_override {
12033 // we are within a comment syntax node, but we don't
12034 // yet know what kind of comment: block, doc or line
12035 match (
12036 language_scope.documentation_comment(),
12037 language_scope.block_comment(),
12038 ) {
12039 (Some(config), _) | (_, Some(config))
12040 if buffer.contains_str_at(indent_end, &config.start) =>
12041 {
12042 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12043 }
12044 (Some(config), _) | (_, Some(config))
12045 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12046 {
12047 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12048 }
12049 (Some(config), _) | (_, Some(config))
12050 if buffer.contains_str_at(indent_end, &config.prefix) =>
12051 {
12052 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12053 }
12054 (_, _) => language_scope
12055 .line_comment_prefixes()
12056 .iter()
12057 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12058 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12059 }
12060 } else {
12061 // we not in an overridden comment node, but we may
12062 // be within a non-overridden line comment node
12063 language_scope
12064 .line_comment_prefixes()
12065 .iter()
12066 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12067 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12068 };
12069
12070 let rewrap_prefix = language_scope
12071 .rewrap_prefixes()
12072 .iter()
12073 .find_map(|prefix_regex| {
12074 prefix_regex.find(&line_text_after_indent).map(|mat| {
12075 if mat.start() == 0 {
12076 Some(mat.as_str().to_string())
12077 } else {
12078 None
12079 }
12080 })
12081 })
12082 .flatten();
12083 (comment_delimiters, rewrap_prefix)
12084 } else {
12085 (None, None)
12086 };
12087 (indent, comment_prefix, rewrap_prefix)
12088 };
12089
12090 let mut ranges = Vec::new();
12091 let from_empty_selection = selection.is_empty();
12092
12093 let mut current_range_start = first_row;
12094 let mut prev_row = first_row;
12095 let (
12096 mut current_range_indent,
12097 mut current_range_comment_delimiters,
12098 mut current_range_rewrap_prefix,
12099 ) = indent_and_prefix_for_row(first_row);
12100
12101 for row in non_blank_rows_iter.skip(1) {
12102 let has_paragraph_break = row > prev_row + 1;
12103
12104 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12105 indent_and_prefix_for_row(row);
12106
12107 let has_indent_change = row_indent != current_range_indent;
12108 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12109
12110 let has_boundary_change = has_comment_change
12111 || row_rewrap_prefix.is_some()
12112 || (has_indent_change && current_range_comment_delimiters.is_some());
12113
12114 if has_paragraph_break || has_boundary_change {
12115 ranges.push((
12116 language_settings.clone(),
12117 Point::new(current_range_start, 0)
12118 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12119 current_range_indent,
12120 current_range_comment_delimiters.clone(),
12121 current_range_rewrap_prefix.clone(),
12122 from_empty_selection,
12123 ));
12124 current_range_start = row;
12125 current_range_indent = row_indent;
12126 current_range_comment_delimiters = row_comment_delimiters;
12127 current_range_rewrap_prefix = row_rewrap_prefix;
12128 }
12129 prev_row = row;
12130 }
12131
12132 ranges.push((
12133 language_settings.clone(),
12134 Point::new(current_range_start, 0)
12135 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12136 current_range_indent,
12137 current_range_comment_delimiters,
12138 current_range_rewrap_prefix,
12139 from_empty_selection,
12140 ));
12141
12142 ranges
12143 });
12144
12145 let mut edits = Vec::new();
12146 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12147
12148 for (
12149 language_settings,
12150 wrap_range,
12151 mut indent_size,
12152 comment_prefix,
12153 rewrap_prefix,
12154 from_empty_selection,
12155 ) in wrap_ranges
12156 {
12157 let mut start_row = wrap_range.start.row;
12158 let mut end_row = wrap_range.end.row;
12159
12160 // Skip selections that overlap with a range that has already been rewrapped.
12161 let selection_range = start_row..end_row;
12162 if rewrapped_row_ranges
12163 .iter()
12164 .any(|range| range.overlaps(&selection_range))
12165 {
12166 continue;
12167 }
12168
12169 let tab_size = language_settings.tab_size;
12170
12171 let (line_prefix, inside_comment) = match &comment_prefix {
12172 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12173 (Some(prefix.as_str()), true)
12174 }
12175 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12176 (Some(prefix.as_ref()), true)
12177 }
12178 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12179 start: _,
12180 end: _,
12181 prefix,
12182 tab_size,
12183 })) => {
12184 indent_size.len += tab_size;
12185 (Some(prefix.as_ref()), true)
12186 }
12187 None => (None, false),
12188 };
12189 let indent_prefix = indent_size.chars().collect::<String>();
12190 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12191
12192 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12193 RewrapBehavior::InComments => inside_comment,
12194 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12195 RewrapBehavior::Anywhere => true,
12196 };
12197
12198 let should_rewrap = options.override_language_settings
12199 || allow_rewrap_based_on_language
12200 || self.hard_wrap.is_some();
12201 if !should_rewrap {
12202 continue;
12203 }
12204
12205 if from_empty_selection {
12206 'expand_upwards: while start_row > 0 {
12207 let prev_row = start_row - 1;
12208 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12209 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12210 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12211 {
12212 start_row = prev_row;
12213 } else {
12214 break 'expand_upwards;
12215 }
12216 }
12217
12218 'expand_downwards: while end_row < buffer.max_point().row {
12219 let next_row = end_row + 1;
12220 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12221 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12222 && !buffer.is_line_blank(MultiBufferRow(next_row))
12223 {
12224 end_row = next_row;
12225 } else {
12226 break 'expand_downwards;
12227 }
12228 }
12229 }
12230
12231 let start = Point::new(start_row, 0);
12232 let start_offset = ToOffset::to_offset(&start, &buffer);
12233 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12234 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12235 let mut first_line_delimiter = None;
12236 let mut last_line_delimiter = None;
12237 let Some(lines_without_prefixes) = selection_text
12238 .lines()
12239 .enumerate()
12240 .map(|(ix, line)| {
12241 let line_trimmed = line.trim_start();
12242 if rewrap_prefix.is_some() && ix > 0 {
12243 Ok(line_trimmed)
12244 } else if let Some(
12245 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12246 start,
12247 prefix,
12248 end,
12249 tab_size,
12250 })
12251 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12252 start,
12253 prefix,
12254 end,
12255 tab_size,
12256 }),
12257 ) = &comment_prefix
12258 {
12259 let line_trimmed = line_trimmed
12260 .strip_prefix(start.as_ref())
12261 .map(|s| {
12262 let mut indent_size = indent_size;
12263 indent_size.len -= tab_size;
12264 let indent_prefix: String = indent_size.chars().collect();
12265 first_line_delimiter = Some((indent_prefix, start));
12266 s.trim_start()
12267 })
12268 .unwrap_or(line_trimmed);
12269 let line_trimmed = line_trimmed
12270 .strip_suffix(end.as_ref())
12271 .map(|s| {
12272 last_line_delimiter = Some(end);
12273 s.trim_end()
12274 })
12275 .unwrap_or(line_trimmed);
12276 let line_trimmed = line_trimmed
12277 .strip_prefix(prefix.as_ref())
12278 .unwrap_or(line_trimmed);
12279 Ok(line_trimmed)
12280 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12281 line_trimmed.strip_prefix(prefix).with_context(|| {
12282 format!("line did not start with prefix {prefix:?}: {line:?}")
12283 })
12284 } else {
12285 line_trimmed
12286 .strip_prefix(&line_prefix.trim_start())
12287 .with_context(|| {
12288 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12289 })
12290 }
12291 })
12292 .collect::<Result<Vec<_>, _>>()
12293 .log_err()
12294 else {
12295 continue;
12296 };
12297
12298 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12299 buffer
12300 .language_settings_at(Point::new(start_row, 0), cx)
12301 .preferred_line_length as usize
12302 });
12303
12304 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12305 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12306 } else {
12307 line_prefix.clone()
12308 };
12309
12310 let wrapped_text = {
12311 let mut wrapped_text = wrap_with_prefix(
12312 line_prefix,
12313 subsequent_lines_prefix,
12314 lines_without_prefixes.join("\n"),
12315 wrap_column,
12316 tab_size,
12317 options.preserve_existing_whitespace,
12318 );
12319
12320 if let Some((indent, delimiter)) = first_line_delimiter {
12321 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12322 }
12323 if let Some(last_line) = last_line_delimiter {
12324 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12325 }
12326
12327 wrapped_text
12328 };
12329
12330 // TODO: should always use char-based diff while still supporting cursor behavior that
12331 // matches vim.
12332 let mut diff_options = DiffOptions::default();
12333 if options.override_language_settings {
12334 diff_options.max_word_diff_len = 0;
12335 diff_options.max_word_diff_line_count = 0;
12336 } else {
12337 diff_options.max_word_diff_len = usize::MAX;
12338 diff_options.max_word_diff_line_count = usize::MAX;
12339 }
12340
12341 for (old_range, new_text) in
12342 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12343 {
12344 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12345 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12346 edits.push((edit_start..edit_end, new_text));
12347 }
12348
12349 rewrapped_row_ranges.push(start_row..=end_row);
12350 }
12351
12352 self.buffer
12353 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12354 }
12355
12356 pub fn cut_common(
12357 &mut self,
12358 cut_no_selection_line: bool,
12359 window: &mut Window,
12360 cx: &mut Context<Self>,
12361 ) -> ClipboardItem {
12362 let mut text = String::new();
12363 let buffer = self.buffer.read(cx).snapshot(cx);
12364 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12365 let mut clipboard_selections = Vec::with_capacity(selections.len());
12366 {
12367 let max_point = buffer.max_point();
12368 let mut is_first = true;
12369 for selection in &mut selections {
12370 let is_entire_line =
12371 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12372 if is_entire_line {
12373 selection.start = Point::new(selection.start.row, 0);
12374 if !selection.is_empty() && selection.end.column == 0 {
12375 selection.end = cmp::min(max_point, selection.end);
12376 } else {
12377 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12378 }
12379 selection.goal = SelectionGoal::None;
12380 }
12381 if is_first {
12382 is_first = false;
12383 } else {
12384 text += "\n";
12385 }
12386 let mut len = 0;
12387 for chunk in buffer.text_for_range(selection.start..selection.end) {
12388 text.push_str(chunk);
12389 len += chunk.len();
12390 }
12391 clipboard_selections.push(ClipboardSelection {
12392 len,
12393 is_entire_line,
12394 first_line_indent: buffer
12395 .indent_size_for_line(MultiBufferRow(selection.start.row))
12396 .len,
12397 });
12398 }
12399 }
12400
12401 self.transact(window, cx, |this, window, cx| {
12402 this.change_selections(Default::default(), window, cx, |s| {
12403 s.select(selections);
12404 });
12405 this.insert("", window, cx);
12406 });
12407 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12408 }
12409
12410 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12412 let item = self.cut_common(true, window, cx);
12413 cx.write_to_clipboard(item);
12414 }
12415
12416 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12417 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12418 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12419 s.move_with(|snapshot, sel| {
12420 if sel.is_empty() {
12421 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12422 }
12423 if sel.is_empty() {
12424 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12425 }
12426 });
12427 });
12428 let item = self.cut_common(false, window, cx);
12429 cx.set_global(KillRing(item))
12430 }
12431
12432 pub fn kill_ring_yank(
12433 &mut self,
12434 _: &KillRingYank,
12435 window: &mut Window,
12436 cx: &mut Context<Self>,
12437 ) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12440 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12441 (kill_ring.text().to_string(), kill_ring.metadata_json())
12442 } else {
12443 return;
12444 }
12445 } else {
12446 return;
12447 };
12448 self.do_paste(&text, metadata, false, window, cx);
12449 }
12450
12451 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12452 self.do_copy(true, cx);
12453 }
12454
12455 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12456 self.do_copy(false, cx);
12457 }
12458
12459 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12460 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12461 let buffer = self.buffer.read(cx).read(cx);
12462 let mut text = String::new();
12463
12464 let mut clipboard_selections = Vec::with_capacity(selections.len());
12465 {
12466 let max_point = buffer.max_point();
12467 let mut is_first = true;
12468 for selection in &selections {
12469 let mut start = selection.start;
12470 let mut end = selection.end;
12471 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12472 let mut add_trailing_newline = false;
12473 if is_entire_line {
12474 start = Point::new(start.row, 0);
12475 let next_line_start = Point::new(end.row + 1, 0);
12476 if next_line_start <= max_point {
12477 end = next_line_start;
12478 } else {
12479 // We're on the last line without a trailing newline.
12480 // Copy to the end of the line and add a newline afterwards.
12481 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12482 add_trailing_newline = true;
12483 }
12484 }
12485
12486 let mut trimmed_selections = Vec::new();
12487 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12488 let row = MultiBufferRow(start.row);
12489 let first_indent = buffer.indent_size_for_line(row);
12490 if first_indent.len == 0 || start.column > first_indent.len {
12491 trimmed_selections.push(start..end);
12492 } else {
12493 trimmed_selections.push(
12494 Point::new(row.0, first_indent.len)
12495 ..Point::new(row.0, buffer.line_len(row)),
12496 );
12497 for row in start.row + 1..=end.row {
12498 let mut line_len = buffer.line_len(MultiBufferRow(row));
12499 if row == end.row {
12500 line_len = end.column;
12501 }
12502 if line_len == 0 {
12503 trimmed_selections
12504 .push(Point::new(row, 0)..Point::new(row, line_len));
12505 continue;
12506 }
12507 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12508 if row_indent_size.len >= first_indent.len {
12509 trimmed_selections.push(
12510 Point::new(row, first_indent.len)..Point::new(row, line_len),
12511 );
12512 } else {
12513 trimmed_selections.clear();
12514 trimmed_selections.push(start..end);
12515 break;
12516 }
12517 }
12518 }
12519 } else {
12520 trimmed_selections.push(start..end);
12521 }
12522
12523 for trimmed_range in trimmed_selections {
12524 if is_first {
12525 is_first = false;
12526 } else {
12527 text += "\n";
12528 }
12529 let mut len = 0;
12530 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12531 text.push_str(chunk);
12532 len += chunk.len();
12533 }
12534 if add_trailing_newline {
12535 text.push('\n');
12536 len += 1;
12537 }
12538 clipboard_selections.push(ClipboardSelection {
12539 len,
12540 is_entire_line,
12541 first_line_indent: buffer
12542 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12543 .len,
12544 });
12545 }
12546 }
12547 }
12548
12549 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12550 text,
12551 clipboard_selections,
12552 ));
12553 }
12554
12555 pub fn do_paste(
12556 &mut self,
12557 text: &String,
12558 clipboard_selections: Option<Vec<ClipboardSelection>>,
12559 handle_entire_lines: bool,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 if self.read_only(cx) {
12564 return;
12565 }
12566
12567 let clipboard_text = Cow::Borrowed(text.as_str());
12568
12569 self.transact(window, cx, |this, window, cx| {
12570 let had_active_edit_prediction = this.has_active_edit_prediction();
12571 let display_map = this.display_snapshot(cx);
12572 let old_selections = this.selections.all::<usize>(&display_map);
12573 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12574
12575 if let Some(mut clipboard_selections) = clipboard_selections {
12576 let all_selections_were_entire_line =
12577 clipboard_selections.iter().all(|s| s.is_entire_line);
12578 let first_selection_indent_column =
12579 clipboard_selections.first().map(|s| s.first_line_indent);
12580 if clipboard_selections.len() != old_selections.len() {
12581 clipboard_selections.drain(..);
12582 }
12583 let mut auto_indent_on_paste = true;
12584
12585 this.buffer.update(cx, |buffer, cx| {
12586 let snapshot = buffer.read(cx);
12587 auto_indent_on_paste = snapshot
12588 .language_settings_at(cursor_offset, cx)
12589 .auto_indent_on_paste;
12590
12591 let mut start_offset = 0;
12592 let mut edits = Vec::new();
12593 let mut original_indent_columns = Vec::new();
12594 for (ix, selection) in old_selections.iter().enumerate() {
12595 let to_insert;
12596 let entire_line;
12597 let original_indent_column;
12598 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12599 let end_offset = start_offset + clipboard_selection.len;
12600 to_insert = &clipboard_text[start_offset..end_offset];
12601 entire_line = clipboard_selection.is_entire_line;
12602 start_offset = end_offset + 1;
12603 original_indent_column = Some(clipboard_selection.first_line_indent);
12604 } else {
12605 to_insert = &*clipboard_text;
12606 entire_line = all_selections_were_entire_line;
12607 original_indent_column = first_selection_indent_column
12608 }
12609
12610 let (range, to_insert) =
12611 if selection.is_empty() && handle_entire_lines && entire_line {
12612 // If the corresponding selection was empty when this slice of the
12613 // clipboard text was written, then the entire line containing the
12614 // selection was copied. If this selection is also currently empty,
12615 // then paste the line before the current line of the buffer.
12616 let column = selection.start.to_point(&snapshot).column as usize;
12617 let line_start = selection.start - column;
12618 (line_start..line_start, Cow::Borrowed(to_insert))
12619 } else {
12620 let language = snapshot.language_at(selection.head());
12621 let range = selection.range();
12622 if let Some(language) = language
12623 && language.name() == "Markdown".into()
12624 {
12625 edit_for_markdown_paste(
12626 &snapshot,
12627 range,
12628 to_insert,
12629 url::Url::parse(to_insert).ok(),
12630 )
12631 } else {
12632 (range, Cow::Borrowed(to_insert))
12633 }
12634 };
12635
12636 edits.push((range, to_insert));
12637 original_indent_columns.push(original_indent_column);
12638 }
12639 drop(snapshot);
12640
12641 buffer.edit(
12642 edits,
12643 if auto_indent_on_paste {
12644 Some(AutoindentMode::Block {
12645 original_indent_columns,
12646 })
12647 } else {
12648 None
12649 },
12650 cx,
12651 );
12652 });
12653
12654 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12655 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12656 } else {
12657 let url = url::Url::parse(&clipboard_text).ok();
12658
12659 let auto_indent_mode = if !clipboard_text.is_empty() {
12660 Some(AutoindentMode::Block {
12661 original_indent_columns: Vec::new(),
12662 })
12663 } else {
12664 None
12665 };
12666
12667 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12668 let snapshot = buffer.snapshot(cx);
12669
12670 let anchors = old_selections
12671 .iter()
12672 .map(|s| {
12673 let anchor = snapshot.anchor_after(s.head());
12674 s.map(|_| anchor)
12675 })
12676 .collect::<Vec<_>>();
12677
12678 let mut edits = Vec::new();
12679
12680 for selection in old_selections.iter() {
12681 let language = snapshot.language_at(selection.head());
12682 let range = selection.range();
12683
12684 let (edit_range, edit_text) = if let Some(language) = language
12685 && language.name() == "Markdown".into()
12686 {
12687 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12688 } else {
12689 (range, clipboard_text.clone())
12690 };
12691
12692 edits.push((edit_range, edit_text));
12693 }
12694
12695 drop(snapshot);
12696 buffer.edit(edits, auto_indent_mode, cx);
12697
12698 anchors
12699 });
12700
12701 this.change_selections(Default::default(), window, cx, |s| {
12702 s.select_anchors(selection_anchors);
12703 });
12704 }
12705
12706 let trigger_in_words =
12707 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12708
12709 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12710 });
12711 }
12712
12713 pub fn diff_clipboard_with_selection(
12714 &mut self,
12715 _: &DiffClipboardWithSelection,
12716 window: &mut Window,
12717 cx: &mut Context<Self>,
12718 ) {
12719 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12720
12721 if selections.is_empty() {
12722 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12723 return;
12724 };
12725
12726 let clipboard_text = match cx.read_from_clipboard() {
12727 Some(item) => match item.entries().first() {
12728 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12729 _ => None,
12730 },
12731 None => None,
12732 };
12733
12734 let Some(clipboard_text) = clipboard_text else {
12735 log::warn!("Clipboard doesn't contain text.");
12736 return;
12737 };
12738
12739 window.dispatch_action(
12740 Box::new(DiffClipboardWithSelectionData {
12741 clipboard_text,
12742 editor: cx.entity(),
12743 }),
12744 cx,
12745 );
12746 }
12747
12748 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12750 if let Some(item) = cx.read_from_clipboard() {
12751 let entries = item.entries();
12752
12753 match entries.first() {
12754 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12755 // of all the pasted entries.
12756 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12757 .do_paste(
12758 clipboard_string.text(),
12759 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12760 true,
12761 window,
12762 cx,
12763 ),
12764 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12765 }
12766 }
12767 }
12768
12769 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12770 if self.read_only(cx) {
12771 return;
12772 }
12773
12774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12775
12776 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12777 if let Some((selections, _)) =
12778 self.selection_history.transaction(transaction_id).cloned()
12779 {
12780 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12781 s.select_anchors(selections.to_vec());
12782 });
12783 } else {
12784 log::error!(
12785 "No entry in selection_history found for undo. \
12786 This may correspond to a bug where undo does not update the selection. \
12787 If this is occurring, please add details to \
12788 https://github.com/zed-industries/zed/issues/22692"
12789 );
12790 }
12791 self.request_autoscroll(Autoscroll::fit(), cx);
12792 self.unmark_text(window, cx);
12793 self.refresh_edit_prediction(true, false, window, cx);
12794 cx.emit(EditorEvent::Edited { transaction_id });
12795 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12796 }
12797 }
12798
12799 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12800 if self.read_only(cx) {
12801 return;
12802 }
12803
12804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12805
12806 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12807 if let Some((_, Some(selections))) =
12808 self.selection_history.transaction(transaction_id).cloned()
12809 {
12810 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12811 s.select_anchors(selections.to_vec());
12812 });
12813 } else {
12814 log::error!(
12815 "No entry in selection_history found for redo. \
12816 This may correspond to a bug where undo does not update the selection. \
12817 If this is occurring, please add details to \
12818 https://github.com/zed-industries/zed/issues/22692"
12819 );
12820 }
12821 self.request_autoscroll(Autoscroll::fit(), cx);
12822 self.unmark_text(window, cx);
12823 self.refresh_edit_prediction(true, false, window, cx);
12824 cx.emit(EditorEvent::Edited { transaction_id });
12825 }
12826 }
12827
12828 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12829 self.buffer
12830 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12831 }
12832
12833 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12834 self.buffer
12835 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12836 }
12837
12838 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12840 self.change_selections(Default::default(), window, cx, |s| {
12841 s.move_with(|map, selection| {
12842 let cursor = if selection.is_empty() {
12843 movement::left(map, selection.start)
12844 } else {
12845 selection.start
12846 };
12847 selection.collapse_to(cursor, SelectionGoal::None);
12848 });
12849 })
12850 }
12851
12852 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12853 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12854 self.change_selections(Default::default(), window, cx, |s| {
12855 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12856 })
12857 }
12858
12859 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12861 self.change_selections(Default::default(), window, cx, |s| {
12862 s.move_with(|map, selection| {
12863 let cursor = if selection.is_empty() {
12864 movement::right(map, selection.end)
12865 } else {
12866 selection.end
12867 };
12868 selection.collapse_to(cursor, SelectionGoal::None)
12869 });
12870 })
12871 }
12872
12873 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12875 self.change_selections(Default::default(), window, cx, |s| {
12876 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12877 });
12878 }
12879
12880 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12881 if self.take_rename(true, window, cx).is_some() {
12882 return;
12883 }
12884
12885 if self.mode.is_single_line() {
12886 cx.propagate();
12887 return;
12888 }
12889
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12891
12892 let text_layout_details = &self.text_layout_details(window);
12893 let selection_count = self.selections.count();
12894 let first_selection = self.selections.first_anchor();
12895
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_with(|map, selection| {
12898 if !selection.is_empty() {
12899 selection.goal = SelectionGoal::None;
12900 }
12901 let (cursor, goal) = movement::up(
12902 map,
12903 selection.start,
12904 selection.goal,
12905 false,
12906 text_layout_details,
12907 );
12908 selection.collapse_to(cursor, goal);
12909 });
12910 });
12911
12912 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12913 {
12914 cx.propagate();
12915 }
12916 }
12917
12918 pub fn move_up_by_lines(
12919 &mut self,
12920 action: &MoveUpByLines,
12921 window: &mut Window,
12922 cx: &mut Context<Self>,
12923 ) {
12924 if self.take_rename(true, window, cx).is_some() {
12925 return;
12926 }
12927
12928 if self.mode.is_single_line() {
12929 cx.propagate();
12930 return;
12931 }
12932
12933 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12934
12935 let text_layout_details = &self.text_layout_details(window);
12936
12937 self.change_selections(Default::default(), window, cx, |s| {
12938 s.move_with(|map, selection| {
12939 if !selection.is_empty() {
12940 selection.goal = SelectionGoal::None;
12941 }
12942 let (cursor, goal) = movement::up_by_rows(
12943 map,
12944 selection.start,
12945 action.lines,
12946 selection.goal,
12947 false,
12948 text_layout_details,
12949 );
12950 selection.collapse_to(cursor, goal);
12951 });
12952 })
12953 }
12954
12955 pub fn move_down_by_lines(
12956 &mut self,
12957 action: &MoveDownByLines,
12958 window: &mut Window,
12959 cx: &mut Context<Self>,
12960 ) {
12961 if self.take_rename(true, window, cx).is_some() {
12962 return;
12963 }
12964
12965 if self.mode.is_single_line() {
12966 cx.propagate();
12967 return;
12968 }
12969
12970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12971
12972 let text_layout_details = &self.text_layout_details(window);
12973
12974 self.change_selections(Default::default(), window, cx, |s| {
12975 s.move_with(|map, selection| {
12976 if !selection.is_empty() {
12977 selection.goal = SelectionGoal::None;
12978 }
12979 let (cursor, goal) = movement::down_by_rows(
12980 map,
12981 selection.start,
12982 action.lines,
12983 selection.goal,
12984 false,
12985 text_layout_details,
12986 );
12987 selection.collapse_to(cursor, goal);
12988 });
12989 })
12990 }
12991
12992 pub fn select_down_by_lines(
12993 &mut self,
12994 action: &SelectDownByLines,
12995 window: &mut Window,
12996 cx: &mut Context<Self>,
12997 ) {
12998 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12999 let text_layout_details = &self.text_layout_details(window);
13000 self.change_selections(Default::default(), window, cx, |s| {
13001 s.move_heads_with(|map, head, goal| {
13002 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13003 })
13004 })
13005 }
13006
13007 pub fn select_up_by_lines(
13008 &mut self,
13009 action: &SelectUpByLines,
13010 window: &mut Window,
13011 cx: &mut Context<Self>,
13012 ) {
13013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13014 let text_layout_details = &self.text_layout_details(window);
13015 self.change_selections(Default::default(), window, cx, |s| {
13016 s.move_heads_with(|map, head, goal| {
13017 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13018 })
13019 })
13020 }
13021
13022 pub fn select_page_up(
13023 &mut self,
13024 _: &SelectPageUp,
13025 window: &mut Window,
13026 cx: &mut Context<Self>,
13027 ) {
13028 let Some(row_count) = self.visible_row_count() else {
13029 return;
13030 };
13031
13032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13033
13034 let text_layout_details = &self.text_layout_details(window);
13035
13036 self.change_selections(Default::default(), window, cx, |s| {
13037 s.move_heads_with(|map, head, goal| {
13038 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13039 })
13040 })
13041 }
13042
13043 pub fn move_page_up(
13044 &mut self,
13045 action: &MovePageUp,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 if self.take_rename(true, window, cx).is_some() {
13050 return;
13051 }
13052
13053 if self
13054 .context_menu
13055 .borrow_mut()
13056 .as_mut()
13057 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13058 .unwrap_or(false)
13059 {
13060 return;
13061 }
13062
13063 if matches!(self.mode, EditorMode::SingleLine) {
13064 cx.propagate();
13065 return;
13066 }
13067
13068 let Some(row_count) = self.visible_row_count() else {
13069 return;
13070 };
13071
13072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13073
13074 let effects = if action.center_cursor {
13075 SelectionEffects::scroll(Autoscroll::center())
13076 } else {
13077 SelectionEffects::default()
13078 };
13079
13080 let text_layout_details = &self.text_layout_details(window);
13081
13082 self.change_selections(effects, window, cx, |s| {
13083 s.move_with(|map, selection| {
13084 if !selection.is_empty() {
13085 selection.goal = SelectionGoal::None;
13086 }
13087 let (cursor, goal) = movement::up_by_rows(
13088 map,
13089 selection.end,
13090 row_count,
13091 selection.goal,
13092 false,
13093 text_layout_details,
13094 );
13095 selection.collapse_to(cursor, goal);
13096 });
13097 });
13098 }
13099
13100 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13102 let text_layout_details = &self.text_layout_details(window);
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, goal| {
13105 movement::up(map, head, goal, false, text_layout_details)
13106 })
13107 })
13108 }
13109
13110 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13111 self.take_rename(true, window, cx);
13112
13113 if self.mode.is_single_line() {
13114 cx.propagate();
13115 return;
13116 }
13117
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13119
13120 let text_layout_details = &self.text_layout_details(window);
13121 let selection_count = self.selections.count();
13122 let first_selection = self.selections.first_anchor();
13123
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_with(|map, selection| {
13126 if !selection.is_empty() {
13127 selection.goal = SelectionGoal::None;
13128 }
13129 let (cursor, goal) = movement::down(
13130 map,
13131 selection.end,
13132 selection.goal,
13133 false,
13134 text_layout_details,
13135 );
13136 selection.collapse_to(cursor, goal);
13137 });
13138 });
13139
13140 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13141 {
13142 cx.propagate();
13143 }
13144 }
13145
13146 pub fn select_page_down(
13147 &mut self,
13148 _: &SelectPageDown,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 let Some(row_count) = self.visible_row_count() else {
13153 return;
13154 };
13155
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13157
13158 let text_layout_details = &self.text_layout_details(window);
13159
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_heads_with(|map, head, goal| {
13162 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13163 })
13164 })
13165 }
13166
13167 pub fn move_page_down(
13168 &mut self,
13169 action: &MovePageDown,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 if self.take_rename(true, window, cx).is_some() {
13174 return;
13175 }
13176
13177 if self
13178 .context_menu
13179 .borrow_mut()
13180 .as_mut()
13181 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13182 .unwrap_or(false)
13183 {
13184 return;
13185 }
13186
13187 if matches!(self.mode, EditorMode::SingleLine) {
13188 cx.propagate();
13189 return;
13190 }
13191
13192 let Some(row_count) = self.visible_row_count() else {
13193 return;
13194 };
13195
13196 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13197
13198 let effects = if action.center_cursor {
13199 SelectionEffects::scroll(Autoscroll::center())
13200 } else {
13201 SelectionEffects::default()
13202 };
13203
13204 let text_layout_details = &self.text_layout_details(window);
13205 self.change_selections(effects, window, cx, |s| {
13206 s.move_with(|map, selection| {
13207 if !selection.is_empty() {
13208 selection.goal = SelectionGoal::None;
13209 }
13210 let (cursor, goal) = movement::down_by_rows(
13211 map,
13212 selection.end,
13213 row_count,
13214 selection.goal,
13215 false,
13216 text_layout_details,
13217 );
13218 selection.collapse_to(cursor, goal);
13219 });
13220 });
13221 }
13222
13223 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13225 let text_layout_details = &self.text_layout_details(window);
13226 self.change_selections(Default::default(), window, cx, |s| {
13227 s.move_heads_with(|map, head, goal| {
13228 movement::down(map, head, goal, false, text_layout_details)
13229 })
13230 });
13231 }
13232
13233 pub fn context_menu_first(
13234 &mut self,
13235 _: &ContextMenuFirst,
13236 window: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13240 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13241 }
13242 }
13243
13244 pub fn context_menu_prev(
13245 &mut self,
13246 _: &ContextMenuPrevious,
13247 window: &mut Window,
13248 cx: &mut Context<Self>,
13249 ) {
13250 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13251 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13252 }
13253 }
13254
13255 pub fn context_menu_next(
13256 &mut self,
13257 _: &ContextMenuNext,
13258 window: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13262 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13263 }
13264 }
13265
13266 pub fn context_menu_last(
13267 &mut self,
13268 _: &ContextMenuLast,
13269 window: &mut Window,
13270 cx: &mut Context<Self>,
13271 ) {
13272 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13273 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13274 }
13275 }
13276
13277 pub fn signature_help_prev(
13278 &mut self,
13279 _: &SignatureHelpPrevious,
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 == 0 {
13285 popover.current_signature = popover.signatures.len() - 1;
13286 } else {
13287 popover.current_signature -= 1;
13288 }
13289 cx.notify();
13290 }
13291 }
13292
13293 pub fn signature_help_next(
13294 &mut self,
13295 _: &SignatureHelpNext,
13296 _: &mut Window,
13297 cx: &mut Context<Self>,
13298 ) {
13299 if let Some(popover) = self.signature_help_state.popover_mut() {
13300 if popover.current_signature + 1 == popover.signatures.len() {
13301 popover.current_signature = 0;
13302 } else {
13303 popover.current_signature += 1;
13304 }
13305 cx.notify();
13306 }
13307 }
13308
13309 pub fn move_to_previous_word_start(
13310 &mut self,
13311 _: &MoveToPreviousWordStart,
13312 window: &mut Window,
13313 cx: &mut Context<Self>,
13314 ) {
13315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13316 self.change_selections(Default::default(), window, cx, |s| {
13317 s.move_cursors_with(|map, head, _| {
13318 (
13319 movement::previous_word_start(map, head),
13320 SelectionGoal::None,
13321 )
13322 });
13323 })
13324 }
13325
13326 pub fn move_to_previous_subword_start(
13327 &mut self,
13328 _: &MoveToPreviousSubwordStart,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) {
13332 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13333 self.change_selections(Default::default(), window, cx, |s| {
13334 s.move_cursors_with(|map, head, _| {
13335 (
13336 movement::previous_subword_start(map, head),
13337 SelectionGoal::None,
13338 )
13339 });
13340 })
13341 }
13342
13343 pub fn select_to_previous_word_start(
13344 &mut self,
13345 _: &SelectToPreviousWordStart,
13346 window: &mut Window,
13347 cx: &mut Context<Self>,
13348 ) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350 self.change_selections(Default::default(), window, cx, |s| {
13351 s.move_heads_with(|map, head, _| {
13352 (
13353 movement::previous_word_start(map, head),
13354 SelectionGoal::None,
13355 )
13356 });
13357 })
13358 }
13359
13360 pub fn select_to_previous_subword_start(
13361 &mut self,
13362 _: &SelectToPreviousSubwordStart,
13363 window: &mut Window,
13364 cx: &mut Context<Self>,
13365 ) {
13366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, _| {
13369 (
13370 movement::previous_subword_start(map, head),
13371 SelectionGoal::None,
13372 )
13373 });
13374 })
13375 }
13376
13377 pub fn delete_to_previous_word_start(
13378 &mut self,
13379 action: &DeleteToPreviousWordStart,
13380 window: &mut Window,
13381 cx: &mut Context<Self>,
13382 ) {
13383 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13384 self.transact(window, cx, |this, window, cx| {
13385 this.select_autoclose_pair(window, cx);
13386 this.change_selections(Default::default(), window, cx, |s| {
13387 s.move_with(|map, selection| {
13388 if selection.is_empty() {
13389 let mut cursor = if action.ignore_newlines {
13390 movement::previous_word_start(map, selection.head())
13391 } else {
13392 movement::previous_word_start_or_newline(map, selection.head())
13393 };
13394 cursor = movement::adjust_greedy_deletion(
13395 map,
13396 selection.head(),
13397 cursor,
13398 action.ignore_brackets,
13399 );
13400 selection.set_head(cursor, SelectionGoal::None);
13401 }
13402 });
13403 });
13404 this.insert("", window, cx);
13405 });
13406 }
13407
13408 pub fn delete_to_previous_subword_start(
13409 &mut self,
13410 _: &DeleteToPreviousSubwordStart,
13411 window: &mut Window,
13412 cx: &mut Context<Self>,
13413 ) {
13414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13415 self.transact(window, cx, |this, window, cx| {
13416 this.select_autoclose_pair(window, cx);
13417 this.change_selections(Default::default(), window, cx, |s| {
13418 s.move_with(|map, selection| {
13419 if selection.is_empty() {
13420 let mut cursor = movement::previous_subword_start(map, selection.head());
13421 cursor =
13422 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13423 selection.set_head(cursor, SelectionGoal::None);
13424 }
13425 });
13426 });
13427 this.insert("", window, cx);
13428 });
13429 }
13430
13431 pub fn move_to_next_word_end(
13432 &mut self,
13433 _: &MoveToNextWordEnd,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 self.change_selections(Default::default(), window, cx, |s| {
13439 s.move_cursors_with(|map, head, _| {
13440 (movement::next_word_end(map, head), SelectionGoal::None)
13441 });
13442 })
13443 }
13444
13445 pub fn move_to_next_subword_end(
13446 &mut self,
13447 _: &MoveToNextSubwordEnd,
13448 window: &mut Window,
13449 cx: &mut Context<Self>,
13450 ) {
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 self.change_selections(Default::default(), window, cx, |s| {
13453 s.move_cursors_with(|map, head, _| {
13454 (movement::next_subword_end(map, head), SelectionGoal::None)
13455 });
13456 })
13457 }
13458
13459 pub fn select_to_next_word_end(
13460 &mut self,
13461 _: &SelectToNextWordEnd,
13462 window: &mut Window,
13463 cx: &mut Context<Self>,
13464 ) {
13465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13466 self.change_selections(Default::default(), window, cx, |s| {
13467 s.move_heads_with(|map, head, _| {
13468 (movement::next_word_end(map, head), SelectionGoal::None)
13469 });
13470 })
13471 }
13472
13473 pub fn select_to_next_subword_end(
13474 &mut self,
13475 _: &SelectToNextSubwordEnd,
13476 window: &mut Window,
13477 cx: &mut Context<Self>,
13478 ) {
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13480 self.change_selections(Default::default(), window, cx, |s| {
13481 s.move_heads_with(|map, head, _| {
13482 (movement::next_subword_end(map, head), SelectionGoal::None)
13483 });
13484 })
13485 }
13486
13487 pub fn delete_to_next_word_end(
13488 &mut self,
13489 action: &DeleteToNextWordEnd,
13490 window: &mut Window,
13491 cx: &mut Context<Self>,
13492 ) {
13493 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13494 self.transact(window, cx, |this, window, cx| {
13495 this.change_selections(Default::default(), window, cx, |s| {
13496 s.move_with(|map, selection| {
13497 if selection.is_empty() {
13498 let mut cursor = if action.ignore_newlines {
13499 movement::next_word_end(map, selection.head())
13500 } else {
13501 movement::next_word_end_or_newline(map, selection.head())
13502 };
13503 cursor = movement::adjust_greedy_deletion(
13504 map,
13505 selection.head(),
13506 cursor,
13507 action.ignore_brackets,
13508 );
13509 selection.set_head(cursor, SelectionGoal::None);
13510 }
13511 });
13512 });
13513 this.insert("", window, cx);
13514 });
13515 }
13516
13517 pub fn delete_to_next_subword_end(
13518 &mut self,
13519 _: &DeleteToNextSubwordEnd,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13524 self.transact(window, cx, |this, window, cx| {
13525 this.change_selections(Default::default(), window, cx, |s| {
13526 s.move_with(|map, selection| {
13527 if selection.is_empty() {
13528 let mut cursor = movement::next_subword_end(map, selection.head());
13529 cursor =
13530 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13531 selection.set_head(cursor, SelectionGoal::None);
13532 }
13533 });
13534 });
13535 this.insert("", window, cx);
13536 });
13537 }
13538
13539 pub fn move_to_beginning_of_line(
13540 &mut self,
13541 action: &MoveToBeginningOfLine,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13546 self.change_selections(Default::default(), window, cx, |s| {
13547 s.move_cursors_with(|map, head, _| {
13548 (
13549 movement::indented_line_beginning(
13550 map,
13551 head,
13552 action.stop_at_soft_wraps,
13553 action.stop_at_indent,
13554 ),
13555 SelectionGoal::None,
13556 )
13557 });
13558 })
13559 }
13560
13561 pub fn select_to_beginning_of_line(
13562 &mut self,
13563 action: &SelectToBeginningOfLine,
13564 window: &mut Window,
13565 cx: &mut Context<Self>,
13566 ) {
13567 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13568 self.change_selections(Default::default(), window, cx, |s| {
13569 s.move_heads_with(|map, head, _| {
13570 (
13571 movement::indented_line_beginning(
13572 map,
13573 head,
13574 action.stop_at_soft_wraps,
13575 action.stop_at_indent,
13576 ),
13577 SelectionGoal::None,
13578 )
13579 });
13580 });
13581 }
13582
13583 pub fn delete_to_beginning_of_line(
13584 &mut self,
13585 action: &DeleteToBeginningOfLine,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13590 self.transact(window, cx, |this, window, cx| {
13591 this.change_selections(Default::default(), window, cx, |s| {
13592 s.move_with(|_, selection| {
13593 selection.reversed = true;
13594 });
13595 });
13596
13597 this.select_to_beginning_of_line(
13598 &SelectToBeginningOfLine {
13599 stop_at_soft_wraps: false,
13600 stop_at_indent: action.stop_at_indent,
13601 },
13602 window,
13603 cx,
13604 );
13605 this.backspace(&Backspace, window, cx);
13606 });
13607 }
13608
13609 pub fn move_to_end_of_line(
13610 &mut self,
13611 action: &MoveToEndOfLine,
13612 window: &mut Window,
13613 cx: &mut Context<Self>,
13614 ) {
13615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13616 self.change_selections(Default::default(), window, cx, |s| {
13617 s.move_cursors_with(|map, head, _| {
13618 (
13619 movement::line_end(map, head, action.stop_at_soft_wraps),
13620 SelectionGoal::None,
13621 )
13622 });
13623 })
13624 }
13625
13626 pub fn select_to_end_of_line(
13627 &mut self,
13628 action: &SelectToEndOfLine,
13629 window: &mut Window,
13630 cx: &mut Context<Self>,
13631 ) {
13632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13633 self.change_selections(Default::default(), window, cx, |s| {
13634 s.move_heads_with(|map, head, _| {
13635 (
13636 movement::line_end(map, head, action.stop_at_soft_wraps),
13637 SelectionGoal::None,
13638 )
13639 });
13640 })
13641 }
13642
13643 pub fn delete_to_end_of_line(
13644 &mut self,
13645 _: &DeleteToEndOfLine,
13646 window: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13650 self.transact(window, cx, |this, window, cx| {
13651 this.select_to_end_of_line(
13652 &SelectToEndOfLine {
13653 stop_at_soft_wraps: false,
13654 },
13655 window,
13656 cx,
13657 );
13658 this.delete(&Delete, window, cx);
13659 });
13660 }
13661
13662 pub fn cut_to_end_of_line(
13663 &mut self,
13664 action: &CutToEndOfLine,
13665 window: &mut Window,
13666 cx: &mut Context<Self>,
13667 ) {
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13669 self.transact(window, cx, |this, window, cx| {
13670 this.select_to_end_of_line(
13671 &SelectToEndOfLine {
13672 stop_at_soft_wraps: false,
13673 },
13674 window,
13675 cx,
13676 );
13677 if !action.stop_at_newlines {
13678 this.change_selections(Default::default(), window, cx, |s| {
13679 s.move_with(|_, sel| {
13680 if sel.is_empty() {
13681 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13682 }
13683 });
13684 });
13685 }
13686 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13687 let item = this.cut_common(false, window, cx);
13688 cx.write_to_clipboard(item);
13689 });
13690 }
13691
13692 pub fn move_to_start_of_paragraph(
13693 &mut self,
13694 _: &MoveToStartOfParagraph,
13695 window: &mut Window,
13696 cx: &mut Context<Self>,
13697 ) {
13698 if matches!(self.mode, EditorMode::SingleLine) {
13699 cx.propagate();
13700 return;
13701 }
13702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13703 self.change_selections(Default::default(), window, cx, |s| {
13704 s.move_with(|map, selection| {
13705 selection.collapse_to(
13706 movement::start_of_paragraph(map, selection.head(), 1),
13707 SelectionGoal::None,
13708 )
13709 });
13710 })
13711 }
13712
13713 pub fn move_to_end_of_paragraph(
13714 &mut self,
13715 _: &MoveToEndOfParagraph,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 if matches!(self.mode, EditorMode::SingleLine) {
13720 cx.propagate();
13721 return;
13722 }
13723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|map, selection| {
13726 selection.collapse_to(
13727 movement::end_of_paragraph(map, selection.head(), 1),
13728 SelectionGoal::None,
13729 )
13730 });
13731 })
13732 }
13733
13734 pub fn select_to_start_of_paragraph(
13735 &mut self,
13736 _: &SelectToStartOfParagraph,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 if matches!(self.mode, EditorMode::SingleLine) {
13741 cx.propagate();
13742 return;
13743 }
13744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13745 self.change_selections(Default::default(), window, cx, |s| {
13746 s.move_heads_with(|map, head, _| {
13747 (
13748 movement::start_of_paragraph(map, head, 1),
13749 SelectionGoal::None,
13750 )
13751 });
13752 })
13753 }
13754
13755 pub fn select_to_end_of_paragraph(
13756 &mut self,
13757 _: &SelectToEndOfParagraph,
13758 window: &mut Window,
13759 cx: &mut Context<Self>,
13760 ) {
13761 if matches!(self.mode, EditorMode::SingleLine) {
13762 cx.propagate();
13763 return;
13764 }
13765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13766 self.change_selections(Default::default(), window, cx, |s| {
13767 s.move_heads_with(|map, head, _| {
13768 (
13769 movement::end_of_paragraph(map, head, 1),
13770 SelectionGoal::None,
13771 )
13772 });
13773 })
13774 }
13775
13776 pub fn move_to_start_of_excerpt(
13777 &mut self,
13778 _: &MoveToStartOfExcerpt,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) {
13782 if matches!(self.mode, EditorMode::SingleLine) {
13783 cx.propagate();
13784 return;
13785 }
13786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13787 self.change_selections(Default::default(), window, cx, |s| {
13788 s.move_with(|map, selection| {
13789 selection.collapse_to(
13790 movement::start_of_excerpt(
13791 map,
13792 selection.head(),
13793 workspace::searchable::Direction::Prev,
13794 ),
13795 SelectionGoal::None,
13796 )
13797 });
13798 })
13799 }
13800
13801 pub fn move_to_start_of_next_excerpt(
13802 &mut self,
13803 _: &MoveToStartOfNextExcerpt,
13804 window: &mut Window,
13805 cx: &mut Context<Self>,
13806 ) {
13807 if matches!(self.mode, EditorMode::SingleLine) {
13808 cx.propagate();
13809 return;
13810 }
13811
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_with(|map, selection| {
13814 selection.collapse_to(
13815 movement::start_of_excerpt(
13816 map,
13817 selection.head(),
13818 workspace::searchable::Direction::Next,
13819 ),
13820 SelectionGoal::None,
13821 )
13822 });
13823 })
13824 }
13825
13826 pub fn move_to_end_of_excerpt(
13827 &mut self,
13828 _: &MoveToEndOfExcerpt,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 if matches!(self.mode, EditorMode::SingleLine) {
13833 cx.propagate();
13834 return;
13835 }
13836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13837 self.change_selections(Default::default(), window, cx, |s| {
13838 s.move_with(|map, selection| {
13839 selection.collapse_to(
13840 movement::end_of_excerpt(
13841 map,
13842 selection.head(),
13843 workspace::searchable::Direction::Next,
13844 ),
13845 SelectionGoal::None,
13846 )
13847 });
13848 })
13849 }
13850
13851 pub fn move_to_end_of_previous_excerpt(
13852 &mut self,
13853 _: &MoveToEndOfPreviousExcerpt,
13854 window: &mut Window,
13855 cx: &mut Context<Self>,
13856 ) {
13857 if matches!(self.mode, EditorMode::SingleLine) {
13858 cx.propagate();
13859 return;
13860 }
13861 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13862 self.change_selections(Default::default(), window, cx, |s| {
13863 s.move_with(|map, selection| {
13864 selection.collapse_to(
13865 movement::end_of_excerpt(
13866 map,
13867 selection.head(),
13868 workspace::searchable::Direction::Prev,
13869 ),
13870 SelectionGoal::None,
13871 )
13872 });
13873 })
13874 }
13875
13876 pub fn select_to_start_of_excerpt(
13877 &mut self,
13878 _: &SelectToStartOfExcerpt,
13879 window: &mut Window,
13880 cx: &mut Context<Self>,
13881 ) {
13882 if matches!(self.mode, EditorMode::SingleLine) {
13883 cx.propagate();
13884 return;
13885 }
13886 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13887 self.change_selections(Default::default(), window, cx, |s| {
13888 s.move_heads_with(|map, head, _| {
13889 (
13890 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn select_to_start_of_next_excerpt(
13898 &mut self,
13899 _: &SelectToStartOfNextExcerpt,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if matches!(self.mode, EditorMode::SingleLine) {
13904 cx.propagate();
13905 return;
13906 }
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 self.change_selections(Default::default(), window, cx, |s| {
13909 s.move_heads_with(|map, head, _| {
13910 (
13911 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13912 SelectionGoal::None,
13913 )
13914 });
13915 })
13916 }
13917
13918 pub fn select_to_end_of_excerpt(
13919 &mut self,
13920 _: &SelectToEndOfExcerpt,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 if matches!(self.mode, EditorMode::SingleLine) {
13925 cx.propagate();
13926 return;
13927 }
13928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13929 self.change_selections(Default::default(), window, cx, |s| {
13930 s.move_heads_with(|map, head, _| {
13931 (
13932 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13933 SelectionGoal::None,
13934 )
13935 });
13936 })
13937 }
13938
13939 pub fn select_to_end_of_previous_excerpt(
13940 &mut self,
13941 _: &SelectToEndOfPreviousExcerpt,
13942 window: &mut Window,
13943 cx: &mut Context<Self>,
13944 ) {
13945 if matches!(self.mode, EditorMode::SingleLine) {
13946 cx.propagate();
13947 return;
13948 }
13949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13950 self.change_selections(Default::default(), window, cx, |s| {
13951 s.move_heads_with(|map, head, _| {
13952 (
13953 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13954 SelectionGoal::None,
13955 )
13956 });
13957 })
13958 }
13959
13960 pub fn move_to_beginning(
13961 &mut self,
13962 _: &MoveToBeginning,
13963 window: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 if matches!(self.mode, EditorMode::SingleLine) {
13967 cx.propagate();
13968 return;
13969 }
13970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.select_ranges(vec![0..0]);
13973 });
13974 }
13975
13976 pub fn select_to_beginning(
13977 &mut self,
13978 _: &SelectToBeginning,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13983 selection.set_head(Point::zero(), SelectionGoal::None);
13984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13985 self.change_selections(Default::default(), window, cx, |s| {
13986 s.select(vec![selection]);
13987 });
13988 }
13989
13990 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13991 if matches!(self.mode, EditorMode::SingleLine) {
13992 cx.propagate();
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 let cursor = self.buffer.read(cx).read(cx).len();
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.select_ranges(vec![cursor..cursor])
13999 });
14000 }
14001
14002 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14003 self.nav_history = nav_history;
14004 }
14005
14006 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14007 self.nav_history.as_ref()
14008 }
14009
14010 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14011 self.push_to_nav_history(
14012 self.selections.newest_anchor().head(),
14013 None,
14014 false,
14015 true,
14016 cx,
14017 );
14018 }
14019
14020 fn push_to_nav_history(
14021 &mut self,
14022 cursor_anchor: Anchor,
14023 new_position: Option<Point>,
14024 is_deactivate: bool,
14025 always: bool,
14026 cx: &mut Context<Self>,
14027 ) {
14028 if let Some(nav_history) = self.nav_history.as_mut() {
14029 let buffer = self.buffer.read(cx).read(cx);
14030 let cursor_position = cursor_anchor.to_point(&buffer);
14031 let scroll_state = self.scroll_manager.anchor();
14032 let scroll_top_row = scroll_state.top_row(&buffer);
14033 drop(buffer);
14034
14035 if let Some(new_position) = new_position {
14036 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14037 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14038 return;
14039 }
14040 }
14041
14042 nav_history.push(
14043 Some(NavigationData {
14044 cursor_anchor,
14045 cursor_position,
14046 scroll_anchor: scroll_state,
14047 scroll_top_row,
14048 }),
14049 cx,
14050 );
14051 cx.emit(EditorEvent::PushedToNavHistory {
14052 anchor: cursor_anchor,
14053 is_deactivate,
14054 })
14055 }
14056 }
14057
14058 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14060 let buffer = self.buffer.read(cx).snapshot(cx);
14061 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14062 selection.set_head(buffer.len(), SelectionGoal::None);
14063 self.change_selections(Default::default(), window, cx, |s| {
14064 s.select(vec![selection]);
14065 });
14066 }
14067
14068 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 let end = self.buffer.read(cx).read(cx).len();
14071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14072 s.select_ranges(vec![0..end]);
14073 });
14074 }
14075
14076 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14078 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14079 let mut selections = self.selections.all::<Point>(&display_map);
14080 let max_point = display_map.buffer_snapshot().max_point();
14081 for selection in &mut selections {
14082 let rows = selection.spanned_rows(true, &display_map);
14083 selection.start = Point::new(rows.start.0, 0);
14084 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14085 selection.reversed = false;
14086 }
14087 self.change_selections(Default::default(), window, cx, |s| {
14088 s.select(selections);
14089 });
14090 }
14091
14092 pub fn split_selection_into_lines(
14093 &mut self,
14094 action: &SplitSelectionIntoLines,
14095 window: &mut Window,
14096 cx: &mut Context<Self>,
14097 ) {
14098 let selections = self
14099 .selections
14100 .all::<Point>(&self.display_snapshot(cx))
14101 .into_iter()
14102 .map(|selection| selection.start..selection.end)
14103 .collect::<Vec<_>>();
14104 self.unfold_ranges(&selections, true, true, cx);
14105
14106 let mut new_selection_ranges = Vec::new();
14107 {
14108 let buffer = self.buffer.read(cx).read(cx);
14109 for selection in selections {
14110 for row in selection.start.row..selection.end.row {
14111 let line_start = Point::new(row, 0);
14112 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14113
14114 if action.keep_selections {
14115 // Keep the selection range for each line
14116 let selection_start = if row == selection.start.row {
14117 selection.start
14118 } else {
14119 line_start
14120 };
14121 new_selection_ranges.push(selection_start..line_end);
14122 } else {
14123 // Collapse to cursor at end of line
14124 new_selection_ranges.push(line_end..line_end);
14125 }
14126 }
14127
14128 let is_multiline_selection = selection.start.row != selection.end.row;
14129 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14130 // so this action feels more ergonomic when paired with other selection operations
14131 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14132 if !should_skip_last {
14133 if action.keep_selections {
14134 if is_multiline_selection {
14135 let line_start = Point::new(selection.end.row, 0);
14136 new_selection_ranges.push(line_start..selection.end);
14137 } else {
14138 new_selection_ranges.push(selection.start..selection.end);
14139 }
14140 } else {
14141 new_selection_ranges.push(selection.end..selection.end);
14142 }
14143 }
14144 }
14145 }
14146 self.change_selections(Default::default(), window, cx, |s| {
14147 s.select_ranges(new_selection_ranges);
14148 });
14149 }
14150
14151 pub fn add_selection_above(
14152 &mut self,
14153 action: &AddSelectionAbove,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 self.add_selection(true, action.skip_soft_wrap, window, cx);
14158 }
14159
14160 pub fn add_selection_below(
14161 &mut self,
14162 action: &AddSelectionBelow,
14163 window: &mut Window,
14164 cx: &mut Context<Self>,
14165 ) {
14166 self.add_selection(false, action.skip_soft_wrap, window, cx);
14167 }
14168
14169 fn add_selection(
14170 &mut self,
14171 above: bool,
14172 skip_soft_wrap: bool,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14177
14178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14179 let all_selections = self.selections.all::<Point>(&display_map);
14180 let text_layout_details = self.text_layout_details(window);
14181
14182 let (mut columnar_selections, new_selections_to_columnarize) = {
14183 if let Some(state) = self.add_selections_state.as_ref() {
14184 let columnar_selection_ids: HashSet<_> = state
14185 .groups
14186 .iter()
14187 .flat_map(|group| group.stack.iter())
14188 .copied()
14189 .collect();
14190
14191 all_selections
14192 .into_iter()
14193 .partition(|s| columnar_selection_ids.contains(&s.id))
14194 } else {
14195 (Vec::new(), all_selections)
14196 }
14197 };
14198
14199 let mut state = self
14200 .add_selections_state
14201 .take()
14202 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14203
14204 for selection in new_selections_to_columnarize {
14205 let range = selection.display_range(&display_map).sorted();
14206 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14207 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14208 let positions = start_x.min(end_x)..start_x.max(end_x);
14209 let mut stack = Vec::new();
14210 for row in range.start.row().0..=range.end.row().0 {
14211 if let Some(selection) = self.selections.build_columnar_selection(
14212 &display_map,
14213 DisplayRow(row),
14214 &positions,
14215 selection.reversed,
14216 &text_layout_details,
14217 ) {
14218 stack.push(selection.id);
14219 columnar_selections.push(selection);
14220 }
14221 }
14222 if !stack.is_empty() {
14223 if above {
14224 stack.reverse();
14225 }
14226 state.groups.push(AddSelectionsGroup { above, stack });
14227 }
14228 }
14229
14230 let mut final_selections = Vec::new();
14231 let end_row = if above {
14232 DisplayRow(0)
14233 } else {
14234 display_map.max_point().row()
14235 };
14236
14237 let mut last_added_item_per_group = HashMap::default();
14238 for group in state.groups.iter_mut() {
14239 if let Some(last_id) = group.stack.last() {
14240 last_added_item_per_group.insert(*last_id, group);
14241 }
14242 }
14243
14244 for selection in columnar_selections {
14245 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14246 if above == group.above {
14247 let range = selection.display_range(&display_map).sorted();
14248 debug_assert_eq!(range.start.row(), range.end.row());
14249 let mut row = range.start.row();
14250 let positions =
14251 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14252 Pixels::from(start)..Pixels::from(end)
14253 } else {
14254 let start_x =
14255 display_map.x_for_display_point(range.start, &text_layout_details);
14256 let end_x =
14257 display_map.x_for_display_point(range.end, &text_layout_details);
14258 start_x.min(end_x)..start_x.max(end_x)
14259 };
14260
14261 let mut maybe_new_selection = None;
14262 let direction = if above { -1 } else { 1 };
14263
14264 while row != end_row {
14265 if skip_soft_wrap {
14266 row = display_map
14267 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14268 .row();
14269 } else if above {
14270 row.0 -= 1;
14271 } else {
14272 row.0 += 1;
14273 }
14274
14275 if let Some(new_selection) = self.selections.build_columnar_selection(
14276 &display_map,
14277 row,
14278 &positions,
14279 selection.reversed,
14280 &text_layout_details,
14281 ) {
14282 maybe_new_selection = Some(new_selection);
14283 break;
14284 }
14285 }
14286
14287 if let Some(new_selection) = maybe_new_selection {
14288 group.stack.push(new_selection.id);
14289 if above {
14290 final_selections.push(new_selection);
14291 final_selections.push(selection);
14292 } else {
14293 final_selections.push(selection);
14294 final_selections.push(new_selection);
14295 }
14296 } else {
14297 final_selections.push(selection);
14298 }
14299 } else {
14300 group.stack.pop();
14301 }
14302 } else {
14303 final_selections.push(selection);
14304 }
14305 }
14306
14307 self.change_selections(Default::default(), window, cx, |s| {
14308 s.select(final_selections);
14309 });
14310
14311 let final_selection_ids: HashSet<_> = self
14312 .selections
14313 .all::<Point>(&display_map)
14314 .iter()
14315 .map(|s| s.id)
14316 .collect();
14317 state.groups.retain_mut(|group| {
14318 // selections might get merged above so we remove invalid items from stacks
14319 group.stack.retain(|id| final_selection_ids.contains(id));
14320
14321 // single selection in stack can be treated as initial state
14322 group.stack.len() > 1
14323 });
14324
14325 if !state.groups.is_empty() {
14326 self.add_selections_state = Some(state);
14327 }
14328 }
14329
14330 fn select_match_ranges(
14331 &mut self,
14332 range: Range<usize>,
14333 reversed: bool,
14334 replace_newest: bool,
14335 auto_scroll: Option<Autoscroll>,
14336 window: &mut Window,
14337 cx: &mut Context<Editor>,
14338 ) {
14339 self.unfold_ranges(
14340 std::slice::from_ref(&range),
14341 false,
14342 auto_scroll.is_some(),
14343 cx,
14344 );
14345 let effects = if let Some(scroll) = auto_scroll {
14346 SelectionEffects::scroll(scroll)
14347 } else {
14348 SelectionEffects::no_scroll()
14349 };
14350 self.change_selections(effects, window, cx, |s| {
14351 if replace_newest {
14352 s.delete(s.newest_anchor().id);
14353 }
14354 if reversed {
14355 s.insert_range(range.end..range.start);
14356 } else {
14357 s.insert_range(range);
14358 }
14359 });
14360 }
14361
14362 pub fn select_next_match_internal(
14363 &mut self,
14364 display_map: &DisplaySnapshot,
14365 replace_newest: bool,
14366 autoscroll: Option<Autoscroll>,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) -> Result<()> {
14370 let buffer = display_map.buffer_snapshot();
14371 let mut selections = self.selections.all::<usize>(&display_map);
14372 if let Some(mut select_next_state) = self.select_next_state.take() {
14373 let query = &select_next_state.query;
14374 if !select_next_state.done {
14375 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14376 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14377 let mut next_selected_range = None;
14378
14379 let bytes_after_last_selection =
14380 buffer.bytes_in_range(last_selection.end..buffer.len());
14381 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14382 let query_matches = query
14383 .stream_find_iter(bytes_after_last_selection)
14384 .map(|result| (last_selection.end, result))
14385 .chain(
14386 query
14387 .stream_find_iter(bytes_before_first_selection)
14388 .map(|result| (0, result)),
14389 );
14390
14391 for (start_offset, query_match) in query_matches {
14392 let query_match = query_match.unwrap(); // can only fail due to I/O
14393 let offset_range =
14394 start_offset + query_match.start()..start_offset + query_match.end();
14395
14396 if !select_next_state.wordwise
14397 || (!buffer.is_inside_word(offset_range.start, None)
14398 && !buffer.is_inside_word(offset_range.end, None))
14399 {
14400 let idx = selections
14401 .partition_point(|selection| selection.end <= offset_range.start);
14402 let overlaps = selections
14403 .get(idx)
14404 .map_or(false, |selection| selection.start < offset_range.end);
14405
14406 if !overlaps {
14407 next_selected_range = Some(offset_range);
14408 break;
14409 }
14410 }
14411 }
14412
14413 if let Some(next_selected_range) = next_selected_range {
14414 self.select_match_ranges(
14415 next_selected_range,
14416 last_selection.reversed,
14417 replace_newest,
14418 autoscroll,
14419 window,
14420 cx,
14421 );
14422 } else {
14423 select_next_state.done = true;
14424 }
14425 }
14426
14427 self.select_next_state = Some(select_next_state);
14428 } else {
14429 let mut only_carets = true;
14430 let mut same_text_selected = true;
14431 let mut selected_text = None;
14432
14433 let mut selections_iter = selections.iter().peekable();
14434 while let Some(selection) = selections_iter.next() {
14435 if selection.start != selection.end {
14436 only_carets = false;
14437 }
14438
14439 if same_text_selected {
14440 if selected_text.is_none() {
14441 selected_text =
14442 Some(buffer.text_for_range(selection.range()).collect::<String>());
14443 }
14444
14445 if let Some(next_selection) = selections_iter.peek() {
14446 if next_selection.range().len() == selection.range().len() {
14447 let next_selected_text = buffer
14448 .text_for_range(next_selection.range())
14449 .collect::<String>();
14450 if Some(next_selected_text) != selected_text {
14451 same_text_selected = false;
14452 selected_text = None;
14453 }
14454 } else {
14455 same_text_selected = false;
14456 selected_text = None;
14457 }
14458 }
14459 }
14460 }
14461
14462 if only_carets {
14463 for selection in &mut selections {
14464 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14465 selection.start = word_range.start;
14466 selection.end = word_range.end;
14467 selection.goal = SelectionGoal::None;
14468 selection.reversed = false;
14469 self.select_match_ranges(
14470 selection.start..selection.end,
14471 selection.reversed,
14472 replace_newest,
14473 autoscroll,
14474 window,
14475 cx,
14476 );
14477 }
14478
14479 if selections.len() == 1 {
14480 let selection = selections
14481 .last()
14482 .expect("ensured that there's only one selection");
14483 let query = buffer
14484 .text_for_range(selection.start..selection.end)
14485 .collect::<String>();
14486 let is_empty = query.is_empty();
14487 let select_state = SelectNextState {
14488 query: AhoCorasick::new(&[query])?,
14489 wordwise: true,
14490 done: is_empty,
14491 };
14492 self.select_next_state = Some(select_state);
14493 } else {
14494 self.select_next_state = None;
14495 }
14496 } else if let Some(selected_text) = selected_text {
14497 self.select_next_state = Some(SelectNextState {
14498 query: AhoCorasick::new(&[selected_text])?,
14499 wordwise: false,
14500 done: false,
14501 });
14502 self.select_next_match_internal(
14503 display_map,
14504 replace_newest,
14505 autoscroll,
14506 window,
14507 cx,
14508 )?;
14509 }
14510 }
14511 Ok(())
14512 }
14513
14514 pub fn select_all_matches(
14515 &mut self,
14516 _action: &SelectAllMatches,
14517 window: &mut Window,
14518 cx: &mut Context<Self>,
14519 ) -> Result<()> {
14520 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14521
14522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14523
14524 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14525 let Some(select_next_state) = self.select_next_state.as_mut() else {
14526 return Ok(());
14527 };
14528 if select_next_state.done {
14529 return Ok(());
14530 }
14531
14532 let mut new_selections = Vec::new();
14533
14534 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14535 let buffer = display_map.buffer_snapshot();
14536 let query_matches = select_next_state
14537 .query
14538 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14539
14540 for query_match in query_matches.into_iter() {
14541 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14542 let offset_range = if reversed {
14543 query_match.end()..query_match.start()
14544 } else {
14545 query_match.start()..query_match.end()
14546 };
14547
14548 if !select_next_state.wordwise
14549 || (!buffer.is_inside_word(offset_range.start, None)
14550 && !buffer.is_inside_word(offset_range.end, None))
14551 {
14552 new_selections.push(offset_range.start..offset_range.end);
14553 }
14554 }
14555
14556 select_next_state.done = true;
14557
14558 if new_selections.is_empty() {
14559 log::error!("bug: new_selections is empty in select_all_matches");
14560 return Ok(());
14561 }
14562
14563 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14564 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14565 selections.select_ranges(new_selections)
14566 });
14567
14568 Ok(())
14569 }
14570
14571 pub fn select_next(
14572 &mut self,
14573 action: &SelectNext,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) -> Result<()> {
14577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14579 self.select_next_match_internal(
14580 &display_map,
14581 action.replace_newest,
14582 Some(Autoscroll::newest()),
14583 window,
14584 cx,
14585 )?;
14586 Ok(())
14587 }
14588
14589 pub fn select_previous(
14590 &mut self,
14591 action: &SelectPrevious,
14592 window: &mut Window,
14593 cx: &mut Context<Self>,
14594 ) -> Result<()> {
14595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14597 let buffer = display_map.buffer_snapshot();
14598 let mut selections = self.selections.all::<usize>(&display_map);
14599 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14600 let query = &select_prev_state.query;
14601 if !select_prev_state.done {
14602 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14603 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14604 let mut next_selected_range = None;
14605 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14606 let bytes_before_last_selection =
14607 buffer.reversed_bytes_in_range(0..last_selection.start);
14608 let bytes_after_first_selection =
14609 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14610 let query_matches = query
14611 .stream_find_iter(bytes_before_last_selection)
14612 .map(|result| (last_selection.start, result))
14613 .chain(
14614 query
14615 .stream_find_iter(bytes_after_first_selection)
14616 .map(|result| (buffer.len(), result)),
14617 );
14618 for (end_offset, query_match) in query_matches {
14619 let query_match = query_match.unwrap(); // can only fail due to I/O
14620 let offset_range =
14621 end_offset - query_match.end()..end_offset - query_match.start();
14622
14623 if !select_prev_state.wordwise
14624 || (!buffer.is_inside_word(offset_range.start, None)
14625 && !buffer.is_inside_word(offset_range.end, None))
14626 {
14627 next_selected_range = Some(offset_range);
14628 break;
14629 }
14630 }
14631
14632 if let Some(next_selected_range) = next_selected_range {
14633 self.select_match_ranges(
14634 next_selected_range,
14635 last_selection.reversed,
14636 action.replace_newest,
14637 Some(Autoscroll::newest()),
14638 window,
14639 cx,
14640 );
14641 } else {
14642 select_prev_state.done = true;
14643 }
14644 }
14645
14646 self.select_prev_state = Some(select_prev_state);
14647 } else {
14648 let mut only_carets = true;
14649 let mut same_text_selected = true;
14650 let mut selected_text = None;
14651
14652 let mut selections_iter = selections.iter().peekable();
14653 while let Some(selection) = selections_iter.next() {
14654 if selection.start != selection.end {
14655 only_carets = false;
14656 }
14657
14658 if same_text_selected {
14659 if selected_text.is_none() {
14660 selected_text =
14661 Some(buffer.text_for_range(selection.range()).collect::<String>());
14662 }
14663
14664 if let Some(next_selection) = selections_iter.peek() {
14665 if next_selection.range().len() == selection.range().len() {
14666 let next_selected_text = buffer
14667 .text_for_range(next_selection.range())
14668 .collect::<String>();
14669 if Some(next_selected_text) != selected_text {
14670 same_text_selected = false;
14671 selected_text = None;
14672 }
14673 } else {
14674 same_text_selected = false;
14675 selected_text = None;
14676 }
14677 }
14678 }
14679 }
14680
14681 if only_carets {
14682 for selection in &mut selections {
14683 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14684 selection.start = word_range.start;
14685 selection.end = word_range.end;
14686 selection.goal = SelectionGoal::None;
14687 selection.reversed = false;
14688 self.select_match_ranges(
14689 selection.start..selection.end,
14690 selection.reversed,
14691 action.replace_newest,
14692 Some(Autoscroll::newest()),
14693 window,
14694 cx,
14695 );
14696 }
14697 if selections.len() == 1 {
14698 let selection = selections
14699 .last()
14700 .expect("ensured that there's only one selection");
14701 let query = buffer
14702 .text_for_range(selection.start..selection.end)
14703 .collect::<String>();
14704 let is_empty = query.is_empty();
14705 let select_state = SelectNextState {
14706 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14707 wordwise: true,
14708 done: is_empty,
14709 };
14710 self.select_prev_state = Some(select_state);
14711 } else {
14712 self.select_prev_state = None;
14713 }
14714 } else if let Some(selected_text) = selected_text {
14715 self.select_prev_state = Some(SelectNextState {
14716 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14717 wordwise: false,
14718 done: false,
14719 });
14720 self.select_previous(action, window, cx)?;
14721 }
14722 }
14723 Ok(())
14724 }
14725
14726 pub fn find_next_match(
14727 &mut self,
14728 _: &FindNextMatch,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) -> Result<()> {
14732 let selections = self.selections.disjoint_anchors_arc();
14733 match selections.first() {
14734 Some(first) if selections.len() >= 2 => {
14735 self.change_selections(Default::default(), window, cx, |s| {
14736 s.select_ranges([first.range()]);
14737 });
14738 }
14739 _ => self.select_next(
14740 &SelectNext {
14741 replace_newest: true,
14742 },
14743 window,
14744 cx,
14745 )?,
14746 }
14747 Ok(())
14748 }
14749
14750 pub fn find_previous_match(
14751 &mut self,
14752 _: &FindPreviousMatch,
14753 window: &mut Window,
14754 cx: &mut Context<Self>,
14755 ) -> Result<()> {
14756 let selections = self.selections.disjoint_anchors_arc();
14757 match selections.last() {
14758 Some(last) if selections.len() >= 2 => {
14759 self.change_selections(Default::default(), window, cx, |s| {
14760 s.select_ranges([last.range()]);
14761 });
14762 }
14763 _ => self.select_previous(
14764 &SelectPrevious {
14765 replace_newest: true,
14766 },
14767 window,
14768 cx,
14769 )?,
14770 }
14771 Ok(())
14772 }
14773
14774 pub fn toggle_comments(
14775 &mut self,
14776 action: &ToggleComments,
14777 window: &mut Window,
14778 cx: &mut Context<Self>,
14779 ) {
14780 if self.read_only(cx) {
14781 return;
14782 }
14783 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14784 let text_layout_details = &self.text_layout_details(window);
14785 self.transact(window, cx, |this, window, cx| {
14786 let mut selections = this
14787 .selections
14788 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14789 let mut edits = Vec::new();
14790 let mut selection_edit_ranges = Vec::new();
14791 let mut last_toggled_row = None;
14792 let snapshot = this.buffer.read(cx).read(cx);
14793 let empty_str: Arc<str> = Arc::default();
14794 let mut suffixes_inserted = Vec::new();
14795 let ignore_indent = action.ignore_indent;
14796
14797 fn comment_prefix_range(
14798 snapshot: &MultiBufferSnapshot,
14799 row: MultiBufferRow,
14800 comment_prefix: &str,
14801 comment_prefix_whitespace: &str,
14802 ignore_indent: bool,
14803 ) -> Range<Point> {
14804 let indent_size = if ignore_indent {
14805 0
14806 } else {
14807 snapshot.indent_size_for_line(row).len
14808 };
14809
14810 let start = Point::new(row.0, indent_size);
14811
14812 let mut line_bytes = snapshot
14813 .bytes_in_range(start..snapshot.max_point())
14814 .flatten()
14815 .copied();
14816
14817 // If this line currently begins with the line comment prefix, then record
14818 // the range containing the prefix.
14819 if line_bytes
14820 .by_ref()
14821 .take(comment_prefix.len())
14822 .eq(comment_prefix.bytes())
14823 {
14824 // Include any whitespace that matches the comment prefix.
14825 let matching_whitespace_len = line_bytes
14826 .zip(comment_prefix_whitespace.bytes())
14827 .take_while(|(a, b)| a == b)
14828 .count() as u32;
14829 let end = Point::new(
14830 start.row,
14831 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14832 );
14833 start..end
14834 } else {
14835 start..start
14836 }
14837 }
14838
14839 fn comment_suffix_range(
14840 snapshot: &MultiBufferSnapshot,
14841 row: MultiBufferRow,
14842 comment_suffix: &str,
14843 comment_suffix_has_leading_space: bool,
14844 ) -> Range<Point> {
14845 let end = Point::new(row.0, snapshot.line_len(row));
14846 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14847
14848 let mut line_end_bytes = snapshot
14849 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14850 .flatten()
14851 .copied();
14852
14853 let leading_space_len = if suffix_start_column > 0
14854 && line_end_bytes.next() == Some(b' ')
14855 && comment_suffix_has_leading_space
14856 {
14857 1
14858 } else {
14859 0
14860 };
14861
14862 // If this line currently begins with the line comment prefix, then record
14863 // the range containing the prefix.
14864 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14865 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14866 start..end
14867 } else {
14868 end..end
14869 }
14870 }
14871
14872 // TODO: Handle selections that cross excerpts
14873 for selection in &mut selections {
14874 let start_column = snapshot
14875 .indent_size_for_line(MultiBufferRow(selection.start.row))
14876 .len;
14877 let language = if let Some(language) =
14878 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14879 {
14880 language
14881 } else {
14882 continue;
14883 };
14884
14885 selection_edit_ranges.clear();
14886
14887 // If multiple selections contain a given row, avoid processing that
14888 // row more than once.
14889 let mut start_row = MultiBufferRow(selection.start.row);
14890 if last_toggled_row == Some(start_row) {
14891 start_row = start_row.next_row();
14892 }
14893 let end_row =
14894 if selection.end.row > selection.start.row && selection.end.column == 0 {
14895 MultiBufferRow(selection.end.row - 1)
14896 } else {
14897 MultiBufferRow(selection.end.row)
14898 };
14899 last_toggled_row = Some(end_row);
14900
14901 if start_row > end_row {
14902 continue;
14903 }
14904
14905 // If the language has line comments, toggle those.
14906 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14907
14908 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14909 if ignore_indent {
14910 full_comment_prefixes = full_comment_prefixes
14911 .into_iter()
14912 .map(|s| Arc::from(s.trim_end()))
14913 .collect();
14914 }
14915
14916 if !full_comment_prefixes.is_empty() {
14917 let first_prefix = full_comment_prefixes
14918 .first()
14919 .expect("prefixes is non-empty");
14920 let prefix_trimmed_lengths = full_comment_prefixes
14921 .iter()
14922 .map(|p| p.trim_end_matches(' ').len())
14923 .collect::<SmallVec<[usize; 4]>>();
14924
14925 let mut all_selection_lines_are_comments = true;
14926
14927 for row in start_row.0..=end_row.0 {
14928 let row = MultiBufferRow(row);
14929 if start_row < end_row && snapshot.is_line_blank(row) {
14930 continue;
14931 }
14932
14933 let prefix_range = full_comment_prefixes
14934 .iter()
14935 .zip(prefix_trimmed_lengths.iter().copied())
14936 .map(|(prefix, trimmed_prefix_len)| {
14937 comment_prefix_range(
14938 snapshot.deref(),
14939 row,
14940 &prefix[..trimmed_prefix_len],
14941 &prefix[trimmed_prefix_len..],
14942 ignore_indent,
14943 )
14944 })
14945 .max_by_key(|range| range.end.column - range.start.column)
14946 .expect("prefixes is non-empty");
14947
14948 if prefix_range.is_empty() {
14949 all_selection_lines_are_comments = false;
14950 }
14951
14952 selection_edit_ranges.push(prefix_range);
14953 }
14954
14955 if all_selection_lines_are_comments {
14956 edits.extend(
14957 selection_edit_ranges
14958 .iter()
14959 .cloned()
14960 .map(|range| (range, empty_str.clone())),
14961 );
14962 } else {
14963 let min_column = selection_edit_ranges
14964 .iter()
14965 .map(|range| range.start.column)
14966 .min()
14967 .unwrap_or(0);
14968 edits.extend(selection_edit_ranges.iter().map(|range| {
14969 let position = Point::new(range.start.row, min_column);
14970 (position..position, first_prefix.clone())
14971 }));
14972 }
14973 } else if let Some(BlockCommentConfig {
14974 start: full_comment_prefix,
14975 end: comment_suffix,
14976 ..
14977 }) = language.block_comment()
14978 {
14979 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14980 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14981 let prefix_range = comment_prefix_range(
14982 snapshot.deref(),
14983 start_row,
14984 comment_prefix,
14985 comment_prefix_whitespace,
14986 ignore_indent,
14987 );
14988 let suffix_range = comment_suffix_range(
14989 snapshot.deref(),
14990 end_row,
14991 comment_suffix.trim_start_matches(' '),
14992 comment_suffix.starts_with(' '),
14993 );
14994
14995 if prefix_range.is_empty() || suffix_range.is_empty() {
14996 edits.push((
14997 prefix_range.start..prefix_range.start,
14998 full_comment_prefix.clone(),
14999 ));
15000 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15001 suffixes_inserted.push((end_row, comment_suffix.len()));
15002 } else {
15003 edits.push((prefix_range, empty_str.clone()));
15004 edits.push((suffix_range, empty_str.clone()));
15005 }
15006 } else {
15007 continue;
15008 }
15009 }
15010
15011 drop(snapshot);
15012 this.buffer.update(cx, |buffer, cx| {
15013 buffer.edit(edits, None, cx);
15014 });
15015
15016 // Adjust selections so that they end before any comment suffixes that
15017 // were inserted.
15018 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15019 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15020 let snapshot = this.buffer.read(cx).read(cx);
15021 for selection in &mut selections {
15022 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15023 match row.cmp(&MultiBufferRow(selection.end.row)) {
15024 Ordering::Less => {
15025 suffixes_inserted.next();
15026 continue;
15027 }
15028 Ordering::Greater => break,
15029 Ordering::Equal => {
15030 if selection.end.column == snapshot.line_len(row) {
15031 if selection.is_empty() {
15032 selection.start.column -= suffix_len as u32;
15033 }
15034 selection.end.column -= suffix_len as u32;
15035 }
15036 break;
15037 }
15038 }
15039 }
15040 }
15041
15042 drop(snapshot);
15043 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15044
15045 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15046 let selections_on_single_row = selections.windows(2).all(|selections| {
15047 selections[0].start.row == selections[1].start.row
15048 && selections[0].end.row == selections[1].end.row
15049 && selections[0].start.row == selections[0].end.row
15050 });
15051 let selections_selecting = selections
15052 .iter()
15053 .any(|selection| selection.start != selection.end);
15054 let advance_downwards = action.advance_downwards
15055 && selections_on_single_row
15056 && !selections_selecting
15057 && !matches!(this.mode, EditorMode::SingleLine);
15058
15059 if advance_downwards {
15060 let snapshot = this.buffer.read(cx).snapshot(cx);
15061
15062 this.change_selections(Default::default(), window, cx, |s| {
15063 s.move_cursors_with(|display_snapshot, display_point, _| {
15064 let mut point = display_point.to_point(display_snapshot);
15065 point.row += 1;
15066 point = snapshot.clip_point(point, Bias::Left);
15067 let display_point = point.to_display_point(display_snapshot);
15068 let goal = SelectionGoal::HorizontalPosition(
15069 display_snapshot
15070 .x_for_display_point(display_point, text_layout_details)
15071 .into(),
15072 );
15073 (display_point, goal)
15074 })
15075 });
15076 }
15077 });
15078 }
15079
15080 pub fn select_enclosing_symbol(
15081 &mut self,
15082 _: &SelectEnclosingSymbol,
15083 window: &mut Window,
15084 cx: &mut Context<Self>,
15085 ) {
15086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15087
15088 let buffer = self.buffer.read(cx).snapshot(cx);
15089 let old_selections = self
15090 .selections
15091 .all::<usize>(&self.display_snapshot(cx))
15092 .into_boxed_slice();
15093
15094 fn update_selection(
15095 selection: &Selection<usize>,
15096 buffer_snap: &MultiBufferSnapshot,
15097 ) -> Option<Selection<usize>> {
15098 let cursor = selection.head();
15099 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15100 for symbol in symbols.iter().rev() {
15101 let start = symbol.range.start.to_offset(buffer_snap);
15102 let end = symbol.range.end.to_offset(buffer_snap);
15103 let new_range = start..end;
15104 if start < selection.start || end > selection.end {
15105 return Some(Selection {
15106 id: selection.id,
15107 start: new_range.start,
15108 end: new_range.end,
15109 goal: SelectionGoal::None,
15110 reversed: selection.reversed,
15111 });
15112 }
15113 }
15114 None
15115 }
15116
15117 let mut selected_larger_symbol = false;
15118 let new_selections = old_selections
15119 .iter()
15120 .map(|selection| match update_selection(selection, &buffer) {
15121 Some(new_selection) => {
15122 if new_selection.range() != selection.range() {
15123 selected_larger_symbol = true;
15124 }
15125 new_selection
15126 }
15127 None => selection.clone(),
15128 })
15129 .collect::<Vec<_>>();
15130
15131 if selected_larger_symbol {
15132 self.change_selections(Default::default(), window, cx, |s| {
15133 s.select(new_selections);
15134 });
15135 }
15136 }
15137
15138 pub fn select_larger_syntax_node(
15139 &mut self,
15140 _: &SelectLargerSyntaxNode,
15141 window: &mut Window,
15142 cx: &mut Context<Self>,
15143 ) {
15144 let Some(visible_row_count) = self.visible_row_count() else {
15145 return;
15146 };
15147 let old_selections: Box<[_]> = self
15148 .selections
15149 .all::<usize>(&self.display_snapshot(cx))
15150 .into();
15151 if old_selections.is_empty() {
15152 return;
15153 }
15154
15155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15156
15157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15158 let buffer = self.buffer.read(cx).snapshot(cx);
15159
15160 let mut selected_larger_node = false;
15161 let mut new_selections = old_selections
15162 .iter()
15163 .map(|selection| {
15164 let old_range = selection.start..selection.end;
15165
15166 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15167 // manually select word at selection
15168 if ["string_content", "inline"].contains(&node.kind()) {
15169 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15170 // ignore if word is already selected
15171 if !word_range.is_empty() && old_range != word_range {
15172 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15173 // only select word if start and end point belongs to same word
15174 if word_range == last_word_range {
15175 selected_larger_node = true;
15176 return Selection {
15177 id: selection.id,
15178 start: word_range.start,
15179 end: word_range.end,
15180 goal: SelectionGoal::None,
15181 reversed: selection.reversed,
15182 };
15183 }
15184 }
15185 }
15186 }
15187
15188 let mut new_range = old_range.clone();
15189 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15190 new_range = range;
15191 if !node.is_named() {
15192 continue;
15193 }
15194 if !display_map.intersects_fold(new_range.start)
15195 && !display_map.intersects_fold(new_range.end)
15196 {
15197 break;
15198 }
15199 }
15200
15201 selected_larger_node |= new_range != old_range;
15202 Selection {
15203 id: selection.id,
15204 start: new_range.start,
15205 end: new_range.end,
15206 goal: SelectionGoal::None,
15207 reversed: selection.reversed,
15208 }
15209 })
15210 .collect::<Vec<_>>();
15211
15212 if !selected_larger_node {
15213 return; // don't put this call in the history
15214 }
15215
15216 // scroll based on transformation done to the last selection created by the user
15217 let (last_old, last_new) = old_selections
15218 .last()
15219 .zip(new_selections.last().cloned())
15220 .expect("old_selections isn't empty");
15221
15222 // revert selection
15223 let is_selection_reversed = {
15224 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15225 new_selections.last_mut().expect("checked above").reversed =
15226 should_newest_selection_be_reversed;
15227 should_newest_selection_be_reversed
15228 };
15229
15230 if selected_larger_node {
15231 self.select_syntax_node_history.disable_clearing = true;
15232 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15233 s.select(new_selections.clone());
15234 });
15235 self.select_syntax_node_history.disable_clearing = false;
15236 }
15237
15238 let start_row = last_new.start.to_display_point(&display_map).row().0;
15239 let end_row = last_new.end.to_display_point(&display_map).row().0;
15240 let selection_height = end_row - start_row + 1;
15241 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15242
15243 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15244 let scroll_behavior = if fits_on_the_screen {
15245 self.request_autoscroll(Autoscroll::fit(), cx);
15246 SelectSyntaxNodeScrollBehavior::FitSelection
15247 } else if is_selection_reversed {
15248 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15249 SelectSyntaxNodeScrollBehavior::CursorTop
15250 } else {
15251 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15252 SelectSyntaxNodeScrollBehavior::CursorBottom
15253 };
15254
15255 self.select_syntax_node_history.push((
15256 old_selections,
15257 scroll_behavior,
15258 is_selection_reversed,
15259 ));
15260 }
15261
15262 pub fn select_smaller_syntax_node(
15263 &mut self,
15264 _: &SelectSmallerSyntaxNode,
15265 window: &mut Window,
15266 cx: &mut Context<Self>,
15267 ) {
15268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15269
15270 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15271 self.select_syntax_node_history.pop()
15272 {
15273 if let Some(selection) = selections.last_mut() {
15274 selection.reversed = is_selection_reversed;
15275 }
15276
15277 self.select_syntax_node_history.disable_clearing = true;
15278 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15279 s.select(selections.to_vec());
15280 });
15281 self.select_syntax_node_history.disable_clearing = false;
15282
15283 match scroll_behavior {
15284 SelectSyntaxNodeScrollBehavior::CursorTop => {
15285 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15286 }
15287 SelectSyntaxNodeScrollBehavior::FitSelection => {
15288 self.request_autoscroll(Autoscroll::fit(), cx);
15289 }
15290 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15291 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15292 }
15293 }
15294 }
15295 }
15296
15297 pub fn unwrap_syntax_node(
15298 &mut self,
15299 _: &UnwrapSyntaxNode,
15300 window: &mut Window,
15301 cx: &mut Context<Self>,
15302 ) {
15303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15304
15305 let buffer = self.buffer.read(cx).snapshot(cx);
15306 let selections = self
15307 .selections
15308 .all::<usize>(&self.display_snapshot(cx))
15309 .into_iter()
15310 // subtracting the offset requires sorting
15311 .sorted_by_key(|i| i.start);
15312
15313 let full_edits = selections
15314 .into_iter()
15315 .filter_map(|selection| {
15316 let child = if selection.is_empty()
15317 && let Some((_, ancestor_range)) =
15318 buffer.syntax_ancestor(selection.start..selection.end)
15319 {
15320 ancestor_range
15321 } else {
15322 selection.range()
15323 };
15324
15325 let mut parent = child.clone();
15326 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15327 parent = ancestor_range;
15328 if parent.start < child.start || parent.end > child.end {
15329 break;
15330 }
15331 }
15332
15333 if parent == child {
15334 return None;
15335 }
15336 let text = buffer.text_for_range(child).collect::<String>();
15337 Some((selection.id, parent, text))
15338 })
15339 .collect::<Vec<_>>();
15340 if full_edits.is_empty() {
15341 return;
15342 }
15343
15344 self.transact(window, cx, |this, window, cx| {
15345 this.buffer.update(cx, |buffer, cx| {
15346 buffer.edit(
15347 full_edits
15348 .iter()
15349 .map(|(_, p, t)| (p.clone(), t.clone()))
15350 .collect::<Vec<_>>(),
15351 None,
15352 cx,
15353 );
15354 });
15355 this.change_selections(Default::default(), window, cx, |s| {
15356 let mut offset = 0;
15357 let mut selections = vec![];
15358 for (id, parent, text) in full_edits {
15359 let start = parent.start - offset;
15360 offset += parent.len() - text.len();
15361 selections.push(Selection {
15362 id,
15363 start,
15364 end: start + text.len(),
15365 reversed: false,
15366 goal: Default::default(),
15367 });
15368 }
15369 s.select(selections);
15370 });
15371 });
15372 }
15373
15374 pub fn select_next_syntax_node(
15375 &mut self,
15376 _: &SelectNextSyntaxNode,
15377 window: &mut Window,
15378 cx: &mut Context<Self>,
15379 ) {
15380 let old_selections: Box<[_]> = self
15381 .selections
15382 .all::<usize>(&self.display_snapshot(cx))
15383 .into();
15384 if old_selections.is_empty() {
15385 return;
15386 }
15387
15388 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15389
15390 let buffer = self.buffer.read(cx).snapshot(cx);
15391 let mut selected_sibling = false;
15392
15393 let new_selections = old_selections
15394 .iter()
15395 .map(|selection| {
15396 let old_range = selection.start..selection.end;
15397
15398 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15399 let new_range = node.byte_range();
15400 selected_sibling = true;
15401 Selection {
15402 id: selection.id,
15403 start: new_range.start,
15404 end: new_range.end,
15405 goal: SelectionGoal::None,
15406 reversed: selection.reversed,
15407 }
15408 } else {
15409 selection.clone()
15410 }
15411 })
15412 .collect::<Vec<_>>();
15413
15414 if selected_sibling {
15415 self.change_selections(
15416 SelectionEffects::scroll(Autoscroll::fit()),
15417 window,
15418 cx,
15419 |s| {
15420 s.select(new_selections);
15421 },
15422 );
15423 }
15424 }
15425
15426 pub fn select_prev_syntax_node(
15427 &mut self,
15428 _: &SelectPreviousSyntaxNode,
15429 window: &mut Window,
15430 cx: &mut Context<Self>,
15431 ) {
15432 let old_selections: Box<[_]> = self
15433 .selections
15434 .all::<usize>(&self.display_snapshot(cx))
15435 .into();
15436 if old_selections.is_empty() {
15437 return;
15438 }
15439
15440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15441
15442 let buffer = self.buffer.read(cx).snapshot(cx);
15443 let mut selected_sibling = false;
15444
15445 let new_selections = old_selections
15446 .iter()
15447 .map(|selection| {
15448 let old_range = selection.start..selection.end;
15449
15450 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15451 let new_range = node.byte_range();
15452 selected_sibling = true;
15453 Selection {
15454 id: selection.id,
15455 start: new_range.start,
15456 end: new_range.end,
15457 goal: SelectionGoal::None,
15458 reversed: selection.reversed,
15459 }
15460 } else {
15461 selection.clone()
15462 }
15463 })
15464 .collect::<Vec<_>>();
15465
15466 if selected_sibling {
15467 self.change_selections(
15468 SelectionEffects::scroll(Autoscroll::fit()),
15469 window,
15470 cx,
15471 |s| {
15472 s.select(new_selections);
15473 },
15474 );
15475 }
15476 }
15477
15478 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15479 if !EditorSettings::get_global(cx).gutter.runnables {
15480 self.clear_tasks();
15481 return Task::ready(());
15482 }
15483 let project = self.project().map(Entity::downgrade);
15484 let task_sources = self.lsp_task_sources(cx);
15485 let multi_buffer = self.buffer.downgrade();
15486 cx.spawn_in(window, async move |editor, cx| {
15487 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15488 let Some(project) = project.and_then(|p| p.upgrade()) else {
15489 return;
15490 };
15491 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15492 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15493 }) else {
15494 return;
15495 };
15496
15497 let hide_runnables = project
15498 .update(cx, |project, _| project.is_via_collab())
15499 .unwrap_or(true);
15500 if hide_runnables {
15501 return;
15502 }
15503 let new_rows =
15504 cx.background_spawn({
15505 let snapshot = display_snapshot.clone();
15506 async move {
15507 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15508 }
15509 })
15510 .await;
15511 let Ok(lsp_tasks) =
15512 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15513 else {
15514 return;
15515 };
15516 let lsp_tasks = lsp_tasks.await;
15517
15518 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15519 lsp_tasks
15520 .into_iter()
15521 .flat_map(|(kind, tasks)| {
15522 tasks.into_iter().filter_map(move |(location, task)| {
15523 Some((kind.clone(), location?, task))
15524 })
15525 })
15526 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15527 let buffer = location.target.buffer;
15528 let buffer_snapshot = buffer.read(cx).snapshot();
15529 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15530 |(excerpt_id, snapshot, _)| {
15531 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15532 display_snapshot
15533 .buffer_snapshot()
15534 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15535 } else {
15536 None
15537 }
15538 },
15539 );
15540 if let Some(offset) = offset {
15541 let task_buffer_range =
15542 location.target.range.to_point(&buffer_snapshot);
15543 let context_buffer_range =
15544 task_buffer_range.to_offset(&buffer_snapshot);
15545 let context_range = BufferOffset(context_buffer_range.start)
15546 ..BufferOffset(context_buffer_range.end);
15547
15548 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15549 .or_insert_with(|| RunnableTasks {
15550 templates: Vec::new(),
15551 offset,
15552 column: task_buffer_range.start.column,
15553 extra_variables: HashMap::default(),
15554 context_range,
15555 })
15556 .templates
15557 .push((kind, task.original_task().clone()));
15558 }
15559
15560 acc
15561 })
15562 }) else {
15563 return;
15564 };
15565
15566 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15567 buffer.language_settings(cx).tasks.prefer_lsp
15568 }) else {
15569 return;
15570 };
15571
15572 let rows = Self::runnable_rows(
15573 project,
15574 display_snapshot,
15575 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15576 new_rows,
15577 cx.clone(),
15578 )
15579 .await;
15580 editor
15581 .update(cx, |editor, _| {
15582 editor.clear_tasks();
15583 for (key, mut value) in rows {
15584 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15585 value.templates.extend(lsp_tasks.templates);
15586 }
15587
15588 editor.insert_tasks(key, value);
15589 }
15590 for (key, value) in lsp_tasks_by_rows {
15591 editor.insert_tasks(key, value);
15592 }
15593 })
15594 .ok();
15595 })
15596 }
15597 fn fetch_runnable_ranges(
15598 snapshot: &DisplaySnapshot,
15599 range: Range<Anchor>,
15600 ) -> Vec<language::RunnableRange> {
15601 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15602 }
15603
15604 fn runnable_rows(
15605 project: Entity<Project>,
15606 snapshot: DisplaySnapshot,
15607 prefer_lsp: bool,
15608 runnable_ranges: Vec<RunnableRange>,
15609 cx: AsyncWindowContext,
15610 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15611 cx.spawn(async move |cx| {
15612 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15613 for mut runnable in runnable_ranges {
15614 let Some(tasks) = cx
15615 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15616 .ok()
15617 else {
15618 continue;
15619 };
15620 let mut tasks = tasks.await;
15621
15622 if prefer_lsp {
15623 tasks.retain(|(task_kind, _)| {
15624 !matches!(task_kind, TaskSourceKind::Language { .. })
15625 });
15626 }
15627 if tasks.is_empty() {
15628 continue;
15629 }
15630
15631 let point = runnable
15632 .run_range
15633 .start
15634 .to_point(&snapshot.buffer_snapshot());
15635 let Some(row) = snapshot
15636 .buffer_snapshot()
15637 .buffer_line_for_row(MultiBufferRow(point.row))
15638 .map(|(_, range)| range.start.row)
15639 else {
15640 continue;
15641 };
15642
15643 let context_range =
15644 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15645 runnable_rows.push((
15646 (runnable.buffer_id, row),
15647 RunnableTasks {
15648 templates: tasks,
15649 offset: snapshot
15650 .buffer_snapshot()
15651 .anchor_before(runnable.run_range.start),
15652 context_range,
15653 column: point.column,
15654 extra_variables: runnable.extra_captures,
15655 },
15656 ));
15657 }
15658 runnable_rows
15659 })
15660 }
15661
15662 fn templates_with_tags(
15663 project: &Entity<Project>,
15664 runnable: &mut Runnable,
15665 cx: &mut App,
15666 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15667 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15668 let (worktree_id, file) = project
15669 .buffer_for_id(runnable.buffer, cx)
15670 .and_then(|buffer| buffer.read(cx).file())
15671 .map(|file| (file.worktree_id(cx), file.clone()))
15672 .unzip();
15673
15674 (
15675 project.task_store().read(cx).task_inventory().cloned(),
15676 worktree_id,
15677 file,
15678 )
15679 });
15680
15681 let tags = mem::take(&mut runnable.tags);
15682 let language = runnable.language.clone();
15683 cx.spawn(async move |cx| {
15684 let mut templates_with_tags = Vec::new();
15685 if let Some(inventory) = inventory {
15686 for RunnableTag(tag) in tags {
15687 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15688 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15689 }) else {
15690 return templates_with_tags;
15691 };
15692 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15693 move |(_, template)| {
15694 template.tags.iter().any(|source_tag| source_tag == &tag)
15695 },
15696 ));
15697 }
15698 }
15699 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15700
15701 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15702 // Strongest source wins; if we have worktree tag binding, prefer that to
15703 // global and language bindings;
15704 // if we have a global binding, prefer that to language binding.
15705 let first_mismatch = templates_with_tags
15706 .iter()
15707 .position(|(tag_source, _)| tag_source != leading_tag_source);
15708 if let Some(index) = first_mismatch {
15709 templates_with_tags.truncate(index);
15710 }
15711 }
15712
15713 templates_with_tags
15714 })
15715 }
15716
15717 pub fn move_to_enclosing_bracket(
15718 &mut self,
15719 _: &MoveToEnclosingBracket,
15720 window: &mut Window,
15721 cx: &mut Context<Self>,
15722 ) {
15723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15724 self.change_selections(Default::default(), window, cx, |s| {
15725 s.move_offsets_with(|snapshot, selection| {
15726 let Some(enclosing_bracket_ranges) =
15727 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15728 else {
15729 return;
15730 };
15731
15732 let mut best_length = usize::MAX;
15733 let mut best_inside = false;
15734 let mut best_in_bracket_range = false;
15735 let mut best_destination = None;
15736 for (open, close) in enclosing_bracket_ranges {
15737 let close = close.to_inclusive();
15738 let length = close.end() - open.start;
15739 let inside = selection.start >= open.end && selection.end <= *close.start();
15740 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15741 || close.contains(&selection.head());
15742
15743 // If best is next to a bracket and current isn't, skip
15744 if !in_bracket_range && best_in_bracket_range {
15745 continue;
15746 }
15747
15748 // Prefer smaller lengths unless best is inside and current isn't
15749 if length > best_length && (best_inside || !inside) {
15750 continue;
15751 }
15752
15753 best_length = length;
15754 best_inside = inside;
15755 best_in_bracket_range = in_bracket_range;
15756 best_destination = Some(
15757 if close.contains(&selection.start) && close.contains(&selection.end) {
15758 if inside { open.end } else { open.start }
15759 } else if inside {
15760 *close.start()
15761 } else {
15762 *close.end()
15763 },
15764 );
15765 }
15766
15767 if let Some(destination) = best_destination {
15768 selection.collapse_to(destination, SelectionGoal::None);
15769 }
15770 })
15771 });
15772 }
15773
15774 pub fn undo_selection(
15775 &mut self,
15776 _: &UndoSelection,
15777 window: &mut Window,
15778 cx: &mut Context<Self>,
15779 ) {
15780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15781 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15782 self.selection_history.mode = SelectionHistoryMode::Undoing;
15783 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15784 this.end_selection(window, cx);
15785 this.change_selections(
15786 SelectionEffects::scroll(Autoscroll::newest()),
15787 window,
15788 cx,
15789 |s| s.select_anchors(entry.selections.to_vec()),
15790 );
15791 });
15792 self.selection_history.mode = SelectionHistoryMode::Normal;
15793
15794 self.select_next_state = entry.select_next_state;
15795 self.select_prev_state = entry.select_prev_state;
15796 self.add_selections_state = entry.add_selections_state;
15797 }
15798 }
15799
15800 pub fn redo_selection(
15801 &mut self,
15802 _: &RedoSelection,
15803 window: &mut Window,
15804 cx: &mut Context<Self>,
15805 ) {
15806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15807 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15808 self.selection_history.mode = SelectionHistoryMode::Redoing;
15809 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15810 this.end_selection(window, cx);
15811 this.change_selections(
15812 SelectionEffects::scroll(Autoscroll::newest()),
15813 window,
15814 cx,
15815 |s| s.select_anchors(entry.selections.to_vec()),
15816 );
15817 });
15818 self.selection_history.mode = SelectionHistoryMode::Normal;
15819
15820 self.select_next_state = entry.select_next_state;
15821 self.select_prev_state = entry.select_prev_state;
15822 self.add_selections_state = entry.add_selections_state;
15823 }
15824 }
15825
15826 pub fn expand_excerpts(
15827 &mut self,
15828 action: &ExpandExcerpts,
15829 _: &mut Window,
15830 cx: &mut Context<Self>,
15831 ) {
15832 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15833 }
15834
15835 pub fn expand_excerpts_down(
15836 &mut self,
15837 action: &ExpandExcerptsDown,
15838 _: &mut Window,
15839 cx: &mut Context<Self>,
15840 ) {
15841 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15842 }
15843
15844 pub fn expand_excerpts_up(
15845 &mut self,
15846 action: &ExpandExcerptsUp,
15847 _: &mut Window,
15848 cx: &mut Context<Self>,
15849 ) {
15850 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15851 }
15852
15853 pub fn expand_excerpts_for_direction(
15854 &mut self,
15855 lines: u32,
15856 direction: ExpandExcerptDirection,
15857
15858 cx: &mut Context<Self>,
15859 ) {
15860 let selections = self.selections.disjoint_anchors_arc();
15861
15862 let lines = if lines == 0 {
15863 EditorSettings::get_global(cx).expand_excerpt_lines
15864 } else {
15865 lines
15866 };
15867
15868 self.buffer.update(cx, |buffer, cx| {
15869 let snapshot = buffer.snapshot(cx);
15870 let mut excerpt_ids = selections
15871 .iter()
15872 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15873 .collect::<Vec<_>>();
15874 excerpt_ids.sort();
15875 excerpt_ids.dedup();
15876 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15877 })
15878 }
15879
15880 pub fn expand_excerpt(
15881 &mut self,
15882 excerpt: ExcerptId,
15883 direction: ExpandExcerptDirection,
15884 window: &mut Window,
15885 cx: &mut Context<Self>,
15886 ) {
15887 let current_scroll_position = self.scroll_position(cx);
15888 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15889 let mut scroll = None;
15890
15891 if direction == ExpandExcerptDirection::Down {
15892 let multi_buffer = self.buffer.read(cx);
15893 let snapshot = multi_buffer.snapshot(cx);
15894 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15895 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15896 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15897 {
15898 let buffer_snapshot = buffer.read(cx).snapshot();
15899 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15900 let last_row = buffer_snapshot.max_point().row;
15901 let lines_below = last_row.saturating_sub(excerpt_end_row);
15902 if lines_below >= lines_to_expand {
15903 scroll = Some(
15904 current_scroll_position
15905 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15906 );
15907 }
15908 }
15909 }
15910 if direction == ExpandExcerptDirection::Up
15911 && self
15912 .buffer
15913 .read(cx)
15914 .snapshot(cx)
15915 .excerpt_before(excerpt)
15916 .is_none()
15917 {
15918 scroll = Some(current_scroll_position);
15919 }
15920
15921 self.buffer.update(cx, |buffer, cx| {
15922 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15923 });
15924
15925 if let Some(new_scroll_position) = scroll {
15926 self.set_scroll_position(new_scroll_position, window, cx);
15927 }
15928 }
15929
15930 pub fn go_to_singleton_buffer_point(
15931 &mut self,
15932 point: Point,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 self.go_to_singleton_buffer_range(point..point, window, cx);
15937 }
15938
15939 pub fn go_to_singleton_buffer_range(
15940 &mut self,
15941 range: Range<Point>,
15942 window: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) {
15945 let multibuffer = self.buffer().read(cx);
15946 let Some(buffer) = multibuffer.as_singleton() else {
15947 return;
15948 };
15949 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15950 return;
15951 };
15952 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15953 return;
15954 };
15955 self.change_selections(
15956 SelectionEffects::default().nav_history(true),
15957 window,
15958 cx,
15959 |s| s.select_anchor_ranges([start..end]),
15960 );
15961 }
15962
15963 pub fn go_to_diagnostic(
15964 &mut self,
15965 action: &GoToDiagnostic,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) {
15969 if !self.diagnostics_enabled() {
15970 return;
15971 }
15972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15973 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15974 }
15975
15976 pub fn go_to_prev_diagnostic(
15977 &mut self,
15978 action: &GoToPreviousDiagnostic,
15979 window: &mut Window,
15980 cx: &mut Context<Self>,
15981 ) {
15982 if !self.diagnostics_enabled() {
15983 return;
15984 }
15985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15986 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15987 }
15988
15989 pub fn go_to_diagnostic_impl(
15990 &mut self,
15991 direction: Direction,
15992 severity: GoToDiagnosticSeverityFilter,
15993 window: &mut Window,
15994 cx: &mut Context<Self>,
15995 ) {
15996 let buffer = self.buffer.read(cx).snapshot(cx);
15997 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15998
15999 let mut active_group_id = None;
16000 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16001 && active_group.active_range.start.to_offset(&buffer) == selection.start
16002 {
16003 active_group_id = Some(active_group.group_id);
16004 }
16005
16006 fn filtered<'a>(
16007 severity: GoToDiagnosticSeverityFilter,
16008 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16009 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16010 diagnostics
16011 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16012 .filter(|entry| entry.range.start != entry.range.end)
16013 .filter(|entry| !entry.diagnostic.is_unnecessary)
16014 }
16015
16016 let before = filtered(
16017 severity,
16018 buffer
16019 .diagnostics_in_range(0..selection.start)
16020 .filter(|entry| entry.range.start <= selection.start),
16021 );
16022 let after = filtered(
16023 severity,
16024 buffer
16025 .diagnostics_in_range(selection.start..buffer.len())
16026 .filter(|entry| entry.range.start >= selection.start),
16027 );
16028
16029 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16030 if direction == Direction::Prev {
16031 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16032 {
16033 for diagnostic in prev_diagnostics.into_iter().rev() {
16034 if diagnostic.range.start != selection.start
16035 || active_group_id
16036 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16037 {
16038 found = Some(diagnostic);
16039 break 'outer;
16040 }
16041 }
16042 }
16043 } else {
16044 for diagnostic in after.chain(before) {
16045 if diagnostic.range.start != selection.start
16046 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16047 {
16048 found = Some(diagnostic);
16049 break;
16050 }
16051 }
16052 }
16053 let Some(next_diagnostic) = found else {
16054 return;
16055 };
16056
16057 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16058 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16059 return;
16060 };
16061 let snapshot = self.snapshot(window, cx);
16062 if snapshot.intersects_fold(next_diagnostic.range.start) {
16063 self.unfold_ranges(
16064 std::slice::from_ref(&next_diagnostic.range),
16065 true,
16066 false,
16067 cx,
16068 );
16069 }
16070 self.change_selections(Default::default(), window, cx, |s| {
16071 s.select_ranges(vec![
16072 next_diagnostic.range.start..next_diagnostic.range.start,
16073 ])
16074 });
16075 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16076 self.refresh_edit_prediction(false, true, window, cx);
16077 }
16078
16079 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16081 let snapshot = self.snapshot(window, cx);
16082 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16083 self.go_to_hunk_before_or_after_position(
16084 &snapshot,
16085 selection.head(),
16086 Direction::Next,
16087 window,
16088 cx,
16089 );
16090 }
16091
16092 pub fn go_to_hunk_before_or_after_position(
16093 &mut self,
16094 snapshot: &EditorSnapshot,
16095 position: Point,
16096 direction: Direction,
16097 window: &mut Window,
16098 cx: &mut Context<Editor>,
16099 ) {
16100 let row = if direction == Direction::Next {
16101 self.hunk_after_position(snapshot, position)
16102 .map(|hunk| hunk.row_range.start)
16103 } else {
16104 self.hunk_before_position(snapshot, position)
16105 };
16106
16107 if let Some(row) = row {
16108 let destination = Point::new(row.0, 0);
16109 let autoscroll = Autoscroll::center();
16110
16111 self.unfold_ranges(&[destination..destination], false, false, cx);
16112 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16113 s.select_ranges([destination..destination]);
16114 });
16115 }
16116 }
16117
16118 fn hunk_after_position(
16119 &mut self,
16120 snapshot: &EditorSnapshot,
16121 position: Point,
16122 ) -> Option<MultiBufferDiffHunk> {
16123 snapshot
16124 .buffer_snapshot()
16125 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16126 .find(|hunk| hunk.row_range.start.0 > position.row)
16127 .or_else(|| {
16128 snapshot
16129 .buffer_snapshot()
16130 .diff_hunks_in_range(Point::zero()..position)
16131 .find(|hunk| hunk.row_range.end.0 < position.row)
16132 })
16133 }
16134
16135 fn go_to_prev_hunk(
16136 &mut self,
16137 _: &GoToPreviousHunk,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16142 let snapshot = self.snapshot(window, cx);
16143 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16144 self.go_to_hunk_before_or_after_position(
16145 &snapshot,
16146 selection.head(),
16147 Direction::Prev,
16148 window,
16149 cx,
16150 );
16151 }
16152
16153 fn hunk_before_position(
16154 &mut self,
16155 snapshot: &EditorSnapshot,
16156 position: Point,
16157 ) -> Option<MultiBufferRow> {
16158 snapshot
16159 .buffer_snapshot()
16160 .diff_hunk_before(position)
16161 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16162 }
16163
16164 fn go_to_next_change(
16165 &mut self,
16166 _: &GoToNextChange,
16167 window: &mut Window,
16168 cx: &mut Context<Self>,
16169 ) {
16170 if let Some(selections) = self
16171 .change_list
16172 .next_change(1, Direction::Next)
16173 .map(|s| s.to_vec())
16174 {
16175 self.change_selections(Default::default(), window, cx, |s| {
16176 let map = s.display_map();
16177 s.select_display_ranges(selections.iter().map(|a| {
16178 let point = a.to_display_point(&map);
16179 point..point
16180 }))
16181 })
16182 }
16183 }
16184
16185 fn go_to_previous_change(
16186 &mut self,
16187 _: &GoToPreviousChange,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 if let Some(selections) = self
16192 .change_list
16193 .next_change(1, Direction::Prev)
16194 .map(|s| s.to_vec())
16195 {
16196 self.change_selections(Default::default(), window, cx, |s| {
16197 let map = s.display_map();
16198 s.select_display_ranges(selections.iter().map(|a| {
16199 let point = a.to_display_point(&map);
16200 point..point
16201 }))
16202 })
16203 }
16204 }
16205
16206 pub fn go_to_next_document_highlight(
16207 &mut self,
16208 _: &GoToNextDocumentHighlight,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16213 }
16214
16215 pub fn go_to_prev_document_highlight(
16216 &mut self,
16217 _: &GoToPreviousDocumentHighlight,
16218 window: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16222 }
16223
16224 pub fn go_to_document_highlight_before_or_after_position(
16225 &mut self,
16226 direction: Direction,
16227 window: &mut Window,
16228 cx: &mut Context<Editor>,
16229 ) {
16230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16231 let snapshot = self.snapshot(window, cx);
16232 let buffer = &snapshot.buffer_snapshot();
16233 let position = self
16234 .selections
16235 .newest::<Point>(&snapshot.display_snapshot)
16236 .head();
16237 let anchor_position = buffer.anchor_after(position);
16238
16239 // Get all document highlights (both read and write)
16240 let mut all_highlights = Vec::new();
16241
16242 if let Some((_, read_highlights)) = self
16243 .background_highlights
16244 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16245 {
16246 all_highlights.extend(read_highlights.iter());
16247 }
16248
16249 if let Some((_, write_highlights)) = self
16250 .background_highlights
16251 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16252 {
16253 all_highlights.extend(write_highlights.iter());
16254 }
16255
16256 if all_highlights.is_empty() {
16257 return;
16258 }
16259
16260 // Sort highlights by position
16261 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16262
16263 let target_highlight = match direction {
16264 Direction::Next => {
16265 // Find the first highlight after the current position
16266 all_highlights
16267 .iter()
16268 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16269 }
16270 Direction::Prev => {
16271 // Find the last highlight before the current position
16272 all_highlights
16273 .iter()
16274 .rev()
16275 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16276 }
16277 };
16278
16279 if let Some(highlight) = target_highlight {
16280 let destination = highlight.start.to_point(buffer);
16281 let autoscroll = Autoscroll::center();
16282
16283 self.unfold_ranges(&[destination..destination], false, false, cx);
16284 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16285 s.select_ranges([destination..destination]);
16286 });
16287 }
16288 }
16289
16290 fn go_to_line<T: 'static>(
16291 &mut self,
16292 position: Anchor,
16293 highlight_color: Option<Hsla>,
16294 window: &mut Window,
16295 cx: &mut Context<Self>,
16296 ) {
16297 let snapshot = self.snapshot(window, cx).display_snapshot;
16298 let position = position.to_point(&snapshot.buffer_snapshot());
16299 let start = snapshot
16300 .buffer_snapshot()
16301 .clip_point(Point::new(position.row, 0), Bias::Left);
16302 let end = start + Point::new(1, 0);
16303 let start = snapshot.buffer_snapshot().anchor_before(start);
16304 let end = snapshot.buffer_snapshot().anchor_before(end);
16305
16306 self.highlight_rows::<T>(
16307 start..end,
16308 highlight_color
16309 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16310 Default::default(),
16311 cx,
16312 );
16313
16314 if self.buffer.read(cx).is_singleton() {
16315 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16316 }
16317 }
16318
16319 pub fn go_to_definition(
16320 &mut self,
16321 _: &GoToDefinition,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) -> Task<Result<Navigated>> {
16325 let definition =
16326 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16327 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16328 cx.spawn_in(window, async move |editor, cx| {
16329 if definition.await? == Navigated::Yes {
16330 return Ok(Navigated::Yes);
16331 }
16332 match fallback_strategy {
16333 GoToDefinitionFallback::None => Ok(Navigated::No),
16334 GoToDefinitionFallback::FindAllReferences => {
16335 match editor.update_in(cx, |editor, window, cx| {
16336 editor.find_all_references(&FindAllReferences, window, cx)
16337 })? {
16338 Some(references) => references.await,
16339 None => Ok(Navigated::No),
16340 }
16341 }
16342 }
16343 })
16344 }
16345
16346 pub fn go_to_declaration(
16347 &mut self,
16348 _: &GoToDeclaration,
16349 window: &mut Window,
16350 cx: &mut Context<Self>,
16351 ) -> Task<Result<Navigated>> {
16352 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16353 }
16354
16355 pub fn go_to_declaration_split(
16356 &mut self,
16357 _: &GoToDeclaration,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Task<Result<Navigated>> {
16361 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16362 }
16363
16364 pub fn go_to_implementation(
16365 &mut self,
16366 _: &GoToImplementation,
16367 window: &mut Window,
16368 cx: &mut Context<Self>,
16369 ) -> Task<Result<Navigated>> {
16370 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16371 }
16372
16373 pub fn go_to_implementation_split(
16374 &mut self,
16375 _: &GoToImplementationSplit,
16376 window: &mut Window,
16377 cx: &mut Context<Self>,
16378 ) -> Task<Result<Navigated>> {
16379 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16380 }
16381
16382 pub fn go_to_type_definition(
16383 &mut self,
16384 _: &GoToTypeDefinition,
16385 window: &mut Window,
16386 cx: &mut Context<Self>,
16387 ) -> Task<Result<Navigated>> {
16388 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16389 }
16390
16391 pub fn go_to_definition_split(
16392 &mut self,
16393 _: &GoToDefinitionSplit,
16394 window: &mut Window,
16395 cx: &mut Context<Self>,
16396 ) -> Task<Result<Navigated>> {
16397 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16398 }
16399
16400 pub fn go_to_type_definition_split(
16401 &mut self,
16402 _: &GoToTypeDefinitionSplit,
16403 window: &mut Window,
16404 cx: &mut Context<Self>,
16405 ) -> Task<Result<Navigated>> {
16406 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16407 }
16408
16409 fn go_to_definition_of_kind(
16410 &mut self,
16411 kind: GotoDefinitionKind,
16412 split: bool,
16413 window: &mut Window,
16414 cx: &mut Context<Self>,
16415 ) -> Task<Result<Navigated>> {
16416 let Some(provider) = self.semantics_provider.clone() else {
16417 return Task::ready(Ok(Navigated::No));
16418 };
16419 let head = self
16420 .selections
16421 .newest::<usize>(&self.display_snapshot(cx))
16422 .head();
16423 let buffer = self.buffer.read(cx);
16424 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16425 return Task::ready(Ok(Navigated::No));
16426 };
16427 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16428 return Task::ready(Ok(Navigated::No));
16429 };
16430
16431 cx.spawn_in(window, async move |editor, cx| {
16432 let Some(definitions) = definitions.await? else {
16433 return Ok(Navigated::No);
16434 };
16435 let navigated = editor
16436 .update_in(cx, |editor, window, cx| {
16437 editor.navigate_to_hover_links(
16438 Some(kind),
16439 definitions
16440 .into_iter()
16441 .filter(|location| {
16442 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16443 })
16444 .map(HoverLink::Text)
16445 .collect::<Vec<_>>(),
16446 split,
16447 window,
16448 cx,
16449 )
16450 })?
16451 .await?;
16452 anyhow::Ok(navigated)
16453 })
16454 }
16455
16456 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16457 let selection = self.selections.newest_anchor();
16458 let head = selection.head();
16459 let tail = selection.tail();
16460
16461 let Some((buffer, start_position)) =
16462 self.buffer.read(cx).text_anchor_for_position(head, cx)
16463 else {
16464 return;
16465 };
16466
16467 let end_position = if head != tail {
16468 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16469 return;
16470 };
16471 Some(pos)
16472 } else {
16473 None
16474 };
16475
16476 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16477 let url = if let Some(end_pos) = end_position {
16478 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16479 } else {
16480 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16481 };
16482
16483 if let Some(url) = url {
16484 cx.update(|window, cx| {
16485 if parse_zed_link(&url, cx).is_some() {
16486 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16487 } else {
16488 cx.open_url(&url);
16489 }
16490 })?;
16491 }
16492
16493 anyhow::Ok(())
16494 });
16495
16496 url_finder.detach();
16497 }
16498
16499 pub fn open_selected_filename(
16500 &mut self,
16501 _: &OpenSelectedFilename,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) {
16505 let Some(workspace) = self.workspace() else {
16506 return;
16507 };
16508
16509 let position = self.selections.newest_anchor().head();
16510
16511 let Some((buffer, buffer_position)) =
16512 self.buffer.read(cx).text_anchor_for_position(position, cx)
16513 else {
16514 return;
16515 };
16516
16517 let project = self.project.clone();
16518
16519 cx.spawn_in(window, async move |_, cx| {
16520 let result = find_file(&buffer, project, buffer_position, cx).await;
16521
16522 if let Some((_, path)) = result {
16523 workspace
16524 .update_in(cx, |workspace, window, cx| {
16525 workspace.open_resolved_path(path, window, cx)
16526 })?
16527 .await?;
16528 }
16529 anyhow::Ok(())
16530 })
16531 .detach();
16532 }
16533
16534 pub(crate) fn navigate_to_hover_links(
16535 &mut self,
16536 kind: Option<GotoDefinitionKind>,
16537 definitions: Vec<HoverLink>,
16538 split: bool,
16539 window: &mut Window,
16540 cx: &mut Context<Editor>,
16541 ) -> Task<Result<Navigated>> {
16542 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16543 let mut first_url_or_file = None;
16544 let definitions: Vec<_> = definitions
16545 .into_iter()
16546 .filter_map(|def| match def {
16547 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16548 HoverLink::InlayHint(lsp_location, server_id) => {
16549 let computation =
16550 self.compute_target_location(lsp_location, server_id, window, cx);
16551 Some(cx.background_spawn(computation))
16552 }
16553 HoverLink::Url(url) => {
16554 first_url_or_file = Some(Either::Left(url));
16555 None
16556 }
16557 HoverLink::File(path) => {
16558 first_url_or_file = Some(Either::Right(path));
16559 None
16560 }
16561 })
16562 .collect();
16563
16564 let workspace = self.workspace();
16565
16566 cx.spawn_in(window, async move |editor, cx| {
16567 let locations: Vec<Location> = future::join_all(definitions)
16568 .await
16569 .into_iter()
16570 .filter_map(|location| location.transpose())
16571 .collect::<Result<_>>()
16572 .context("location tasks")?;
16573 let mut locations = cx.update(|_, cx| {
16574 locations
16575 .into_iter()
16576 .map(|location| {
16577 let buffer = location.buffer.read(cx);
16578 (location.buffer, location.range.to_point(buffer))
16579 })
16580 .into_group_map()
16581 })?;
16582 let mut num_locations = 0;
16583 for ranges in locations.values_mut() {
16584 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16585 ranges.dedup();
16586 num_locations += ranges.len();
16587 }
16588
16589 if num_locations > 1 {
16590 let Some(workspace) = workspace else {
16591 return Ok(Navigated::No);
16592 };
16593
16594 let tab_kind = match kind {
16595 Some(GotoDefinitionKind::Implementation) => "Implementations",
16596 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16597 Some(GotoDefinitionKind::Declaration) => "Declarations",
16598 Some(GotoDefinitionKind::Type) => "Types",
16599 };
16600 let title = editor
16601 .update_in(cx, |_, _, cx| {
16602 let target = locations
16603 .iter()
16604 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16605 .map(|(buffer, location)| {
16606 buffer
16607 .read(cx)
16608 .text_for_range(location.clone())
16609 .collect::<String>()
16610 })
16611 .filter(|text| !text.contains('\n'))
16612 .unique()
16613 .take(3)
16614 .join(", ");
16615 if target.is_empty() {
16616 tab_kind.to_owned()
16617 } else {
16618 format!("{tab_kind} for {target}")
16619 }
16620 })
16621 .context("buffer title")?;
16622
16623 let opened = workspace
16624 .update_in(cx, |workspace, window, cx| {
16625 Self::open_locations_in_multibuffer(
16626 workspace,
16627 locations,
16628 title,
16629 split,
16630 MultibufferSelectionMode::First,
16631 window,
16632 cx,
16633 )
16634 })
16635 .is_ok();
16636
16637 anyhow::Ok(Navigated::from_bool(opened))
16638 } else if num_locations == 0 {
16639 // If there is one url or file, open it directly
16640 match first_url_or_file {
16641 Some(Either::Left(url)) => {
16642 cx.update(|_, cx| cx.open_url(&url))?;
16643 Ok(Navigated::Yes)
16644 }
16645 Some(Either::Right(path)) => {
16646 let Some(workspace) = workspace else {
16647 return Ok(Navigated::No);
16648 };
16649
16650 workspace
16651 .update_in(cx, |workspace, window, cx| {
16652 workspace.open_resolved_path(path, window, cx)
16653 })?
16654 .await?;
16655 Ok(Navigated::Yes)
16656 }
16657 None => Ok(Navigated::No),
16658 }
16659 } else {
16660 let Some(workspace) = workspace else {
16661 return Ok(Navigated::No);
16662 };
16663
16664 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16665 let target_range = target_ranges.first().unwrap().clone();
16666
16667 editor.update_in(cx, |editor, window, cx| {
16668 let range = target_range.to_point(target_buffer.read(cx));
16669 let range = editor.range_for_match(&range, false);
16670 let range = collapse_multiline_range(range);
16671
16672 if !split
16673 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16674 {
16675 editor.go_to_singleton_buffer_range(range, window, cx);
16676 } else {
16677 let pane = workspace.read(cx).active_pane().clone();
16678 window.defer(cx, move |window, cx| {
16679 let target_editor: Entity<Self> =
16680 workspace.update(cx, |workspace, cx| {
16681 let pane = if split {
16682 workspace.adjacent_pane(window, cx)
16683 } else {
16684 workspace.active_pane().clone()
16685 };
16686
16687 workspace.open_project_item(
16688 pane,
16689 target_buffer.clone(),
16690 true,
16691 true,
16692 window,
16693 cx,
16694 )
16695 });
16696 target_editor.update(cx, |target_editor, cx| {
16697 // When selecting a definition in a different buffer, disable the nav history
16698 // to avoid creating a history entry at the previous cursor location.
16699 pane.update(cx, |pane, _| pane.disable_history());
16700 target_editor.go_to_singleton_buffer_range(range, window, cx);
16701 pane.update(cx, |pane, _| pane.enable_history());
16702 });
16703 });
16704 }
16705 Navigated::Yes
16706 })
16707 }
16708 })
16709 }
16710
16711 fn compute_target_location(
16712 &self,
16713 lsp_location: lsp::Location,
16714 server_id: LanguageServerId,
16715 window: &mut Window,
16716 cx: &mut Context<Self>,
16717 ) -> Task<anyhow::Result<Option<Location>>> {
16718 let Some(project) = self.project.clone() else {
16719 return Task::ready(Ok(None));
16720 };
16721
16722 cx.spawn_in(window, async move |editor, cx| {
16723 let location_task = editor.update(cx, |_, cx| {
16724 project.update(cx, |project, cx| {
16725 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16726 })
16727 })?;
16728 let location = Some({
16729 let target_buffer_handle = location_task.await.context("open local buffer")?;
16730 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16731 let target_start = target_buffer
16732 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16733 let target_end = target_buffer
16734 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16735 target_buffer.anchor_after(target_start)
16736 ..target_buffer.anchor_before(target_end)
16737 })?;
16738 Location {
16739 buffer: target_buffer_handle,
16740 range,
16741 }
16742 });
16743 Ok(location)
16744 })
16745 }
16746
16747 fn go_to_next_reference(
16748 &mut self,
16749 _: &GoToNextReference,
16750 window: &mut Window,
16751 cx: &mut Context<Self>,
16752 ) {
16753 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16754 if let Some(task) = task {
16755 task.detach();
16756 };
16757 }
16758
16759 fn go_to_prev_reference(
16760 &mut self,
16761 _: &GoToPreviousReference,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) {
16765 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16766 if let Some(task) = task {
16767 task.detach();
16768 };
16769 }
16770
16771 pub fn go_to_reference_before_or_after_position(
16772 &mut self,
16773 direction: Direction,
16774 count: usize,
16775 window: &mut Window,
16776 cx: &mut Context<Self>,
16777 ) -> Option<Task<Result<()>>> {
16778 let selection = self.selections.newest_anchor();
16779 let head = selection.head();
16780
16781 let multi_buffer = self.buffer.read(cx);
16782
16783 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16784 let workspace = self.workspace()?;
16785 let project = workspace.read(cx).project().clone();
16786 let references =
16787 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16788 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16789 let Some(locations) = references.await? else {
16790 return Ok(());
16791 };
16792
16793 if locations.is_empty() {
16794 // totally normal - the cursor may be on something which is not
16795 // a symbol (e.g. a keyword)
16796 log::info!("no references found under cursor");
16797 return Ok(());
16798 }
16799
16800 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16801
16802 let multi_buffer_snapshot =
16803 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16804
16805 let (locations, current_location_index) =
16806 multi_buffer.update(cx, |multi_buffer, cx| {
16807 let mut locations = locations
16808 .into_iter()
16809 .filter_map(|loc| {
16810 let start = multi_buffer.buffer_anchor_to_anchor(
16811 &loc.buffer,
16812 loc.range.start,
16813 cx,
16814 )?;
16815 let end = multi_buffer.buffer_anchor_to_anchor(
16816 &loc.buffer,
16817 loc.range.end,
16818 cx,
16819 )?;
16820 Some(start..end)
16821 })
16822 .collect::<Vec<_>>();
16823
16824 // There is an O(n) implementation, but given this list will be
16825 // small (usually <100 items), the extra O(log(n)) factor isn't
16826 // worth the (surprisingly large amount of) extra complexity.
16827 locations
16828 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16829
16830 let head_offset = head.to_offset(&multi_buffer_snapshot);
16831
16832 let current_location_index = locations.iter().position(|loc| {
16833 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16834 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16835 });
16836
16837 (locations, current_location_index)
16838 })?;
16839
16840 let Some(current_location_index) = current_location_index else {
16841 // This indicates something has gone wrong, because we already
16842 // handle the "no references" case above
16843 log::error!(
16844 "failed to find current reference under cursor. Total references: {}",
16845 locations.len()
16846 );
16847 return Ok(());
16848 };
16849
16850 let destination_location_index = match direction {
16851 Direction::Next => (current_location_index + count) % locations.len(),
16852 Direction::Prev => {
16853 (current_location_index + locations.len() - count % locations.len())
16854 % locations.len()
16855 }
16856 };
16857
16858 // TODO(cameron): is this needed?
16859 // the thinking is to avoid "jumping to the current location" (avoid
16860 // polluting "jumplist" in vim terms)
16861 if current_location_index == destination_location_index {
16862 return Ok(());
16863 }
16864
16865 let Range { start, end } = locations[destination_location_index];
16866
16867 editor.update_in(cx, |editor, window, cx| {
16868 let effects = SelectionEffects::default();
16869
16870 editor.unfold_ranges(&[start..end], false, false, cx);
16871 editor.change_selections(effects, window, cx, |s| {
16872 s.select_ranges([start..start]);
16873 });
16874 })?;
16875
16876 Ok(())
16877 }))
16878 }
16879
16880 pub fn find_all_references(
16881 &mut self,
16882 _: &FindAllReferences,
16883 window: &mut Window,
16884 cx: &mut Context<Self>,
16885 ) -> Option<Task<Result<Navigated>>> {
16886 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16887 let multi_buffer = self.buffer.read(cx);
16888 let head = selection.head();
16889
16890 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16891 let head_anchor = multi_buffer_snapshot.anchor_at(
16892 head,
16893 if head < selection.tail() {
16894 Bias::Right
16895 } else {
16896 Bias::Left
16897 },
16898 );
16899
16900 match self
16901 .find_all_references_task_sources
16902 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16903 {
16904 Ok(_) => {
16905 log::info!(
16906 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16907 );
16908 return None;
16909 }
16910 Err(i) => {
16911 self.find_all_references_task_sources.insert(i, head_anchor);
16912 }
16913 }
16914
16915 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16916 let workspace = self.workspace()?;
16917 let project = workspace.read(cx).project().clone();
16918 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16919 Some(cx.spawn_in(window, async move |editor, cx| {
16920 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16921 if let Ok(i) = editor
16922 .find_all_references_task_sources
16923 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16924 {
16925 editor.find_all_references_task_sources.remove(i);
16926 }
16927 });
16928
16929 let Some(locations) = references.await? else {
16930 return anyhow::Ok(Navigated::No);
16931 };
16932 let mut locations = cx.update(|_, cx| {
16933 locations
16934 .into_iter()
16935 .map(|location| {
16936 let buffer = location.buffer.read(cx);
16937 (location.buffer, location.range.to_point(buffer))
16938 })
16939 .into_group_map()
16940 })?;
16941 if locations.is_empty() {
16942 return anyhow::Ok(Navigated::No);
16943 }
16944 for ranges in locations.values_mut() {
16945 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16946 ranges.dedup();
16947 }
16948
16949 workspace.update_in(cx, |workspace, window, cx| {
16950 let target = locations
16951 .iter()
16952 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16953 .map(|(buffer, location)| {
16954 buffer
16955 .read(cx)
16956 .text_for_range(location.clone())
16957 .collect::<String>()
16958 })
16959 .filter(|text| !text.contains('\n'))
16960 .unique()
16961 .take(3)
16962 .join(", ");
16963 let title = if target.is_empty() {
16964 "References".to_owned()
16965 } else {
16966 format!("References to {target}")
16967 };
16968 Self::open_locations_in_multibuffer(
16969 workspace,
16970 locations,
16971 title,
16972 false,
16973 MultibufferSelectionMode::First,
16974 window,
16975 cx,
16976 );
16977 Navigated::Yes
16978 })
16979 }))
16980 }
16981
16982 /// Opens a multibuffer with the given project locations in it
16983 pub fn open_locations_in_multibuffer(
16984 workspace: &mut Workspace,
16985 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16986 title: String,
16987 split: bool,
16988 multibuffer_selection_mode: MultibufferSelectionMode,
16989 window: &mut Window,
16990 cx: &mut Context<Workspace>,
16991 ) {
16992 if locations.is_empty() {
16993 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16994 return;
16995 }
16996
16997 let capability = workspace.project().read(cx).capability();
16998 let mut ranges = <Vec<Range<Anchor>>>::new();
16999
17000 // a key to find existing multibuffer editors with the same set of locations
17001 // to prevent us from opening more and more multibuffer tabs for searches and the like
17002 let mut key = (title.clone(), vec![]);
17003 let excerpt_buffer = cx.new(|cx| {
17004 let key = &mut key.1;
17005 let mut multibuffer = MultiBuffer::new(capability);
17006 for (buffer, mut ranges_for_buffer) in locations {
17007 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17008 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17009 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17010 PathKey::for_buffer(&buffer, cx),
17011 buffer.clone(),
17012 ranges_for_buffer,
17013 multibuffer_context_lines(cx),
17014 cx,
17015 );
17016 ranges.extend(new_ranges)
17017 }
17018
17019 multibuffer.with_title(title)
17020 });
17021 let existing = workspace.active_pane().update(cx, |pane, cx| {
17022 pane.items()
17023 .filter_map(|item| item.downcast::<Editor>())
17024 .find(|editor| {
17025 editor
17026 .read(cx)
17027 .lookup_key
17028 .as_ref()
17029 .and_then(|it| {
17030 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17031 })
17032 .is_some_and(|it| *it == key)
17033 })
17034 });
17035 let editor = existing.unwrap_or_else(|| {
17036 cx.new(|cx| {
17037 let mut editor = Editor::for_multibuffer(
17038 excerpt_buffer,
17039 Some(workspace.project().clone()),
17040 window,
17041 cx,
17042 );
17043 editor.lookup_key = Some(Box::new(key));
17044 editor
17045 })
17046 });
17047 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17048 MultibufferSelectionMode::First => {
17049 if let Some(first_range) = ranges.first() {
17050 editor.change_selections(
17051 SelectionEffects::no_scroll(),
17052 window,
17053 cx,
17054 |selections| {
17055 selections.clear_disjoint();
17056 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17057 },
17058 );
17059 }
17060 editor.highlight_background::<Self>(
17061 &ranges,
17062 |theme| theme.colors().editor_highlighted_line_background,
17063 cx,
17064 );
17065 }
17066 MultibufferSelectionMode::All => {
17067 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17068 selections.clear_disjoint();
17069 selections.select_anchor_ranges(ranges);
17070 });
17071 }
17072 });
17073
17074 let item = Box::new(editor);
17075 let item_id = item.item_id();
17076
17077 if split {
17078 let pane = workspace.adjacent_pane(window, cx);
17079 workspace.add_item(pane, item, None, true, true, window, cx);
17080 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17081 let (preview_item_id, preview_item_idx) =
17082 workspace.active_pane().read_with(cx, |pane, _| {
17083 (pane.preview_item_id(), pane.preview_item_idx())
17084 });
17085
17086 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17087
17088 if let Some(preview_item_id) = preview_item_id {
17089 workspace.active_pane().update(cx, |pane, cx| {
17090 pane.remove_item(preview_item_id, false, false, window, cx);
17091 });
17092 }
17093 } else {
17094 workspace.add_item_to_active_pane(item, None, true, window, cx);
17095 }
17096 workspace.active_pane().update(cx, |pane, cx| {
17097 pane.set_preview_item_id(Some(item_id), cx);
17098 });
17099 }
17100
17101 pub fn rename(
17102 &mut self,
17103 _: &Rename,
17104 window: &mut Window,
17105 cx: &mut Context<Self>,
17106 ) -> Option<Task<Result<()>>> {
17107 use language::ToOffset as _;
17108
17109 let provider = self.semantics_provider.clone()?;
17110 let selection = self.selections.newest_anchor().clone();
17111 let (cursor_buffer, cursor_buffer_position) = self
17112 .buffer
17113 .read(cx)
17114 .text_anchor_for_position(selection.head(), cx)?;
17115 let (tail_buffer, cursor_buffer_position_end) = self
17116 .buffer
17117 .read(cx)
17118 .text_anchor_for_position(selection.tail(), cx)?;
17119 if tail_buffer != cursor_buffer {
17120 return None;
17121 }
17122
17123 let snapshot = cursor_buffer.read(cx).snapshot();
17124 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17125 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17126 let prepare_rename = provider
17127 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17128 .unwrap_or_else(|| Task::ready(Ok(None)));
17129 drop(snapshot);
17130
17131 Some(cx.spawn_in(window, async move |this, cx| {
17132 let rename_range = if let Some(range) = prepare_rename.await? {
17133 Some(range)
17134 } else {
17135 this.update(cx, |this, cx| {
17136 let buffer = this.buffer.read(cx).snapshot(cx);
17137 let mut buffer_highlights = this
17138 .document_highlights_for_position(selection.head(), &buffer)
17139 .filter(|highlight| {
17140 highlight.start.excerpt_id == selection.head().excerpt_id
17141 && highlight.end.excerpt_id == selection.head().excerpt_id
17142 });
17143 buffer_highlights
17144 .next()
17145 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17146 })?
17147 };
17148 if let Some(rename_range) = rename_range {
17149 this.update_in(cx, |this, window, cx| {
17150 let snapshot = cursor_buffer.read(cx).snapshot();
17151 let rename_buffer_range = rename_range.to_offset(&snapshot);
17152 let cursor_offset_in_rename_range =
17153 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17154 let cursor_offset_in_rename_range_end =
17155 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17156
17157 this.take_rename(false, window, cx);
17158 let buffer = this.buffer.read(cx).read(cx);
17159 let cursor_offset = selection.head().to_offset(&buffer);
17160 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17161 let rename_end = rename_start + rename_buffer_range.len();
17162 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17163 let mut old_highlight_id = None;
17164 let old_name: Arc<str> = buffer
17165 .chunks(rename_start..rename_end, true)
17166 .map(|chunk| {
17167 if old_highlight_id.is_none() {
17168 old_highlight_id = chunk.syntax_highlight_id;
17169 }
17170 chunk.text
17171 })
17172 .collect::<String>()
17173 .into();
17174
17175 drop(buffer);
17176
17177 // Position the selection in the rename editor so that it matches the current selection.
17178 this.show_local_selections = false;
17179 let rename_editor = cx.new(|cx| {
17180 let mut editor = Editor::single_line(window, cx);
17181 editor.buffer.update(cx, |buffer, cx| {
17182 buffer.edit([(0..0, old_name.clone())], None, cx)
17183 });
17184 let rename_selection_range = match cursor_offset_in_rename_range
17185 .cmp(&cursor_offset_in_rename_range_end)
17186 {
17187 Ordering::Equal => {
17188 editor.select_all(&SelectAll, window, cx);
17189 return editor;
17190 }
17191 Ordering::Less => {
17192 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17193 }
17194 Ordering::Greater => {
17195 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17196 }
17197 };
17198 if rename_selection_range.end > old_name.len() {
17199 editor.select_all(&SelectAll, window, cx);
17200 } else {
17201 editor.change_selections(Default::default(), window, cx, |s| {
17202 s.select_ranges([rename_selection_range]);
17203 });
17204 }
17205 editor
17206 });
17207 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17208 if e == &EditorEvent::Focused {
17209 cx.emit(EditorEvent::FocusedIn)
17210 }
17211 })
17212 .detach();
17213
17214 let write_highlights =
17215 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17216 let read_highlights =
17217 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17218 let ranges = write_highlights
17219 .iter()
17220 .flat_map(|(_, ranges)| ranges.iter())
17221 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17222 .cloned()
17223 .collect();
17224
17225 this.highlight_text::<Rename>(
17226 ranges,
17227 HighlightStyle {
17228 fade_out: Some(0.6),
17229 ..Default::default()
17230 },
17231 cx,
17232 );
17233 let rename_focus_handle = rename_editor.focus_handle(cx);
17234 window.focus(&rename_focus_handle);
17235 let block_id = this.insert_blocks(
17236 [BlockProperties {
17237 style: BlockStyle::Flex,
17238 placement: BlockPlacement::Below(range.start),
17239 height: Some(1),
17240 render: Arc::new({
17241 let rename_editor = rename_editor.clone();
17242 move |cx: &mut BlockContext| {
17243 let mut text_style = cx.editor_style.text.clone();
17244 if let Some(highlight_style) = old_highlight_id
17245 .and_then(|h| h.style(&cx.editor_style.syntax))
17246 {
17247 text_style = text_style.highlight(highlight_style);
17248 }
17249 div()
17250 .block_mouse_except_scroll()
17251 .pl(cx.anchor_x)
17252 .child(EditorElement::new(
17253 &rename_editor,
17254 EditorStyle {
17255 background: cx.theme().system().transparent,
17256 local_player: cx.editor_style.local_player,
17257 text: text_style,
17258 scrollbar_width: cx.editor_style.scrollbar_width,
17259 syntax: cx.editor_style.syntax.clone(),
17260 status: cx.editor_style.status.clone(),
17261 inlay_hints_style: HighlightStyle {
17262 font_weight: Some(FontWeight::BOLD),
17263 ..make_inlay_hints_style(cx.app)
17264 },
17265 edit_prediction_styles: make_suggestion_styles(
17266 cx.app,
17267 ),
17268 ..EditorStyle::default()
17269 },
17270 ))
17271 .into_any_element()
17272 }
17273 }),
17274 priority: 0,
17275 }],
17276 Some(Autoscroll::fit()),
17277 cx,
17278 )[0];
17279 this.pending_rename = Some(RenameState {
17280 range,
17281 old_name,
17282 editor: rename_editor,
17283 block_id,
17284 });
17285 })?;
17286 }
17287
17288 Ok(())
17289 }))
17290 }
17291
17292 pub fn confirm_rename(
17293 &mut self,
17294 _: &ConfirmRename,
17295 window: &mut Window,
17296 cx: &mut Context<Self>,
17297 ) -> Option<Task<Result<()>>> {
17298 let rename = self.take_rename(false, window, cx)?;
17299 let workspace = self.workspace()?.downgrade();
17300 let (buffer, start) = self
17301 .buffer
17302 .read(cx)
17303 .text_anchor_for_position(rename.range.start, cx)?;
17304 let (end_buffer, _) = self
17305 .buffer
17306 .read(cx)
17307 .text_anchor_for_position(rename.range.end, cx)?;
17308 if buffer != end_buffer {
17309 return None;
17310 }
17311
17312 let old_name = rename.old_name;
17313 let new_name = rename.editor.read(cx).text(cx);
17314
17315 let rename = self.semantics_provider.as_ref()?.perform_rename(
17316 &buffer,
17317 start,
17318 new_name.clone(),
17319 cx,
17320 )?;
17321
17322 Some(cx.spawn_in(window, async move |editor, cx| {
17323 let project_transaction = rename.await?;
17324 Self::open_project_transaction(
17325 &editor,
17326 workspace,
17327 project_transaction,
17328 format!("Rename: {} → {}", old_name, new_name),
17329 cx,
17330 )
17331 .await?;
17332
17333 editor.update(cx, |editor, cx| {
17334 editor.refresh_document_highlights(cx);
17335 })?;
17336 Ok(())
17337 }))
17338 }
17339
17340 fn take_rename(
17341 &mut self,
17342 moving_cursor: bool,
17343 window: &mut Window,
17344 cx: &mut Context<Self>,
17345 ) -> Option<RenameState> {
17346 let rename = self.pending_rename.take()?;
17347 if rename.editor.focus_handle(cx).is_focused(window) {
17348 window.focus(&self.focus_handle);
17349 }
17350
17351 self.remove_blocks(
17352 [rename.block_id].into_iter().collect(),
17353 Some(Autoscroll::fit()),
17354 cx,
17355 );
17356 self.clear_highlights::<Rename>(cx);
17357 self.show_local_selections = true;
17358
17359 if moving_cursor {
17360 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17361 editor
17362 .selections
17363 .newest::<usize>(&editor.display_snapshot(cx))
17364 .head()
17365 });
17366
17367 // Update the selection to match the position of the selection inside
17368 // the rename editor.
17369 let snapshot = self.buffer.read(cx).read(cx);
17370 let rename_range = rename.range.to_offset(&snapshot);
17371 let cursor_in_editor = snapshot
17372 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17373 .min(rename_range.end);
17374 drop(snapshot);
17375
17376 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17377 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17378 });
17379 } else {
17380 self.refresh_document_highlights(cx);
17381 }
17382
17383 Some(rename)
17384 }
17385
17386 pub fn pending_rename(&self) -> Option<&RenameState> {
17387 self.pending_rename.as_ref()
17388 }
17389
17390 fn format(
17391 &mut self,
17392 _: &Format,
17393 window: &mut Window,
17394 cx: &mut Context<Self>,
17395 ) -> Option<Task<Result<()>>> {
17396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17397
17398 let project = match &self.project {
17399 Some(project) => project.clone(),
17400 None => return None,
17401 };
17402
17403 Some(self.perform_format(
17404 project,
17405 FormatTrigger::Manual,
17406 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17407 window,
17408 cx,
17409 ))
17410 }
17411
17412 fn format_selections(
17413 &mut self,
17414 _: &FormatSelections,
17415 window: &mut Window,
17416 cx: &mut Context<Self>,
17417 ) -> Option<Task<Result<()>>> {
17418 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17419
17420 let project = match &self.project {
17421 Some(project) => project.clone(),
17422 None => return None,
17423 };
17424
17425 let ranges = self
17426 .selections
17427 .all_adjusted(&self.display_snapshot(cx))
17428 .into_iter()
17429 .map(|selection| selection.range())
17430 .collect_vec();
17431
17432 Some(self.perform_format(
17433 project,
17434 FormatTrigger::Manual,
17435 FormatTarget::Ranges(ranges),
17436 window,
17437 cx,
17438 ))
17439 }
17440
17441 fn perform_format(
17442 &mut self,
17443 project: Entity<Project>,
17444 trigger: FormatTrigger,
17445 target: FormatTarget,
17446 window: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) -> Task<Result<()>> {
17449 let buffer = self.buffer.clone();
17450 let (buffers, target) = match target {
17451 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17452 FormatTarget::Ranges(selection_ranges) => {
17453 let multi_buffer = buffer.read(cx);
17454 let snapshot = multi_buffer.read(cx);
17455 let mut buffers = HashSet::default();
17456 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17457 BTreeMap::new();
17458 for selection_range in selection_ranges {
17459 for (buffer, buffer_range, _) in
17460 snapshot.range_to_buffer_ranges(selection_range)
17461 {
17462 let buffer_id = buffer.remote_id();
17463 let start = buffer.anchor_before(buffer_range.start);
17464 let end = buffer.anchor_after(buffer_range.end);
17465 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17466 buffer_id_to_ranges
17467 .entry(buffer_id)
17468 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17469 .or_insert_with(|| vec![start..end]);
17470 }
17471 }
17472 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17473 }
17474 };
17475
17476 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17477 let selections_prev = transaction_id_prev
17478 .and_then(|transaction_id_prev| {
17479 // default to selections as they were after the last edit, if we have them,
17480 // instead of how they are now.
17481 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17482 // will take you back to where you made the last edit, instead of staying where you scrolled
17483 self.selection_history
17484 .transaction(transaction_id_prev)
17485 .map(|t| t.0.clone())
17486 })
17487 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17488
17489 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17490 let format = project.update(cx, |project, cx| {
17491 project.format(buffers, target, true, trigger, cx)
17492 });
17493
17494 cx.spawn_in(window, async move |editor, cx| {
17495 let transaction = futures::select_biased! {
17496 transaction = format.log_err().fuse() => transaction,
17497 () = timeout => {
17498 log::warn!("timed out waiting for formatting");
17499 None
17500 }
17501 };
17502
17503 buffer
17504 .update(cx, |buffer, cx| {
17505 if let Some(transaction) = transaction
17506 && !buffer.is_singleton()
17507 {
17508 buffer.push_transaction(&transaction.0, cx);
17509 }
17510 cx.notify();
17511 })
17512 .ok();
17513
17514 if let Some(transaction_id_now) =
17515 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17516 {
17517 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17518 if has_new_transaction {
17519 _ = editor.update(cx, |editor, _| {
17520 editor
17521 .selection_history
17522 .insert_transaction(transaction_id_now, selections_prev);
17523 });
17524 }
17525 }
17526
17527 Ok(())
17528 })
17529 }
17530
17531 fn organize_imports(
17532 &mut self,
17533 _: &OrganizeImports,
17534 window: &mut Window,
17535 cx: &mut Context<Self>,
17536 ) -> Option<Task<Result<()>>> {
17537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17538 let project = match &self.project {
17539 Some(project) => project.clone(),
17540 None => return None,
17541 };
17542 Some(self.perform_code_action_kind(
17543 project,
17544 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17545 window,
17546 cx,
17547 ))
17548 }
17549
17550 fn perform_code_action_kind(
17551 &mut self,
17552 project: Entity<Project>,
17553 kind: CodeActionKind,
17554 window: &mut Window,
17555 cx: &mut Context<Self>,
17556 ) -> Task<Result<()>> {
17557 let buffer = self.buffer.clone();
17558 let buffers = buffer.read(cx).all_buffers();
17559 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17560 let apply_action = project.update(cx, |project, cx| {
17561 project.apply_code_action_kind(buffers, kind, true, cx)
17562 });
17563 cx.spawn_in(window, async move |_, cx| {
17564 let transaction = futures::select_biased! {
17565 () = timeout => {
17566 log::warn!("timed out waiting for executing code action");
17567 None
17568 }
17569 transaction = apply_action.log_err().fuse() => transaction,
17570 };
17571 buffer
17572 .update(cx, |buffer, cx| {
17573 // check if we need this
17574 if let Some(transaction) = transaction
17575 && !buffer.is_singleton()
17576 {
17577 buffer.push_transaction(&transaction.0, cx);
17578 }
17579 cx.notify();
17580 })
17581 .ok();
17582 Ok(())
17583 })
17584 }
17585
17586 pub fn restart_language_server(
17587 &mut self,
17588 _: &RestartLanguageServer,
17589 _: &mut Window,
17590 cx: &mut Context<Self>,
17591 ) {
17592 if let Some(project) = self.project.clone() {
17593 self.buffer.update(cx, |multi_buffer, cx| {
17594 project.update(cx, |project, cx| {
17595 project.restart_language_servers_for_buffers(
17596 multi_buffer.all_buffers().into_iter().collect(),
17597 HashSet::default(),
17598 cx,
17599 );
17600 });
17601 })
17602 }
17603 }
17604
17605 pub fn stop_language_server(
17606 &mut self,
17607 _: &StopLanguageServer,
17608 _: &mut Window,
17609 cx: &mut Context<Self>,
17610 ) {
17611 if let Some(project) = self.project.clone() {
17612 self.buffer.update(cx, |multi_buffer, cx| {
17613 project.update(cx, |project, cx| {
17614 project.stop_language_servers_for_buffers(
17615 multi_buffer.all_buffers().into_iter().collect(),
17616 HashSet::default(),
17617 cx,
17618 );
17619 });
17620 });
17621 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17622 }
17623 }
17624
17625 fn cancel_language_server_work(
17626 workspace: &mut Workspace,
17627 _: &actions::CancelLanguageServerWork,
17628 _: &mut Window,
17629 cx: &mut Context<Workspace>,
17630 ) {
17631 let project = workspace.project();
17632 let buffers = workspace
17633 .active_item(cx)
17634 .and_then(|item| item.act_as::<Editor>(cx))
17635 .map_or(HashSet::default(), |editor| {
17636 editor.read(cx).buffer.read(cx).all_buffers()
17637 });
17638 project.update(cx, |project, cx| {
17639 project.cancel_language_server_work_for_buffers(buffers, cx);
17640 });
17641 }
17642
17643 fn show_character_palette(
17644 &mut self,
17645 _: &ShowCharacterPalette,
17646 window: &mut Window,
17647 _: &mut Context<Self>,
17648 ) {
17649 window.show_character_palette();
17650 }
17651
17652 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17653 if !self.diagnostics_enabled() {
17654 return;
17655 }
17656
17657 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17658 let buffer = self.buffer.read(cx).snapshot(cx);
17659 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17660 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17661 let is_valid = buffer
17662 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17663 .any(|entry| {
17664 entry.diagnostic.is_primary
17665 && !entry.range.is_empty()
17666 && entry.range.start == primary_range_start
17667 && entry.diagnostic.message == active_diagnostics.active_message
17668 });
17669
17670 if !is_valid {
17671 self.dismiss_diagnostics(cx);
17672 }
17673 }
17674 }
17675
17676 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17677 match &self.active_diagnostics {
17678 ActiveDiagnostic::Group(group) => Some(group),
17679 _ => None,
17680 }
17681 }
17682
17683 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17684 if !self.diagnostics_enabled() {
17685 return;
17686 }
17687 self.dismiss_diagnostics(cx);
17688 self.active_diagnostics = ActiveDiagnostic::All;
17689 }
17690
17691 fn activate_diagnostics(
17692 &mut self,
17693 buffer_id: BufferId,
17694 diagnostic: DiagnosticEntryRef<'_, usize>,
17695 window: &mut Window,
17696 cx: &mut Context<Self>,
17697 ) {
17698 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17699 return;
17700 }
17701 self.dismiss_diagnostics(cx);
17702 let snapshot = self.snapshot(window, cx);
17703 let buffer = self.buffer.read(cx).snapshot(cx);
17704 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17705 return;
17706 };
17707
17708 let diagnostic_group = buffer
17709 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17710 .collect::<Vec<_>>();
17711
17712 let blocks =
17713 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17714
17715 let blocks = self.display_map.update(cx, |display_map, cx| {
17716 display_map.insert_blocks(blocks, cx).into_iter().collect()
17717 });
17718 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17719 active_range: buffer.anchor_before(diagnostic.range.start)
17720 ..buffer.anchor_after(diagnostic.range.end),
17721 active_message: diagnostic.diagnostic.message.clone(),
17722 group_id: diagnostic.diagnostic.group_id,
17723 blocks,
17724 });
17725 cx.notify();
17726 }
17727
17728 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17729 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17730 return;
17731 };
17732
17733 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17734 if let ActiveDiagnostic::Group(group) = prev {
17735 self.display_map.update(cx, |display_map, cx| {
17736 display_map.remove_blocks(group.blocks, cx);
17737 });
17738 cx.notify();
17739 }
17740 }
17741
17742 /// Disable inline diagnostics rendering for this editor.
17743 pub fn disable_inline_diagnostics(&mut self) {
17744 self.inline_diagnostics_enabled = false;
17745 self.inline_diagnostics_update = Task::ready(());
17746 self.inline_diagnostics.clear();
17747 }
17748
17749 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17750 self.diagnostics_enabled = false;
17751 self.dismiss_diagnostics(cx);
17752 self.inline_diagnostics_update = Task::ready(());
17753 self.inline_diagnostics.clear();
17754 }
17755
17756 pub fn disable_word_completions(&mut self) {
17757 self.word_completions_enabled = false;
17758 }
17759
17760 pub fn diagnostics_enabled(&self) -> bool {
17761 self.diagnostics_enabled && self.mode.is_full()
17762 }
17763
17764 pub fn inline_diagnostics_enabled(&self) -> bool {
17765 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17766 }
17767
17768 pub fn show_inline_diagnostics(&self) -> bool {
17769 self.show_inline_diagnostics
17770 }
17771
17772 pub fn toggle_inline_diagnostics(
17773 &mut self,
17774 _: &ToggleInlineDiagnostics,
17775 window: &mut Window,
17776 cx: &mut Context<Editor>,
17777 ) {
17778 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17779 self.refresh_inline_diagnostics(false, window, cx);
17780 }
17781
17782 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17783 self.diagnostics_max_severity = severity;
17784 self.display_map.update(cx, |display_map, _| {
17785 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17786 });
17787 }
17788
17789 pub fn toggle_diagnostics(
17790 &mut self,
17791 _: &ToggleDiagnostics,
17792 window: &mut Window,
17793 cx: &mut Context<Editor>,
17794 ) {
17795 if !self.diagnostics_enabled() {
17796 return;
17797 }
17798
17799 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17800 EditorSettings::get_global(cx)
17801 .diagnostics_max_severity
17802 .filter(|severity| severity != &DiagnosticSeverity::Off)
17803 .unwrap_or(DiagnosticSeverity::Hint)
17804 } else {
17805 DiagnosticSeverity::Off
17806 };
17807 self.set_max_diagnostics_severity(new_severity, cx);
17808 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17809 self.active_diagnostics = ActiveDiagnostic::None;
17810 self.inline_diagnostics_update = Task::ready(());
17811 self.inline_diagnostics.clear();
17812 } else {
17813 self.refresh_inline_diagnostics(false, window, cx);
17814 }
17815
17816 cx.notify();
17817 }
17818
17819 pub fn toggle_minimap(
17820 &mut self,
17821 _: &ToggleMinimap,
17822 window: &mut Window,
17823 cx: &mut Context<Editor>,
17824 ) {
17825 if self.supports_minimap(cx) {
17826 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17827 }
17828 }
17829
17830 fn refresh_inline_diagnostics(
17831 &mut self,
17832 debounce: bool,
17833 window: &mut Window,
17834 cx: &mut Context<Self>,
17835 ) {
17836 let max_severity = ProjectSettings::get_global(cx)
17837 .diagnostics
17838 .inline
17839 .max_severity
17840 .unwrap_or(self.diagnostics_max_severity);
17841
17842 if !self.inline_diagnostics_enabled()
17843 || !self.diagnostics_enabled()
17844 || !self.show_inline_diagnostics
17845 || max_severity == DiagnosticSeverity::Off
17846 {
17847 self.inline_diagnostics_update = Task::ready(());
17848 self.inline_diagnostics.clear();
17849 return;
17850 }
17851
17852 let debounce_ms = ProjectSettings::get_global(cx)
17853 .diagnostics
17854 .inline
17855 .update_debounce_ms;
17856 let debounce = if debounce && debounce_ms > 0 {
17857 Some(Duration::from_millis(debounce_ms))
17858 } else {
17859 None
17860 };
17861 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17862 if let Some(debounce) = debounce {
17863 cx.background_executor().timer(debounce).await;
17864 }
17865 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17866 editor
17867 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17868 .ok()
17869 }) else {
17870 return;
17871 };
17872
17873 let new_inline_diagnostics = cx
17874 .background_spawn(async move {
17875 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17876 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17877 let message = diagnostic_entry
17878 .diagnostic
17879 .message
17880 .split_once('\n')
17881 .map(|(line, _)| line)
17882 .map(SharedString::new)
17883 .unwrap_or_else(|| {
17884 SharedString::new(&*diagnostic_entry.diagnostic.message)
17885 });
17886 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17887 let (Ok(i) | Err(i)) = inline_diagnostics
17888 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17889 inline_diagnostics.insert(
17890 i,
17891 (
17892 start_anchor,
17893 InlineDiagnostic {
17894 message,
17895 group_id: diagnostic_entry.diagnostic.group_id,
17896 start: diagnostic_entry.range.start.to_point(&snapshot),
17897 is_primary: diagnostic_entry.diagnostic.is_primary,
17898 severity: diagnostic_entry.diagnostic.severity,
17899 },
17900 ),
17901 );
17902 }
17903 inline_diagnostics
17904 })
17905 .await;
17906
17907 editor
17908 .update(cx, |editor, cx| {
17909 editor.inline_diagnostics = new_inline_diagnostics;
17910 cx.notify();
17911 })
17912 .ok();
17913 });
17914 }
17915
17916 fn pull_diagnostics(
17917 &mut self,
17918 buffer_id: Option<BufferId>,
17919 window: &Window,
17920 cx: &mut Context<Self>,
17921 ) -> Option<()> {
17922 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17923 return None;
17924 }
17925 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17926 .diagnostics
17927 .lsp_pull_diagnostics;
17928 if !pull_diagnostics_settings.enabled {
17929 return None;
17930 }
17931 let project = self.project()?.downgrade();
17932 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17933 let mut buffers = self.buffer.read(cx).all_buffers();
17934 buffers.retain(|buffer| {
17935 let buffer_id_to_retain = buffer.read(cx).remote_id();
17936 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17937 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17938 });
17939 if buffers.is_empty() {
17940 self.pull_diagnostics_task = Task::ready(());
17941 return None;
17942 }
17943
17944 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17945 cx.background_executor().timer(debounce).await;
17946
17947 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17948 buffers
17949 .into_iter()
17950 .filter_map(|buffer| {
17951 project
17952 .update(cx, |project, cx| {
17953 project.lsp_store().update(cx, |lsp_store, cx| {
17954 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17955 })
17956 })
17957 .ok()
17958 })
17959 .collect::<FuturesUnordered<_>>()
17960 }) else {
17961 return;
17962 };
17963
17964 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17965 match pull_task {
17966 Ok(()) => {
17967 if editor
17968 .update_in(cx, |editor, window, cx| {
17969 editor.update_diagnostics_state(window, cx);
17970 })
17971 .is_err()
17972 {
17973 return;
17974 }
17975 }
17976 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17977 }
17978 }
17979 });
17980
17981 Some(())
17982 }
17983
17984 pub fn set_selections_from_remote(
17985 &mut self,
17986 selections: Vec<Selection<Anchor>>,
17987 pending_selection: Option<Selection<Anchor>>,
17988 window: &mut Window,
17989 cx: &mut Context<Self>,
17990 ) {
17991 let old_cursor_position = self.selections.newest_anchor().head();
17992 self.selections.change_with(cx, |s| {
17993 s.select_anchors(selections);
17994 if let Some(pending_selection) = pending_selection {
17995 s.set_pending(pending_selection, SelectMode::Character);
17996 } else {
17997 s.clear_pending();
17998 }
17999 });
18000 self.selections_did_change(
18001 false,
18002 &old_cursor_position,
18003 SelectionEffects::default(),
18004 window,
18005 cx,
18006 );
18007 }
18008
18009 pub fn transact(
18010 &mut self,
18011 window: &mut Window,
18012 cx: &mut Context<Self>,
18013 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18014 ) -> Option<TransactionId> {
18015 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18016 this.start_transaction_at(Instant::now(), window, cx);
18017 update(this, window, cx);
18018 this.end_transaction_at(Instant::now(), cx)
18019 })
18020 }
18021
18022 pub fn start_transaction_at(
18023 &mut self,
18024 now: Instant,
18025 window: &mut Window,
18026 cx: &mut Context<Self>,
18027 ) -> Option<TransactionId> {
18028 self.end_selection(window, cx);
18029 if let Some(tx_id) = self
18030 .buffer
18031 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18032 {
18033 self.selection_history
18034 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18035 cx.emit(EditorEvent::TransactionBegun {
18036 transaction_id: tx_id,
18037 });
18038 Some(tx_id)
18039 } else {
18040 None
18041 }
18042 }
18043
18044 pub fn end_transaction_at(
18045 &mut self,
18046 now: Instant,
18047 cx: &mut Context<Self>,
18048 ) -> Option<TransactionId> {
18049 if let Some(transaction_id) = self
18050 .buffer
18051 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18052 {
18053 if let Some((_, end_selections)) =
18054 self.selection_history.transaction_mut(transaction_id)
18055 {
18056 *end_selections = Some(self.selections.disjoint_anchors_arc());
18057 } else {
18058 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18059 }
18060
18061 cx.emit(EditorEvent::Edited { transaction_id });
18062 Some(transaction_id)
18063 } else {
18064 None
18065 }
18066 }
18067
18068 pub fn modify_transaction_selection_history(
18069 &mut self,
18070 transaction_id: TransactionId,
18071 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18072 ) -> bool {
18073 self.selection_history
18074 .transaction_mut(transaction_id)
18075 .map(modify)
18076 .is_some()
18077 }
18078
18079 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18080 if self.selection_mark_mode {
18081 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18082 s.move_with(|_, sel| {
18083 sel.collapse_to(sel.head(), SelectionGoal::None);
18084 });
18085 })
18086 }
18087 self.selection_mark_mode = true;
18088 cx.notify();
18089 }
18090
18091 pub fn swap_selection_ends(
18092 &mut self,
18093 _: &actions::SwapSelectionEnds,
18094 window: &mut Window,
18095 cx: &mut Context<Self>,
18096 ) {
18097 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18098 s.move_with(|_, sel| {
18099 if sel.start != sel.end {
18100 sel.reversed = !sel.reversed
18101 }
18102 });
18103 });
18104 self.request_autoscroll(Autoscroll::newest(), cx);
18105 cx.notify();
18106 }
18107
18108 pub fn toggle_focus(
18109 workspace: &mut Workspace,
18110 _: &actions::ToggleFocus,
18111 window: &mut Window,
18112 cx: &mut Context<Workspace>,
18113 ) {
18114 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18115 return;
18116 };
18117 workspace.activate_item(&item, true, true, window, cx);
18118 }
18119
18120 pub fn toggle_fold(
18121 &mut self,
18122 _: &actions::ToggleFold,
18123 window: &mut Window,
18124 cx: &mut Context<Self>,
18125 ) {
18126 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18128 let selection = self.selections.newest::<Point>(&display_map);
18129
18130 let range = if selection.is_empty() {
18131 let point = selection.head().to_display_point(&display_map);
18132 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18133 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18134 .to_point(&display_map);
18135 start..end
18136 } else {
18137 selection.range()
18138 };
18139 if display_map.folds_in_range(range).next().is_some() {
18140 self.unfold_lines(&Default::default(), window, cx)
18141 } else {
18142 self.fold(&Default::default(), window, cx)
18143 }
18144 } else {
18145 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18146 let buffer_ids: HashSet<_> = self
18147 .selections
18148 .disjoint_anchor_ranges()
18149 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18150 .collect();
18151
18152 let should_unfold = buffer_ids
18153 .iter()
18154 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18155
18156 for buffer_id in buffer_ids {
18157 if should_unfold {
18158 self.unfold_buffer(buffer_id, cx);
18159 } else {
18160 self.fold_buffer(buffer_id, cx);
18161 }
18162 }
18163 }
18164 }
18165
18166 pub fn toggle_fold_recursive(
18167 &mut self,
18168 _: &actions::ToggleFoldRecursive,
18169 window: &mut Window,
18170 cx: &mut Context<Self>,
18171 ) {
18172 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18173
18174 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18175 let range = if selection.is_empty() {
18176 let point = selection.head().to_display_point(&display_map);
18177 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18178 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18179 .to_point(&display_map);
18180 start..end
18181 } else {
18182 selection.range()
18183 };
18184 if display_map.folds_in_range(range).next().is_some() {
18185 self.unfold_recursive(&Default::default(), window, cx)
18186 } else {
18187 self.fold_recursive(&Default::default(), window, cx)
18188 }
18189 }
18190
18191 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18192 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18193 let mut to_fold = Vec::new();
18194 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18195 let selections = self.selections.all_adjusted(&display_map);
18196
18197 for selection in selections {
18198 let range = selection.range().sorted();
18199 let buffer_start_row = range.start.row;
18200
18201 if range.start.row != range.end.row {
18202 let mut found = false;
18203 let mut row = range.start.row;
18204 while row <= range.end.row {
18205 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18206 {
18207 found = true;
18208 row = crease.range().end.row + 1;
18209 to_fold.push(crease);
18210 } else {
18211 row += 1
18212 }
18213 }
18214 if found {
18215 continue;
18216 }
18217 }
18218
18219 for row in (0..=range.start.row).rev() {
18220 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18221 && crease.range().end.row >= buffer_start_row
18222 {
18223 to_fold.push(crease);
18224 if row <= range.start.row {
18225 break;
18226 }
18227 }
18228 }
18229 }
18230
18231 self.fold_creases(to_fold, true, window, cx);
18232 } else {
18233 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18234 let buffer_ids = self
18235 .selections
18236 .disjoint_anchor_ranges()
18237 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18238 .collect::<HashSet<_>>();
18239 for buffer_id in buffer_ids {
18240 self.fold_buffer(buffer_id, cx);
18241 }
18242 }
18243 }
18244
18245 pub fn toggle_fold_all(
18246 &mut self,
18247 _: &actions::ToggleFoldAll,
18248 window: &mut Window,
18249 cx: &mut Context<Self>,
18250 ) {
18251 if self.buffer.read(cx).is_singleton() {
18252 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18253 let has_folds = display_map
18254 .folds_in_range(0..display_map.buffer_snapshot().len())
18255 .next()
18256 .is_some();
18257
18258 if has_folds {
18259 self.unfold_all(&actions::UnfoldAll, window, cx);
18260 } else {
18261 self.fold_all(&actions::FoldAll, window, cx);
18262 }
18263 } else {
18264 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18265 let should_unfold = buffer_ids
18266 .iter()
18267 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18268
18269 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18270 editor
18271 .update_in(cx, |editor, _, cx| {
18272 for buffer_id in buffer_ids {
18273 if should_unfold {
18274 editor.unfold_buffer(buffer_id, cx);
18275 } else {
18276 editor.fold_buffer(buffer_id, cx);
18277 }
18278 }
18279 })
18280 .ok();
18281 });
18282 }
18283 }
18284
18285 fn fold_at_level(
18286 &mut self,
18287 fold_at: &FoldAtLevel,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) {
18291 if !self.buffer.read(cx).is_singleton() {
18292 return;
18293 }
18294
18295 let fold_at_level = fold_at.0;
18296 let snapshot = self.buffer.read(cx).snapshot(cx);
18297 let mut to_fold = Vec::new();
18298 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18299
18300 let row_ranges_to_keep: Vec<Range<u32>> = self
18301 .selections
18302 .all::<Point>(&self.display_snapshot(cx))
18303 .into_iter()
18304 .map(|sel| sel.start.row..sel.end.row)
18305 .collect();
18306
18307 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18308 while start_row < end_row {
18309 match self
18310 .snapshot(window, cx)
18311 .crease_for_buffer_row(MultiBufferRow(start_row))
18312 {
18313 Some(crease) => {
18314 let nested_start_row = crease.range().start.row + 1;
18315 let nested_end_row = crease.range().end.row;
18316
18317 if current_level < fold_at_level {
18318 stack.push((nested_start_row, nested_end_row, current_level + 1));
18319 } else if current_level == fold_at_level {
18320 // Fold iff there is no selection completely contained within the fold region
18321 if !row_ranges_to_keep.iter().any(|selection| {
18322 selection.end >= nested_start_row
18323 && selection.start <= nested_end_row
18324 }) {
18325 to_fold.push(crease);
18326 }
18327 }
18328
18329 start_row = nested_end_row + 1;
18330 }
18331 None => start_row += 1,
18332 }
18333 }
18334 }
18335
18336 self.fold_creases(to_fold, true, window, cx);
18337 }
18338
18339 pub fn fold_at_level_1(
18340 &mut self,
18341 _: &actions::FoldAtLevel1,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) {
18345 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18346 }
18347
18348 pub fn fold_at_level_2(
18349 &mut self,
18350 _: &actions::FoldAtLevel2,
18351 window: &mut Window,
18352 cx: &mut Context<Self>,
18353 ) {
18354 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18355 }
18356
18357 pub fn fold_at_level_3(
18358 &mut self,
18359 _: &actions::FoldAtLevel3,
18360 window: &mut Window,
18361 cx: &mut Context<Self>,
18362 ) {
18363 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18364 }
18365
18366 pub fn fold_at_level_4(
18367 &mut self,
18368 _: &actions::FoldAtLevel4,
18369 window: &mut Window,
18370 cx: &mut Context<Self>,
18371 ) {
18372 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18373 }
18374
18375 pub fn fold_at_level_5(
18376 &mut self,
18377 _: &actions::FoldAtLevel5,
18378 window: &mut Window,
18379 cx: &mut Context<Self>,
18380 ) {
18381 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18382 }
18383
18384 pub fn fold_at_level_6(
18385 &mut self,
18386 _: &actions::FoldAtLevel6,
18387 window: &mut Window,
18388 cx: &mut Context<Self>,
18389 ) {
18390 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18391 }
18392
18393 pub fn fold_at_level_7(
18394 &mut self,
18395 _: &actions::FoldAtLevel7,
18396 window: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18400 }
18401
18402 pub fn fold_at_level_8(
18403 &mut self,
18404 _: &actions::FoldAtLevel8,
18405 window: &mut Window,
18406 cx: &mut Context<Self>,
18407 ) {
18408 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18409 }
18410
18411 pub fn fold_at_level_9(
18412 &mut self,
18413 _: &actions::FoldAtLevel9,
18414 window: &mut Window,
18415 cx: &mut Context<Self>,
18416 ) {
18417 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18418 }
18419
18420 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18421 if self.buffer.read(cx).is_singleton() {
18422 let mut fold_ranges = Vec::new();
18423 let snapshot = self.buffer.read(cx).snapshot(cx);
18424
18425 for row in 0..snapshot.max_row().0 {
18426 if let Some(foldable_range) = self
18427 .snapshot(window, cx)
18428 .crease_for_buffer_row(MultiBufferRow(row))
18429 {
18430 fold_ranges.push(foldable_range);
18431 }
18432 }
18433
18434 self.fold_creases(fold_ranges, true, window, cx);
18435 } else {
18436 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18437 editor
18438 .update_in(cx, |editor, _, cx| {
18439 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18440 editor.fold_buffer(buffer_id, cx);
18441 }
18442 })
18443 .ok();
18444 });
18445 }
18446 }
18447
18448 pub fn fold_function_bodies(
18449 &mut self,
18450 _: &actions::FoldFunctionBodies,
18451 window: &mut Window,
18452 cx: &mut Context<Self>,
18453 ) {
18454 let snapshot = self.buffer.read(cx).snapshot(cx);
18455
18456 let ranges = snapshot
18457 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18458 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18459 .collect::<Vec<_>>();
18460
18461 let creases = ranges
18462 .into_iter()
18463 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18464 .collect();
18465
18466 self.fold_creases(creases, true, window, cx);
18467 }
18468
18469 pub fn fold_recursive(
18470 &mut self,
18471 _: &actions::FoldRecursive,
18472 window: &mut Window,
18473 cx: &mut Context<Self>,
18474 ) {
18475 let mut to_fold = Vec::new();
18476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18477 let selections = self.selections.all_adjusted(&display_map);
18478
18479 for selection in selections {
18480 let range = selection.range().sorted();
18481 let buffer_start_row = range.start.row;
18482
18483 if range.start.row != range.end.row {
18484 let mut found = false;
18485 for row in range.start.row..=range.end.row {
18486 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18487 found = true;
18488 to_fold.push(crease);
18489 }
18490 }
18491 if found {
18492 continue;
18493 }
18494 }
18495
18496 for row in (0..=range.start.row).rev() {
18497 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18498 if crease.range().end.row >= buffer_start_row {
18499 to_fold.push(crease);
18500 } else {
18501 break;
18502 }
18503 }
18504 }
18505 }
18506
18507 self.fold_creases(to_fold, true, window, cx);
18508 }
18509
18510 pub fn fold_at(
18511 &mut self,
18512 buffer_row: MultiBufferRow,
18513 window: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) {
18516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18517
18518 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18519 let autoscroll = self
18520 .selections
18521 .all::<Point>(&display_map)
18522 .iter()
18523 .any(|selection| crease.range().overlaps(&selection.range()));
18524
18525 self.fold_creases(vec![crease], autoscroll, window, cx);
18526 }
18527 }
18528
18529 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18530 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18532 let buffer = display_map.buffer_snapshot();
18533 let selections = self.selections.all::<Point>(&display_map);
18534 let ranges = selections
18535 .iter()
18536 .map(|s| {
18537 let range = s.display_range(&display_map).sorted();
18538 let mut start = range.start.to_point(&display_map);
18539 let mut end = range.end.to_point(&display_map);
18540 start.column = 0;
18541 end.column = buffer.line_len(MultiBufferRow(end.row));
18542 start..end
18543 })
18544 .collect::<Vec<_>>();
18545
18546 self.unfold_ranges(&ranges, true, true, cx);
18547 } else {
18548 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18549 let buffer_ids = self
18550 .selections
18551 .disjoint_anchor_ranges()
18552 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18553 .collect::<HashSet<_>>();
18554 for buffer_id in buffer_ids {
18555 self.unfold_buffer(buffer_id, cx);
18556 }
18557 }
18558 }
18559
18560 pub fn unfold_recursive(
18561 &mut self,
18562 _: &UnfoldRecursive,
18563 _window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18567 let selections = self.selections.all::<Point>(&display_map);
18568 let ranges = selections
18569 .iter()
18570 .map(|s| {
18571 let mut range = s.display_range(&display_map).sorted();
18572 *range.start.column_mut() = 0;
18573 *range.end.column_mut() = display_map.line_len(range.end.row());
18574 let start = range.start.to_point(&display_map);
18575 let end = range.end.to_point(&display_map);
18576 start..end
18577 })
18578 .collect::<Vec<_>>();
18579
18580 self.unfold_ranges(&ranges, true, true, cx);
18581 }
18582
18583 pub fn unfold_at(
18584 &mut self,
18585 buffer_row: MultiBufferRow,
18586 _window: &mut Window,
18587 cx: &mut Context<Self>,
18588 ) {
18589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18590
18591 let intersection_range = Point::new(buffer_row.0, 0)
18592 ..Point::new(
18593 buffer_row.0,
18594 display_map.buffer_snapshot().line_len(buffer_row),
18595 );
18596
18597 let autoscroll = self
18598 .selections
18599 .all::<Point>(&display_map)
18600 .iter()
18601 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18602
18603 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18604 }
18605
18606 pub fn unfold_all(
18607 &mut self,
18608 _: &actions::UnfoldAll,
18609 _window: &mut Window,
18610 cx: &mut Context<Self>,
18611 ) {
18612 if self.buffer.read(cx).is_singleton() {
18613 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18614 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18615 } else {
18616 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18617 editor
18618 .update(cx, |editor, cx| {
18619 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18620 editor.unfold_buffer(buffer_id, cx);
18621 }
18622 })
18623 .ok();
18624 });
18625 }
18626 }
18627
18628 pub fn fold_selected_ranges(
18629 &mut self,
18630 _: &FoldSelectedRanges,
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 selections = self.selections.all_adjusted(&display_map);
18636 let ranges = selections
18637 .into_iter()
18638 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18639 .collect::<Vec<_>>();
18640 self.fold_creases(ranges, true, window, cx);
18641 }
18642
18643 pub fn fold_ranges<T: ToOffset + Clone>(
18644 &mut self,
18645 ranges: Vec<Range<T>>,
18646 auto_scroll: bool,
18647 window: &mut Window,
18648 cx: &mut Context<Self>,
18649 ) {
18650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18651 let ranges = ranges
18652 .into_iter()
18653 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18654 .collect::<Vec<_>>();
18655 self.fold_creases(ranges, auto_scroll, window, cx);
18656 }
18657
18658 pub fn fold_creases<T: ToOffset + Clone>(
18659 &mut self,
18660 creases: Vec<Crease<T>>,
18661 auto_scroll: bool,
18662 _window: &mut Window,
18663 cx: &mut Context<Self>,
18664 ) {
18665 if creases.is_empty() {
18666 return;
18667 }
18668
18669 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18670
18671 if auto_scroll {
18672 self.request_autoscroll(Autoscroll::fit(), cx);
18673 }
18674
18675 cx.notify();
18676
18677 self.scrollbar_marker_state.dirty = true;
18678 self.folds_did_change(cx);
18679 }
18680
18681 /// Removes any folds whose ranges intersect any of the given ranges.
18682 pub fn unfold_ranges<T: ToOffset + Clone>(
18683 &mut self,
18684 ranges: &[Range<T>],
18685 inclusive: bool,
18686 auto_scroll: bool,
18687 cx: &mut Context<Self>,
18688 ) {
18689 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18690 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18691 });
18692 self.folds_did_change(cx);
18693 }
18694
18695 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18696 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18697 return;
18698 }
18699 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18700 self.display_map.update(cx, |display_map, cx| {
18701 display_map.fold_buffers([buffer_id], cx)
18702 });
18703 cx.emit(EditorEvent::BufferFoldToggled {
18704 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18705 folded: true,
18706 });
18707 cx.notify();
18708 }
18709
18710 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18711 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18712 return;
18713 }
18714 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18715 self.display_map.update(cx, |display_map, cx| {
18716 display_map.unfold_buffers([buffer_id], cx);
18717 });
18718 cx.emit(EditorEvent::BufferFoldToggled {
18719 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18720 folded: false,
18721 });
18722 cx.notify();
18723 }
18724
18725 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18726 self.display_map.read(cx).is_buffer_folded(buffer)
18727 }
18728
18729 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18730 self.display_map.read(cx).folded_buffers()
18731 }
18732
18733 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18734 self.display_map.update(cx, |display_map, cx| {
18735 display_map.disable_header_for_buffer(buffer_id, cx);
18736 });
18737 cx.notify();
18738 }
18739
18740 /// Removes any folds with the given ranges.
18741 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18742 &mut self,
18743 ranges: &[Range<T>],
18744 type_id: TypeId,
18745 auto_scroll: bool,
18746 cx: &mut Context<Self>,
18747 ) {
18748 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18749 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18750 });
18751 self.folds_did_change(cx);
18752 }
18753
18754 fn remove_folds_with<T: ToOffset + Clone>(
18755 &mut self,
18756 ranges: &[Range<T>],
18757 auto_scroll: bool,
18758 cx: &mut Context<Self>,
18759 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18760 ) {
18761 if ranges.is_empty() {
18762 return;
18763 }
18764
18765 let mut buffers_affected = HashSet::default();
18766 let multi_buffer = self.buffer().read(cx);
18767 for range in ranges {
18768 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18769 buffers_affected.insert(buffer.read(cx).remote_id());
18770 };
18771 }
18772
18773 self.display_map.update(cx, update);
18774
18775 if auto_scroll {
18776 self.request_autoscroll(Autoscroll::fit(), cx);
18777 }
18778
18779 cx.notify();
18780 self.scrollbar_marker_state.dirty = true;
18781 self.active_indent_guides_state.dirty = true;
18782 }
18783
18784 pub fn update_renderer_widths(
18785 &mut self,
18786 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18787 cx: &mut Context<Self>,
18788 ) -> bool {
18789 self.display_map
18790 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18791 }
18792
18793 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18794 self.display_map.read(cx).fold_placeholder.clone()
18795 }
18796
18797 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18798 self.buffer.update(cx, |buffer, cx| {
18799 buffer.set_all_diff_hunks_expanded(cx);
18800 });
18801 }
18802
18803 pub fn expand_all_diff_hunks(
18804 &mut self,
18805 _: &ExpandAllDiffHunks,
18806 _window: &mut Window,
18807 cx: &mut Context<Self>,
18808 ) {
18809 self.buffer.update(cx, |buffer, cx| {
18810 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18811 });
18812 }
18813
18814 pub fn collapse_all_diff_hunks(
18815 &mut self,
18816 _: &CollapseAllDiffHunks,
18817 _window: &mut Window,
18818 cx: &mut Context<Self>,
18819 ) {
18820 self.buffer.update(cx, |buffer, cx| {
18821 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18822 });
18823 }
18824
18825 pub fn toggle_selected_diff_hunks(
18826 &mut self,
18827 _: &ToggleSelectedDiffHunks,
18828 _window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 let ranges: Vec<_> = self
18832 .selections
18833 .disjoint_anchors()
18834 .iter()
18835 .map(|s| s.range())
18836 .collect();
18837 self.toggle_diff_hunks_in_ranges(ranges, cx);
18838 }
18839
18840 pub fn diff_hunks_in_ranges<'a>(
18841 &'a self,
18842 ranges: &'a [Range<Anchor>],
18843 buffer: &'a MultiBufferSnapshot,
18844 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18845 ranges.iter().flat_map(move |range| {
18846 let end_excerpt_id = range.end.excerpt_id;
18847 let range = range.to_point(buffer);
18848 let mut peek_end = range.end;
18849 if range.end.row < buffer.max_row().0 {
18850 peek_end = Point::new(range.end.row + 1, 0);
18851 }
18852 buffer
18853 .diff_hunks_in_range(range.start..peek_end)
18854 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18855 })
18856 }
18857
18858 pub fn has_stageable_diff_hunks_in_ranges(
18859 &self,
18860 ranges: &[Range<Anchor>],
18861 snapshot: &MultiBufferSnapshot,
18862 ) -> bool {
18863 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18864 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18865 }
18866
18867 pub fn toggle_staged_selected_diff_hunks(
18868 &mut self,
18869 _: &::git::ToggleStaged,
18870 _: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 let snapshot = self.buffer.read(cx).snapshot(cx);
18874 let ranges: Vec<_> = self
18875 .selections
18876 .disjoint_anchors()
18877 .iter()
18878 .map(|s| s.range())
18879 .collect();
18880 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18881 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18882 }
18883
18884 pub fn set_render_diff_hunk_controls(
18885 &mut self,
18886 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18887 cx: &mut Context<Self>,
18888 ) {
18889 self.render_diff_hunk_controls = render_diff_hunk_controls;
18890 cx.notify();
18891 }
18892
18893 pub fn stage_and_next(
18894 &mut self,
18895 _: &::git::StageAndNext,
18896 window: &mut Window,
18897 cx: &mut Context<Self>,
18898 ) {
18899 self.do_stage_or_unstage_and_next(true, window, cx);
18900 }
18901
18902 pub fn unstage_and_next(
18903 &mut self,
18904 _: &::git::UnstageAndNext,
18905 window: &mut Window,
18906 cx: &mut Context<Self>,
18907 ) {
18908 self.do_stage_or_unstage_and_next(false, window, cx);
18909 }
18910
18911 pub fn stage_or_unstage_diff_hunks(
18912 &mut self,
18913 stage: bool,
18914 ranges: Vec<Range<Anchor>>,
18915 cx: &mut Context<Self>,
18916 ) {
18917 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18918 cx.spawn(async move |this, cx| {
18919 task.await?;
18920 this.update(cx, |this, cx| {
18921 let snapshot = this.buffer.read(cx).snapshot(cx);
18922 let chunk_by = this
18923 .diff_hunks_in_ranges(&ranges, &snapshot)
18924 .chunk_by(|hunk| hunk.buffer_id);
18925 for (buffer_id, hunks) in &chunk_by {
18926 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18927 }
18928 })
18929 })
18930 .detach_and_log_err(cx);
18931 }
18932
18933 fn save_buffers_for_ranges_if_needed(
18934 &mut self,
18935 ranges: &[Range<Anchor>],
18936 cx: &mut Context<Editor>,
18937 ) -> Task<Result<()>> {
18938 let multibuffer = self.buffer.read(cx);
18939 let snapshot = multibuffer.read(cx);
18940 let buffer_ids: HashSet<_> = ranges
18941 .iter()
18942 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18943 .collect();
18944 drop(snapshot);
18945
18946 let mut buffers = HashSet::default();
18947 for buffer_id in buffer_ids {
18948 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18949 let buffer = buffer_entity.read(cx);
18950 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18951 {
18952 buffers.insert(buffer_entity);
18953 }
18954 }
18955 }
18956
18957 if let Some(project) = &self.project {
18958 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18959 } else {
18960 Task::ready(Ok(()))
18961 }
18962 }
18963
18964 fn do_stage_or_unstage_and_next(
18965 &mut self,
18966 stage: bool,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18971
18972 if ranges.iter().any(|range| range.start != range.end) {
18973 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18974 return;
18975 }
18976
18977 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18978 let snapshot = self.snapshot(window, cx);
18979 let position = self
18980 .selections
18981 .newest::<Point>(&snapshot.display_snapshot)
18982 .head();
18983 let mut row = snapshot
18984 .buffer_snapshot()
18985 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18986 .find(|hunk| hunk.row_range.start.0 > position.row)
18987 .map(|hunk| hunk.row_range.start);
18988
18989 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18990 // Outside of the project diff editor, wrap around to the beginning.
18991 if !all_diff_hunks_expanded {
18992 row = row.or_else(|| {
18993 snapshot
18994 .buffer_snapshot()
18995 .diff_hunks_in_range(Point::zero()..position)
18996 .find(|hunk| hunk.row_range.end.0 < position.row)
18997 .map(|hunk| hunk.row_range.start)
18998 });
18999 }
19000
19001 if let Some(row) = row {
19002 let destination = Point::new(row.0, 0);
19003 let autoscroll = Autoscroll::center();
19004
19005 self.unfold_ranges(&[destination..destination], false, false, cx);
19006 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19007 s.select_ranges([destination..destination]);
19008 });
19009 }
19010 }
19011
19012 fn do_stage_or_unstage(
19013 &self,
19014 stage: bool,
19015 buffer_id: BufferId,
19016 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19017 cx: &mut App,
19018 ) -> Option<()> {
19019 let project = self.project()?;
19020 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19021 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19022 let buffer_snapshot = buffer.read(cx).snapshot();
19023 let file_exists = buffer_snapshot
19024 .file()
19025 .is_some_and(|file| file.disk_state().exists());
19026 diff.update(cx, |diff, cx| {
19027 diff.stage_or_unstage_hunks(
19028 stage,
19029 &hunks
19030 .map(|hunk| buffer_diff::DiffHunk {
19031 buffer_range: hunk.buffer_range,
19032 diff_base_byte_range: hunk.diff_base_byte_range,
19033 secondary_status: hunk.secondary_status,
19034 range: Point::zero()..Point::zero(), // unused
19035 })
19036 .collect::<Vec<_>>(),
19037 &buffer_snapshot,
19038 file_exists,
19039 cx,
19040 )
19041 });
19042 None
19043 }
19044
19045 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19046 let ranges: Vec<_> = self
19047 .selections
19048 .disjoint_anchors()
19049 .iter()
19050 .map(|s| s.range())
19051 .collect();
19052 self.buffer
19053 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19054 }
19055
19056 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19057 self.buffer.update(cx, |buffer, cx| {
19058 let ranges = vec![Anchor::min()..Anchor::max()];
19059 if !buffer.all_diff_hunks_expanded()
19060 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19061 {
19062 buffer.collapse_diff_hunks(ranges, cx);
19063 true
19064 } else {
19065 false
19066 }
19067 })
19068 }
19069
19070 fn toggle_diff_hunks_in_ranges(
19071 &mut self,
19072 ranges: Vec<Range<Anchor>>,
19073 cx: &mut Context<Editor>,
19074 ) {
19075 self.buffer.update(cx, |buffer, cx| {
19076 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19077 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19078 })
19079 }
19080
19081 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19082 self.buffer.update(cx, |buffer, cx| {
19083 let snapshot = buffer.snapshot(cx);
19084 let excerpt_id = range.end.excerpt_id;
19085 let point_range = range.to_point(&snapshot);
19086 let expand = !buffer.single_hunk_is_expanded(range, cx);
19087 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19088 })
19089 }
19090
19091 pub(crate) fn apply_all_diff_hunks(
19092 &mut self,
19093 _: &ApplyAllDiffHunks,
19094 window: &mut Window,
19095 cx: &mut Context<Self>,
19096 ) {
19097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19098
19099 let buffers = self.buffer.read(cx).all_buffers();
19100 for branch_buffer in buffers {
19101 branch_buffer.update(cx, |branch_buffer, cx| {
19102 branch_buffer.merge_into_base(Vec::new(), cx);
19103 });
19104 }
19105
19106 if let Some(project) = self.project.clone() {
19107 self.save(
19108 SaveOptions {
19109 format: true,
19110 autosave: false,
19111 },
19112 project,
19113 window,
19114 cx,
19115 )
19116 .detach_and_log_err(cx);
19117 }
19118 }
19119
19120 pub(crate) fn apply_selected_diff_hunks(
19121 &mut self,
19122 _: &ApplyDiffHunk,
19123 window: &mut Window,
19124 cx: &mut Context<Self>,
19125 ) {
19126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19127 let snapshot = self.snapshot(window, cx);
19128 let hunks = snapshot.hunks_for_ranges(
19129 self.selections
19130 .all(&snapshot.display_snapshot)
19131 .into_iter()
19132 .map(|selection| selection.range()),
19133 );
19134 let mut ranges_by_buffer = HashMap::default();
19135 self.transact(window, cx, |editor, _window, cx| {
19136 for hunk in hunks {
19137 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19138 ranges_by_buffer
19139 .entry(buffer.clone())
19140 .or_insert_with(Vec::new)
19141 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19142 }
19143 }
19144
19145 for (buffer, ranges) in ranges_by_buffer {
19146 buffer.update(cx, |buffer, cx| {
19147 buffer.merge_into_base(ranges, cx);
19148 });
19149 }
19150 });
19151
19152 if let Some(project) = self.project.clone() {
19153 self.save(
19154 SaveOptions {
19155 format: true,
19156 autosave: false,
19157 },
19158 project,
19159 window,
19160 cx,
19161 )
19162 .detach_and_log_err(cx);
19163 }
19164 }
19165
19166 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19167 if hovered != self.gutter_hovered {
19168 self.gutter_hovered = hovered;
19169 cx.notify();
19170 }
19171 }
19172
19173 pub fn insert_blocks(
19174 &mut self,
19175 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19176 autoscroll: Option<Autoscroll>,
19177 cx: &mut Context<Self>,
19178 ) -> Vec<CustomBlockId> {
19179 let blocks = self
19180 .display_map
19181 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19182 if let Some(autoscroll) = autoscroll {
19183 self.request_autoscroll(autoscroll, cx);
19184 }
19185 cx.notify();
19186 blocks
19187 }
19188
19189 pub fn resize_blocks(
19190 &mut self,
19191 heights: HashMap<CustomBlockId, u32>,
19192 autoscroll: Option<Autoscroll>,
19193 cx: &mut Context<Self>,
19194 ) {
19195 self.display_map
19196 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19197 if let Some(autoscroll) = autoscroll {
19198 self.request_autoscroll(autoscroll, cx);
19199 }
19200 cx.notify();
19201 }
19202
19203 pub fn replace_blocks(
19204 &mut self,
19205 renderers: HashMap<CustomBlockId, RenderBlock>,
19206 autoscroll: Option<Autoscroll>,
19207 cx: &mut Context<Self>,
19208 ) {
19209 self.display_map
19210 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19211 if let Some(autoscroll) = autoscroll {
19212 self.request_autoscroll(autoscroll, cx);
19213 }
19214 cx.notify();
19215 }
19216
19217 pub fn remove_blocks(
19218 &mut self,
19219 block_ids: HashSet<CustomBlockId>,
19220 autoscroll: Option<Autoscroll>,
19221 cx: &mut Context<Self>,
19222 ) {
19223 self.display_map.update(cx, |display_map, cx| {
19224 display_map.remove_blocks(block_ids, cx)
19225 });
19226 if let Some(autoscroll) = autoscroll {
19227 self.request_autoscroll(autoscroll, cx);
19228 }
19229 cx.notify();
19230 }
19231
19232 pub fn row_for_block(
19233 &self,
19234 block_id: CustomBlockId,
19235 cx: &mut Context<Self>,
19236 ) -> Option<DisplayRow> {
19237 self.display_map
19238 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19239 }
19240
19241 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19242 self.focused_block = Some(focused_block);
19243 }
19244
19245 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19246 self.focused_block.take()
19247 }
19248
19249 pub fn insert_creases(
19250 &mut self,
19251 creases: impl IntoIterator<Item = Crease<Anchor>>,
19252 cx: &mut Context<Self>,
19253 ) -> Vec<CreaseId> {
19254 self.display_map
19255 .update(cx, |map, cx| map.insert_creases(creases, cx))
19256 }
19257
19258 pub fn remove_creases(
19259 &mut self,
19260 ids: impl IntoIterator<Item = CreaseId>,
19261 cx: &mut Context<Self>,
19262 ) -> Vec<(CreaseId, Range<Anchor>)> {
19263 self.display_map
19264 .update(cx, |map, cx| map.remove_creases(ids, cx))
19265 }
19266
19267 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19268 self.display_map
19269 .update(cx, |map, cx| map.snapshot(cx))
19270 .longest_row()
19271 }
19272
19273 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19274 self.display_map
19275 .update(cx, |map, cx| map.snapshot(cx))
19276 .max_point()
19277 }
19278
19279 pub fn text(&self, cx: &App) -> String {
19280 self.buffer.read(cx).read(cx).text()
19281 }
19282
19283 pub fn is_empty(&self, cx: &App) -> bool {
19284 self.buffer.read(cx).read(cx).is_empty()
19285 }
19286
19287 pub fn text_option(&self, cx: &App) -> Option<String> {
19288 let text = self.text(cx);
19289 let text = text.trim();
19290
19291 if text.is_empty() {
19292 return None;
19293 }
19294
19295 Some(text.to_string())
19296 }
19297
19298 pub fn set_text(
19299 &mut self,
19300 text: impl Into<Arc<str>>,
19301 window: &mut Window,
19302 cx: &mut Context<Self>,
19303 ) {
19304 self.transact(window, cx, |this, _, cx| {
19305 this.buffer
19306 .read(cx)
19307 .as_singleton()
19308 .expect("you can only call set_text on editors for singleton buffers")
19309 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19310 });
19311 }
19312
19313 pub fn display_text(&self, cx: &mut App) -> String {
19314 self.display_map
19315 .update(cx, |map, cx| map.snapshot(cx))
19316 .text()
19317 }
19318
19319 fn create_minimap(
19320 &self,
19321 minimap_settings: MinimapSettings,
19322 window: &mut Window,
19323 cx: &mut Context<Self>,
19324 ) -> Option<Entity<Self>> {
19325 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19326 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19327 }
19328
19329 fn initialize_new_minimap(
19330 &self,
19331 minimap_settings: MinimapSettings,
19332 window: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) -> Entity<Self> {
19335 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19336
19337 let mut minimap = Editor::new_internal(
19338 EditorMode::Minimap {
19339 parent: cx.weak_entity(),
19340 },
19341 self.buffer.clone(),
19342 None,
19343 Some(self.display_map.clone()),
19344 window,
19345 cx,
19346 );
19347 minimap.scroll_manager.clone_state(&self.scroll_manager);
19348 minimap.set_text_style_refinement(TextStyleRefinement {
19349 font_size: Some(MINIMAP_FONT_SIZE),
19350 font_weight: Some(MINIMAP_FONT_WEIGHT),
19351 ..Default::default()
19352 });
19353 minimap.update_minimap_configuration(minimap_settings, cx);
19354 cx.new(|_| minimap)
19355 }
19356
19357 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19358 let current_line_highlight = minimap_settings
19359 .current_line_highlight
19360 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19361 self.set_current_line_highlight(Some(current_line_highlight));
19362 }
19363
19364 pub fn minimap(&self) -> Option<&Entity<Self>> {
19365 self.minimap
19366 .as_ref()
19367 .filter(|_| self.minimap_visibility.visible())
19368 }
19369
19370 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19371 let mut wrap_guides = smallvec![];
19372
19373 if self.show_wrap_guides == Some(false) {
19374 return wrap_guides;
19375 }
19376
19377 let settings = self.buffer.read(cx).language_settings(cx);
19378 if settings.show_wrap_guides {
19379 match self.soft_wrap_mode(cx) {
19380 SoftWrap::Column(soft_wrap) => {
19381 wrap_guides.push((soft_wrap as usize, true));
19382 }
19383 SoftWrap::Bounded(soft_wrap) => {
19384 wrap_guides.push((soft_wrap as usize, true));
19385 }
19386 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19387 }
19388 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19389 }
19390
19391 wrap_guides
19392 }
19393
19394 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19395 let settings = self.buffer.read(cx).language_settings(cx);
19396 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19397 match mode {
19398 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19399 SoftWrap::None
19400 }
19401 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19402 language_settings::SoftWrap::PreferredLineLength => {
19403 SoftWrap::Column(settings.preferred_line_length)
19404 }
19405 language_settings::SoftWrap::Bounded => {
19406 SoftWrap::Bounded(settings.preferred_line_length)
19407 }
19408 }
19409 }
19410
19411 pub fn set_soft_wrap_mode(
19412 &mut self,
19413 mode: language_settings::SoftWrap,
19414
19415 cx: &mut Context<Self>,
19416 ) {
19417 self.soft_wrap_mode_override = Some(mode);
19418 cx.notify();
19419 }
19420
19421 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19422 self.hard_wrap = hard_wrap;
19423 cx.notify();
19424 }
19425
19426 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19427 self.text_style_refinement = Some(style);
19428 }
19429
19430 /// called by the Element so we know what style we were most recently rendered with.
19431 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19432 // We intentionally do not inform the display map about the minimap style
19433 // so that wrapping is not recalculated and stays consistent for the editor
19434 // and its linked minimap.
19435 if !self.mode.is_minimap() {
19436 let font = style.text.font();
19437 let font_size = style.text.font_size.to_pixels(window.rem_size());
19438 let display_map = self
19439 .placeholder_display_map
19440 .as_ref()
19441 .filter(|_| self.is_empty(cx))
19442 .unwrap_or(&self.display_map);
19443
19444 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19445 }
19446 self.style = Some(style);
19447 }
19448
19449 pub fn style(&self) -> Option<&EditorStyle> {
19450 self.style.as_ref()
19451 }
19452
19453 // Called by the element. This method is not designed to be called outside of the editor
19454 // element's layout code because it does not notify when rewrapping is computed synchronously.
19455 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19456 if self.is_empty(cx) {
19457 self.placeholder_display_map
19458 .as_ref()
19459 .map_or(false, |display_map| {
19460 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19461 })
19462 } else {
19463 self.display_map
19464 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19465 }
19466 }
19467
19468 pub fn set_soft_wrap(&mut self) {
19469 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19470 }
19471
19472 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19473 if self.soft_wrap_mode_override.is_some() {
19474 self.soft_wrap_mode_override.take();
19475 } else {
19476 let soft_wrap = match self.soft_wrap_mode(cx) {
19477 SoftWrap::GitDiff => return,
19478 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19479 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19480 language_settings::SoftWrap::None
19481 }
19482 };
19483 self.soft_wrap_mode_override = Some(soft_wrap);
19484 }
19485 cx.notify();
19486 }
19487
19488 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19489 let Some(workspace) = self.workspace() else {
19490 return;
19491 };
19492 let fs = workspace.read(cx).app_state().fs.clone();
19493 let current_show = TabBarSettings::get_global(cx).show;
19494 update_settings_file(fs, cx, move |setting, _| {
19495 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19496 });
19497 }
19498
19499 pub fn toggle_indent_guides(
19500 &mut self,
19501 _: &ToggleIndentGuides,
19502 _: &mut Window,
19503 cx: &mut Context<Self>,
19504 ) {
19505 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19506 self.buffer
19507 .read(cx)
19508 .language_settings(cx)
19509 .indent_guides
19510 .enabled
19511 });
19512 self.show_indent_guides = Some(!currently_enabled);
19513 cx.notify();
19514 }
19515
19516 fn should_show_indent_guides(&self) -> Option<bool> {
19517 self.show_indent_guides
19518 }
19519
19520 pub fn toggle_line_numbers(
19521 &mut self,
19522 _: &ToggleLineNumbers,
19523 _: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 let mut editor_settings = EditorSettings::get_global(cx).clone();
19527 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19528 EditorSettings::override_global(editor_settings, cx);
19529 }
19530
19531 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19532 if let Some(show_line_numbers) = self.show_line_numbers {
19533 return show_line_numbers;
19534 }
19535 EditorSettings::get_global(cx).gutter.line_numbers
19536 }
19537
19538 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19539 match (
19540 self.use_relative_line_numbers,
19541 EditorSettings::get_global(cx).relative_line_numbers,
19542 ) {
19543 (None, setting) => setting,
19544 (Some(false), _) => RelativeLineNumbers::Disabled,
19545 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19546 (Some(true), _) => RelativeLineNumbers::Enabled,
19547 }
19548 }
19549
19550 pub fn toggle_relative_line_numbers(
19551 &mut self,
19552 _: &ToggleRelativeLineNumbers,
19553 _: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 let is_relative = self.relative_line_numbers(cx);
19557 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19558 }
19559
19560 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19561 self.use_relative_line_numbers = is_relative;
19562 cx.notify();
19563 }
19564
19565 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19566 self.show_gutter = show_gutter;
19567 cx.notify();
19568 }
19569
19570 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19571 self.show_scrollbars = ScrollbarAxes {
19572 horizontal: show,
19573 vertical: show,
19574 };
19575 cx.notify();
19576 }
19577
19578 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19579 self.show_scrollbars.vertical = show;
19580 cx.notify();
19581 }
19582
19583 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19584 self.show_scrollbars.horizontal = show;
19585 cx.notify();
19586 }
19587
19588 pub fn set_minimap_visibility(
19589 &mut self,
19590 minimap_visibility: MinimapVisibility,
19591 window: &mut Window,
19592 cx: &mut Context<Self>,
19593 ) {
19594 if self.minimap_visibility != minimap_visibility {
19595 if minimap_visibility.visible() && self.minimap.is_none() {
19596 let minimap_settings = EditorSettings::get_global(cx).minimap;
19597 self.minimap =
19598 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19599 }
19600 self.minimap_visibility = minimap_visibility;
19601 cx.notify();
19602 }
19603 }
19604
19605 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19606 self.set_show_scrollbars(false, cx);
19607 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19608 }
19609
19610 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19611 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19612 }
19613
19614 /// Normally the text in full mode and auto height editors is padded on the
19615 /// left side by roughly half a character width for improved hit testing.
19616 ///
19617 /// Use this method to disable this for cases where this is not wanted (e.g.
19618 /// if you want to align the editor text with some other text above or below)
19619 /// or if you want to add this padding to single-line editors.
19620 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19621 self.offset_content = offset_content;
19622 cx.notify();
19623 }
19624
19625 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19626 self.show_line_numbers = Some(show_line_numbers);
19627 cx.notify();
19628 }
19629
19630 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19631 self.disable_expand_excerpt_buttons = true;
19632 cx.notify();
19633 }
19634
19635 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19636 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19637 cx.notify();
19638 }
19639
19640 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19641 self.show_code_actions = Some(show_code_actions);
19642 cx.notify();
19643 }
19644
19645 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19646 self.show_runnables = Some(show_runnables);
19647 cx.notify();
19648 }
19649
19650 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19651 self.show_breakpoints = Some(show_breakpoints);
19652 cx.notify();
19653 }
19654
19655 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19656 if self.display_map.read(cx).masked != masked {
19657 self.display_map.update(cx, |map, _| map.masked = masked);
19658 }
19659 cx.notify()
19660 }
19661
19662 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19663 self.show_wrap_guides = Some(show_wrap_guides);
19664 cx.notify();
19665 }
19666
19667 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19668 self.show_indent_guides = Some(show_indent_guides);
19669 cx.notify();
19670 }
19671
19672 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19673 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19674 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19675 && let Some(dir) = file.abs_path(cx).parent()
19676 {
19677 return Some(dir.to_owned());
19678 }
19679 }
19680
19681 None
19682 }
19683
19684 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19685 self.active_excerpt(cx)?
19686 .1
19687 .read(cx)
19688 .file()
19689 .and_then(|f| f.as_local())
19690 }
19691
19692 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19693 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19694 let buffer = buffer.read(cx);
19695 if let Some(project_path) = buffer.project_path(cx) {
19696 let project = self.project()?.read(cx);
19697 project.absolute_path(&project_path, cx)
19698 } else {
19699 buffer
19700 .file()
19701 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19702 }
19703 })
19704 }
19705
19706 pub fn reveal_in_finder(
19707 &mut self,
19708 _: &RevealInFileManager,
19709 _window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 if let Some(target) = self.target_file(cx) {
19713 cx.reveal_path(&target.abs_path(cx));
19714 }
19715 }
19716
19717 pub fn copy_path(
19718 &mut self,
19719 _: &zed_actions::workspace::CopyPath,
19720 _window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) {
19723 if let Some(path) = self.target_file_abs_path(cx)
19724 && let Some(path) = path.to_str()
19725 {
19726 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19727 } else {
19728 cx.propagate();
19729 }
19730 }
19731
19732 pub fn copy_relative_path(
19733 &mut self,
19734 _: &zed_actions::workspace::CopyRelativePath,
19735 _window: &mut Window,
19736 cx: &mut Context<Self>,
19737 ) {
19738 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19739 let project = self.project()?.read(cx);
19740 let path = buffer.read(cx).file()?.path();
19741 let path = path.display(project.path_style(cx));
19742 Some(path)
19743 }) {
19744 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19745 } else {
19746 cx.propagate();
19747 }
19748 }
19749
19750 /// Returns the project path for the editor's buffer, if any buffer is
19751 /// opened in the editor.
19752 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19753 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19754 buffer.read(cx).project_path(cx)
19755 } else {
19756 None
19757 }
19758 }
19759
19760 // Returns true if the editor handled a go-to-line request
19761 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19762 maybe!({
19763 let breakpoint_store = self.breakpoint_store.as_ref()?;
19764
19765 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19766 else {
19767 self.clear_row_highlights::<ActiveDebugLine>();
19768 return None;
19769 };
19770
19771 let position = active_stack_frame.position;
19772 let buffer_id = position.buffer_id?;
19773 let snapshot = self
19774 .project
19775 .as_ref()?
19776 .read(cx)
19777 .buffer_for_id(buffer_id, cx)?
19778 .read(cx)
19779 .snapshot();
19780
19781 let mut handled = false;
19782 for (id, ExcerptRange { context, .. }) in
19783 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19784 {
19785 if context.start.cmp(&position, &snapshot).is_ge()
19786 || context.end.cmp(&position, &snapshot).is_lt()
19787 {
19788 continue;
19789 }
19790 let snapshot = self.buffer.read(cx).snapshot(cx);
19791 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19792
19793 handled = true;
19794 self.clear_row_highlights::<ActiveDebugLine>();
19795
19796 self.go_to_line::<ActiveDebugLine>(
19797 multibuffer_anchor,
19798 Some(cx.theme().colors().editor_debugger_active_line_background),
19799 window,
19800 cx,
19801 );
19802
19803 cx.notify();
19804 }
19805
19806 handled.then_some(())
19807 })
19808 .is_some()
19809 }
19810
19811 pub fn copy_file_name_without_extension(
19812 &mut self,
19813 _: &CopyFileNameWithoutExtension,
19814 _: &mut Window,
19815 cx: &mut Context<Self>,
19816 ) {
19817 if let Some(file) = self.target_file(cx)
19818 && let Some(file_stem) = file.path().file_stem()
19819 {
19820 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19821 }
19822 }
19823
19824 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19825 if let Some(file) = self.target_file(cx)
19826 && let Some(name) = file.path().file_name()
19827 {
19828 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19829 }
19830 }
19831
19832 pub fn toggle_git_blame(
19833 &mut self,
19834 _: &::git::Blame,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19839
19840 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19841 self.start_git_blame(true, window, cx);
19842 }
19843
19844 cx.notify();
19845 }
19846
19847 pub fn toggle_git_blame_inline(
19848 &mut self,
19849 _: &ToggleGitBlameInline,
19850 window: &mut Window,
19851 cx: &mut Context<Self>,
19852 ) {
19853 self.toggle_git_blame_inline_internal(true, window, cx);
19854 cx.notify();
19855 }
19856
19857 pub fn open_git_blame_commit(
19858 &mut self,
19859 _: &OpenGitBlameCommit,
19860 window: &mut Window,
19861 cx: &mut Context<Self>,
19862 ) {
19863 self.open_git_blame_commit_internal(window, cx);
19864 }
19865
19866 fn open_git_blame_commit_internal(
19867 &mut self,
19868 window: &mut Window,
19869 cx: &mut Context<Self>,
19870 ) -> Option<()> {
19871 let blame = self.blame.as_ref()?;
19872 let snapshot = self.snapshot(window, cx);
19873 let cursor = self
19874 .selections
19875 .newest::<Point>(&snapshot.display_snapshot)
19876 .head();
19877 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19878 let (_, blame_entry) = blame
19879 .update(cx, |blame, cx| {
19880 blame
19881 .blame_for_rows(
19882 &[RowInfo {
19883 buffer_id: Some(buffer.remote_id()),
19884 buffer_row: Some(point.row),
19885 ..Default::default()
19886 }],
19887 cx,
19888 )
19889 .next()
19890 })
19891 .flatten()?;
19892 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19893 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19894 let workspace = self.workspace()?.downgrade();
19895 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19896 None
19897 }
19898
19899 pub fn git_blame_inline_enabled(&self) -> bool {
19900 self.git_blame_inline_enabled
19901 }
19902
19903 pub fn toggle_selection_menu(
19904 &mut self,
19905 _: &ToggleSelectionMenu,
19906 _: &mut Window,
19907 cx: &mut Context<Self>,
19908 ) {
19909 self.show_selection_menu = self
19910 .show_selection_menu
19911 .map(|show_selections_menu| !show_selections_menu)
19912 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19913
19914 cx.notify();
19915 }
19916
19917 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19918 self.show_selection_menu
19919 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19920 }
19921
19922 fn start_git_blame(
19923 &mut self,
19924 user_triggered: bool,
19925 window: &mut Window,
19926 cx: &mut Context<Self>,
19927 ) {
19928 if let Some(project) = self.project() {
19929 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19930 && buffer.read(cx).file().is_none()
19931 {
19932 return;
19933 }
19934
19935 let focused = self.focus_handle(cx).contains_focused(window, cx);
19936
19937 let project = project.clone();
19938 let blame = cx
19939 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19940 self.blame_subscription =
19941 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19942 self.blame = Some(blame);
19943 }
19944 }
19945
19946 fn toggle_git_blame_inline_internal(
19947 &mut self,
19948 user_triggered: bool,
19949 window: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 if self.git_blame_inline_enabled {
19953 self.git_blame_inline_enabled = false;
19954 self.show_git_blame_inline = false;
19955 self.show_git_blame_inline_delay_task.take();
19956 } else {
19957 self.git_blame_inline_enabled = true;
19958 self.start_git_blame_inline(user_triggered, window, cx);
19959 }
19960
19961 cx.notify();
19962 }
19963
19964 fn start_git_blame_inline(
19965 &mut self,
19966 user_triggered: bool,
19967 window: &mut Window,
19968 cx: &mut Context<Self>,
19969 ) {
19970 self.start_git_blame(user_triggered, window, cx);
19971
19972 if ProjectSettings::get_global(cx)
19973 .git
19974 .inline_blame_delay()
19975 .is_some()
19976 {
19977 self.start_inline_blame_timer(window, cx);
19978 } else {
19979 self.show_git_blame_inline = true
19980 }
19981 }
19982
19983 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19984 self.blame.as_ref()
19985 }
19986
19987 pub fn show_git_blame_gutter(&self) -> bool {
19988 self.show_git_blame_gutter
19989 }
19990
19991 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19992 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19993 }
19994
19995 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19996 self.show_git_blame_inline
19997 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19998 && !self.newest_selection_head_on_empty_line(cx)
19999 && self.has_blame_entries(cx)
20000 }
20001
20002 fn has_blame_entries(&self, cx: &App) -> bool {
20003 self.blame()
20004 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20005 }
20006
20007 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20008 let cursor_anchor = self.selections.newest_anchor().head();
20009
20010 let snapshot = self.buffer.read(cx).snapshot(cx);
20011 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20012
20013 snapshot.line_len(buffer_row) == 0
20014 }
20015
20016 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20017 let buffer_and_selection = maybe!({
20018 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20019 let selection_range = selection.range();
20020
20021 let multi_buffer = self.buffer().read(cx);
20022 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20023 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20024
20025 let (buffer, range, _) = if selection.reversed {
20026 buffer_ranges.first()
20027 } else {
20028 buffer_ranges.last()
20029 }?;
20030
20031 let selection = text::ToPoint::to_point(&range.start, buffer).row
20032 ..text::ToPoint::to_point(&range.end, buffer).row;
20033 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20034 });
20035
20036 let Some((buffer, selection)) = buffer_and_selection else {
20037 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20038 };
20039
20040 let Some(project) = self.project() else {
20041 return Task::ready(Err(anyhow!("editor does not have project")));
20042 };
20043
20044 project.update(cx, |project, cx| {
20045 project.get_permalink_to_line(&buffer, selection, cx)
20046 })
20047 }
20048
20049 pub fn copy_permalink_to_line(
20050 &mut self,
20051 _: &CopyPermalinkToLine,
20052 window: &mut Window,
20053 cx: &mut Context<Self>,
20054 ) {
20055 let permalink_task = self.get_permalink_to_line(cx);
20056 let workspace = self.workspace();
20057
20058 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20059 Ok(permalink) => {
20060 cx.update(|_, cx| {
20061 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20062 })
20063 .ok();
20064 }
20065 Err(err) => {
20066 let message = format!("Failed to copy permalink: {err}");
20067
20068 anyhow::Result::<()>::Err(err).log_err();
20069
20070 if let Some(workspace) = workspace {
20071 workspace
20072 .update_in(cx, |workspace, _, cx| {
20073 struct CopyPermalinkToLine;
20074
20075 workspace.show_toast(
20076 Toast::new(
20077 NotificationId::unique::<CopyPermalinkToLine>(),
20078 message,
20079 ),
20080 cx,
20081 )
20082 })
20083 .ok();
20084 }
20085 }
20086 })
20087 .detach();
20088 }
20089
20090 pub fn copy_file_location(
20091 &mut self,
20092 _: &CopyFileLocation,
20093 _: &mut Window,
20094 cx: &mut Context<Self>,
20095 ) {
20096 let selection = self
20097 .selections
20098 .newest::<Point>(&self.display_snapshot(cx))
20099 .start
20100 .row
20101 + 1;
20102 if let Some(file) = self.target_file(cx) {
20103 let path = file.path().display(file.path_style(cx));
20104 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20105 }
20106 }
20107
20108 pub fn open_permalink_to_line(
20109 &mut self,
20110 _: &OpenPermalinkToLine,
20111 window: &mut Window,
20112 cx: &mut Context<Self>,
20113 ) {
20114 let permalink_task = self.get_permalink_to_line(cx);
20115 let workspace = self.workspace();
20116
20117 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20118 Ok(permalink) => {
20119 cx.update(|_, cx| {
20120 cx.open_url(permalink.as_ref());
20121 })
20122 .ok();
20123 }
20124 Err(err) => {
20125 let message = format!("Failed to open permalink: {err}");
20126
20127 anyhow::Result::<()>::Err(err).log_err();
20128
20129 if let Some(workspace) = workspace {
20130 workspace
20131 .update(cx, |workspace, cx| {
20132 struct OpenPermalinkToLine;
20133
20134 workspace.show_toast(
20135 Toast::new(
20136 NotificationId::unique::<OpenPermalinkToLine>(),
20137 message,
20138 ),
20139 cx,
20140 )
20141 })
20142 .ok();
20143 }
20144 }
20145 })
20146 .detach();
20147 }
20148
20149 pub fn insert_uuid_v4(
20150 &mut self,
20151 _: &InsertUuidV4,
20152 window: &mut Window,
20153 cx: &mut Context<Self>,
20154 ) {
20155 self.insert_uuid(UuidVersion::V4, window, cx);
20156 }
20157
20158 pub fn insert_uuid_v7(
20159 &mut self,
20160 _: &InsertUuidV7,
20161 window: &mut Window,
20162 cx: &mut Context<Self>,
20163 ) {
20164 self.insert_uuid(UuidVersion::V7, window, cx);
20165 }
20166
20167 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20169 self.transact(window, cx, |this, window, cx| {
20170 let edits = this
20171 .selections
20172 .all::<Point>(&this.display_snapshot(cx))
20173 .into_iter()
20174 .map(|selection| {
20175 let uuid = match version {
20176 UuidVersion::V4 => uuid::Uuid::new_v4(),
20177 UuidVersion::V7 => uuid::Uuid::now_v7(),
20178 };
20179
20180 (selection.range(), uuid.to_string())
20181 });
20182 this.edit(edits, cx);
20183 this.refresh_edit_prediction(true, false, window, cx);
20184 });
20185 }
20186
20187 pub fn open_selections_in_multibuffer(
20188 &mut self,
20189 _: &OpenSelectionsInMultibuffer,
20190 window: &mut Window,
20191 cx: &mut Context<Self>,
20192 ) {
20193 let multibuffer = self.buffer.read(cx);
20194
20195 let Some(buffer) = multibuffer.as_singleton() else {
20196 return;
20197 };
20198
20199 let Some(workspace) = self.workspace() else {
20200 return;
20201 };
20202
20203 let title = multibuffer.title(cx).to_string();
20204
20205 let locations = self
20206 .selections
20207 .all_anchors(cx)
20208 .iter()
20209 .map(|selection| {
20210 (
20211 buffer.clone(),
20212 (selection.start.text_anchor..selection.end.text_anchor)
20213 .to_point(buffer.read(cx)),
20214 )
20215 })
20216 .into_group_map();
20217
20218 cx.spawn_in(window, async move |_, cx| {
20219 workspace.update_in(cx, |workspace, window, cx| {
20220 Self::open_locations_in_multibuffer(
20221 workspace,
20222 locations,
20223 format!("Selections for '{title}'"),
20224 false,
20225 MultibufferSelectionMode::All,
20226 window,
20227 cx,
20228 );
20229 })
20230 })
20231 .detach();
20232 }
20233
20234 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20235 /// last highlight added will be used.
20236 ///
20237 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20238 pub fn highlight_rows<T: 'static>(
20239 &mut self,
20240 range: Range<Anchor>,
20241 color: Hsla,
20242 options: RowHighlightOptions,
20243 cx: &mut Context<Self>,
20244 ) {
20245 let snapshot = self.buffer().read(cx).snapshot(cx);
20246 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20247 let ix = row_highlights.binary_search_by(|highlight| {
20248 Ordering::Equal
20249 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20250 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20251 });
20252
20253 if let Err(mut ix) = ix {
20254 let index = post_inc(&mut self.highlight_order);
20255
20256 // If this range intersects with the preceding highlight, then merge it with
20257 // the preceding highlight. Otherwise insert a new highlight.
20258 let mut merged = false;
20259 if ix > 0 {
20260 let prev_highlight = &mut row_highlights[ix - 1];
20261 if prev_highlight
20262 .range
20263 .end
20264 .cmp(&range.start, &snapshot)
20265 .is_ge()
20266 {
20267 ix -= 1;
20268 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20269 prev_highlight.range.end = range.end;
20270 }
20271 merged = true;
20272 prev_highlight.index = index;
20273 prev_highlight.color = color;
20274 prev_highlight.options = options;
20275 }
20276 }
20277
20278 if !merged {
20279 row_highlights.insert(
20280 ix,
20281 RowHighlight {
20282 range,
20283 index,
20284 color,
20285 options,
20286 type_id: TypeId::of::<T>(),
20287 },
20288 );
20289 }
20290
20291 // If any of the following highlights intersect with this one, merge them.
20292 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20293 let highlight = &row_highlights[ix];
20294 if next_highlight
20295 .range
20296 .start
20297 .cmp(&highlight.range.end, &snapshot)
20298 .is_le()
20299 {
20300 if next_highlight
20301 .range
20302 .end
20303 .cmp(&highlight.range.end, &snapshot)
20304 .is_gt()
20305 {
20306 row_highlights[ix].range.end = next_highlight.range.end;
20307 }
20308 row_highlights.remove(ix + 1);
20309 } else {
20310 break;
20311 }
20312 }
20313 }
20314 }
20315
20316 /// Remove any highlighted row ranges of the given type that intersect the
20317 /// given ranges.
20318 pub fn remove_highlighted_rows<T: 'static>(
20319 &mut self,
20320 ranges_to_remove: Vec<Range<Anchor>>,
20321 cx: &mut Context<Self>,
20322 ) {
20323 let snapshot = self.buffer().read(cx).snapshot(cx);
20324 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20325 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20326 row_highlights.retain(|highlight| {
20327 while let Some(range_to_remove) = ranges_to_remove.peek() {
20328 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20329 Ordering::Less | Ordering::Equal => {
20330 ranges_to_remove.next();
20331 }
20332 Ordering::Greater => {
20333 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20334 Ordering::Less | Ordering::Equal => {
20335 return false;
20336 }
20337 Ordering::Greater => break,
20338 }
20339 }
20340 }
20341 }
20342
20343 true
20344 })
20345 }
20346
20347 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20348 pub fn clear_row_highlights<T: 'static>(&mut self) {
20349 self.highlighted_rows.remove(&TypeId::of::<T>());
20350 }
20351
20352 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20353 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20354 self.highlighted_rows
20355 .get(&TypeId::of::<T>())
20356 .map_or(&[] as &[_], |vec| vec.as_slice())
20357 .iter()
20358 .map(|highlight| (highlight.range.clone(), highlight.color))
20359 }
20360
20361 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20362 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20363 /// Allows to ignore certain kinds of highlights.
20364 pub fn highlighted_display_rows(
20365 &self,
20366 window: &mut Window,
20367 cx: &mut App,
20368 ) -> BTreeMap<DisplayRow, LineHighlight> {
20369 let snapshot = self.snapshot(window, cx);
20370 let mut used_highlight_orders = HashMap::default();
20371 self.highlighted_rows
20372 .iter()
20373 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20374 .fold(
20375 BTreeMap::<DisplayRow, LineHighlight>::new(),
20376 |mut unique_rows, highlight| {
20377 let start = highlight.range.start.to_display_point(&snapshot);
20378 let end = highlight.range.end.to_display_point(&snapshot);
20379 let start_row = start.row().0;
20380 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20381 && end.column() == 0
20382 {
20383 end.row().0.saturating_sub(1)
20384 } else {
20385 end.row().0
20386 };
20387 for row in start_row..=end_row {
20388 let used_index =
20389 used_highlight_orders.entry(row).or_insert(highlight.index);
20390 if highlight.index >= *used_index {
20391 *used_index = highlight.index;
20392 unique_rows.insert(
20393 DisplayRow(row),
20394 LineHighlight {
20395 include_gutter: highlight.options.include_gutter,
20396 border: None,
20397 background: highlight.color.into(),
20398 type_id: Some(highlight.type_id),
20399 },
20400 );
20401 }
20402 }
20403 unique_rows
20404 },
20405 )
20406 }
20407
20408 pub fn highlighted_display_row_for_autoscroll(
20409 &self,
20410 snapshot: &DisplaySnapshot,
20411 ) -> Option<DisplayRow> {
20412 self.highlighted_rows
20413 .values()
20414 .flat_map(|highlighted_rows| highlighted_rows.iter())
20415 .filter_map(|highlight| {
20416 if highlight.options.autoscroll {
20417 Some(highlight.range.start.to_display_point(snapshot).row())
20418 } else {
20419 None
20420 }
20421 })
20422 .min()
20423 }
20424
20425 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20426 self.highlight_background::<SearchWithinRange>(
20427 ranges,
20428 |colors| colors.colors().editor_document_highlight_read_background,
20429 cx,
20430 )
20431 }
20432
20433 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20434 self.breadcrumb_header = Some(new_header);
20435 }
20436
20437 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20438 self.clear_background_highlights::<SearchWithinRange>(cx);
20439 }
20440
20441 pub fn highlight_background<T: 'static>(
20442 &mut self,
20443 ranges: &[Range<Anchor>],
20444 color_fetcher: fn(&Theme) -> Hsla,
20445 cx: &mut Context<Self>,
20446 ) {
20447 self.background_highlights.insert(
20448 HighlightKey::Type(TypeId::of::<T>()),
20449 (color_fetcher, Arc::from(ranges)),
20450 );
20451 self.scrollbar_marker_state.dirty = true;
20452 cx.notify();
20453 }
20454
20455 pub fn highlight_background_key<T: 'static>(
20456 &mut self,
20457 key: usize,
20458 ranges: &[Range<Anchor>],
20459 color_fetcher: fn(&Theme) -> Hsla,
20460 cx: &mut Context<Self>,
20461 ) {
20462 self.background_highlights.insert(
20463 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20464 (color_fetcher, Arc::from(ranges)),
20465 );
20466 self.scrollbar_marker_state.dirty = true;
20467 cx.notify();
20468 }
20469
20470 pub fn clear_background_highlights<T: 'static>(
20471 &mut self,
20472 cx: &mut Context<Self>,
20473 ) -> Option<BackgroundHighlight> {
20474 let text_highlights = self
20475 .background_highlights
20476 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20477 if !text_highlights.1.is_empty() {
20478 self.scrollbar_marker_state.dirty = true;
20479 cx.notify();
20480 }
20481 Some(text_highlights)
20482 }
20483
20484 pub fn highlight_gutter<T: 'static>(
20485 &mut self,
20486 ranges: impl Into<Vec<Range<Anchor>>>,
20487 color_fetcher: fn(&App) -> Hsla,
20488 cx: &mut Context<Self>,
20489 ) {
20490 self.gutter_highlights
20491 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20492 cx.notify();
20493 }
20494
20495 pub fn clear_gutter_highlights<T: 'static>(
20496 &mut self,
20497 cx: &mut Context<Self>,
20498 ) -> Option<GutterHighlight> {
20499 cx.notify();
20500 self.gutter_highlights.remove(&TypeId::of::<T>())
20501 }
20502
20503 pub fn insert_gutter_highlight<T: 'static>(
20504 &mut self,
20505 range: Range<Anchor>,
20506 color_fetcher: fn(&App) -> Hsla,
20507 cx: &mut Context<Self>,
20508 ) {
20509 let snapshot = self.buffer().read(cx).snapshot(cx);
20510 let mut highlights = self
20511 .gutter_highlights
20512 .remove(&TypeId::of::<T>())
20513 .map(|(_, highlights)| highlights)
20514 .unwrap_or_default();
20515 let ix = highlights.binary_search_by(|highlight| {
20516 Ordering::Equal
20517 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20518 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20519 });
20520 if let Err(ix) = ix {
20521 highlights.insert(ix, range);
20522 }
20523 self.gutter_highlights
20524 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20525 }
20526
20527 pub fn remove_gutter_highlights<T: 'static>(
20528 &mut self,
20529 ranges_to_remove: Vec<Range<Anchor>>,
20530 cx: &mut Context<Self>,
20531 ) {
20532 let snapshot = self.buffer().read(cx).snapshot(cx);
20533 let Some((color_fetcher, mut gutter_highlights)) =
20534 self.gutter_highlights.remove(&TypeId::of::<T>())
20535 else {
20536 return;
20537 };
20538 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20539 gutter_highlights.retain(|highlight| {
20540 while let Some(range_to_remove) = ranges_to_remove.peek() {
20541 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20542 Ordering::Less | Ordering::Equal => {
20543 ranges_to_remove.next();
20544 }
20545 Ordering::Greater => {
20546 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20547 Ordering::Less | Ordering::Equal => {
20548 return false;
20549 }
20550 Ordering::Greater => break,
20551 }
20552 }
20553 }
20554 }
20555
20556 true
20557 });
20558 self.gutter_highlights
20559 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20560 }
20561
20562 #[cfg(feature = "test-support")]
20563 pub fn all_text_highlights(
20564 &self,
20565 window: &mut Window,
20566 cx: &mut Context<Self>,
20567 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20568 let snapshot = self.snapshot(window, cx);
20569 self.display_map.update(cx, |display_map, _| {
20570 display_map
20571 .all_text_highlights()
20572 .map(|highlight| {
20573 let (style, ranges) = highlight.as_ref();
20574 (
20575 *style,
20576 ranges
20577 .iter()
20578 .map(|range| range.clone().to_display_points(&snapshot))
20579 .collect(),
20580 )
20581 })
20582 .collect()
20583 })
20584 }
20585
20586 #[cfg(feature = "test-support")]
20587 pub fn all_text_background_highlights(
20588 &self,
20589 window: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20592 let snapshot = self.snapshot(window, cx);
20593 let buffer = &snapshot.buffer_snapshot();
20594 let start = buffer.anchor_before(0);
20595 let end = buffer.anchor_after(buffer.len());
20596 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20597 }
20598
20599 #[cfg(any(test, feature = "test-support"))]
20600 pub fn sorted_background_highlights_in_range(
20601 &self,
20602 search_range: Range<Anchor>,
20603 display_snapshot: &DisplaySnapshot,
20604 theme: &Theme,
20605 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20606 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20607 res.sort_by(|a, b| {
20608 a.0.start
20609 .cmp(&b.0.start)
20610 .then_with(|| a.0.end.cmp(&b.0.end))
20611 .then_with(|| a.1.cmp(&b.1))
20612 });
20613 res
20614 }
20615
20616 #[cfg(feature = "test-support")]
20617 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20618 let snapshot = self.buffer().read(cx).snapshot(cx);
20619
20620 let highlights = self
20621 .background_highlights
20622 .get(&HighlightKey::Type(TypeId::of::<
20623 items::BufferSearchHighlights,
20624 >()));
20625
20626 if let Some((_color, ranges)) = highlights {
20627 ranges
20628 .iter()
20629 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20630 .collect_vec()
20631 } else {
20632 vec![]
20633 }
20634 }
20635
20636 fn document_highlights_for_position<'a>(
20637 &'a self,
20638 position: Anchor,
20639 buffer: &'a MultiBufferSnapshot,
20640 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20641 let read_highlights = self
20642 .background_highlights
20643 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20644 .map(|h| &h.1);
20645 let write_highlights = self
20646 .background_highlights
20647 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20648 .map(|h| &h.1);
20649 let left_position = position.bias_left(buffer);
20650 let right_position = position.bias_right(buffer);
20651 read_highlights
20652 .into_iter()
20653 .chain(write_highlights)
20654 .flat_map(move |ranges| {
20655 let start_ix = match ranges.binary_search_by(|probe| {
20656 let cmp = probe.end.cmp(&left_position, buffer);
20657 if cmp.is_ge() {
20658 Ordering::Greater
20659 } else {
20660 Ordering::Less
20661 }
20662 }) {
20663 Ok(i) | Err(i) => i,
20664 };
20665
20666 ranges[start_ix..]
20667 .iter()
20668 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20669 })
20670 }
20671
20672 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20673 self.background_highlights
20674 .get(&HighlightKey::Type(TypeId::of::<T>()))
20675 .is_some_and(|(_, highlights)| !highlights.is_empty())
20676 }
20677
20678 /// Returns all background highlights for a given range.
20679 ///
20680 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20681 pub fn background_highlights_in_range(
20682 &self,
20683 search_range: Range<Anchor>,
20684 display_snapshot: &DisplaySnapshot,
20685 theme: &Theme,
20686 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20687 let mut results = Vec::new();
20688 for (color_fetcher, ranges) in self.background_highlights.values() {
20689 let color = color_fetcher(theme);
20690 let start_ix = match ranges.binary_search_by(|probe| {
20691 let cmp = probe
20692 .end
20693 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20694 if cmp.is_gt() {
20695 Ordering::Greater
20696 } else {
20697 Ordering::Less
20698 }
20699 }) {
20700 Ok(i) | Err(i) => i,
20701 };
20702 for range in &ranges[start_ix..] {
20703 if range
20704 .start
20705 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20706 .is_ge()
20707 {
20708 break;
20709 }
20710
20711 let start = range.start.to_display_point(display_snapshot);
20712 let end = range.end.to_display_point(display_snapshot);
20713 results.push((start..end, color))
20714 }
20715 }
20716 results
20717 }
20718
20719 pub fn gutter_highlights_in_range(
20720 &self,
20721 search_range: Range<Anchor>,
20722 display_snapshot: &DisplaySnapshot,
20723 cx: &App,
20724 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20725 let mut results = Vec::new();
20726 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20727 let color = color_fetcher(cx);
20728 let start_ix = match ranges.binary_search_by(|probe| {
20729 let cmp = probe
20730 .end
20731 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20732 if cmp.is_gt() {
20733 Ordering::Greater
20734 } else {
20735 Ordering::Less
20736 }
20737 }) {
20738 Ok(i) | Err(i) => i,
20739 };
20740 for range in &ranges[start_ix..] {
20741 if range
20742 .start
20743 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20744 .is_ge()
20745 {
20746 break;
20747 }
20748
20749 let start = range.start.to_display_point(display_snapshot);
20750 let end = range.end.to_display_point(display_snapshot);
20751 results.push((start..end, color))
20752 }
20753 }
20754 results
20755 }
20756
20757 /// Get the text ranges corresponding to the redaction query
20758 pub fn redacted_ranges(
20759 &self,
20760 search_range: Range<Anchor>,
20761 display_snapshot: &DisplaySnapshot,
20762 cx: &App,
20763 ) -> Vec<Range<DisplayPoint>> {
20764 display_snapshot
20765 .buffer_snapshot()
20766 .redacted_ranges(search_range, |file| {
20767 if let Some(file) = file {
20768 file.is_private()
20769 && EditorSettings::get(
20770 Some(SettingsLocation {
20771 worktree_id: file.worktree_id(cx),
20772 path: file.path().as_ref(),
20773 }),
20774 cx,
20775 )
20776 .redact_private_values
20777 } else {
20778 false
20779 }
20780 })
20781 .map(|range| {
20782 range.start.to_display_point(display_snapshot)
20783 ..range.end.to_display_point(display_snapshot)
20784 })
20785 .collect()
20786 }
20787
20788 pub fn highlight_text_key<T: 'static>(
20789 &mut self,
20790 key: usize,
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(
20797 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20798 ranges,
20799 style,
20800 );
20801 });
20802 cx.notify();
20803 }
20804
20805 pub fn highlight_text<T: 'static>(
20806 &mut self,
20807 ranges: Vec<Range<Anchor>>,
20808 style: HighlightStyle,
20809 cx: &mut Context<Self>,
20810 ) {
20811 self.display_map.update(cx, |map, _| {
20812 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20813 });
20814 cx.notify();
20815 }
20816
20817 pub fn text_highlights<'a, T: 'static>(
20818 &'a self,
20819 cx: &'a App,
20820 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20821 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20822 }
20823
20824 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20825 let cleared = self
20826 .display_map
20827 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20828 if cleared {
20829 cx.notify();
20830 }
20831 }
20832
20833 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20834 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20835 && self.focus_handle.is_focused(window)
20836 }
20837
20838 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20839 self.show_cursor_when_unfocused = is_enabled;
20840 cx.notify();
20841 }
20842
20843 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20844 cx.notify();
20845 }
20846
20847 fn on_debug_session_event(
20848 &mut self,
20849 _session: Entity<Session>,
20850 event: &SessionEvent,
20851 cx: &mut Context<Self>,
20852 ) {
20853 if let SessionEvent::InvalidateInlineValue = event {
20854 self.refresh_inline_values(cx);
20855 }
20856 }
20857
20858 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20859 let Some(project) = self.project.clone() else {
20860 return;
20861 };
20862
20863 if !self.inline_value_cache.enabled {
20864 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20865 self.splice_inlays(&inlays, Vec::new(), cx);
20866 return;
20867 }
20868
20869 let current_execution_position = self
20870 .highlighted_rows
20871 .get(&TypeId::of::<ActiveDebugLine>())
20872 .and_then(|lines| lines.last().map(|line| line.range.end));
20873
20874 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20875 let inline_values = editor
20876 .update(cx, |editor, cx| {
20877 let Some(current_execution_position) = current_execution_position else {
20878 return Some(Task::ready(Ok(Vec::new())));
20879 };
20880
20881 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20882 let snapshot = buffer.snapshot(cx);
20883
20884 let excerpt = snapshot.excerpt_containing(
20885 current_execution_position..current_execution_position,
20886 )?;
20887
20888 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20889 })?;
20890
20891 let range =
20892 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20893
20894 project.inline_values(buffer, range, cx)
20895 })
20896 .ok()
20897 .flatten()?
20898 .await
20899 .context("refreshing debugger inlays")
20900 .log_err()?;
20901
20902 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20903
20904 for (buffer_id, inline_value) in inline_values
20905 .into_iter()
20906 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20907 {
20908 buffer_inline_values
20909 .entry(buffer_id)
20910 .or_default()
20911 .push(inline_value);
20912 }
20913
20914 editor
20915 .update(cx, |editor, cx| {
20916 let snapshot = editor.buffer.read(cx).snapshot(cx);
20917 let mut new_inlays = Vec::default();
20918
20919 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20920 let buffer_id = buffer_snapshot.remote_id();
20921 buffer_inline_values
20922 .get(&buffer_id)
20923 .into_iter()
20924 .flatten()
20925 .for_each(|hint| {
20926 let inlay = Inlay::debugger(
20927 post_inc(&mut editor.next_inlay_id),
20928 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20929 hint.text(),
20930 );
20931 if !inlay.text().chars().contains(&'\n') {
20932 new_inlays.push(inlay);
20933 }
20934 });
20935 }
20936
20937 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20938 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20939
20940 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20941 })
20942 .ok()?;
20943 Some(())
20944 });
20945 }
20946
20947 fn on_buffer_event(
20948 &mut self,
20949 multibuffer: &Entity<MultiBuffer>,
20950 event: &multi_buffer::Event,
20951 window: &mut Window,
20952 cx: &mut Context<Self>,
20953 ) {
20954 match event {
20955 multi_buffer::Event::Edited { edited_buffer } => {
20956 self.scrollbar_marker_state.dirty = true;
20957 self.active_indent_guides_state.dirty = true;
20958 self.refresh_active_diagnostics(cx);
20959 self.refresh_code_actions(window, cx);
20960 self.refresh_selected_text_highlights(true, window, cx);
20961 self.refresh_single_line_folds(window, cx);
20962 self.refresh_matching_bracket_highlights(window, cx);
20963 if self.has_active_edit_prediction() {
20964 self.update_visible_edit_prediction(window, cx);
20965 }
20966
20967 if let Some(buffer) = edited_buffer {
20968 if buffer.read(cx).file().is_none() {
20969 cx.emit(EditorEvent::TitleChanged);
20970 }
20971
20972 if self.project.is_some() {
20973 let buffer_id = buffer.read(cx).remote_id();
20974 self.register_buffer(buffer_id, cx);
20975 self.update_lsp_data(Some(buffer_id), window, cx);
20976 self.refresh_inlay_hints(
20977 InlayHintRefreshReason::BufferEdited(buffer_id),
20978 cx,
20979 );
20980 }
20981 }
20982
20983 cx.emit(EditorEvent::BufferEdited);
20984 cx.emit(SearchEvent::MatchesInvalidated);
20985
20986 let Some(project) = &self.project else { return };
20987 let (telemetry, is_via_ssh) = {
20988 let project = project.read(cx);
20989 let telemetry = project.client().telemetry().clone();
20990 let is_via_ssh = project.is_via_remote_server();
20991 (telemetry, is_via_ssh)
20992 };
20993 telemetry.log_edit_event("editor", is_via_ssh);
20994 }
20995 multi_buffer::Event::ExcerptsAdded {
20996 buffer,
20997 predecessor,
20998 excerpts,
20999 } => {
21000 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21001 let buffer_id = buffer.read(cx).remote_id();
21002 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21003 && let Some(project) = &self.project
21004 {
21005 update_uncommitted_diff_for_buffer(
21006 cx.entity(),
21007 project,
21008 [buffer.clone()],
21009 self.buffer.clone(),
21010 cx,
21011 )
21012 .detach();
21013 }
21014 self.update_lsp_data(Some(buffer_id), window, cx);
21015 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21016 cx.emit(EditorEvent::ExcerptsAdded {
21017 buffer: buffer.clone(),
21018 predecessor: *predecessor,
21019 excerpts: excerpts.clone(),
21020 });
21021 }
21022 multi_buffer::Event::ExcerptsRemoved {
21023 ids,
21024 removed_buffer_ids,
21025 } => {
21026 if let Some(inlay_hints) = &mut self.inlay_hints {
21027 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21028 }
21029 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21030 for buffer_id in removed_buffer_ids {
21031 self.registered_buffers.remove(buffer_id);
21032 }
21033 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21034 cx.emit(EditorEvent::ExcerptsRemoved {
21035 ids: ids.clone(),
21036 removed_buffer_ids: removed_buffer_ids.clone(),
21037 });
21038 }
21039 multi_buffer::Event::ExcerptsEdited {
21040 excerpt_ids,
21041 buffer_ids,
21042 } => {
21043 self.display_map.update(cx, |map, cx| {
21044 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21045 });
21046 cx.emit(EditorEvent::ExcerptsEdited {
21047 ids: excerpt_ids.clone(),
21048 });
21049 }
21050 multi_buffer::Event::ExcerptsExpanded { ids } => {
21051 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21052 self.refresh_document_highlights(cx);
21053 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21054 }
21055 multi_buffer::Event::Reparsed(buffer_id) => {
21056 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21057 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21058
21059 cx.emit(EditorEvent::Reparsed(*buffer_id));
21060 }
21061 multi_buffer::Event::DiffHunksToggled => {
21062 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21063 }
21064 multi_buffer::Event::LanguageChanged(buffer_id) => {
21065 self.registered_buffers.remove(&buffer_id);
21066 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21067 cx.emit(EditorEvent::Reparsed(*buffer_id));
21068 cx.notify();
21069 }
21070 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21071 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21072 multi_buffer::Event::FileHandleChanged
21073 | multi_buffer::Event::Reloaded
21074 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21075 multi_buffer::Event::DiagnosticsUpdated => {
21076 self.update_diagnostics_state(window, cx);
21077 }
21078 _ => {}
21079 };
21080 }
21081
21082 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21083 if !self.diagnostics_enabled() {
21084 return;
21085 }
21086 self.refresh_active_diagnostics(cx);
21087 self.refresh_inline_diagnostics(true, window, cx);
21088 self.scrollbar_marker_state.dirty = true;
21089 cx.notify();
21090 }
21091
21092 pub fn start_temporary_diff_override(&mut self) {
21093 self.load_diff_task.take();
21094 self.temporary_diff_override = true;
21095 }
21096
21097 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21098 self.temporary_diff_override = false;
21099 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21100 self.buffer.update(cx, |buffer, cx| {
21101 buffer.set_all_diff_hunks_collapsed(cx);
21102 });
21103
21104 if let Some(project) = self.project.clone() {
21105 self.load_diff_task = Some(
21106 update_uncommitted_diff_for_buffer(
21107 cx.entity(),
21108 &project,
21109 self.buffer.read(cx).all_buffers(),
21110 self.buffer.clone(),
21111 cx,
21112 )
21113 .shared(),
21114 );
21115 }
21116 }
21117
21118 fn on_display_map_changed(
21119 &mut self,
21120 _: Entity<DisplayMap>,
21121 _: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 cx.notify();
21125 }
21126
21127 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21128 if self.diagnostics_enabled() {
21129 let new_severity = EditorSettings::get_global(cx)
21130 .diagnostics_max_severity
21131 .unwrap_or(DiagnosticSeverity::Hint);
21132 self.set_max_diagnostics_severity(new_severity, cx);
21133 }
21134 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21135 self.update_edit_prediction_settings(cx);
21136 self.refresh_edit_prediction(true, false, window, cx);
21137 self.refresh_inline_values(cx);
21138 self.refresh_inlay_hints(
21139 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21140 self.selections.newest_anchor().head(),
21141 &self.buffer.read(cx).snapshot(cx),
21142 cx,
21143 )),
21144 cx,
21145 );
21146
21147 let old_cursor_shape = self.cursor_shape;
21148 let old_show_breadcrumbs = self.show_breadcrumbs;
21149
21150 {
21151 let editor_settings = EditorSettings::get_global(cx);
21152 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21153 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21154 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21155 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21156 }
21157
21158 if old_cursor_shape != self.cursor_shape {
21159 cx.emit(EditorEvent::CursorShapeChanged);
21160 }
21161
21162 if old_show_breadcrumbs != self.show_breadcrumbs {
21163 cx.emit(EditorEvent::BreadcrumbsChanged);
21164 }
21165
21166 let project_settings = ProjectSettings::get_global(cx);
21167 self.serialize_dirty_buffers =
21168 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21169
21170 if self.mode.is_full() {
21171 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21172 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21173 if self.show_inline_diagnostics != show_inline_diagnostics {
21174 self.show_inline_diagnostics = show_inline_diagnostics;
21175 self.refresh_inline_diagnostics(false, window, cx);
21176 }
21177
21178 if self.git_blame_inline_enabled != inline_blame_enabled {
21179 self.toggle_git_blame_inline_internal(false, window, cx);
21180 }
21181
21182 let minimap_settings = EditorSettings::get_global(cx).minimap;
21183 if self.minimap_visibility != MinimapVisibility::Disabled {
21184 if self.minimap_visibility.settings_visibility()
21185 != minimap_settings.minimap_enabled()
21186 {
21187 self.set_minimap_visibility(
21188 MinimapVisibility::for_mode(self.mode(), cx),
21189 window,
21190 cx,
21191 );
21192 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21193 minimap_entity.update(cx, |minimap_editor, cx| {
21194 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21195 })
21196 }
21197 }
21198 }
21199
21200 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21201 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21202 }) {
21203 if !inlay_splice.is_empty() {
21204 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21205 }
21206 self.refresh_colors_for_visible_range(None, window, cx);
21207 }
21208
21209 cx.notify();
21210 }
21211
21212 pub fn set_searchable(&mut self, searchable: bool) {
21213 self.searchable = searchable;
21214 }
21215
21216 pub fn searchable(&self) -> bool {
21217 self.searchable
21218 }
21219
21220 pub fn open_excerpts_in_split(
21221 &mut self,
21222 _: &OpenExcerptsSplit,
21223 window: &mut Window,
21224 cx: &mut Context<Self>,
21225 ) {
21226 self.open_excerpts_common(None, true, window, cx)
21227 }
21228
21229 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21230 self.open_excerpts_common(None, false, window, cx)
21231 }
21232
21233 fn open_excerpts_common(
21234 &mut self,
21235 jump_data: Option<JumpData>,
21236 split: bool,
21237 window: &mut Window,
21238 cx: &mut Context<Self>,
21239 ) {
21240 let Some(workspace) = self.workspace() else {
21241 cx.propagate();
21242 return;
21243 };
21244
21245 if self.buffer.read(cx).is_singleton() {
21246 cx.propagate();
21247 return;
21248 }
21249
21250 let mut new_selections_by_buffer = HashMap::default();
21251 match &jump_data {
21252 Some(JumpData::MultiBufferPoint {
21253 excerpt_id,
21254 position,
21255 anchor,
21256 line_offset_from_top,
21257 }) => {
21258 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21259 if let Some(buffer) = multi_buffer_snapshot
21260 .buffer_id_for_excerpt(*excerpt_id)
21261 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21262 {
21263 let buffer_snapshot = buffer.read(cx).snapshot();
21264 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21265 language::ToPoint::to_point(anchor, &buffer_snapshot)
21266 } else {
21267 buffer_snapshot.clip_point(*position, Bias::Left)
21268 };
21269 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21270 new_selections_by_buffer.insert(
21271 buffer,
21272 (
21273 vec![jump_to_offset..jump_to_offset],
21274 Some(*line_offset_from_top),
21275 ),
21276 );
21277 }
21278 }
21279 Some(JumpData::MultiBufferRow {
21280 row,
21281 line_offset_from_top,
21282 }) => {
21283 let point = MultiBufferPoint::new(row.0, 0);
21284 if let Some((buffer, buffer_point, _)) =
21285 self.buffer.read(cx).point_to_buffer_point(point, cx)
21286 {
21287 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21288 new_selections_by_buffer
21289 .entry(buffer)
21290 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21291 .0
21292 .push(buffer_offset..buffer_offset)
21293 }
21294 }
21295 None => {
21296 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21297 let multi_buffer = self.buffer.read(cx);
21298 for selection in selections {
21299 for (snapshot, range, _, anchor) in multi_buffer
21300 .snapshot(cx)
21301 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21302 {
21303 if let Some(anchor) = anchor {
21304 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21305 else {
21306 continue;
21307 };
21308 let offset = text::ToOffset::to_offset(
21309 &anchor.text_anchor,
21310 &buffer_handle.read(cx).snapshot(),
21311 );
21312 let range = offset..offset;
21313 new_selections_by_buffer
21314 .entry(buffer_handle)
21315 .or_insert((Vec::new(), None))
21316 .0
21317 .push(range)
21318 } else {
21319 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21320 else {
21321 continue;
21322 };
21323 new_selections_by_buffer
21324 .entry(buffer_handle)
21325 .or_insert((Vec::new(), None))
21326 .0
21327 .push(range)
21328 }
21329 }
21330 }
21331 }
21332 }
21333
21334 new_selections_by_buffer
21335 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21336
21337 if new_selections_by_buffer.is_empty() {
21338 return;
21339 }
21340
21341 // We defer the pane interaction because we ourselves are a workspace item
21342 // and activating a new item causes the pane to call a method on us reentrantly,
21343 // which panics if we're on the stack.
21344 window.defer(cx, move |window, cx| {
21345 workspace.update(cx, |workspace, cx| {
21346 let pane = if split {
21347 workspace.adjacent_pane(window, cx)
21348 } else {
21349 workspace.active_pane().clone()
21350 };
21351
21352 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21353 let editor = buffer
21354 .read(cx)
21355 .file()
21356 .is_none()
21357 .then(|| {
21358 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21359 // so `workspace.open_project_item` will never find them, always opening a new editor.
21360 // Instead, we try to activate the existing editor in the pane first.
21361 let (editor, pane_item_index) =
21362 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21363 let editor = item.downcast::<Editor>()?;
21364 let singleton_buffer =
21365 editor.read(cx).buffer().read(cx).as_singleton()?;
21366 if singleton_buffer == buffer {
21367 Some((editor, i))
21368 } else {
21369 None
21370 }
21371 })?;
21372 pane.update(cx, |pane, cx| {
21373 pane.activate_item(pane_item_index, true, true, window, cx)
21374 });
21375 Some(editor)
21376 })
21377 .flatten()
21378 .unwrap_or_else(|| {
21379 workspace.open_project_item::<Self>(
21380 pane.clone(),
21381 buffer,
21382 true,
21383 true,
21384 window,
21385 cx,
21386 )
21387 });
21388
21389 editor.update(cx, |editor, cx| {
21390 let autoscroll = match scroll_offset {
21391 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21392 None => Autoscroll::newest(),
21393 };
21394 let nav_history = editor.nav_history.take();
21395 editor.change_selections(
21396 SelectionEffects::scroll(autoscroll),
21397 window,
21398 cx,
21399 |s| {
21400 s.select_ranges(ranges);
21401 },
21402 );
21403 editor.nav_history = nav_history;
21404 });
21405 }
21406 })
21407 });
21408 }
21409
21410 // For now, don't allow opening excerpts in buffers that aren't backed by
21411 // regular project files.
21412 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21413 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21414 }
21415
21416 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21417 let snapshot = self.buffer.read(cx).read(cx);
21418 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21419 Some(
21420 ranges
21421 .iter()
21422 .map(move |range| {
21423 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21424 })
21425 .collect(),
21426 )
21427 }
21428
21429 fn selection_replacement_ranges(
21430 &self,
21431 range: Range<OffsetUtf16>,
21432 cx: &mut App,
21433 ) -> Vec<Range<OffsetUtf16>> {
21434 let selections = self
21435 .selections
21436 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21437 let newest_selection = selections
21438 .iter()
21439 .max_by_key(|selection| selection.id)
21440 .unwrap();
21441 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21442 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21443 let snapshot = self.buffer.read(cx).read(cx);
21444 selections
21445 .into_iter()
21446 .map(|mut selection| {
21447 selection.start.0 =
21448 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21449 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21450 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21451 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21452 })
21453 .collect()
21454 }
21455
21456 fn report_editor_event(
21457 &self,
21458 reported_event: ReportEditorEvent,
21459 file_extension: Option<String>,
21460 cx: &App,
21461 ) {
21462 if cfg!(any(test, feature = "test-support")) {
21463 return;
21464 }
21465
21466 let Some(project) = &self.project else { return };
21467
21468 // If None, we are in a file without an extension
21469 let file = self
21470 .buffer
21471 .read(cx)
21472 .as_singleton()
21473 .and_then(|b| b.read(cx).file());
21474 let file_extension = file_extension.or(file
21475 .as_ref()
21476 .and_then(|file| Path::new(file.file_name(cx)).extension())
21477 .and_then(|e| e.to_str())
21478 .map(|a| a.to_string()));
21479
21480 let vim_mode = vim_flavor(cx).is_some();
21481
21482 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21483 let copilot_enabled = edit_predictions_provider
21484 == language::language_settings::EditPredictionProvider::Copilot;
21485 let copilot_enabled_for_language = self
21486 .buffer
21487 .read(cx)
21488 .language_settings(cx)
21489 .show_edit_predictions;
21490
21491 let project = project.read(cx);
21492 let event_type = reported_event.event_type();
21493
21494 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21495 telemetry::event!(
21496 event_type,
21497 type = if auto_saved {"autosave"} else {"manual"},
21498 file_extension,
21499 vim_mode,
21500 copilot_enabled,
21501 copilot_enabled_for_language,
21502 edit_predictions_provider,
21503 is_via_ssh = project.is_via_remote_server(),
21504 );
21505 } else {
21506 telemetry::event!(
21507 event_type,
21508 file_extension,
21509 vim_mode,
21510 copilot_enabled,
21511 copilot_enabled_for_language,
21512 edit_predictions_provider,
21513 is_via_ssh = project.is_via_remote_server(),
21514 );
21515 };
21516 }
21517
21518 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21519 /// with each line being an array of {text, highlight} objects.
21520 fn copy_highlight_json(
21521 &mut self,
21522 _: &CopyHighlightJson,
21523 window: &mut Window,
21524 cx: &mut Context<Self>,
21525 ) {
21526 #[derive(Serialize)]
21527 struct Chunk<'a> {
21528 text: String,
21529 highlight: Option<&'a str>,
21530 }
21531
21532 let snapshot = self.buffer.read(cx).snapshot(cx);
21533 let range = self
21534 .selected_text_range(false, window, cx)
21535 .and_then(|selection| {
21536 if selection.range.is_empty() {
21537 None
21538 } else {
21539 Some(
21540 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21541 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21542 )
21543 }
21544 })
21545 .unwrap_or_else(|| 0..snapshot.len());
21546
21547 let chunks = snapshot.chunks(range, true);
21548 let mut lines = Vec::new();
21549 let mut line: VecDeque<Chunk> = VecDeque::new();
21550
21551 let Some(style) = self.style.as_ref() else {
21552 return;
21553 };
21554
21555 for chunk in chunks {
21556 let highlight = chunk
21557 .syntax_highlight_id
21558 .and_then(|id| id.name(&style.syntax));
21559 let mut chunk_lines = chunk.text.split('\n').peekable();
21560 while let Some(text) = chunk_lines.next() {
21561 let mut merged_with_last_token = false;
21562 if let Some(last_token) = line.back_mut()
21563 && last_token.highlight == highlight
21564 {
21565 last_token.text.push_str(text);
21566 merged_with_last_token = true;
21567 }
21568
21569 if !merged_with_last_token {
21570 line.push_back(Chunk {
21571 text: text.into(),
21572 highlight,
21573 });
21574 }
21575
21576 if chunk_lines.peek().is_some() {
21577 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21578 line.pop_front();
21579 }
21580 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21581 line.pop_back();
21582 }
21583
21584 lines.push(mem::take(&mut line));
21585 }
21586 }
21587 }
21588
21589 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21590 return;
21591 };
21592 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21593 }
21594
21595 pub fn open_context_menu(
21596 &mut self,
21597 _: &OpenContextMenu,
21598 window: &mut Window,
21599 cx: &mut Context<Self>,
21600 ) {
21601 self.request_autoscroll(Autoscroll::newest(), cx);
21602 let position = self
21603 .selections
21604 .newest_display(&self.display_snapshot(cx))
21605 .start;
21606 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21607 }
21608
21609 pub fn replay_insert_event(
21610 &mut self,
21611 text: &str,
21612 relative_utf16_range: Option<Range<isize>>,
21613 window: &mut Window,
21614 cx: &mut Context<Self>,
21615 ) {
21616 if !self.input_enabled {
21617 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21618 return;
21619 }
21620 if let Some(relative_utf16_range) = relative_utf16_range {
21621 let selections = self
21622 .selections
21623 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21624 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21625 let new_ranges = selections.into_iter().map(|range| {
21626 let start = OffsetUtf16(
21627 range
21628 .head()
21629 .0
21630 .saturating_add_signed(relative_utf16_range.start),
21631 );
21632 let end = OffsetUtf16(
21633 range
21634 .head()
21635 .0
21636 .saturating_add_signed(relative_utf16_range.end),
21637 );
21638 start..end
21639 });
21640 s.select_ranges(new_ranges);
21641 });
21642 }
21643
21644 self.handle_input(text, window, cx);
21645 }
21646
21647 pub fn is_focused(&self, window: &Window) -> bool {
21648 self.focus_handle.is_focused(window)
21649 }
21650
21651 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21652 cx.emit(EditorEvent::Focused);
21653
21654 if let Some(descendant) = self
21655 .last_focused_descendant
21656 .take()
21657 .and_then(|descendant| descendant.upgrade())
21658 {
21659 window.focus(&descendant);
21660 } else {
21661 if let Some(blame) = self.blame.as_ref() {
21662 blame.update(cx, GitBlame::focus)
21663 }
21664
21665 self.blink_manager.update(cx, BlinkManager::enable);
21666 self.show_cursor_names(window, cx);
21667 self.buffer.update(cx, |buffer, cx| {
21668 buffer.finalize_last_transaction(cx);
21669 if self.leader_id.is_none() {
21670 buffer.set_active_selections(
21671 &self.selections.disjoint_anchors_arc(),
21672 self.selections.line_mode(),
21673 self.cursor_shape,
21674 cx,
21675 );
21676 }
21677 });
21678 }
21679 }
21680
21681 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21682 cx.emit(EditorEvent::FocusedIn)
21683 }
21684
21685 fn handle_focus_out(
21686 &mut self,
21687 event: FocusOutEvent,
21688 _window: &mut Window,
21689 cx: &mut Context<Self>,
21690 ) {
21691 if event.blurred != self.focus_handle {
21692 self.last_focused_descendant = Some(event.blurred);
21693 }
21694 self.selection_drag_state = SelectionDragState::None;
21695 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21696 }
21697
21698 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21699 self.blink_manager.update(cx, BlinkManager::disable);
21700 self.buffer
21701 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21702
21703 if let Some(blame) = self.blame.as_ref() {
21704 blame.update(cx, GitBlame::blur)
21705 }
21706 if !self.hover_state.focused(window, cx) {
21707 hide_hover(self, cx);
21708 }
21709 if !self
21710 .context_menu
21711 .borrow()
21712 .as_ref()
21713 .is_some_and(|context_menu| context_menu.focused(window, cx))
21714 {
21715 self.hide_context_menu(window, cx);
21716 }
21717 self.take_active_edit_prediction(cx);
21718 cx.emit(EditorEvent::Blurred);
21719 cx.notify();
21720 }
21721
21722 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21723 let mut pending: String = window
21724 .pending_input_keystrokes()
21725 .into_iter()
21726 .flatten()
21727 .filter_map(|keystroke| {
21728 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21729 keystroke.key_char.clone()
21730 } else {
21731 None
21732 }
21733 })
21734 .collect();
21735
21736 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21737 pending = "".to_string();
21738 }
21739
21740 let existing_pending = self
21741 .text_highlights::<PendingInput>(cx)
21742 .map(|(_, ranges)| ranges.to_vec());
21743 if existing_pending.is_none() && pending.is_empty() {
21744 return;
21745 }
21746 let transaction =
21747 self.transact(window, cx, |this, window, cx| {
21748 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21749 let edits = selections
21750 .iter()
21751 .map(|selection| (selection.end..selection.end, pending.clone()));
21752 this.edit(edits, cx);
21753 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21754 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21755 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21756 }));
21757 });
21758 if let Some(existing_ranges) = existing_pending {
21759 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21760 this.edit(edits, cx);
21761 }
21762 });
21763
21764 let snapshot = self.snapshot(window, cx);
21765 let ranges = self
21766 .selections
21767 .all::<usize>(&snapshot.display_snapshot)
21768 .into_iter()
21769 .map(|selection| {
21770 snapshot.buffer_snapshot().anchor_after(selection.end)
21771 ..snapshot
21772 .buffer_snapshot()
21773 .anchor_before(selection.end + pending.len())
21774 })
21775 .collect();
21776
21777 if pending.is_empty() {
21778 self.clear_highlights::<PendingInput>(cx);
21779 } else {
21780 self.highlight_text::<PendingInput>(
21781 ranges,
21782 HighlightStyle {
21783 underline: Some(UnderlineStyle {
21784 thickness: px(1.),
21785 color: None,
21786 wavy: false,
21787 }),
21788 ..Default::default()
21789 },
21790 cx,
21791 );
21792 }
21793
21794 self.ime_transaction = self.ime_transaction.or(transaction);
21795 if let Some(transaction) = self.ime_transaction {
21796 self.buffer.update(cx, |buffer, cx| {
21797 buffer.group_until_transaction(transaction, cx);
21798 });
21799 }
21800
21801 if self.text_highlights::<PendingInput>(cx).is_none() {
21802 self.ime_transaction.take();
21803 }
21804 }
21805
21806 pub fn register_action_renderer(
21807 &mut self,
21808 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21809 ) -> Subscription {
21810 let id = self.next_editor_action_id.post_inc();
21811 self.editor_actions
21812 .borrow_mut()
21813 .insert(id, Box::new(listener));
21814
21815 let editor_actions = self.editor_actions.clone();
21816 Subscription::new(move || {
21817 editor_actions.borrow_mut().remove(&id);
21818 })
21819 }
21820
21821 pub fn register_action<A: Action>(
21822 &mut self,
21823 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21824 ) -> Subscription {
21825 let id = self.next_editor_action_id.post_inc();
21826 let listener = Arc::new(listener);
21827 self.editor_actions.borrow_mut().insert(
21828 id,
21829 Box::new(move |_, window, _| {
21830 let listener = listener.clone();
21831 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21832 let action = action.downcast_ref().unwrap();
21833 if phase == DispatchPhase::Bubble {
21834 listener(action, window, cx)
21835 }
21836 })
21837 }),
21838 );
21839
21840 let editor_actions = self.editor_actions.clone();
21841 Subscription::new(move || {
21842 editor_actions.borrow_mut().remove(&id);
21843 })
21844 }
21845
21846 pub fn file_header_size(&self) -> u32 {
21847 FILE_HEADER_HEIGHT
21848 }
21849
21850 pub fn restore(
21851 &mut self,
21852 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21853 window: &mut Window,
21854 cx: &mut Context<Self>,
21855 ) {
21856 let workspace = self.workspace();
21857 let project = self.project();
21858 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21859 let mut tasks = Vec::new();
21860 for (buffer_id, changes) in revert_changes {
21861 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21862 buffer.update(cx, |buffer, cx| {
21863 buffer.edit(
21864 changes
21865 .into_iter()
21866 .map(|(range, text)| (range, text.to_string())),
21867 None,
21868 cx,
21869 );
21870 });
21871
21872 if let Some(project) =
21873 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21874 {
21875 project.update(cx, |project, cx| {
21876 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21877 })
21878 }
21879 }
21880 }
21881 tasks
21882 });
21883 cx.spawn_in(window, async move |_, cx| {
21884 for (buffer, task) in save_tasks {
21885 let result = task.await;
21886 if result.is_err() {
21887 let Some(path) = buffer
21888 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21889 .ok()
21890 else {
21891 continue;
21892 };
21893 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21894 let Some(task) = cx
21895 .update_window_entity(workspace, |workspace, window, cx| {
21896 workspace
21897 .open_path_preview(path, None, false, false, false, window, cx)
21898 })
21899 .ok()
21900 else {
21901 continue;
21902 };
21903 task.await.log_err();
21904 }
21905 }
21906 }
21907 })
21908 .detach();
21909 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21910 selections.refresh()
21911 });
21912 }
21913
21914 pub fn to_pixel_point(
21915 &self,
21916 source: multi_buffer::Anchor,
21917 editor_snapshot: &EditorSnapshot,
21918 window: &mut Window,
21919 ) -> Option<gpui::Point<Pixels>> {
21920 let source_point = source.to_display_point(editor_snapshot);
21921 self.display_to_pixel_point(source_point, editor_snapshot, window)
21922 }
21923
21924 pub fn display_to_pixel_point(
21925 &self,
21926 source: DisplayPoint,
21927 editor_snapshot: &EditorSnapshot,
21928 window: &mut Window,
21929 ) -> Option<gpui::Point<Pixels>> {
21930 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21931 let text_layout_details = self.text_layout_details(window);
21932 let scroll_top = text_layout_details
21933 .scroll_anchor
21934 .scroll_position(editor_snapshot)
21935 .y;
21936
21937 if source.row().as_f64() < scroll_top.floor() {
21938 return None;
21939 }
21940 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21941 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21942 Some(gpui::Point::new(source_x, source_y))
21943 }
21944
21945 pub fn has_visible_completions_menu(&self) -> bool {
21946 !self.edit_prediction_preview_is_active()
21947 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21948 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21949 })
21950 }
21951
21952 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21953 if self.mode.is_minimap() {
21954 return;
21955 }
21956 self.addons
21957 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21958 }
21959
21960 pub fn unregister_addon<T: Addon>(&mut self) {
21961 self.addons.remove(&std::any::TypeId::of::<T>());
21962 }
21963
21964 pub fn addon<T: Addon>(&self) -> Option<&T> {
21965 let type_id = std::any::TypeId::of::<T>();
21966 self.addons
21967 .get(&type_id)
21968 .and_then(|item| item.to_any().downcast_ref::<T>())
21969 }
21970
21971 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21972 let type_id = std::any::TypeId::of::<T>();
21973 self.addons
21974 .get_mut(&type_id)
21975 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21976 }
21977
21978 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21979 let text_layout_details = self.text_layout_details(window);
21980 let style = &text_layout_details.editor_style;
21981 let font_id = window.text_system().resolve_font(&style.text.font());
21982 let font_size = style.text.font_size.to_pixels(window.rem_size());
21983 let line_height = style.text.line_height_in_pixels(window.rem_size());
21984 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21985 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21986
21987 CharacterDimensions {
21988 em_width,
21989 em_advance,
21990 line_height,
21991 }
21992 }
21993
21994 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21995 self.load_diff_task.clone()
21996 }
21997
21998 fn read_metadata_from_db(
21999 &mut self,
22000 item_id: u64,
22001 workspace_id: WorkspaceId,
22002 window: &mut Window,
22003 cx: &mut Context<Editor>,
22004 ) {
22005 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22006 && !self.mode.is_minimap()
22007 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22008 {
22009 let buffer_snapshot = OnceCell::new();
22010
22011 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22012 && !folds.is_empty()
22013 {
22014 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22015 self.fold_ranges(
22016 folds
22017 .into_iter()
22018 .map(|(start, end)| {
22019 snapshot.clip_offset(start, Bias::Left)
22020 ..snapshot.clip_offset(end, Bias::Right)
22021 })
22022 .collect(),
22023 false,
22024 window,
22025 cx,
22026 );
22027 }
22028
22029 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22030 && !selections.is_empty()
22031 {
22032 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22033 // skip adding the initial selection to selection history
22034 self.selection_history.mode = SelectionHistoryMode::Skipping;
22035 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22036 s.select_ranges(selections.into_iter().map(|(start, end)| {
22037 snapshot.clip_offset(start, Bias::Left)
22038 ..snapshot.clip_offset(end, Bias::Right)
22039 }));
22040 });
22041 self.selection_history.mode = SelectionHistoryMode::Normal;
22042 };
22043 }
22044
22045 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22046 }
22047
22048 fn update_lsp_data(
22049 &mut self,
22050 for_buffer: Option<BufferId>,
22051 window: &mut Window,
22052 cx: &mut Context<'_, Self>,
22053 ) {
22054 self.pull_diagnostics(for_buffer, window, cx);
22055 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22056 }
22057
22058 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22059 if self.ignore_lsp_data() {
22060 return;
22061 }
22062 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22063 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22064 }
22065 }
22066
22067 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22068 if !self.registered_buffers.contains_key(&buffer_id)
22069 && let Some(project) = self.project.as_ref()
22070 {
22071 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22072 project.update(cx, |project, cx| {
22073 self.registered_buffers.insert(
22074 buffer_id,
22075 project.register_buffer_with_language_servers(&buffer, cx),
22076 );
22077 });
22078 } else {
22079 self.registered_buffers.remove(&buffer_id);
22080 }
22081 }
22082 }
22083
22084 fn ignore_lsp_data(&self) -> bool {
22085 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22086 // skip any LSP updates for it.
22087 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22088 }
22089}
22090
22091fn edit_for_markdown_paste<'a>(
22092 buffer: &MultiBufferSnapshot,
22093 range: Range<usize>,
22094 to_insert: &'a str,
22095 url: Option<url::Url>,
22096) -> (Range<usize>, Cow<'a, str>) {
22097 if url.is_none() {
22098 return (range, Cow::Borrowed(to_insert));
22099 };
22100
22101 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22102
22103 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22104 Cow::Borrowed(to_insert)
22105 } else {
22106 Cow::Owned(format!("[{old_text}]({to_insert})"))
22107 };
22108 (range, new_text)
22109}
22110
22111#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22112pub enum VimFlavor {
22113 Vim,
22114 Helix,
22115}
22116
22117pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22118 if vim_mode_setting::HelixModeSetting::try_get(cx)
22119 .map(|helix_mode| helix_mode.0)
22120 .unwrap_or(false)
22121 {
22122 Some(VimFlavor::Helix)
22123 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22124 .map(|vim_mode| vim_mode.0)
22125 .unwrap_or(false)
22126 {
22127 Some(VimFlavor::Vim)
22128 } else {
22129 None // neither vim nor helix mode
22130 }
22131}
22132
22133fn process_completion_for_edit(
22134 completion: &Completion,
22135 intent: CompletionIntent,
22136 buffer: &Entity<Buffer>,
22137 cursor_position: &text::Anchor,
22138 cx: &mut Context<Editor>,
22139) -> CompletionEdit {
22140 let buffer = buffer.read(cx);
22141 let buffer_snapshot = buffer.snapshot();
22142 let (snippet, new_text) = if completion.is_snippet() {
22143 let mut snippet_source = completion.new_text.clone();
22144 // Workaround for typescript language server issues so that methods don't expand within
22145 // strings and functions with type expressions. The previous point is used because the query
22146 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22147 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22148 let previous_point = if previous_point.column > 0 {
22149 cursor_position.to_previous_offset(&buffer_snapshot)
22150 } else {
22151 cursor_position.to_offset(&buffer_snapshot)
22152 };
22153 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22154 && scope.prefers_label_for_snippet_in_completion()
22155 && let Some(label) = completion.label()
22156 && matches!(
22157 completion.kind(),
22158 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22159 )
22160 {
22161 snippet_source = label;
22162 }
22163 match Snippet::parse(&snippet_source).log_err() {
22164 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22165 None => (None, completion.new_text.clone()),
22166 }
22167 } else {
22168 (None, completion.new_text.clone())
22169 };
22170
22171 let mut range_to_replace = {
22172 let replace_range = &completion.replace_range;
22173 if let CompletionSource::Lsp {
22174 insert_range: Some(insert_range),
22175 ..
22176 } = &completion.source
22177 {
22178 debug_assert_eq!(
22179 insert_range.start, replace_range.start,
22180 "insert_range and replace_range should start at the same position"
22181 );
22182 debug_assert!(
22183 insert_range
22184 .start
22185 .cmp(cursor_position, &buffer_snapshot)
22186 .is_le(),
22187 "insert_range should start before or at cursor position"
22188 );
22189 debug_assert!(
22190 replace_range
22191 .start
22192 .cmp(cursor_position, &buffer_snapshot)
22193 .is_le(),
22194 "replace_range should start before or at cursor position"
22195 );
22196
22197 let should_replace = match intent {
22198 CompletionIntent::CompleteWithInsert => false,
22199 CompletionIntent::CompleteWithReplace => true,
22200 CompletionIntent::Complete | CompletionIntent::Compose => {
22201 let insert_mode =
22202 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22203 .completions
22204 .lsp_insert_mode;
22205 match insert_mode {
22206 LspInsertMode::Insert => false,
22207 LspInsertMode::Replace => true,
22208 LspInsertMode::ReplaceSubsequence => {
22209 let mut text_to_replace = buffer.chars_for_range(
22210 buffer.anchor_before(replace_range.start)
22211 ..buffer.anchor_after(replace_range.end),
22212 );
22213 let mut current_needle = text_to_replace.next();
22214 for haystack_ch in completion.label.text.chars() {
22215 if let Some(needle_ch) = current_needle
22216 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22217 {
22218 current_needle = text_to_replace.next();
22219 }
22220 }
22221 current_needle.is_none()
22222 }
22223 LspInsertMode::ReplaceSuffix => {
22224 if replace_range
22225 .end
22226 .cmp(cursor_position, &buffer_snapshot)
22227 .is_gt()
22228 {
22229 let range_after_cursor = *cursor_position..replace_range.end;
22230 let text_after_cursor = buffer
22231 .text_for_range(
22232 buffer.anchor_before(range_after_cursor.start)
22233 ..buffer.anchor_after(range_after_cursor.end),
22234 )
22235 .collect::<String>()
22236 .to_ascii_lowercase();
22237 completion
22238 .label
22239 .text
22240 .to_ascii_lowercase()
22241 .ends_with(&text_after_cursor)
22242 } else {
22243 true
22244 }
22245 }
22246 }
22247 }
22248 };
22249
22250 if should_replace {
22251 replace_range.clone()
22252 } else {
22253 insert_range.clone()
22254 }
22255 } else {
22256 replace_range.clone()
22257 }
22258 };
22259
22260 if range_to_replace
22261 .end
22262 .cmp(cursor_position, &buffer_snapshot)
22263 .is_lt()
22264 {
22265 range_to_replace.end = *cursor_position;
22266 }
22267
22268 CompletionEdit {
22269 new_text,
22270 replace_range: range_to_replace.to_offset(buffer),
22271 snippet,
22272 }
22273}
22274
22275struct CompletionEdit {
22276 new_text: String,
22277 replace_range: Range<usize>,
22278 snippet: Option<Snippet>,
22279}
22280
22281fn insert_extra_newline_brackets(
22282 buffer: &MultiBufferSnapshot,
22283 range: Range<usize>,
22284 language: &language::LanguageScope,
22285) -> bool {
22286 let leading_whitespace_len = buffer
22287 .reversed_chars_at(range.start)
22288 .take_while(|c| c.is_whitespace() && *c != '\n')
22289 .map(|c| c.len_utf8())
22290 .sum::<usize>();
22291 let trailing_whitespace_len = buffer
22292 .chars_at(range.end)
22293 .take_while(|c| c.is_whitespace() && *c != '\n')
22294 .map(|c| c.len_utf8())
22295 .sum::<usize>();
22296 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22297
22298 language.brackets().any(|(pair, enabled)| {
22299 let pair_start = pair.start.trim_end();
22300 let pair_end = pair.end.trim_start();
22301
22302 enabled
22303 && pair.newline
22304 && buffer.contains_str_at(range.end, pair_end)
22305 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22306 })
22307}
22308
22309fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22310 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22311 [(buffer, range, _)] => (*buffer, range.clone()),
22312 _ => return false,
22313 };
22314 let pair = {
22315 let mut result: Option<BracketMatch> = None;
22316
22317 for pair in buffer
22318 .all_bracket_ranges(range.clone())
22319 .filter(move |pair| {
22320 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22321 })
22322 {
22323 let len = pair.close_range.end - pair.open_range.start;
22324
22325 if let Some(existing) = &result {
22326 let existing_len = existing.close_range.end - existing.open_range.start;
22327 if len > existing_len {
22328 continue;
22329 }
22330 }
22331
22332 result = Some(pair);
22333 }
22334
22335 result
22336 };
22337 let Some(pair) = pair else {
22338 return false;
22339 };
22340 pair.newline_only
22341 && buffer
22342 .chars_for_range(pair.open_range.end..range.start)
22343 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22344 .all(|c| c.is_whitespace() && c != '\n')
22345}
22346
22347fn update_uncommitted_diff_for_buffer(
22348 editor: Entity<Editor>,
22349 project: &Entity<Project>,
22350 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22351 buffer: Entity<MultiBuffer>,
22352 cx: &mut App,
22353) -> Task<()> {
22354 let mut tasks = Vec::new();
22355 project.update(cx, |project, cx| {
22356 for buffer in buffers {
22357 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22358 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22359 }
22360 }
22361 });
22362 cx.spawn(async move |cx| {
22363 let diffs = future::join_all(tasks).await;
22364 if editor
22365 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22366 .unwrap_or(false)
22367 {
22368 return;
22369 }
22370
22371 buffer
22372 .update(cx, |buffer, cx| {
22373 for diff in diffs.into_iter().flatten() {
22374 buffer.add_diff(diff, cx);
22375 }
22376 })
22377 .ok();
22378 })
22379}
22380
22381fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22382 let tab_size = tab_size.get() as usize;
22383 let mut width = offset;
22384
22385 for ch in text.chars() {
22386 width += if ch == '\t' {
22387 tab_size - (width % tab_size)
22388 } else {
22389 1
22390 };
22391 }
22392
22393 width - offset
22394}
22395
22396#[cfg(test)]
22397mod tests {
22398 use super::*;
22399
22400 #[test]
22401 fn test_string_size_with_expanded_tabs() {
22402 let nz = |val| NonZeroU32::new(val).unwrap();
22403 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22404 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22405 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22406 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22407 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22408 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22409 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22410 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22411 }
22412}
22413
22414/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22415struct WordBreakingTokenizer<'a> {
22416 input: &'a str,
22417}
22418
22419impl<'a> WordBreakingTokenizer<'a> {
22420 fn new(input: &'a str) -> Self {
22421 Self { input }
22422 }
22423}
22424
22425fn is_char_ideographic(ch: char) -> bool {
22426 use unicode_script::Script::*;
22427 use unicode_script::UnicodeScript;
22428 matches!(ch.script(), Han | Tangut | Yi)
22429}
22430
22431fn is_grapheme_ideographic(text: &str) -> bool {
22432 text.chars().any(is_char_ideographic)
22433}
22434
22435fn is_grapheme_whitespace(text: &str) -> bool {
22436 text.chars().any(|x| x.is_whitespace())
22437}
22438
22439fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22440 text.chars()
22441 .next()
22442 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22443}
22444
22445#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22446enum WordBreakToken<'a> {
22447 Word { token: &'a str, grapheme_len: usize },
22448 InlineWhitespace { token: &'a str, grapheme_len: usize },
22449 Newline,
22450}
22451
22452impl<'a> Iterator for WordBreakingTokenizer<'a> {
22453 /// Yields a span, the count of graphemes in the token, and whether it was
22454 /// whitespace. Note that it also breaks at word boundaries.
22455 type Item = WordBreakToken<'a>;
22456
22457 fn next(&mut self) -> Option<Self::Item> {
22458 use unicode_segmentation::UnicodeSegmentation;
22459 if self.input.is_empty() {
22460 return None;
22461 }
22462
22463 let mut iter = self.input.graphemes(true).peekable();
22464 let mut offset = 0;
22465 let mut grapheme_len = 0;
22466 if let Some(first_grapheme) = iter.next() {
22467 let is_newline = first_grapheme == "\n";
22468 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22469 offset += first_grapheme.len();
22470 grapheme_len += 1;
22471 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22472 if let Some(grapheme) = iter.peek().copied()
22473 && should_stay_with_preceding_ideograph(grapheme)
22474 {
22475 offset += grapheme.len();
22476 grapheme_len += 1;
22477 }
22478 } else {
22479 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22480 let mut next_word_bound = words.peek().copied();
22481 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22482 next_word_bound = words.next();
22483 }
22484 while let Some(grapheme) = iter.peek().copied() {
22485 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22486 break;
22487 };
22488 if is_grapheme_whitespace(grapheme) != is_whitespace
22489 || (grapheme == "\n") != is_newline
22490 {
22491 break;
22492 };
22493 offset += grapheme.len();
22494 grapheme_len += 1;
22495 iter.next();
22496 }
22497 }
22498 let token = &self.input[..offset];
22499 self.input = &self.input[offset..];
22500 if token == "\n" {
22501 Some(WordBreakToken::Newline)
22502 } else if is_whitespace {
22503 Some(WordBreakToken::InlineWhitespace {
22504 token,
22505 grapheme_len,
22506 })
22507 } else {
22508 Some(WordBreakToken::Word {
22509 token,
22510 grapheme_len,
22511 })
22512 }
22513 } else {
22514 None
22515 }
22516 }
22517}
22518
22519#[test]
22520fn test_word_breaking_tokenizer() {
22521 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22522 ("", &[]),
22523 (" ", &[whitespace(" ", 2)]),
22524 ("Ʒ", &[word("Ʒ", 1)]),
22525 ("Ǽ", &[word("Ǽ", 1)]),
22526 ("⋑", &[word("⋑", 1)]),
22527 ("⋑⋑", &[word("⋑⋑", 2)]),
22528 (
22529 "原理,进而",
22530 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22531 ),
22532 (
22533 "hello world",
22534 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22535 ),
22536 (
22537 "hello, world",
22538 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22539 ),
22540 (
22541 " hello world",
22542 &[
22543 whitespace(" ", 2),
22544 word("hello", 5),
22545 whitespace(" ", 1),
22546 word("world", 5),
22547 ],
22548 ),
22549 (
22550 "这是什么 \n 钢笔",
22551 &[
22552 word("这", 1),
22553 word("是", 1),
22554 word("什", 1),
22555 word("么", 1),
22556 whitespace(" ", 1),
22557 newline(),
22558 whitespace(" ", 1),
22559 word("钢", 1),
22560 word("笔", 1),
22561 ],
22562 ),
22563 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22564 ];
22565
22566 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22567 WordBreakToken::Word {
22568 token,
22569 grapheme_len,
22570 }
22571 }
22572
22573 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22574 WordBreakToken::InlineWhitespace {
22575 token,
22576 grapheme_len,
22577 }
22578 }
22579
22580 fn newline() -> WordBreakToken<'static> {
22581 WordBreakToken::Newline
22582 }
22583
22584 for (input, result) in tests {
22585 assert_eq!(
22586 WordBreakingTokenizer::new(input)
22587 .collect::<Vec<_>>()
22588 .as_slice(),
22589 *result,
22590 );
22591 }
22592}
22593
22594fn wrap_with_prefix(
22595 first_line_prefix: String,
22596 subsequent_lines_prefix: String,
22597 unwrapped_text: String,
22598 wrap_column: usize,
22599 tab_size: NonZeroU32,
22600 preserve_existing_whitespace: bool,
22601) -> String {
22602 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22603 let subsequent_lines_prefix_len =
22604 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22605 let mut wrapped_text = String::new();
22606 let mut current_line = first_line_prefix;
22607 let mut is_first_line = true;
22608
22609 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22610 let mut current_line_len = first_line_prefix_len;
22611 let mut in_whitespace = false;
22612 for token in tokenizer {
22613 let have_preceding_whitespace = in_whitespace;
22614 match token {
22615 WordBreakToken::Word {
22616 token,
22617 grapheme_len,
22618 } => {
22619 in_whitespace = false;
22620 let current_prefix_len = if is_first_line {
22621 first_line_prefix_len
22622 } else {
22623 subsequent_lines_prefix_len
22624 };
22625 if current_line_len + grapheme_len > wrap_column
22626 && current_line_len != current_prefix_len
22627 {
22628 wrapped_text.push_str(current_line.trim_end());
22629 wrapped_text.push('\n');
22630 is_first_line = false;
22631 current_line = subsequent_lines_prefix.clone();
22632 current_line_len = subsequent_lines_prefix_len;
22633 }
22634 current_line.push_str(token);
22635 current_line_len += grapheme_len;
22636 }
22637 WordBreakToken::InlineWhitespace {
22638 mut token,
22639 mut grapheme_len,
22640 } => {
22641 in_whitespace = true;
22642 if have_preceding_whitespace && !preserve_existing_whitespace {
22643 continue;
22644 }
22645 if !preserve_existing_whitespace {
22646 // Keep a single whitespace grapheme as-is
22647 if let Some(first) =
22648 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22649 {
22650 token = first;
22651 } else {
22652 token = " ";
22653 }
22654 grapheme_len = 1;
22655 }
22656 let current_prefix_len = if is_first_line {
22657 first_line_prefix_len
22658 } else {
22659 subsequent_lines_prefix_len
22660 };
22661 if current_line_len + grapheme_len > wrap_column {
22662 wrapped_text.push_str(current_line.trim_end());
22663 wrapped_text.push('\n');
22664 is_first_line = false;
22665 current_line = subsequent_lines_prefix.clone();
22666 current_line_len = subsequent_lines_prefix_len;
22667 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22668 current_line.push_str(token);
22669 current_line_len += grapheme_len;
22670 }
22671 }
22672 WordBreakToken::Newline => {
22673 in_whitespace = true;
22674 let current_prefix_len = if is_first_line {
22675 first_line_prefix_len
22676 } else {
22677 subsequent_lines_prefix_len
22678 };
22679 if preserve_existing_whitespace {
22680 wrapped_text.push_str(current_line.trim_end());
22681 wrapped_text.push('\n');
22682 is_first_line = false;
22683 current_line = subsequent_lines_prefix.clone();
22684 current_line_len = subsequent_lines_prefix_len;
22685 } else if have_preceding_whitespace {
22686 continue;
22687 } else if current_line_len + 1 > wrap_column
22688 && current_line_len != current_prefix_len
22689 {
22690 wrapped_text.push_str(current_line.trim_end());
22691 wrapped_text.push('\n');
22692 is_first_line = false;
22693 current_line = subsequent_lines_prefix.clone();
22694 current_line_len = subsequent_lines_prefix_len;
22695 } else if current_line_len != current_prefix_len {
22696 current_line.push(' ');
22697 current_line_len += 1;
22698 }
22699 }
22700 }
22701 }
22702
22703 if !current_line.is_empty() {
22704 wrapped_text.push_str(¤t_line);
22705 }
22706 wrapped_text
22707}
22708
22709#[test]
22710fn test_wrap_with_prefix() {
22711 assert_eq!(
22712 wrap_with_prefix(
22713 "# ".to_string(),
22714 "# ".to_string(),
22715 "abcdefg".to_string(),
22716 4,
22717 NonZeroU32::new(4).unwrap(),
22718 false,
22719 ),
22720 "# abcdefg"
22721 );
22722 assert_eq!(
22723 wrap_with_prefix(
22724 "".to_string(),
22725 "".to_string(),
22726 "\thello world".to_string(),
22727 8,
22728 NonZeroU32::new(4).unwrap(),
22729 false,
22730 ),
22731 "hello\nworld"
22732 );
22733 assert_eq!(
22734 wrap_with_prefix(
22735 "// ".to_string(),
22736 "// ".to_string(),
22737 "xx \nyy zz aa bb cc".to_string(),
22738 12,
22739 NonZeroU32::new(4).unwrap(),
22740 false,
22741 ),
22742 "// xx yy zz\n// aa bb cc"
22743 );
22744 assert_eq!(
22745 wrap_with_prefix(
22746 String::new(),
22747 String::new(),
22748 "这是什么 \n 钢笔".to_string(),
22749 3,
22750 NonZeroU32::new(4).unwrap(),
22751 false,
22752 ),
22753 "这是什\n么 钢\n笔"
22754 );
22755 assert_eq!(
22756 wrap_with_prefix(
22757 String::new(),
22758 String::new(),
22759 format!("foo{}bar", '\u{2009}'), // thin space
22760 80,
22761 NonZeroU32::new(4).unwrap(),
22762 false,
22763 ),
22764 format!("foo{}bar", '\u{2009}')
22765 );
22766}
22767
22768pub trait CollaborationHub {
22769 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22770 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22771 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22772}
22773
22774impl CollaborationHub for Entity<Project> {
22775 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22776 self.read(cx).collaborators()
22777 }
22778
22779 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22780 self.read(cx).user_store().read(cx).participant_indices()
22781 }
22782
22783 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22784 let this = self.read(cx);
22785 let user_ids = this.collaborators().values().map(|c| c.user_id);
22786 this.user_store().read(cx).participant_names(user_ids, cx)
22787 }
22788}
22789
22790pub trait SemanticsProvider {
22791 fn hover(
22792 &self,
22793 buffer: &Entity<Buffer>,
22794 position: text::Anchor,
22795 cx: &mut App,
22796 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22797
22798 fn inline_values(
22799 &self,
22800 buffer_handle: Entity<Buffer>,
22801 range: Range<text::Anchor>,
22802 cx: &mut App,
22803 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22804
22805 fn applicable_inlay_chunks(
22806 &self,
22807 buffer: &Entity<Buffer>,
22808 ranges: &[Range<text::Anchor>],
22809 cx: &mut App,
22810 ) -> Vec<Range<BufferRow>>;
22811
22812 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22813
22814 fn inlay_hints(
22815 &self,
22816 invalidate: InvalidationStrategy,
22817 buffer: Entity<Buffer>,
22818 ranges: Vec<Range<text::Anchor>>,
22819 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22820 cx: &mut App,
22821 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22822
22823 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22824
22825 fn document_highlights(
22826 &self,
22827 buffer: &Entity<Buffer>,
22828 position: text::Anchor,
22829 cx: &mut App,
22830 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22831
22832 fn definitions(
22833 &self,
22834 buffer: &Entity<Buffer>,
22835 position: text::Anchor,
22836 kind: GotoDefinitionKind,
22837 cx: &mut App,
22838 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22839
22840 fn range_for_rename(
22841 &self,
22842 buffer: &Entity<Buffer>,
22843 position: text::Anchor,
22844 cx: &mut App,
22845 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22846
22847 fn perform_rename(
22848 &self,
22849 buffer: &Entity<Buffer>,
22850 position: text::Anchor,
22851 new_name: String,
22852 cx: &mut App,
22853 ) -> Option<Task<Result<ProjectTransaction>>>;
22854}
22855
22856pub trait CompletionProvider {
22857 fn completions(
22858 &self,
22859 excerpt_id: ExcerptId,
22860 buffer: &Entity<Buffer>,
22861 buffer_position: text::Anchor,
22862 trigger: CompletionContext,
22863 window: &mut Window,
22864 cx: &mut Context<Editor>,
22865 ) -> Task<Result<Vec<CompletionResponse>>>;
22866
22867 fn resolve_completions(
22868 &self,
22869 _buffer: Entity<Buffer>,
22870 _completion_indices: Vec<usize>,
22871 _completions: Rc<RefCell<Box<[Completion]>>>,
22872 _cx: &mut Context<Editor>,
22873 ) -> Task<Result<bool>> {
22874 Task::ready(Ok(false))
22875 }
22876
22877 fn apply_additional_edits_for_completion(
22878 &self,
22879 _buffer: Entity<Buffer>,
22880 _completions: Rc<RefCell<Box<[Completion]>>>,
22881 _completion_index: usize,
22882 _push_to_history: bool,
22883 _cx: &mut Context<Editor>,
22884 ) -> Task<Result<Option<language::Transaction>>> {
22885 Task::ready(Ok(None))
22886 }
22887
22888 fn is_completion_trigger(
22889 &self,
22890 buffer: &Entity<Buffer>,
22891 position: language::Anchor,
22892 text: &str,
22893 trigger_in_words: bool,
22894 menu_is_open: bool,
22895 cx: &mut Context<Editor>,
22896 ) -> bool;
22897
22898 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22899
22900 fn sort_completions(&self) -> bool {
22901 true
22902 }
22903
22904 fn filter_completions(&self) -> bool {
22905 true
22906 }
22907}
22908
22909pub trait CodeActionProvider {
22910 fn id(&self) -> Arc<str>;
22911
22912 fn code_actions(
22913 &self,
22914 buffer: &Entity<Buffer>,
22915 range: Range<text::Anchor>,
22916 window: &mut Window,
22917 cx: &mut App,
22918 ) -> Task<Result<Vec<CodeAction>>>;
22919
22920 fn apply_code_action(
22921 &self,
22922 buffer_handle: Entity<Buffer>,
22923 action: CodeAction,
22924 excerpt_id: ExcerptId,
22925 push_to_history: bool,
22926 window: &mut Window,
22927 cx: &mut App,
22928 ) -> Task<Result<ProjectTransaction>>;
22929}
22930
22931impl CodeActionProvider for Entity<Project> {
22932 fn id(&self) -> Arc<str> {
22933 "project".into()
22934 }
22935
22936 fn code_actions(
22937 &self,
22938 buffer: &Entity<Buffer>,
22939 range: Range<text::Anchor>,
22940 _window: &mut Window,
22941 cx: &mut App,
22942 ) -> Task<Result<Vec<CodeAction>>> {
22943 self.update(cx, |project, cx| {
22944 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22945 let code_actions = project.code_actions(buffer, range, None, cx);
22946 cx.background_spawn(async move {
22947 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22948 Ok(code_lens_actions
22949 .context("code lens fetch")?
22950 .into_iter()
22951 .flatten()
22952 .chain(
22953 code_actions
22954 .context("code action fetch")?
22955 .into_iter()
22956 .flatten(),
22957 )
22958 .collect())
22959 })
22960 })
22961 }
22962
22963 fn apply_code_action(
22964 &self,
22965 buffer_handle: Entity<Buffer>,
22966 action: CodeAction,
22967 _excerpt_id: ExcerptId,
22968 push_to_history: bool,
22969 _window: &mut Window,
22970 cx: &mut App,
22971 ) -> Task<Result<ProjectTransaction>> {
22972 self.update(cx, |project, cx| {
22973 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22974 })
22975 }
22976}
22977
22978fn snippet_completions(
22979 project: &Project,
22980 buffer: &Entity<Buffer>,
22981 buffer_position: text::Anchor,
22982 cx: &mut App,
22983) -> Task<Result<CompletionResponse>> {
22984 let languages = buffer.read(cx).languages_at(buffer_position);
22985 let snippet_store = project.snippets().read(cx);
22986
22987 let scopes: Vec<_> = languages
22988 .iter()
22989 .filter_map(|language| {
22990 let language_name = language.lsp_id();
22991 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22992
22993 if snippets.is_empty() {
22994 None
22995 } else {
22996 Some((language.default_scope(), snippets))
22997 }
22998 })
22999 .collect();
23000
23001 if scopes.is_empty() {
23002 return Task::ready(Ok(CompletionResponse {
23003 completions: vec![],
23004 display_options: CompletionDisplayOptions::default(),
23005 is_incomplete: false,
23006 }));
23007 }
23008
23009 let snapshot = buffer.read(cx).text_snapshot();
23010 let executor = cx.background_executor().clone();
23011
23012 cx.background_spawn(async move {
23013 let mut is_incomplete = false;
23014 let mut completions: Vec<Completion> = Vec::new();
23015 for (scope, snippets) in scopes.into_iter() {
23016 let classifier =
23017 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23018
23019 const MAX_WORD_PREFIX_LEN: usize = 128;
23020 let last_word: String = snapshot
23021 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23022 .take(MAX_WORD_PREFIX_LEN)
23023 .take_while(|c| classifier.is_word(*c))
23024 .collect::<String>()
23025 .chars()
23026 .rev()
23027 .collect();
23028
23029 if last_word.is_empty() {
23030 return Ok(CompletionResponse {
23031 completions: vec![],
23032 display_options: CompletionDisplayOptions::default(),
23033 is_incomplete: true,
23034 });
23035 }
23036
23037 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23038 let to_lsp = |point: &text::Anchor| {
23039 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23040 point_to_lsp(end)
23041 };
23042 let lsp_end = to_lsp(&buffer_position);
23043
23044 let candidates = snippets
23045 .iter()
23046 .enumerate()
23047 .flat_map(|(ix, snippet)| {
23048 snippet
23049 .prefix
23050 .iter()
23051 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23052 })
23053 .collect::<Vec<StringMatchCandidate>>();
23054
23055 const MAX_RESULTS: usize = 100;
23056 let mut matches = fuzzy::match_strings(
23057 &candidates,
23058 &last_word,
23059 last_word.chars().any(|c| c.is_uppercase()),
23060 true,
23061 MAX_RESULTS,
23062 &Default::default(),
23063 executor.clone(),
23064 )
23065 .await;
23066
23067 if matches.len() >= MAX_RESULTS {
23068 is_incomplete = true;
23069 }
23070
23071 // Remove all candidates where the query's start does not match the start of any word in the candidate
23072 if let Some(query_start) = last_word.chars().next() {
23073 matches.retain(|string_match| {
23074 split_words(&string_match.string).any(|word| {
23075 // Check that the first codepoint of the word as lowercase matches the first
23076 // codepoint of the query as lowercase
23077 word.chars()
23078 .flat_map(|codepoint| codepoint.to_lowercase())
23079 .zip(query_start.to_lowercase())
23080 .all(|(word_cp, query_cp)| word_cp == query_cp)
23081 })
23082 });
23083 }
23084
23085 let matched_strings = matches
23086 .into_iter()
23087 .map(|m| m.string)
23088 .collect::<HashSet<_>>();
23089
23090 completions.extend(snippets.iter().filter_map(|snippet| {
23091 let matching_prefix = snippet
23092 .prefix
23093 .iter()
23094 .find(|prefix| matched_strings.contains(*prefix))?;
23095 let start = as_offset - last_word.len();
23096 let start = snapshot.anchor_before(start);
23097 let range = start..buffer_position;
23098 let lsp_start = to_lsp(&start);
23099 let lsp_range = lsp::Range {
23100 start: lsp_start,
23101 end: lsp_end,
23102 };
23103 Some(Completion {
23104 replace_range: range,
23105 new_text: snippet.body.clone(),
23106 source: CompletionSource::Lsp {
23107 insert_range: None,
23108 server_id: LanguageServerId(usize::MAX),
23109 resolved: true,
23110 lsp_completion: Box::new(lsp::CompletionItem {
23111 label: snippet.prefix.first().unwrap().clone(),
23112 kind: Some(CompletionItemKind::SNIPPET),
23113 label_details: snippet.description.as_ref().map(|description| {
23114 lsp::CompletionItemLabelDetails {
23115 detail: Some(description.clone()),
23116 description: None,
23117 }
23118 }),
23119 insert_text_format: Some(InsertTextFormat::SNIPPET),
23120 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23121 lsp::InsertReplaceEdit {
23122 new_text: snippet.body.clone(),
23123 insert: lsp_range,
23124 replace: lsp_range,
23125 },
23126 )),
23127 filter_text: Some(snippet.body.clone()),
23128 sort_text: Some(char::MAX.to_string()),
23129 ..lsp::CompletionItem::default()
23130 }),
23131 lsp_defaults: None,
23132 },
23133 label: CodeLabel::plain(matching_prefix.clone(), None),
23134 icon_path: None,
23135 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23136 single_line: snippet.name.clone().into(),
23137 plain_text: snippet
23138 .description
23139 .clone()
23140 .map(|description| description.into()),
23141 }),
23142 insert_text_mode: None,
23143 confirm: None,
23144 })
23145 }))
23146 }
23147
23148 Ok(CompletionResponse {
23149 completions,
23150 display_options: CompletionDisplayOptions::default(),
23151 is_incomplete,
23152 })
23153 })
23154}
23155
23156impl CompletionProvider for Entity<Project> {
23157 fn completions(
23158 &self,
23159 _excerpt_id: ExcerptId,
23160 buffer: &Entity<Buffer>,
23161 buffer_position: text::Anchor,
23162 options: CompletionContext,
23163 _window: &mut Window,
23164 cx: &mut Context<Editor>,
23165 ) -> Task<Result<Vec<CompletionResponse>>> {
23166 self.update(cx, |project, cx| {
23167 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23168 let project_completions = project.completions(buffer, buffer_position, options, cx);
23169 cx.background_spawn(async move {
23170 let mut responses = project_completions.await?;
23171 let snippets = snippets.await?;
23172 if !snippets.completions.is_empty() {
23173 responses.push(snippets);
23174 }
23175 Ok(responses)
23176 })
23177 })
23178 }
23179
23180 fn resolve_completions(
23181 &self,
23182 buffer: Entity<Buffer>,
23183 completion_indices: Vec<usize>,
23184 completions: Rc<RefCell<Box<[Completion]>>>,
23185 cx: &mut Context<Editor>,
23186 ) -> Task<Result<bool>> {
23187 self.update(cx, |project, cx| {
23188 project.lsp_store().update(cx, |lsp_store, cx| {
23189 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23190 })
23191 })
23192 }
23193
23194 fn apply_additional_edits_for_completion(
23195 &self,
23196 buffer: Entity<Buffer>,
23197 completions: Rc<RefCell<Box<[Completion]>>>,
23198 completion_index: usize,
23199 push_to_history: bool,
23200 cx: &mut Context<Editor>,
23201 ) -> Task<Result<Option<language::Transaction>>> {
23202 self.update(cx, |project, cx| {
23203 project.lsp_store().update(cx, |lsp_store, cx| {
23204 lsp_store.apply_additional_edits_for_completion(
23205 buffer,
23206 completions,
23207 completion_index,
23208 push_to_history,
23209 cx,
23210 )
23211 })
23212 })
23213 }
23214
23215 fn is_completion_trigger(
23216 &self,
23217 buffer: &Entity<Buffer>,
23218 position: language::Anchor,
23219 text: &str,
23220 trigger_in_words: bool,
23221 menu_is_open: bool,
23222 cx: &mut Context<Editor>,
23223 ) -> bool {
23224 let mut chars = text.chars();
23225 let char = if let Some(char) = chars.next() {
23226 char
23227 } else {
23228 return false;
23229 };
23230 if chars.next().is_some() {
23231 return false;
23232 }
23233
23234 let buffer = buffer.read(cx);
23235 let snapshot = buffer.snapshot();
23236 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23237 return false;
23238 }
23239 let classifier = snapshot
23240 .char_classifier_at(position)
23241 .scope_context(Some(CharScopeContext::Completion));
23242 if trigger_in_words && classifier.is_word(char) {
23243 return true;
23244 }
23245
23246 buffer.completion_triggers().contains(text)
23247 }
23248}
23249
23250impl SemanticsProvider for Entity<Project> {
23251 fn hover(
23252 &self,
23253 buffer: &Entity<Buffer>,
23254 position: text::Anchor,
23255 cx: &mut App,
23256 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23257 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23258 }
23259
23260 fn document_highlights(
23261 &self,
23262 buffer: &Entity<Buffer>,
23263 position: text::Anchor,
23264 cx: &mut App,
23265 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23266 Some(self.update(cx, |project, cx| {
23267 project.document_highlights(buffer, position, cx)
23268 }))
23269 }
23270
23271 fn definitions(
23272 &self,
23273 buffer: &Entity<Buffer>,
23274 position: text::Anchor,
23275 kind: GotoDefinitionKind,
23276 cx: &mut App,
23277 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23278 Some(self.update(cx, |project, cx| match kind {
23279 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23280 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23281 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23282 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23283 }))
23284 }
23285
23286 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23287 self.update(cx, |project, cx| {
23288 if project
23289 .active_debug_session(cx)
23290 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23291 {
23292 return true;
23293 }
23294
23295 buffer.update(cx, |buffer, cx| {
23296 project.any_language_server_supports_inlay_hints(buffer, cx)
23297 })
23298 })
23299 }
23300
23301 fn inline_values(
23302 &self,
23303 buffer_handle: Entity<Buffer>,
23304 range: Range<text::Anchor>,
23305 cx: &mut App,
23306 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23307 self.update(cx, |project, cx| {
23308 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23309
23310 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23311 })
23312 }
23313
23314 fn applicable_inlay_chunks(
23315 &self,
23316 buffer: &Entity<Buffer>,
23317 ranges: &[Range<text::Anchor>],
23318 cx: &mut App,
23319 ) -> Vec<Range<BufferRow>> {
23320 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23321 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23322 })
23323 }
23324
23325 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23326 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23327 lsp_store.invalidate_inlay_hints(for_buffers)
23328 });
23329 }
23330
23331 fn inlay_hints(
23332 &self,
23333 invalidate: InvalidationStrategy,
23334 buffer: Entity<Buffer>,
23335 ranges: Vec<Range<text::Anchor>>,
23336 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23337 cx: &mut App,
23338 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23339 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23340 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23341 }))
23342 }
23343
23344 fn range_for_rename(
23345 &self,
23346 buffer: &Entity<Buffer>,
23347 position: text::Anchor,
23348 cx: &mut App,
23349 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23350 Some(self.update(cx, |project, cx| {
23351 let buffer = buffer.clone();
23352 let task = project.prepare_rename(buffer.clone(), position, cx);
23353 cx.spawn(async move |_, cx| {
23354 Ok(match task.await? {
23355 PrepareRenameResponse::Success(range) => Some(range),
23356 PrepareRenameResponse::InvalidPosition => None,
23357 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23358 // Fallback on using TreeSitter info to determine identifier range
23359 buffer.read_with(cx, |buffer, _| {
23360 let snapshot = buffer.snapshot();
23361 let (range, kind) = snapshot.surrounding_word(position, None);
23362 if kind != Some(CharKind::Word) {
23363 return None;
23364 }
23365 Some(
23366 snapshot.anchor_before(range.start)
23367 ..snapshot.anchor_after(range.end),
23368 )
23369 })?
23370 }
23371 })
23372 })
23373 }))
23374 }
23375
23376 fn perform_rename(
23377 &self,
23378 buffer: &Entity<Buffer>,
23379 position: text::Anchor,
23380 new_name: String,
23381 cx: &mut App,
23382 ) -> Option<Task<Result<ProjectTransaction>>> {
23383 Some(self.update(cx, |project, cx| {
23384 project.perform_rename(buffer.clone(), position, new_name, cx)
23385 }))
23386 }
23387}
23388
23389fn consume_contiguous_rows(
23390 contiguous_row_selections: &mut Vec<Selection<Point>>,
23391 selection: &Selection<Point>,
23392 display_map: &DisplaySnapshot,
23393 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23394) -> (MultiBufferRow, MultiBufferRow) {
23395 contiguous_row_selections.push(selection.clone());
23396 let start_row = starting_row(selection, display_map);
23397 let mut end_row = ending_row(selection, display_map);
23398
23399 while let Some(next_selection) = selections.peek() {
23400 if next_selection.start.row <= end_row.0 {
23401 end_row = ending_row(next_selection, display_map);
23402 contiguous_row_selections.push(selections.next().unwrap().clone());
23403 } else {
23404 break;
23405 }
23406 }
23407 (start_row, end_row)
23408}
23409
23410fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23411 if selection.start.column > 0 {
23412 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23413 } else {
23414 MultiBufferRow(selection.start.row)
23415 }
23416}
23417
23418fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23419 if next_selection.end.column > 0 || next_selection.is_empty() {
23420 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23421 } else {
23422 MultiBufferRow(next_selection.end.row)
23423 }
23424}
23425
23426impl EditorSnapshot {
23427 pub fn remote_selections_in_range<'a>(
23428 &'a self,
23429 range: &'a Range<Anchor>,
23430 collaboration_hub: &dyn CollaborationHub,
23431 cx: &'a App,
23432 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23433 let participant_names = collaboration_hub.user_names(cx);
23434 let participant_indices = collaboration_hub.user_participant_indices(cx);
23435 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23436 let collaborators_by_replica_id = collaborators_by_peer_id
23437 .values()
23438 .map(|collaborator| (collaborator.replica_id, collaborator))
23439 .collect::<HashMap<_, _>>();
23440 self.buffer_snapshot()
23441 .selections_in_range(range, false)
23442 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23443 if replica_id == ReplicaId::AGENT {
23444 Some(RemoteSelection {
23445 replica_id,
23446 selection,
23447 cursor_shape,
23448 line_mode,
23449 collaborator_id: CollaboratorId::Agent,
23450 user_name: Some("Agent".into()),
23451 color: cx.theme().players().agent(),
23452 })
23453 } else {
23454 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23455 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23456 let user_name = participant_names.get(&collaborator.user_id).cloned();
23457 Some(RemoteSelection {
23458 replica_id,
23459 selection,
23460 cursor_shape,
23461 line_mode,
23462 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23463 user_name,
23464 color: if let Some(index) = participant_index {
23465 cx.theme().players().color_for_participant(index.0)
23466 } else {
23467 cx.theme().players().absent()
23468 },
23469 })
23470 }
23471 })
23472 }
23473
23474 pub fn hunks_for_ranges(
23475 &self,
23476 ranges: impl IntoIterator<Item = Range<Point>>,
23477 ) -> Vec<MultiBufferDiffHunk> {
23478 let mut hunks = Vec::new();
23479 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23480 HashMap::default();
23481 for query_range in ranges {
23482 let query_rows =
23483 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23484 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23485 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23486 ) {
23487 // Include deleted hunks that are adjacent to the query range, because
23488 // otherwise they would be missed.
23489 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23490 if hunk.status().is_deleted() {
23491 intersects_range |= hunk.row_range.start == query_rows.end;
23492 intersects_range |= hunk.row_range.end == query_rows.start;
23493 }
23494 if intersects_range {
23495 if !processed_buffer_rows
23496 .entry(hunk.buffer_id)
23497 .or_default()
23498 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23499 {
23500 continue;
23501 }
23502 hunks.push(hunk);
23503 }
23504 }
23505 }
23506
23507 hunks
23508 }
23509
23510 fn display_diff_hunks_for_rows<'a>(
23511 &'a self,
23512 display_rows: Range<DisplayRow>,
23513 folded_buffers: &'a HashSet<BufferId>,
23514 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23515 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23516 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23517
23518 self.buffer_snapshot()
23519 .diff_hunks_in_range(buffer_start..buffer_end)
23520 .filter_map(|hunk| {
23521 if folded_buffers.contains(&hunk.buffer_id) {
23522 return None;
23523 }
23524
23525 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23526 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23527
23528 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23529 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23530
23531 let display_hunk = if hunk_display_start.column() != 0 {
23532 DisplayDiffHunk::Folded {
23533 display_row: hunk_display_start.row(),
23534 }
23535 } else {
23536 let mut end_row = hunk_display_end.row();
23537 if hunk_display_end.column() > 0 {
23538 end_row.0 += 1;
23539 }
23540 let is_created_file = hunk.is_created_file();
23541 DisplayDiffHunk::Unfolded {
23542 status: hunk.status(),
23543 diff_base_byte_range: hunk.diff_base_byte_range,
23544 display_row_range: hunk_display_start.row()..end_row,
23545 multi_buffer_range: Anchor::range_in_buffer(
23546 hunk.excerpt_id,
23547 hunk.buffer_id,
23548 hunk.buffer_range,
23549 ),
23550 is_created_file,
23551 }
23552 };
23553
23554 Some(display_hunk)
23555 })
23556 }
23557
23558 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23559 self.display_snapshot
23560 .buffer_snapshot()
23561 .language_at(position)
23562 }
23563
23564 pub fn is_focused(&self) -> bool {
23565 self.is_focused
23566 }
23567
23568 pub fn placeholder_text(&self) -> Option<String> {
23569 self.placeholder_display_snapshot
23570 .as_ref()
23571 .map(|display_map| display_map.text())
23572 }
23573
23574 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23575 self.scroll_anchor.scroll_position(&self.display_snapshot)
23576 }
23577
23578 fn gutter_dimensions(
23579 &self,
23580 font_id: FontId,
23581 font_size: Pixels,
23582 max_line_number_width: Pixels,
23583 cx: &App,
23584 ) -> Option<GutterDimensions> {
23585 if !self.show_gutter {
23586 return None;
23587 }
23588
23589 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23590 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23591
23592 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23593 matches!(
23594 ProjectSettings::get_global(cx).git.git_gutter,
23595 GitGutterSetting::TrackedFiles
23596 )
23597 });
23598 let gutter_settings = EditorSettings::get_global(cx).gutter;
23599 let show_line_numbers = self
23600 .show_line_numbers
23601 .unwrap_or(gutter_settings.line_numbers);
23602 let line_gutter_width = if show_line_numbers {
23603 // Avoid flicker-like gutter resizes when the line number gains another digit by
23604 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23605 let min_width_for_number_on_gutter =
23606 ch_advance * gutter_settings.min_line_number_digits as f32;
23607 max_line_number_width.max(min_width_for_number_on_gutter)
23608 } else {
23609 0.0.into()
23610 };
23611
23612 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23613 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23614
23615 let git_blame_entries_width =
23616 self.git_blame_gutter_max_author_length
23617 .map(|max_author_length| {
23618 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23619 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23620
23621 /// The number of characters to dedicate to gaps and margins.
23622 const SPACING_WIDTH: usize = 4;
23623
23624 let max_char_count = max_author_length.min(renderer.max_author_length())
23625 + ::git::SHORT_SHA_LENGTH
23626 + MAX_RELATIVE_TIMESTAMP.len()
23627 + SPACING_WIDTH;
23628
23629 ch_advance * max_char_count
23630 });
23631
23632 let is_singleton = self.buffer_snapshot().is_singleton();
23633
23634 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23635 left_padding += if !is_singleton {
23636 ch_width * 4.0
23637 } else if show_runnables || show_breakpoints {
23638 ch_width * 3.0
23639 } else if show_git_gutter && show_line_numbers {
23640 ch_width * 2.0
23641 } else if show_git_gutter || show_line_numbers {
23642 ch_width
23643 } else {
23644 px(0.)
23645 };
23646
23647 let shows_folds = is_singleton && gutter_settings.folds;
23648
23649 let right_padding = if shows_folds && show_line_numbers {
23650 ch_width * 4.0
23651 } else if shows_folds || (!is_singleton && show_line_numbers) {
23652 ch_width * 3.0
23653 } else if show_line_numbers {
23654 ch_width
23655 } else {
23656 px(0.)
23657 };
23658
23659 Some(GutterDimensions {
23660 left_padding,
23661 right_padding,
23662 width: line_gutter_width + left_padding + right_padding,
23663 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23664 git_blame_entries_width,
23665 })
23666 }
23667
23668 pub fn render_crease_toggle(
23669 &self,
23670 buffer_row: MultiBufferRow,
23671 row_contains_cursor: bool,
23672 editor: Entity<Editor>,
23673 window: &mut Window,
23674 cx: &mut App,
23675 ) -> Option<AnyElement> {
23676 let folded = self.is_line_folded(buffer_row);
23677 let mut is_foldable = false;
23678
23679 if let Some(crease) = self
23680 .crease_snapshot
23681 .query_row(buffer_row, self.buffer_snapshot())
23682 {
23683 is_foldable = true;
23684 match crease {
23685 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23686 if let Some(render_toggle) = render_toggle {
23687 let toggle_callback =
23688 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23689 if folded {
23690 editor.update(cx, |editor, cx| {
23691 editor.fold_at(buffer_row, window, cx)
23692 });
23693 } else {
23694 editor.update(cx, |editor, cx| {
23695 editor.unfold_at(buffer_row, window, cx)
23696 });
23697 }
23698 });
23699 return Some((render_toggle)(
23700 buffer_row,
23701 folded,
23702 toggle_callback,
23703 window,
23704 cx,
23705 ));
23706 }
23707 }
23708 }
23709 }
23710
23711 is_foldable |= self.starts_indent(buffer_row);
23712
23713 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23714 Some(
23715 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23716 .toggle_state(folded)
23717 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23718 if folded {
23719 this.unfold_at(buffer_row, window, cx);
23720 } else {
23721 this.fold_at(buffer_row, window, cx);
23722 }
23723 }))
23724 .into_any_element(),
23725 )
23726 } else {
23727 None
23728 }
23729 }
23730
23731 pub fn render_crease_trailer(
23732 &self,
23733 buffer_row: MultiBufferRow,
23734 window: &mut Window,
23735 cx: &mut App,
23736 ) -> Option<AnyElement> {
23737 let folded = self.is_line_folded(buffer_row);
23738 if let Crease::Inline { render_trailer, .. } = self
23739 .crease_snapshot
23740 .query_row(buffer_row, self.buffer_snapshot())?
23741 {
23742 let render_trailer = render_trailer.as_ref()?;
23743 Some(render_trailer(buffer_row, folded, window, cx))
23744 } else {
23745 None
23746 }
23747 }
23748}
23749
23750impl Deref for EditorSnapshot {
23751 type Target = DisplaySnapshot;
23752
23753 fn deref(&self) -> &Self::Target {
23754 &self.display_snapshot
23755 }
23756}
23757
23758#[derive(Clone, Debug, PartialEq, Eq)]
23759pub enum EditorEvent {
23760 InputIgnored {
23761 text: Arc<str>,
23762 },
23763 InputHandled {
23764 utf16_range_to_replace: Option<Range<isize>>,
23765 text: Arc<str>,
23766 },
23767 ExcerptsAdded {
23768 buffer: Entity<Buffer>,
23769 predecessor: ExcerptId,
23770 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23771 },
23772 ExcerptsRemoved {
23773 ids: Vec<ExcerptId>,
23774 removed_buffer_ids: Vec<BufferId>,
23775 },
23776 BufferFoldToggled {
23777 ids: Vec<ExcerptId>,
23778 folded: bool,
23779 },
23780 ExcerptsEdited {
23781 ids: Vec<ExcerptId>,
23782 },
23783 ExcerptsExpanded {
23784 ids: Vec<ExcerptId>,
23785 },
23786 BufferEdited,
23787 Edited {
23788 transaction_id: clock::Lamport,
23789 },
23790 Reparsed(BufferId),
23791 Focused,
23792 FocusedIn,
23793 Blurred,
23794 DirtyChanged,
23795 Saved,
23796 TitleChanged,
23797 SelectionsChanged {
23798 local: bool,
23799 },
23800 ScrollPositionChanged {
23801 local: bool,
23802 autoscroll: bool,
23803 },
23804 TransactionUndone {
23805 transaction_id: clock::Lamport,
23806 },
23807 TransactionBegun {
23808 transaction_id: clock::Lamport,
23809 },
23810 CursorShapeChanged,
23811 BreadcrumbsChanged,
23812 PushedToNavHistory {
23813 anchor: Anchor,
23814 is_deactivate: bool,
23815 },
23816}
23817
23818impl EventEmitter<EditorEvent> for Editor {}
23819
23820impl Focusable for Editor {
23821 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23822 self.focus_handle.clone()
23823 }
23824}
23825
23826impl Render for Editor {
23827 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23828 let settings = ThemeSettings::get_global(cx);
23829
23830 let mut text_style = match self.mode {
23831 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23832 color: cx.theme().colors().editor_foreground,
23833 font_family: settings.ui_font.family.clone(),
23834 font_features: settings.ui_font.features.clone(),
23835 font_fallbacks: settings.ui_font.fallbacks.clone(),
23836 font_size: rems(0.875).into(),
23837 font_weight: settings.ui_font.weight,
23838 line_height: relative(settings.buffer_line_height.value()),
23839 ..Default::default()
23840 },
23841 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23842 color: cx.theme().colors().editor_foreground,
23843 font_family: settings.buffer_font.family.clone(),
23844 font_features: settings.buffer_font.features.clone(),
23845 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23846 font_size: settings.buffer_font_size(cx).into(),
23847 font_weight: settings.buffer_font.weight,
23848 line_height: relative(settings.buffer_line_height.value()),
23849 ..Default::default()
23850 },
23851 };
23852 if let Some(text_style_refinement) = &self.text_style_refinement {
23853 text_style.refine(text_style_refinement)
23854 }
23855
23856 let background = match self.mode {
23857 EditorMode::SingleLine => cx.theme().system().transparent,
23858 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23859 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23860 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23861 };
23862
23863 EditorElement::new(
23864 &cx.entity(),
23865 EditorStyle {
23866 background,
23867 border: cx.theme().colors().border,
23868 local_player: cx.theme().players().local(),
23869 text: text_style,
23870 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23871 syntax: cx.theme().syntax().clone(),
23872 status: cx.theme().status().clone(),
23873 inlay_hints_style: make_inlay_hints_style(cx),
23874 edit_prediction_styles: make_suggestion_styles(cx),
23875 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23876 show_underlines: self.diagnostics_enabled(),
23877 },
23878 )
23879 }
23880}
23881
23882impl EntityInputHandler for Editor {
23883 fn text_for_range(
23884 &mut self,
23885 range_utf16: Range<usize>,
23886 adjusted_range: &mut Option<Range<usize>>,
23887 _: &mut Window,
23888 cx: &mut Context<Self>,
23889 ) -> Option<String> {
23890 let snapshot = self.buffer.read(cx).read(cx);
23891 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23892 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23893 if (start.0..end.0) != range_utf16 {
23894 adjusted_range.replace(start.0..end.0);
23895 }
23896 Some(snapshot.text_for_range(start..end).collect())
23897 }
23898
23899 fn selected_text_range(
23900 &mut self,
23901 ignore_disabled_input: bool,
23902 _: &mut Window,
23903 cx: &mut Context<Self>,
23904 ) -> Option<UTF16Selection> {
23905 // Prevent the IME menu from appearing when holding down an alphabetic key
23906 // while input is disabled.
23907 if !ignore_disabled_input && !self.input_enabled {
23908 return None;
23909 }
23910
23911 let selection = self
23912 .selections
23913 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23914 let range = selection.range();
23915
23916 Some(UTF16Selection {
23917 range: range.start.0..range.end.0,
23918 reversed: selection.reversed,
23919 })
23920 }
23921
23922 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23923 let snapshot = self.buffer.read(cx).read(cx);
23924 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23925 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23926 }
23927
23928 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23929 self.clear_highlights::<InputComposition>(cx);
23930 self.ime_transaction.take();
23931 }
23932
23933 fn replace_text_in_range(
23934 &mut self,
23935 range_utf16: Option<Range<usize>>,
23936 text: &str,
23937 window: &mut Window,
23938 cx: &mut Context<Self>,
23939 ) {
23940 if !self.input_enabled {
23941 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23942 return;
23943 }
23944
23945 self.transact(window, cx, |this, window, cx| {
23946 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23947 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23948 Some(this.selection_replacement_ranges(range_utf16, cx))
23949 } else {
23950 this.marked_text_ranges(cx)
23951 };
23952
23953 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23954 let newest_selection_id = this.selections.newest_anchor().id;
23955 this.selections
23956 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23957 .iter()
23958 .zip(ranges_to_replace.iter())
23959 .find_map(|(selection, range)| {
23960 if selection.id == newest_selection_id {
23961 Some(
23962 (range.start.0 as isize - selection.head().0 as isize)
23963 ..(range.end.0 as isize - selection.head().0 as isize),
23964 )
23965 } else {
23966 None
23967 }
23968 })
23969 });
23970
23971 cx.emit(EditorEvent::InputHandled {
23972 utf16_range_to_replace: range_to_replace,
23973 text: text.into(),
23974 });
23975
23976 if let Some(new_selected_ranges) = new_selected_ranges {
23977 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23978 selections.select_ranges(new_selected_ranges)
23979 });
23980 this.backspace(&Default::default(), window, cx);
23981 }
23982
23983 this.handle_input(text, window, cx);
23984 });
23985
23986 if let Some(transaction) = self.ime_transaction {
23987 self.buffer.update(cx, |buffer, cx| {
23988 buffer.group_until_transaction(transaction, cx);
23989 });
23990 }
23991
23992 self.unmark_text(window, cx);
23993 }
23994
23995 fn replace_and_mark_text_in_range(
23996 &mut self,
23997 range_utf16: Option<Range<usize>>,
23998 text: &str,
23999 new_selected_range_utf16: Option<Range<usize>>,
24000 window: &mut Window,
24001 cx: &mut Context<Self>,
24002 ) {
24003 if !self.input_enabled {
24004 return;
24005 }
24006
24007 let transaction = self.transact(window, cx, |this, window, cx| {
24008 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24009 let snapshot = this.buffer.read(cx).read(cx);
24010 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24011 for marked_range in &mut marked_ranges {
24012 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24013 marked_range.start.0 += relative_range_utf16.start;
24014 marked_range.start =
24015 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24016 marked_range.end =
24017 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24018 }
24019 }
24020 Some(marked_ranges)
24021 } else if let Some(range_utf16) = range_utf16 {
24022 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24023 Some(this.selection_replacement_ranges(range_utf16, cx))
24024 } else {
24025 None
24026 };
24027
24028 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24029 let newest_selection_id = this.selections.newest_anchor().id;
24030 this.selections
24031 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24032 .iter()
24033 .zip(ranges_to_replace.iter())
24034 .find_map(|(selection, range)| {
24035 if selection.id == newest_selection_id {
24036 Some(
24037 (range.start.0 as isize - selection.head().0 as isize)
24038 ..(range.end.0 as isize - selection.head().0 as isize),
24039 )
24040 } else {
24041 None
24042 }
24043 })
24044 });
24045
24046 cx.emit(EditorEvent::InputHandled {
24047 utf16_range_to_replace: range_to_replace,
24048 text: text.into(),
24049 });
24050
24051 if let Some(ranges) = ranges_to_replace {
24052 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24053 s.select_ranges(ranges)
24054 });
24055 }
24056
24057 let marked_ranges = {
24058 let snapshot = this.buffer.read(cx).read(cx);
24059 this.selections
24060 .disjoint_anchors_arc()
24061 .iter()
24062 .map(|selection| {
24063 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24064 })
24065 .collect::<Vec<_>>()
24066 };
24067
24068 if text.is_empty() {
24069 this.unmark_text(window, cx);
24070 } else {
24071 this.highlight_text::<InputComposition>(
24072 marked_ranges.clone(),
24073 HighlightStyle {
24074 underline: Some(UnderlineStyle {
24075 thickness: px(1.),
24076 color: None,
24077 wavy: false,
24078 }),
24079 ..Default::default()
24080 },
24081 cx,
24082 );
24083 }
24084
24085 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24086 let use_autoclose = this.use_autoclose;
24087 let use_auto_surround = this.use_auto_surround;
24088 this.set_use_autoclose(false);
24089 this.set_use_auto_surround(false);
24090 this.handle_input(text, window, cx);
24091 this.set_use_autoclose(use_autoclose);
24092 this.set_use_auto_surround(use_auto_surround);
24093
24094 if let Some(new_selected_range) = new_selected_range_utf16 {
24095 let snapshot = this.buffer.read(cx).read(cx);
24096 let new_selected_ranges = marked_ranges
24097 .into_iter()
24098 .map(|marked_range| {
24099 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24100 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24101 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24102 snapshot.clip_offset_utf16(new_start, Bias::Left)
24103 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24104 })
24105 .collect::<Vec<_>>();
24106
24107 drop(snapshot);
24108 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24109 selections.select_ranges(new_selected_ranges)
24110 });
24111 }
24112 });
24113
24114 self.ime_transaction = self.ime_transaction.or(transaction);
24115 if let Some(transaction) = self.ime_transaction {
24116 self.buffer.update(cx, |buffer, cx| {
24117 buffer.group_until_transaction(transaction, cx);
24118 });
24119 }
24120
24121 if self.text_highlights::<InputComposition>(cx).is_none() {
24122 self.ime_transaction.take();
24123 }
24124 }
24125
24126 fn bounds_for_range(
24127 &mut self,
24128 range_utf16: Range<usize>,
24129 element_bounds: gpui::Bounds<Pixels>,
24130 window: &mut Window,
24131 cx: &mut Context<Self>,
24132 ) -> Option<gpui::Bounds<Pixels>> {
24133 let text_layout_details = self.text_layout_details(window);
24134 let CharacterDimensions {
24135 em_width,
24136 em_advance,
24137 line_height,
24138 } = self.character_dimensions(window);
24139
24140 let snapshot = self.snapshot(window, cx);
24141 let scroll_position = snapshot.scroll_position();
24142 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24143
24144 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24145 let x = Pixels::from(
24146 ScrollOffset::from(
24147 snapshot.x_for_display_point(start, &text_layout_details)
24148 + self.gutter_dimensions.full_width(),
24149 ) - scroll_left,
24150 );
24151 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24152
24153 Some(Bounds {
24154 origin: element_bounds.origin + point(x, y),
24155 size: size(em_width, line_height),
24156 })
24157 }
24158
24159 fn character_index_for_point(
24160 &mut self,
24161 point: gpui::Point<Pixels>,
24162 _window: &mut Window,
24163 _cx: &mut Context<Self>,
24164 ) -> Option<usize> {
24165 let position_map = self.last_position_map.as_ref()?;
24166 if !position_map.text_hitbox.contains(&point) {
24167 return None;
24168 }
24169 let display_point = position_map.point_for_position(point).previous_valid;
24170 let anchor = position_map
24171 .snapshot
24172 .display_point_to_anchor(display_point, Bias::Left);
24173 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24174 Some(utf16_offset.0)
24175 }
24176
24177 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24178 self.input_enabled
24179 }
24180}
24181
24182trait SelectionExt {
24183 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24184 fn spanned_rows(
24185 &self,
24186 include_end_if_at_line_start: bool,
24187 map: &DisplaySnapshot,
24188 ) -> Range<MultiBufferRow>;
24189}
24190
24191impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24192 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24193 let start = self
24194 .start
24195 .to_point(map.buffer_snapshot())
24196 .to_display_point(map);
24197 let end = self
24198 .end
24199 .to_point(map.buffer_snapshot())
24200 .to_display_point(map);
24201 if self.reversed {
24202 end..start
24203 } else {
24204 start..end
24205 }
24206 }
24207
24208 fn spanned_rows(
24209 &self,
24210 include_end_if_at_line_start: bool,
24211 map: &DisplaySnapshot,
24212 ) -> Range<MultiBufferRow> {
24213 let start = self.start.to_point(map.buffer_snapshot());
24214 let mut end = self.end.to_point(map.buffer_snapshot());
24215 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24216 end.row -= 1;
24217 }
24218
24219 let buffer_start = map.prev_line_boundary(start).0;
24220 let buffer_end = map.next_line_boundary(end).0;
24221 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24222 }
24223}
24224
24225impl<T: InvalidationRegion> InvalidationStack<T> {
24226 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24227 where
24228 S: Clone + ToOffset,
24229 {
24230 while let Some(region) = self.last() {
24231 let all_selections_inside_invalidation_ranges =
24232 if selections.len() == region.ranges().len() {
24233 selections
24234 .iter()
24235 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24236 .all(|(selection, invalidation_range)| {
24237 let head = selection.head().to_offset(buffer);
24238 invalidation_range.start <= head && invalidation_range.end >= head
24239 })
24240 } else {
24241 false
24242 };
24243
24244 if all_selections_inside_invalidation_ranges {
24245 break;
24246 } else {
24247 self.pop();
24248 }
24249 }
24250 }
24251}
24252
24253impl<T> Default for InvalidationStack<T> {
24254 fn default() -> Self {
24255 Self(Default::default())
24256 }
24257}
24258
24259impl<T> Deref for InvalidationStack<T> {
24260 type Target = Vec<T>;
24261
24262 fn deref(&self) -> &Self::Target {
24263 &self.0
24264 }
24265}
24266
24267impl<T> DerefMut for InvalidationStack<T> {
24268 fn deref_mut(&mut self) -> &mut Self::Target {
24269 &mut self.0
24270 }
24271}
24272
24273impl InvalidationRegion for SnippetState {
24274 fn ranges(&self) -> &[Range<Anchor>] {
24275 &self.ranges[self.active_index]
24276 }
24277}
24278
24279fn edit_prediction_edit_text(
24280 current_snapshot: &BufferSnapshot,
24281 edits: &[(Range<Anchor>, String)],
24282 edit_preview: &EditPreview,
24283 include_deletions: bool,
24284 cx: &App,
24285) -> HighlightedText {
24286 let edits = edits
24287 .iter()
24288 .map(|(anchor, text)| {
24289 (
24290 anchor.start.text_anchor..anchor.end.text_anchor,
24291 text.clone(),
24292 )
24293 })
24294 .collect::<Vec<_>>();
24295
24296 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24297}
24298
24299fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24300 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24301 // Just show the raw edit text with basic styling
24302 let mut text = String::new();
24303 let mut highlights = Vec::new();
24304
24305 let insertion_highlight_style = HighlightStyle {
24306 color: Some(cx.theme().colors().text),
24307 ..Default::default()
24308 };
24309
24310 for (_, edit_text) in edits {
24311 let start_offset = text.len();
24312 text.push_str(edit_text);
24313 let end_offset = text.len();
24314
24315 if start_offset < end_offset {
24316 highlights.push((start_offset..end_offset, insertion_highlight_style));
24317 }
24318 }
24319
24320 HighlightedText {
24321 text: text.into(),
24322 highlights,
24323 }
24324}
24325
24326pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24327 match severity {
24328 lsp::DiagnosticSeverity::ERROR => colors.error,
24329 lsp::DiagnosticSeverity::WARNING => colors.warning,
24330 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24331 lsp::DiagnosticSeverity::HINT => colors.info,
24332 _ => colors.ignored,
24333 }
24334}
24335
24336pub fn styled_runs_for_code_label<'a>(
24337 label: &'a CodeLabel,
24338 syntax_theme: &'a theme::SyntaxTheme,
24339) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24340 let fade_out = HighlightStyle {
24341 fade_out: Some(0.35),
24342 ..Default::default()
24343 };
24344
24345 let mut prev_end = label.filter_range.end;
24346 label
24347 .runs
24348 .iter()
24349 .enumerate()
24350 .flat_map(move |(ix, (range, highlight_id))| {
24351 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24352 style
24353 } else {
24354 return Default::default();
24355 };
24356 let muted_style = style.highlight(fade_out);
24357
24358 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24359 if range.start >= label.filter_range.end {
24360 if range.start > prev_end {
24361 runs.push((prev_end..range.start, fade_out));
24362 }
24363 runs.push((range.clone(), muted_style));
24364 } else if range.end <= label.filter_range.end {
24365 runs.push((range.clone(), style));
24366 } else {
24367 runs.push((range.start..label.filter_range.end, style));
24368 runs.push((label.filter_range.end..range.end, muted_style));
24369 }
24370 prev_end = cmp::max(prev_end, range.end);
24371
24372 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24373 runs.push((prev_end..label.text.len(), fade_out));
24374 }
24375
24376 runs
24377 })
24378}
24379
24380pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24381 let mut prev_index = 0;
24382 let mut prev_codepoint: Option<char> = None;
24383 text.char_indices()
24384 .chain([(text.len(), '\0')])
24385 .filter_map(move |(index, codepoint)| {
24386 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24387 let is_boundary = index == text.len()
24388 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24389 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24390 if is_boundary {
24391 let chunk = &text[prev_index..index];
24392 prev_index = index;
24393 Some(chunk)
24394 } else {
24395 None
24396 }
24397 })
24398}
24399
24400pub trait RangeToAnchorExt: Sized {
24401 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24402
24403 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24404 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24405 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24406 }
24407}
24408
24409impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24410 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24411 let start_offset = self.start.to_offset(snapshot);
24412 let end_offset = self.end.to_offset(snapshot);
24413 if start_offset == end_offset {
24414 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24415 } else {
24416 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24417 }
24418 }
24419}
24420
24421pub trait RowExt {
24422 fn as_f64(&self) -> f64;
24423
24424 fn next_row(&self) -> Self;
24425
24426 fn previous_row(&self) -> Self;
24427
24428 fn minus(&self, other: Self) -> u32;
24429}
24430
24431impl RowExt for DisplayRow {
24432 fn as_f64(&self) -> f64 {
24433 self.0 as _
24434 }
24435
24436 fn next_row(&self) -> Self {
24437 Self(self.0 + 1)
24438 }
24439
24440 fn previous_row(&self) -> Self {
24441 Self(self.0.saturating_sub(1))
24442 }
24443
24444 fn minus(&self, other: Self) -> u32 {
24445 self.0 - other.0
24446 }
24447}
24448
24449impl RowExt for MultiBufferRow {
24450 fn as_f64(&self) -> f64 {
24451 self.0 as _
24452 }
24453
24454 fn next_row(&self) -> Self {
24455 Self(self.0 + 1)
24456 }
24457
24458 fn previous_row(&self) -> Self {
24459 Self(self.0.saturating_sub(1))
24460 }
24461
24462 fn minus(&self, other: Self) -> u32 {
24463 self.0 - other.0
24464 }
24465}
24466
24467trait RowRangeExt {
24468 type Row;
24469
24470 fn len(&self) -> usize;
24471
24472 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24473}
24474
24475impl RowRangeExt for Range<MultiBufferRow> {
24476 type Row = MultiBufferRow;
24477
24478 fn len(&self) -> usize {
24479 (self.end.0 - self.start.0) as usize
24480 }
24481
24482 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24483 (self.start.0..self.end.0).map(MultiBufferRow)
24484 }
24485}
24486
24487impl RowRangeExt for Range<DisplayRow> {
24488 type Row = DisplayRow;
24489
24490 fn len(&self) -> usize {
24491 (self.end.0 - self.start.0) as usize
24492 }
24493
24494 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24495 (self.start.0..self.end.0).map(DisplayRow)
24496 }
24497}
24498
24499/// If select range has more than one line, we
24500/// just point the cursor to range.start.
24501fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24502 if range.start.row == range.end.row {
24503 range
24504 } else {
24505 range.start..range.start
24506 }
24507}
24508pub struct KillRing(ClipboardItem);
24509impl Global for KillRing {}
24510
24511const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24512
24513enum BreakpointPromptEditAction {
24514 Log,
24515 Condition,
24516 HitCondition,
24517}
24518
24519struct BreakpointPromptEditor {
24520 pub(crate) prompt: Entity<Editor>,
24521 editor: WeakEntity<Editor>,
24522 breakpoint_anchor: Anchor,
24523 breakpoint: Breakpoint,
24524 edit_action: BreakpointPromptEditAction,
24525 block_ids: HashSet<CustomBlockId>,
24526 editor_margins: Arc<Mutex<EditorMargins>>,
24527 _subscriptions: Vec<Subscription>,
24528}
24529
24530impl BreakpointPromptEditor {
24531 const MAX_LINES: u8 = 4;
24532
24533 fn new(
24534 editor: WeakEntity<Editor>,
24535 breakpoint_anchor: Anchor,
24536 breakpoint: Breakpoint,
24537 edit_action: BreakpointPromptEditAction,
24538 window: &mut Window,
24539 cx: &mut Context<Self>,
24540 ) -> Self {
24541 let base_text = match edit_action {
24542 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24543 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24544 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24545 }
24546 .map(|msg| msg.to_string())
24547 .unwrap_or_default();
24548
24549 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24550 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24551
24552 let prompt = cx.new(|cx| {
24553 let mut prompt = Editor::new(
24554 EditorMode::AutoHeight {
24555 min_lines: 1,
24556 max_lines: Some(Self::MAX_LINES as usize),
24557 },
24558 buffer,
24559 None,
24560 window,
24561 cx,
24562 );
24563 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24564 prompt.set_show_cursor_when_unfocused(false, cx);
24565 prompt.set_placeholder_text(
24566 match edit_action {
24567 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24568 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24569 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24570 },
24571 window,
24572 cx,
24573 );
24574
24575 prompt
24576 });
24577
24578 Self {
24579 prompt,
24580 editor,
24581 breakpoint_anchor,
24582 breakpoint,
24583 edit_action,
24584 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24585 block_ids: Default::default(),
24586 _subscriptions: vec![],
24587 }
24588 }
24589
24590 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24591 self.block_ids.extend(block_ids)
24592 }
24593
24594 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24595 if let Some(editor) = self.editor.upgrade() {
24596 let message = self
24597 .prompt
24598 .read(cx)
24599 .buffer
24600 .read(cx)
24601 .as_singleton()
24602 .expect("A multi buffer in breakpoint prompt isn't possible")
24603 .read(cx)
24604 .as_rope()
24605 .to_string();
24606
24607 editor.update(cx, |editor, cx| {
24608 editor.edit_breakpoint_at_anchor(
24609 self.breakpoint_anchor,
24610 self.breakpoint.clone(),
24611 match self.edit_action {
24612 BreakpointPromptEditAction::Log => {
24613 BreakpointEditAction::EditLogMessage(message.into())
24614 }
24615 BreakpointPromptEditAction::Condition => {
24616 BreakpointEditAction::EditCondition(message.into())
24617 }
24618 BreakpointPromptEditAction::HitCondition => {
24619 BreakpointEditAction::EditHitCondition(message.into())
24620 }
24621 },
24622 cx,
24623 );
24624
24625 editor.remove_blocks(self.block_ids.clone(), None, cx);
24626 cx.focus_self(window);
24627 });
24628 }
24629 }
24630
24631 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24632 self.editor
24633 .update(cx, |editor, cx| {
24634 editor.remove_blocks(self.block_ids.clone(), None, cx);
24635 window.focus(&editor.focus_handle);
24636 })
24637 .log_err();
24638 }
24639
24640 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24641 let settings = ThemeSettings::get_global(cx);
24642 let text_style = TextStyle {
24643 color: if self.prompt.read(cx).read_only(cx) {
24644 cx.theme().colors().text_disabled
24645 } else {
24646 cx.theme().colors().text
24647 },
24648 font_family: settings.buffer_font.family.clone(),
24649 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24650 font_size: settings.buffer_font_size(cx).into(),
24651 font_weight: settings.buffer_font.weight,
24652 line_height: relative(settings.buffer_line_height.value()),
24653 ..Default::default()
24654 };
24655 EditorElement::new(
24656 &self.prompt,
24657 EditorStyle {
24658 background: cx.theme().colors().editor_background,
24659 local_player: cx.theme().players().local(),
24660 text: text_style,
24661 ..Default::default()
24662 },
24663 )
24664 }
24665}
24666
24667impl Render for BreakpointPromptEditor {
24668 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24669 let editor_margins = *self.editor_margins.lock();
24670 let gutter_dimensions = editor_margins.gutter;
24671 h_flex()
24672 .key_context("Editor")
24673 .bg(cx.theme().colors().editor_background)
24674 .border_y_1()
24675 .border_color(cx.theme().status().info_border)
24676 .size_full()
24677 .py(window.line_height() / 2.5)
24678 .on_action(cx.listener(Self::confirm))
24679 .on_action(cx.listener(Self::cancel))
24680 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24681 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24682 }
24683}
24684
24685impl Focusable for BreakpointPromptEditor {
24686 fn focus_handle(&self, cx: &App) -> FocusHandle {
24687 self.prompt.focus_handle(cx)
24688 }
24689}
24690
24691fn all_edits_insertions_or_deletions(
24692 edits: &Vec<(Range<Anchor>, String)>,
24693 snapshot: &MultiBufferSnapshot,
24694) -> bool {
24695 let mut all_insertions = true;
24696 let mut all_deletions = true;
24697
24698 for (range, new_text) in edits.iter() {
24699 let range_is_empty = range.to_offset(snapshot).is_empty();
24700 let text_is_empty = new_text.is_empty();
24701
24702 if range_is_empty != text_is_empty {
24703 if range_is_empty {
24704 all_deletions = false;
24705 } else {
24706 all_insertions = false;
24707 }
24708 } else {
24709 return false;
24710 }
24711
24712 if !all_insertions && !all_deletions {
24713 return false;
24714 }
24715 }
24716 all_insertions || all_deletions
24717}
24718
24719struct MissingEditPredictionKeybindingTooltip;
24720
24721impl Render for MissingEditPredictionKeybindingTooltip {
24722 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24723 ui::tooltip_container(cx, |container, cx| {
24724 container
24725 .flex_shrink_0()
24726 .max_w_80()
24727 .min_h(rems_from_px(124.))
24728 .justify_between()
24729 .child(
24730 v_flex()
24731 .flex_1()
24732 .text_ui_sm(cx)
24733 .child(Label::new("Conflict with Accept Keybinding"))
24734 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24735 )
24736 .child(
24737 h_flex()
24738 .pb_1()
24739 .gap_1()
24740 .items_end()
24741 .w_full()
24742 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24743 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24744 }))
24745 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24746 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24747 })),
24748 )
24749 })
24750 }
24751}
24752
24753#[derive(Debug, Clone, Copy, PartialEq)]
24754pub struct LineHighlight {
24755 pub background: Background,
24756 pub border: Option<gpui::Hsla>,
24757 pub include_gutter: bool,
24758 pub type_id: Option<TypeId>,
24759}
24760
24761struct LineManipulationResult {
24762 pub new_text: String,
24763 pub line_count_before: usize,
24764 pub line_count_after: usize,
24765}
24766
24767fn render_diff_hunk_controls(
24768 row: u32,
24769 status: &DiffHunkStatus,
24770 hunk_range: Range<Anchor>,
24771 is_created_file: bool,
24772 line_height: Pixels,
24773 editor: &Entity<Editor>,
24774 _window: &mut Window,
24775 cx: &mut App,
24776) -> AnyElement {
24777 h_flex()
24778 .h(line_height)
24779 .mr_1()
24780 .gap_1()
24781 .px_0p5()
24782 .pb_1()
24783 .border_x_1()
24784 .border_b_1()
24785 .border_color(cx.theme().colors().border_variant)
24786 .rounded_b_lg()
24787 .bg(cx.theme().colors().editor_background)
24788 .gap_1()
24789 .block_mouse_except_scroll()
24790 .shadow_md()
24791 .child(if status.has_secondary_hunk() {
24792 Button::new(("stage", row as u64), "Stage")
24793 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24794 .tooltip({
24795 let focus_handle = editor.focus_handle(cx);
24796 move |_window, cx| {
24797 Tooltip::for_action_in(
24798 "Stage Hunk",
24799 &::git::ToggleStaged,
24800 &focus_handle,
24801 cx,
24802 )
24803 }
24804 })
24805 .on_click({
24806 let editor = editor.clone();
24807 move |_event, _window, cx| {
24808 editor.update(cx, |editor, cx| {
24809 editor.stage_or_unstage_diff_hunks(
24810 true,
24811 vec![hunk_range.start..hunk_range.start],
24812 cx,
24813 );
24814 });
24815 }
24816 })
24817 } else {
24818 Button::new(("unstage", row as u64), "Unstage")
24819 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24820 .tooltip({
24821 let focus_handle = editor.focus_handle(cx);
24822 move |_window, cx| {
24823 Tooltip::for_action_in(
24824 "Unstage Hunk",
24825 &::git::ToggleStaged,
24826 &focus_handle,
24827 cx,
24828 )
24829 }
24830 })
24831 .on_click({
24832 let editor = editor.clone();
24833 move |_event, _window, cx| {
24834 editor.update(cx, |editor, cx| {
24835 editor.stage_or_unstage_diff_hunks(
24836 false,
24837 vec![hunk_range.start..hunk_range.start],
24838 cx,
24839 );
24840 });
24841 }
24842 })
24843 })
24844 .child(
24845 Button::new(("restore", row as u64), "Restore")
24846 .tooltip({
24847 let focus_handle = editor.focus_handle(cx);
24848 move |_window, cx| {
24849 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24850 }
24851 })
24852 .on_click({
24853 let editor = editor.clone();
24854 move |_event, window, cx| {
24855 editor.update(cx, |editor, cx| {
24856 let snapshot = editor.snapshot(window, cx);
24857 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24858 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24859 });
24860 }
24861 })
24862 .disabled(is_created_file),
24863 )
24864 .when(
24865 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24866 |el| {
24867 el.child(
24868 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24869 .shape(IconButtonShape::Square)
24870 .icon_size(IconSize::Small)
24871 // .disabled(!has_multiple_hunks)
24872 .tooltip({
24873 let focus_handle = editor.focus_handle(cx);
24874 move |_window, cx| {
24875 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24876 }
24877 })
24878 .on_click({
24879 let editor = editor.clone();
24880 move |_event, window, cx| {
24881 editor.update(cx, |editor, cx| {
24882 let snapshot = editor.snapshot(window, cx);
24883 let position =
24884 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24885 editor.go_to_hunk_before_or_after_position(
24886 &snapshot,
24887 position,
24888 Direction::Next,
24889 window,
24890 cx,
24891 );
24892 editor.expand_selected_diff_hunks(cx);
24893 });
24894 }
24895 }),
24896 )
24897 .child(
24898 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24899 .shape(IconButtonShape::Square)
24900 .icon_size(IconSize::Small)
24901 // .disabled(!has_multiple_hunks)
24902 .tooltip({
24903 let focus_handle = editor.focus_handle(cx);
24904 move |_window, cx| {
24905 Tooltip::for_action_in(
24906 "Previous Hunk",
24907 &GoToPreviousHunk,
24908 &focus_handle,
24909 cx,
24910 )
24911 }
24912 })
24913 .on_click({
24914 let editor = editor.clone();
24915 move |_event, window, cx| {
24916 editor.update(cx, |editor, cx| {
24917 let snapshot = editor.snapshot(window, cx);
24918 let point =
24919 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24920 editor.go_to_hunk_before_or_after_position(
24921 &snapshot,
24922 point,
24923 Direction::Prev,
24924 window,
24925 cx,
24926 );
24927 editor.expand_selected_diff_hunks(cx);
24928 });
24929 }
24930 }),
24931 )
24932 },
24933 )
24934 .into_any_element()
24935}
24936
24937pub fn multibuffer_context_lines(cx: &App) -> u32 {
24938 EditorSettings::try_get(cx)
24939 .map(|settings| settings.excerpt_context_lines)
24940 .unwrap_or(2)
24941 .min(32)
24942}