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, AhoCorasickBuilder, BuildError};
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, OutlineItem, Point, Runnable, RunnableRange, Selection,
121 SelectionGoal, 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(cx: &mut App) {
314 cx.set_global(GlobalBlameRenderer(Arc::new(())));
315
316 workspace::register_project_item::<Editor>(cx);
317 workspace::FollowableViewRegistry::register::<Editor>(cx);
318 workspace::register_serializable_item::<Editor>(cx);
319
320 cx.observe_new(
321 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
322 workspace.register_action(Editor::new_file);
323 workspace.register_action(Editor::new_file_split);
324 workspace.register_action(Editor::new_file_vertical);
325 workspace.register_action(Editor::new_file_horizontal);
326 workspace.register_action(Editor::cancel_language_server_work);
327 workspace.register_action(Editor::toggle_focus);
328 },
329 )
330 .detach();
331
332 cx.on_action(move |_: &workspace::NewFile, cx| {
333 let app_state = workspace::AppState::global(cx);
334 if let Some(app_state) = app_state.upgrade() {
335 workspace::open_new(
336 Default::default(),
337 app_state,
338 cx,
339 |workspace, window, cx| {
340 Editor::new_file(workspace, &Default::default(), window, cx)
341 },
342 )
343 .detach();
344 }
345 });
346 cx.on_action(move |_: &workspace::NewWindow, cx| {
347 let app_state = workspace::AppState::global(cx);
348 if let Some(app_state) = app_state.upgrade() {
349 workspace::open_new(
350 Default::default(),
351 app_state,
352 cx,
353 |workspace, window, cx| {
354 cx.activate(true);
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 });
361}
362
363pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
364 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
365}
366
367pub trait DiagnosticRenderer {
368 fn render_group(
369 &self,
370 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
371 buffer_id: BufferId,
372 snapshot: EditorSnapshot,
373 editor: WeakEntity<Editor>,
374 cx: &mut App,
375 ) -> Vec<BlockProperties<Anchor>>;
376
377 fn render_hover(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 range: Range<Point>,
381 buffer_id: BufferId,
382 cx: &mut App,
383 ) -> Option<Entity<markdown::Markdown>>;
384
385 fn open_link(
386 &self,
387 editor: &mut Editor,
388 link: SharedString,
389 window: &mut Window,
390 cx: &mut Context<Editor>,
391 );
392}
393
394pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
395
396impl GlobalDiagnosticRenderer {
397 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
398 cx.try_global::<Self>().map(|g| g.0.clone())
399 }
400}
401
402impl gpui::Global for GlobalDiagnosticRenderer {}
403pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
404 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
405}
406
407pub struct SearchWithinRange;
408
409trait InvalidationRegion {
410 fn ranges(&self) -> &[Range<Anchor>];
411}
412
413#[derive(Clone, Debug, PartialEq)]
414pub enum SelectPhase {
415 Begin {
416 position: DisplayPoint,
417 add: bool,
418 click_count: usize,
419 },
420 BeginColumnar {
421 position: DisplayPoint,
422 reset: bool,
423 mode: ColumnarMode,
424 goal_column: u32,
425 },
426 Extend {
427 position: DisplayPoint,
428 click_count: usize,
429 },
430 Update {
431 position: DisplayPoint,
432 goal_column: u32,
433 scroll_delta: gpui::Point<f32>,
434 },
435 End,
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum ColumnarMode {
440 FromMouse,
441 FromSelection,
442}
443
444#[derive(Clone, Debug)]
445pub enum SelectMode {
446 Character,
447 Word(Range<Anchor>),
448 Line(Range<Anchor>),
449 All,
450}
451
452#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
453pub enum SizingBehavior {
454 /// The editor will layout itself using `size_full` and will include the vertical
455 /// scroll margin as requested by user settings.
456 #[default]
457 Default,
458 /// The editor will layout itself using `size_full`, but will not have any
459 /// vertical overscroll.
460 ExcludeOverscrollMargin,
461 /// The editor will request a vertical size according to its content and will be
462 /// layouted without a vertical scroll margin.
463 SizeByContent,
464}
465
466#[derive(Clone, PartialEq, Eq, Debug)]
467pub enum EditorMode {
468 SingleLine,
469 AutoHeight {
470 min_lines: usize,
471 max_lines: Option<usize>,
472 },
473 Full {
474 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
475 scale_ui_elements_with_buffer_font_size: bool,
476 /// When set to `true`, the editor will render a background for the active line.
477 show_active_line_background: bool,
478 /// Determines the sizing behavior for this editor
479 sizing_behavior: SizingBehavior,
480 },
481 Minimap {
482 parent: WeakEntity<Editor>,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sizing_behavior: SizingBehavior::Default,
492 }
493 }
494
495 #[inline]
496 pub fn is_full(&self) -> bool {
497 matches!(self, Self::Full { .. })
498 }
499
500 #[inline]
501 pub fn is_single_line(&self) -> bool {
502 matches!(self, Self::SingleLine { .. })
503 }
504
505 #[inline]
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub border: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub edit_prediction_styles: EditPredictionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 border: Hsla::default(),
548 local_player: PlayerColor::default(),
549 text: TextStyle::default(),
550 scrollbar_width: Pixels::default(),
551 syntax: Default::default(),
552 // HACK: Status colors don't have a real default.
553 // We should look into removing the status colors from the editor
554 // style and retrieve them directly from the theme.
555 status: StatusColors::dark(),
556 inlay_hints_style: HighlightStyle::default(),
557 edit_prediction_styles: EditPredictionStyles {
558 insertion: HighlightStyle::default(),
559 whitespace: HighlightStyle::default(),
560 },
561 unnecessary_code_fade: Default::default(),
562 show_underlines: true,
563 }
564 }
565}
566
567pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
568 let show_background = language_settings::language_settings(None, None, cx)
569 .inlay_hints
570 .show_background;
571
572 let mut style = cx.theme().syntax().get("hint");
573
574 if style.color.is_none() {
575 style.color = Some(cx.theme().status().hint);
576 }
577
578 if !show_background {
579 style.background_color = None;
580 return style;
581 }
582
583 if style.background_color.is_none() {
584 style.background_color = Some(cx.theme().status().hint_background);
585 }
586
587 style
588}
589
590pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
591 EditPredictionStyles {
592 insertion: HighlightStyle {
593 color: Some(cx.theme().status().predictive),
594 ..HighlightStyle::default()
595 },
596 whitespace: HighlightStyle {
597 background_color: Some(cx.theme().status().created_background),
598 ..HighlightStyle::default()
599 },
600 }
601}
602
603type CompletionId = usize;
604
605pub(crate) enum EditDisplayMode {
606 TabAccept,
607 DiffPopover,
608 Inline,
609}
610
611enum EditPrediction {
612 Edit {
613 edits: Vec<(Range<Anchor>, Arc<str>)>,
614 edit_preview: Option<EditPreview>,
615 display_mode: EditDisplayMode,
616 snapshot: BufferSnapshot,
617 },
618 /// Move to a specific location in the active editor
619 MoveWithin {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623 /// Move to a specific location in a different editor (not the active one)
624 MoveOutside {
625 target: language::Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct EditPredictionState {
631 inlay_ids: Vec<InlayId>,
632 completion: EditPrediction,
633 completion_id: Option<SharedString>,
634 invalidation_range: Option<Range<Anchor>>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum EditPredictionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuEditPredictionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 matches!(*self, Self::Disabled)
773 }
774
775 fn settings_visibility(&self) -> bool {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => setting_configuration,
781 _ => false,
782 }
783 }
784
785 fn visible(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 toggle_override,
790 } => setting_configuration ^ toggle_override,
791 _ => false,
792 }
793 }
794
795 fn toggle_visibility(&self) -> Self {
796 match *self {
797 Self::Enabled {
798 toggle_override,
799 setting_configuration,
800 } => Self::Enabled {
801 setting_configuration,
802 toggle_override: !toggle_override,
803 },
804 Self::Disabled => Self::Disabled,
805 }
806 }
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq)]
810pub enum BufferSerialization {
811 All,
812 NonDirtyBuffers,
813}
814
815impl BufferSerialization {
816 fn new(restore_unsaved_buffers: bool) -> Self {
817 if restore_unsaved_buffers {
818 Self::All
819 } else {
820 Self::NonDirtyBuffers
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859/// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
873 None
874 }
875
876 fn to_any(&self) -> &dyn std::any::Any;
877
878 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
879 None
880 }
881}
882
883struct ChangeLocation {
884 current: Option<Vec<Anchor>>,
885 original: Vec<Anchor>,
886}
887impl ChangeLocation {
888 fn locations(&self) -> &[Anchor] {
889 self.current.as_ref().unwrap_or(&self.original)
890 }
891}
892
893/// A set of caret positions, registered when the editor was edited.
894pub struct ChangeList {
895 changes: Vec<ChangeLocation>,
896 /// Currently "selected" change.
897 position: Option<usize>,
898}
899
900impl ChangeList {
901 pub fn new() -> Self {
902 Self {
903 changes: Vec::new(),
904 position: None,
905 }
906 }
907
908 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
909 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
910 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
911 if self.changes.is_empty() {
912 return None;
913 }
914
915 let prev = self.position.unwrap_or(self.changes.len());
916 let next = if direction == Direction::Prev {
917 prev.saturating_sub(count)
918 } else {
919 (prev + count).min(self.changes.len() - 1)
920 };
921 self.position = Some(next);
922 self.changes.get(next).map(|change| change.locations())
923 }
924
925 /// Adds a new change to the list, resetting the change list position.
926 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
927 self.position.take();
928 if let Some(last) = self.changes.last_mut()
929 && group
930 {
931 last.current = Some(new_positions)
932 } else {
933 self.changes.push(ChangeLocation {
934 original: new_positions,
935 current: None,
936 });
937 }
938 }
939
940 pub fn last(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.locations())
942 }
943
944 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.original.as_slice())
946 }
947
948 pub fn invert_last_group(&mut self) {
949 if let Some(last) = self.changes.last_mut()
950 && let Some(current) = last.current.as_mut()
951 {
952 mem::swap(&mut last.original, current);
953 }
954 }
955}
956
957#[derive(Clone)]
958struct InlineBlamePopoverState {
959 scroll_handle: ScrollHandle,
960 commit_message: Option<ParsedCommitMessage>,
961 markdown: Entity<Markdown>,
962}
963
964struct InlineBlamePopover {
965 position: gpui::Point<Pixels>,
966 hide_task: Option<Task<()>>,
967 popover_bounds: Option<Bounds<Pixels>>,
968 popover_state: InlineBlamePopoverState,
969 keyboard_grace: bool,
970}
971
972enum SelectionDragState {
973 /// State when no drag related activity is detected.
974 None,
975 /// State when the mouse is down on a selection that is about to be dragged.
976 ReadyToDrag {
977 selection: Selection<Anchor>,
978 click_position: gpui::Point<Pixels>,
979 mouse_down_time: Instant,
980 },
981 /// State when the mouse is dragging the selection in the editor.
982 Dragging {
983 selection: Selection<Anchor>,
984 drop_cursor: Selection<Anchor>,
985 hide_drop_cursor: bool,
986 },
987}
988
989enum ColumnarSelectionState {
990 FromMouse {
991 selection_tail: Anchor,
992 display_point: Option<DisplayPoint>,
993 },
994 FromSelection {
995 selection_tail: Anchor,
996 },
997}
998
999/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1000/// a breakpoint on them.
1001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1002struct PhantomBreakpointIndicator {
1003 display_row: DisplayRow,
1004 /// There's a small debounce between hovering over the line and showing the indicator.
1005 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1006 is_active: bool,
1007 collides_with_existing_breakpoint: bool,
1008}
1009
1010/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1011///
1012/// See the [module level documentation](self) for more information.
1013pub struct Editor {
1014 focus_handle: FocusHandle,
1015 last_focused_descendant: Option<WeakFocusHandle>,
1016 /// The text buffer being edited
1017 buffer: Entity<MultiBuffer>,
1018 /// Map of how text in the buffer should be displayed.
1019 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1020 pub display_map: Entity<DisplayMap>,
1021 placeholder_display_map: Option<Entity<DisplayMap>>,
1022 pub selections: SelectionsCollection,
1023 pub scroll_manager: ScrollManager,
1024 /// When inline assist editors are linked, they all render cursors because
1025 /// typing enters text into each of them, even the ones that aren't focused.
1026 pub(crate) show_cursor_when_unfocused: bool,
1027 columnar_selection_state: Option<ColumnarSelectionState>,
1028 add_selections_state: Option<AddSelectionsState>,
1029 select_next_state: Option<SelectNextState>,
1030 select_prev_state: Option<SelectNextState>,
1031 selection_history: SelectionHistory,
1032 defer_selection_effects: bool,
1033 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1034 autoclose_regions: Vec<AutocloseRegion>,
1035 snippet_stack: InvalidationStack<SnippetState>,
1036 select_syntax_node_history: SelectSyntaxNodeHistory,
1037 ime_transaction: Option<TransactionId>,
1038 pub diagnostics_max_severity: DiagnosticSeverity,
1039 active_diagnostics: ActiveDiagnostic,
1040 show_inline_diagnostics: bool,
1041 inline_diagnostics_update: Task<()>,
1042 inline_diagnostics_enabled: bool,
1043 diagnostics_enabled: bool,
1044 word_completions_enabled: bool,
1045 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1046 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1047 hard_wrap: Option<usize>,
1048 project: Option<Entity<Project>>,
1049 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1050 completion_provider: Option<Rc<dyn CompletionProvider>>,
1051 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1052 blink_manager: Entity<BlinkManager>,
1053 show_cursor_names: bool,
1054 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1055 pub show_local_selections: bool,
1056 mode: EditorMode,
1057 show_breadcrumbs: bool,
1058 show_gutter: bool,
1059 show_scrollbars: ScrollbarAxes,
1060 minimap_visibility: MinimapVisibility,
1061 offset_content: bool,
1062 disable_expand_excerpt_buttons: bool,
1063 show_line_numbers: Option<bool>,
1064 use_relative_line_numbers: Option<bool>,
1065 show_git_diff_gutter: Option<bool>,
1066 show_code_actions: Option<bool>,
1067 show_runnables: Option<bool>,
1068 show_breakpoints: Option<bool>,
1069 show_wrap_guides: Option<bool>,
1070 show_indent_guides: Option<bool>,
1071 highlight_order: usize,
1072 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1073 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1074 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1075 scrollbar_marker_state: ScrollbarMarkerState,
1076 active_indent_guides_state: ActiveIndentGuidesState,
1077 nav_history: Option<ItemNavHistory>,
1078 context_menu: RefCell<Option<CodeContextMenu>>,
1079 context_menu_options: Option<ContextMenuOptions>,
1080 mouse_context_menu: Option<MouseContextMenu>,
1081 completion_tasks: Vec<(CompletionId, Task<()>)>,
1082 inline_blame_popover: Option<InlineBlamePopover>,
1083 inline_blame_popover_show_task: Option<Task<()>>,
1084 signature_help_state: SignatureHelpState,
1085 auto_signature_help: Option<bool>,
1086 find_all_references_task_sources: Vec<Anchor>,
1087 next_completion_id: CompletionId,
1088 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1089 code_actions_task: Option<Task<Result<()>>>,
1090 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1091 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1092 document_highlights_task: Option<Task<()>>,
1093 linked_editing_range_task: Option<Task<Option<()>>>,
1094 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1095 pending_rename: Option<RenameState>,
1096 searchable: bool,
1097 cursor_shape: CursorShape,
1098 current_line_highlight: Option<CurrentLineHighlight>,
1099 autoindent_mode: Option<AutoindentMode>,
1100 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1101 input_enabled: bool,
1102 use_modal_editing: bool,
1103 read_only: bool,
1104 leader_id: Option<CollaboratorId>,
1105 remote_id: Option<ViewId>,
1106 pub hover_state: HoverState,
1107 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1108 gutter_hovered: bool,
1109 hovered_link_state: Option<HoveredLinkState>,
1110 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1111 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1112 active_edit_prediction: Option<EditPredictionState>,
1113 /// Used to prevent flickering as the user types while the menu is open
1114 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1115 edit_prediction_settings: EditPredictionSettings,
1116 edit_predictions_hidden_for_vim_mode: bool,
1117 show_edit_predictions_override: Option<bool>,
1118 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1119 edit_prediction_preview: EditPredictionPreview,
1120 edit_prediction_indent_conflict: bool,
1121 edit_prediction_requires_modifier_in_indent_conflict: bool,
1122 next_inlay_id: usize,
1123 next_color_inlay_id: usize,
1124 _subscriptions: Vec<Subscription>,
1125 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1126 gutter_dimensions: GutterDimensions,
1127 style: Option<EditorStyle>,
1128 text_style_refinement: Option<TextStyleRefinement>,
1129 next_editor_action_id: EditorActionId,
1130 editor_actions: Rc<
1131 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1132 >,
1133 use_autoclose: bool,
1134 use_auto_surround: bool,
1135 auto_replace_emoji_shortcode: bool,
1136 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1137 show_git_blame_gutter: bool,
1138 show_git_blame_inline: bool,
1139 show_git_blame_inline_delay_task: Option<Task<()>>,
1140 git_blame_inline_enabled: bool,
1141 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1142 buffer_serialization: Option<BufferSerialization>,
1143 show_selection_menu: Option<bool>,
1144 blame: Option<Entity<GitBlame>>,
1145 blame_subscription: Option<Subscription>,
1146 custom_context_menu: Option<
1147 Box<
1148 dyn 'static
1149 + Fn(
1150 &mut Self,
1151 DisplayPoint,
1152 &mut Window,
1153 &mut Context<Self>,
1154 ) -> Option<Entity<ui::ContextMenu>>,
1155 >,
1156 >,
1157 last_bounds: Option<Bounds<Pixels>>,
1158 last_position_map: Option<Rc<PositionMap>>,
1159 expect_bounds_change: Option<Bounds<Pixels>>,
1160 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1161 tasks_update_task: Option<Task<()>>,
1162 breakpoint_store: Option<Entity<BreakpointStore>>,
1163 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1164 hovered_diff_hunk_row: Option<DisplayRow>,
1165 pull_diagnostics_task: Task<()>,
1166 in_project_search: bool,
1167 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1168 breadcrumb_header: Option<String>,
1169 focused_block: Option<FocusedBlock>,
1170 next_scroll_position: NextScrollCursorCenterTopBottom,
1171 addons: HashMap<TypeId, Box<dyn Addon>>,
1172 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1173 load_diff_task: Option<Shared<Task<()>>>,
1174 /// Whether we are temporarily displaying a diff other than git's
1175 temporary_diff_override: bool,
1176 selection_mark_mode: bool,
1177 toggle_fold_multiple_buffers: Task<()>,
1178 _scroll_cursor_center_top_bottom_task: Task<()>,
1179 serialize_selections: Task<()>,
1180 serialize_folds: Task<()>,
1181 mouse_cursor_hidden: bool,
1182 minimap: Option<Entity<Self>>,
1183 hide_mouse_mode: HideMouseMode,
1184 pub change_list: ChangeList,
1185 inline_value_cache: InlineValueCache,
1186
1187 selection_drag_state: SelectionDragState,
1188 colors: Option<LspColorData>,
1189 post_scroll_update: Task<()>,
1190 refresh_colors_task: Task<()>,
1191 inlay_hints: Option<LspInlayHintData>,
1192 folding_newlines: Task<()>,
1193 select_next_is_case_sensitive: Option<bool>,
1194 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1195}
1196
1197fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1198 if debounce_ms > 0 {
1199 Some(Duration::from_millis(debounce_ms))
1200 } else {
1201 None
1202 }
1203}
1204
1205#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1206enum NextScrollCursorCenterTopBottom {
1207 #[default]
1208 Center,
1209 Top,
1210 Bottom,
1211}
1212
1213impl NextScrollCursorCenterTopBottom {
1214 fn next(&self) -> Self {
1215 match self {
1216 Self::Center => Self::Top,
1217 Self::Top => Self::Bottom,
1218 Self::Bottom => Self::Center,
1219 }
1220 }
1221}
1222
1223#[derive(Clone)]
1224pub struct EditorSnapshot {
1225 pub mode: EditorMode,
1226 show_gutter: bool,
1227 show_line_numbers: Option<bool>,
1228 show_git_diff_gutter: Option<bool>,
1229 show_code_actions: Option<bool>,
1230 show_runnables: Option<bool>,
1231 show_breakpoints: Option<bool>,
1232 git_blame_gutter_max_author_length: Option<usize>,
1233 pub display_snapshot: DisplaySnapshot,
1234 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1235 is_focused: bool,
1236 scroll_anchor: ScrollAnchor,
1237 ongoing_scroll: OngoingScroll,
1238 current_line_highlight: CurrentLineHighlight,
1239 gutter_hovered: bool,
1240}
1241
1242#[derive(Default, Debug, Clone, Copy)]
1243pub struct GutterDimensions {
1244 pub left_padding: Pixels,
1245 pub right_padding: Pixels,
1246 pub width: Pixels,
1247 pub margin: Pixels,
1248 pub git_blame_entries_width: Option<Pixels>,
1249}
1250
1251impl GutterDimensions {
1252 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1253 Self {
1254 margin: Self::default_gutter_margin(font_id, font_size, cx),
1255 ..Default::default()
1256 }
1257 }
1258
1259 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1260 -cx.text_system().descent(font_id, font_size)
1261 }
1262 /// The full width of the space taken up by the gutter.
1263 pub fn full_width(&self) -> Pixels {
1264 self.margin + self.width
1265 }
1266
1267 /// The width of the space reserved for the fold indicators,
1268 /// use alongside 'justify_end' and `gutter_width` to
1269 /// right align content with the line numbers
1270 pub fn fold_area_width(&self) -> Pixels {
1271 self.margin + self.right_padding
1272 }
1273}
1274
1275struct CharacterDimensions {
1276 em_width: Pixels,
1277 em_advance: Pixels,
1278 line_height: Pixels,
1279}
1280
1281#[derive(Debug)]
1282pub struct RemoteSelection {
1283 pub replica_id: ReplicaId,
1284 pub selection: Selection<Anchor>,
1285 pub cursor_shape: CursorShape,
1286 pub collaborator_id: CollaboratorId,
1287 pub line_mode: bool,
1288 pub user_name: Option<SharedString>,
1289 pub color: PlayerColor,
1290}
1291
1292#[derive(Clone, Debug)]
1293struct SelectionHistoryEntry {
1294 selections: Arc<[Selection<Anchor>]>,
1295 select_next_state: Option<SelectNextState>,
1296 select_prev_state: Option<SelectNextState>,
1297 add_selections_state: Option<AddSelectionsState>,
1298}
1299
1300#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1301enum SelectionHistoryMode {
1302 Normal,
1303 Undoing,
1304 Redoing,
1305 Skipping,
1306}
1307
1308#[derive(Clone, PartialEq, Eq, Hash)]
1309struct HoveredCursor {
1310 replica_id: ReplicaId,
1311 selection_id: usize,
1312}
1313
1314impl Default for SelectionHistoryMode {
1315 fn default() -> Self {
1316 Self::Normal
1317 }
1318}
1319
1320#[derive(Debug)]
1321/// SelectionEffects controls the side-effects of updating the selection.
1322///
1323/// The default behaviour does "what you mostly want":
1324/// - it pushes to the nav history if the cursor moved by >10 lines
1325/// - it re-triggers completion requests
1326/// - it scrolls to fit
1327///
1328/// You might want to modify these behaviours. For example when doing a "jump"
1329/// like go to definition, we always want to add to nav history; but when scrolling
1330/// in vim mode we never do.
1331///
1332/// Similarly, you might want to disable scrolling if you don't want the viewport to
1333/// move.
1334#[derive(Clone)]
1335pub struct SelectionEffects {
1336 nav_history: Option<bool>,
1337 completions: bool,
1338 scroll: Option<Autoscroll>,
1339}
1340
1341impl Default for SelectionEffects {
1342 fn default() -> Self {
1343 Self {
1344 nav_history: None,
1345 completions: true,
1346 scroll: Some(Autoscroll::fit()),
1347 }
1348 }
1349}
1350impl SelectionEffects {
1351 pub fn scroll(scroll: Autoscroll) -> Self {
1352 Self {
1353 scroll: Some(scroll),
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn no_scroll() -> Self {
1359 Self {
1360 scroll: None,
1361 ..Default::default()
1362 }
1363 }
1364
1365 pub fn completions(self, completions: bool) -> Self {
1366 Self {
1367 completions,
1368 ..self
1369 }
1370 }
1371
1372 pub fn nav_history(self, nav_history: bool) -> Self {
1373 Self {
1374 nav_history: Some(nav_history),
1375 ..self
1376 }
1377 }
1378}
1379
1380struct DeferredSelectionEffectsState {
1381 changed: bool,
1382 effects: SelectionEffects,
1383 old_cursor_position: Anchor,
1384 history_entry: SelectionHistoryEntry,
1385}
1386
1387#[derive(Default)]
1388struct SelectionHistory {
1389 #[allow(clippy::type_complexity)]
1390 selections_by_transaction:
1391 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1392 mode: SelectionHistoryMode,
1393 undo_stack: VecDeque<SelectionHistoryEntry>,
1394 redo_stack: VecDeque<SelectionHistoryEntry>,
1395}
1396
1397impl SelectionHistory {
1398 #[track_caller]
1399 fn insert_transaction(
1400 &mut self,
1401 transaction_id: TransactionId,
1402 selections: Arc<[Selection<Anchor>]>,
1403 ) {
1404 if selections.is_empty() {
1405 log::error!(
1406 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1407 std::panic::Location::caller()
1408 );
1409 return;
1410 }
1411 self.selections_by_transaction
1412 .insert(transaction_id, (selections, None));
1413 }
1414
1415 #[allow(clippy::type_complexity)]
1416 fn transaction(
1417 &self,
1418 transaction_id: TransactionId,
1419 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1420 self.selections_by_transaction.get(&transaction_id)
1421 }
1422
1423 #[allow(clippy::type_complexity)]
1424 fn transaction_mut(
1425 &mut self,
1426 transaction_id: TransactionId,
1427 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1428 self.selections_by_transaction.get_mut(&transaction_id)
1429 }
1430
1431 fn push(&mut self, entry: SelectionHistoryEntry) {
1432 if !entry.selections.is_empty() {
1433 match self.mode {
1434 SelectionHistoryMode::Normal => {
1435 self.push_undo(entry);
1436 self.redo_stack.clear();
1437 }
1438 SelectionHistoryMode::Undoing => self.push_redo(entry),
1439 SelectionHistoryMode::Redoing => self.push_undo(entry),
1440 SelectionHistoryMode::Skipping => {}
1441 }
1442 }
1443 }
1444
1445 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1446 if self
1447 .undo_stack
1448 .back()
1449 .is_none_or(|e| e.selections != entry.selections)
1450 {
1451 self.undo_stack.push_back(entry);
1452 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1453 self.undo_stack.pop_front();
1454 }
1455 }
1456 }
1457
1458 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1459 if self
1460 .redo_stack
1461 .back()
1462 .is_none_or(|e| e.selections != entry.selections)
1463 {
1464 self.redo_stack.push_back(entry);
1465 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1466 self.redo_stack.pop_front();
1467 }
1468 }
1469 }
1470}
1471
1472#[derive(Clone, Copy)]
1473pub struct RowHighlightOptions {
1474 pub autoscroll: bool,
1475 pub include_gutter: bool,
1476}
1477
1478impl Default for RowHighlightOptions {
1479 fn default() -> Self {
1480 Self {
1481 autoscroll: Default::default(),
1482 include_gutter: true,
1483 }
1484 }
1485}
1486
1487struct RowHighlight {
1488 index: usize,
1489 range: Range<Anchor>,
1490 color: Hsla,
1491 options: RowHighlightOptions,
1492 type_id: TypeId,
1493}
1494
1495#[derive(Clone, Debug)]
1496struct AddSelectionsState {
1497 groups: Vec<AddSelectionsGroup>,
1498}
1499
1500#[derive(Clone, Debug)]
1501struct AddSelectionsGroup {
1502 above: bool,
1503 stack: Vec<usize>,
1504}
1505
1506#[derive(Clone)]
1507struct SelectNextState {
1508 query: AhoCorasick,
1509 wordwise: bool,
1510 done: bool,
1511}
1512
1513impl std::fmt::Debug for SelectNextState {
1514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1515 f.debug_struct(std::any::type_name::<Self>())
1516 .field("wordwise", &self.wordwise)
1517 .field("done", &self.done)
1518 .finish()
1519 }
1520}
1521
1522#[derive(Debug)]
1523struct AutocloseRegion {
1524 selection_id: usize,
1525 range: Range<Anchor>,
1526 pair: BracketPair,
1527}
1528
1529#[derive(Debug)]
1530struct SnippetState {
1531 ranges: Vec<Vec<Range<Anchor>>>,
1532 active_index: usize,
1533 choices: Vec<Option<Vec<String>>>,
1534}
1535
1536#[doc(hidden)]
1537pub struct RenameState {
1538 pub range: Range<Anchor>,
1539 pub old_name: Arc<str>,
1540 pub editor: Entity<Editor>,
1541 block_id: CustomBlockId,
1542}
1543
1544struct InvalidationStack<T>(Vec<T>);
1545
1546struct RegisteredEditPredictionProvider {
1547 provider: Arc<dyn EditPredictionProviderHandle>,
1548 _subscription: Subscription,
1549}
1550
1551#[derive(Debug, PartialEq, Eq)]
1552pub struct ActiveDiagnosticGroup {
1553 pub active_range: Range<Anchor>,
1554 pub active_message: String,
1555 pub group_id: usize,
1556 pub blocks: HashSet<CustomBlockId>,
1557}
1558
1559#[derive(Debug, PartialEq, Eq)]
1560
1561pub(crate) enum ActiveDiagnostic {
1562 None,
1563 All,
1564 Group(ActiveDiagnosticGroup),
1565}
1566
1567#[derive(Serialize, Deserialize, Clone, Debug)]
1568pub struct ClipboardSelection {
1569 /// The number of bytes in this selection.
1570 pub len: usize,
1571 /// Whether this was a full-line selection.
1572 pub is_entire_line: bool,
1573 /// The indentation of the first line when this content was originally copied.
1574 pub first_line_indent: u32,
1575}
1576
1577// selections, scroll behavior, was newest selection reversed
1578type SelectSyntaxNodeHistoryState = (
1579 Box<[Selection<usize>]>,
1580 SelectSyntaxNodeScrollBehavior,
1581 bool,
1582);
1583
1584#[derive(Default)]
1585struct SelectSyntaxNodeHistory {
1586 stack: Vec<SelectSyntaxNodeHistoryState>,
1587 // disable temporarily to allow changing selections without losing the stack
1588 pub disable_clearing: bool,
1589}
1590
1591impl SelectSyntaxNodeHistory {
1592 pub fn try_clear(&mut self) {
1593 if !self.disable_clearing {
1594 self.stack.clear();
1595 }
1596 }
1597
1598 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1599 self.stack.push(selection);
1600 }
1601
1602 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1603 self.stack.pop()
1604 }
1605}
1606
1607enum SelectSyntaxNodeScrollBehavior {
1608 CursorTop,
1609 FitSelection,
1610 CursorBottom,
1611}
1612
1613#[derive(Debug)]
1614pub(crate) struct NavigationData {
1615 cursor_anchor: Anchor,
1616 cursor_position: Point,
1617 scroll_anchor: ScrollAnchor,
1618 scroll_top_row: u32,
1619}
1620
1621#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1622pub enum GotoDefinitionKind {
1623 Symbol,
1624 Declaration,
1625 Type,
1626 Implementation,
1627}
1628
1629pub enum FormatTarget {
1630 Buffers(HashSet<Entity<Buffer>>),
1631 Ranges(Vec<Range<MultiBufferPoint>>),
1632}
1633
1634pub(crate) struct FocusedBlock {
1635 id: BlockId,
1636 focus_handle: WeakFocusHandle,
1637}
1638
1639#[derive(Clone)]
1640enum JumpData {
1641 MultiBufferRow {
1642 row: MultiBufferRow,
1643 line_offset_from_top: u32,
1644 },
1645 MultiBufferPoint {
1646 excerpt_id: ExcerptId,
1647 position: Point,
1648 anchor: text::Anchor,
1649 line_offset_from_top: u32,
1650 },
1651}
1652
1653pub enum MultibufferSelectionMode {
1654 First,
1655 All,
1656}
1657
1658#[derive(Clone, Copy, Debug, Default)]
1659pub struct RewrapOptions {
1660 pub override_language_settings: bool,
1661 pub preserve_existing_whitespace: bool,
1662}
1663
1664impl Editor {
1665 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1666 let buffer = cx.new(|cx| Buffer::local("", cx));
1667 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1668 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1669 }
1670
1671 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1672 let buffer = cx.new(|cx| Buffer::local("", cx));
1673 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1674 Self::new(EditorMode::full(), buffer, None, window, cx)
1675 }
1676
1677 pub fn auto_height(
1678 min_lines: usize,
1679 max_lines: usize,
1680 window: &mut Window,
1681 cx: &mut Context<Self>,
1682 ) -> Self {
1683 let buffer = cx.new(|cx| Buffer::local("", cx));
1684 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1685 Self::new(
1686 EditorMode::AutoHeight {
1687 min_lines,
1688 max_lines: Some(max_lines),
1689 },
1690 buffer,
1691 None,
1692 window,
1693 cx,
1694 )
1695 }
1696
1697 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1698 /// The editor grows as tall as needed to fit its content.
1699 pub fn auto_height_unbounded(
1700 min_lines: usize,
1701 window: &mut Window,
1702 cx: &mut Context<Self>,
1703 ) -> Self {
1704 let buffer = cx.new(|cx| Buffer::local("", cx));
1705 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1706 Self::new(
1707 EditorMode::AutoHeight {
1708 min_lines,
1709 max_lines: None,
1710 },
1711 buffer,
1712 None,
1713 window,
1714 cx,
1715 )
1716 }
1717
1718 pub fn for_buffer(
1719 buffer: Entity<Buffer>,
1720 project: Option<Entity<Project>>,
1721 window: &mut Window,
1722 cx: &mut Context<Self>,
1723 ) -> Self {
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(EditorMode::full(), buffer, project, window, cx)
1726 }
1727
1728 pub fn for_multibuffer(
1729 buffer: Entity<MultiBuffer>,
1730 project: Option<Entity<Project>>,
1731 window: &mut Window,
1732 cx: &mut Context<Self>,
1733 ) -> Self {
1734 Self::new(EditorMode::full(), buffer, project, window, cx)
1735 }
1736
1737 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let mut clone = Self::new(
1739 self.mode.clone(),
1740 self.buffer.clone(),
1741 self.project.clone(),
1742 window,
1743 cx,
1744 );
1745 self.display_map.update(cx, |display_map, cx| {
1746 let snapshot = display_map.snapshot(cx);
1747 clone.display_map.update(cx, |display_map, cx| {
1748 display_map.set_state(&snapshot, cx);
1749 });
1750 });
1751 clone.folds_did_change(cx);
1752 clone.selections.clone_state(&self.selections);
1753 clone.scroll_manager.clone_state(&self.scroll_manager);
1754 clone.searchable = self.searchable;
1755 clone.read_only = self.read_only;
1756 clone
1757 }
1758
1759 pub fn new(
1760 mode: EditorMode,
1761 buffer: Entity<MultiBuffer>,
1762 project: Option<Entity<Project>>,
1763 window: &mut Window,
1764 cx: &mut Context<Self>,
1765 ) -> Self {
1766 Editor::new_internal(mode, buffer, project, None, window, cx)
1767 }
1768
1769 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1770 let multi_buffer = self.buffer().read(cx);
1771 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1772 let multi_buffer_visible_start = self
1773 .scroll_manager
1774 .anchor()
1775 .anchor
1776 .to_point(&multi_buffer_snapshot);
1777 let max_row = multi_buffer_snapshot.max_point().row;
1778
1779 let start_row = (multi_buffer_visible_start.row).min(max_row);
1780 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1781
1782 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1783 let outline_items = buffer
1784 .outline_items_containing(
1785 Point::new(start_row, 0)..Point::new(end_row, 0),
1786 true,
1787 self.style().map(|style| style.syntax.as_ref()),
1788 )
1789 .into_iter()
1790 .map(|outline_item| OutlineItem {
1791 depth: outline_item.depth,
1792 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1793 source_range_for_text: Anchor::range_in_buffer(
1794 *excerpt_id,
1795 buffer_id,
1796 outline_item.source_range_for_text,
1797 ),
1798 text: outline_item.text,
1799 highlight_ranges: outline_item.highlight_ranges,
1800 name_ranges: outline_item.name_ranges,
1801 body_range: outline_item
1802 .body_range
1803 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1804 annotation_range: outline_item
1805 .annotation_range
1806 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1807 });
1808 return Some(outline_items.collect());
1809 }
1810
1811 None
1812 }
1813
1814 fn new_internal(
1815 mode: EditorMode,
1816 multi_buffer: Entity<MultiBuffer>,
1817 project: Option<Entity<Project>>,
1818 display_map: Option<Entity<DisplayMap>>,
1819 window: &mut Window,
1820 cx: &mut Context<Self>,
1821 ) -> Self {
1822 debug_assert!(
1823 display_map.is_none() || mode.is_minimap(),
1824 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1825 );
1826
1827 let full_mode = mode.is_full();
1828 let is_minimap = mode.is_minimap();
1829 let diagnostics_max_severity = if full_mode {
1830 EditorSettings::get_global(cx)
1831 .diagnostics_max_severity
1832 .unwrap_or(DiagnosticSeverity::Hint)
1833 } else {
1834 DiagnosticSeverity::Off
1835 };
1836 let style = window.text_style();
1837 let font_size = style.font_size.to_pixels(window.rem_size());
1838 let editor = cx.entity().downgrade();
1839 let fold_placeholder = FoldPlaceholder {
1840 constrain_width: false,
1841 render: Arc::new(move |fold_id, fold_range, cx| {
1842 let editor = editor.clone();
1843 div()
1844 .id(fold_id)
1845 .bg(cx.theme().colors().ghost_element_background)
1846 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1847 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1848 .rounded_xs()
1849 .size_full()
1850 .cursor_pointer()
1851 .child("⋯")
1852 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1853 .on_click(move |_, _window, cx| {
1854 editor
1855 .update(cx, |editor, cx| {
1856 editor.unfold_ranges(
1857 &[fold_range.start..fold_range.end],
1858 true,
1859 false,
1860 cx,
1861 );
1862 cx.stop_propagation();
1863 })
1864 .ok();
1865 })
1866 .into_any()
1867 }),
1868 merge_adjacent: true,
1869 ..FoldPlaceholder::default()
1870 };
1871 let display_map = display_map.unwrap_or_else(|| {
1872 cx.new(|cx| {
1873 DisplayMap::new(
1874 multi_buffer.clone(),
1875 style.font(),
1876 font_size,
1877 None,
1878 FILE_HEADER_HEIGHT,
1879 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1880 fold_placeholder,
1881 diagnostics_max_severity,
1882 cx,
1883 )
1884 })
1885 });
1886
1887 let selections = SelectionsCollection::new();
1888
1889 let blink_manager = cx.new(|cx| {
1890 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1891 if is_minimap {
1892 blink_manager.disable(cx);
1893 }
1894 blink_manager
1895 });
1896
1897 let soft_wrap_mode_override =
1898 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1899
1900 let mut project_subscriptions = Vec::new();
1901 if full_mode && let Some(project) = project.as_ref() {
1902 project_subscriptions.push(cx.subscribe_in(
1903 project,
1904 window,
1905 |editor, _, event, window, cx| match event {
1906 project::Event::RefreshCodeLens => {
1907 // we always query lens with actions, without storing them, always refreshing them
1908 }
1909 project::Event::RefreshInlayHints {
1910 server_id,
1911 request_id,
1912 } => {
1913 editor.refresh_inlay_hints(
1914 InlayHintRefreshReason::RefreshRequested {
1915 server_id: *server_id,
1916 request_id: *request_id,
1917 },
1918 cx,
1919 );
1920 }
1921 project::Event::LanguageServerRemoved(..) => {
1922 if editor.tasks_update_task.is_none() {
1923 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1924 }
1925 editor.registered_buffers.clear();
1926 editor.register_visible_buffers(cx);
1927 }
1928 project::Event::LanguageServerAdded(..) => {
1929 if editor.tasks_update_task.is_none() {
1930 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1931 }
1932 }
1933 project::Event::SnippetEdit(id, snippet_edits) => {
1934 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1935 let focus_handle = editor.focus_handle(cx);
1936 if focus_handle.is_focused(window) {
1937 let snapshot = buffer.read(cx).snapshot();
1938 for (range, snippet) in snippet_edits {
1939 let editor_range =
1940 language::range_from_lsp(*range).to_offset(&snapshot);
1941 editor
1942 .insert_snippet(
1943 &[editor_range],
1944 snippet.clone(),
1945 window,
1946 cx,
1947 )
1948 .ok();
1949 }
1950 }
1951 }
1952 }
1953 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1954 let buffer_id = *buffer_id;
1955 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1956 editor.register_buffer(buffer_id, cx);
1957 editor.update_lsp_data(Some(buffer_id), window, cx);
1958 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1959 refresh_linked_ranges(editor, window, cx);
1960 editor.refresh_code_actions(window, cx);
1961 editor.refresh_document_highlights(cx);
1962 }
1963 }
1964
1965 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1966 let Some(workspace) = editor.workspace() else {
1967 return;
1968 };
1969 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1970 else {
1971 return;
1972 };
1973
1974 if active_editor.entity_id() == cx.entity_id() {
1975 let entity_id = cx.entity_id();
1976 workspace.update(cx, |this, cx| {
1977 this.panes_mut()
1978 .iter_mut()
1979 .filter(|pane| pane.entity_id() != entity_id)
1980 .for_each(|p| {
1981 p.update(cx, |pane, _| {
1982 pane.nav_history_mut().rename_item(
1983 entity_id,
1984 project_path.clone(),
1985 abs_path.clone().into(),
1986 );
1987 })
1988 });
1989 });
1990 let edited_buffers_already_open = {
1991 let other_editors: Vec<Entity<Editor>> = workspace
1992 .read(cx)
1993 .panes()
1994 .iter()
1995 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1996 .filter(|editor| editor.entity_id() != cx.entity_id())
1997 .collect();
1998
1999 transaction.0.keys().all(|buffer| {
2000 other_editors.iter().any(|editor| {
2001 let multi_buffer = editor.read(cx).buffer();
2002 multi_buffer.read(cx).is_singleton()
2003 && multi_buffer.read(cx).as_singleton().map_or(
2004 false,
2005 |singleton| {
2006 singleton.entity_id() == buffer.entity_id()
2007 },
2008 )
2009 })
2010 })
2011 };
2012 if !edited_buffers_already_open {
2013 let workspace = workspace.downgrade();
2014 let transaction = transaction.clone();
2015 cx.defer_in(window, move |_, window, cx| {
2016 cx.spawn_in(window, async move |editor, cx| {
2017 Self::open_project_transaction(
2018 &editor,
2019 workspace,
2020 transaction,
2021 "Rename".to_string(),
2022 cx,
2023 )
2024 .await
2025 .ok()
2026 })
2027 .detach();
2028 });
2029 }
2030 }
2031 }
2032
2033 _ => {}
2034 },
2035 ));
2036 if let Some(task_inventory) = project
2037 .read(cx)
2038 .task_store()
2039 .read(cx)
2040 .task_inventory()
2041 .cloned()
2042 {
2043 project_subscriptions.push(cx.observe_in(
2044 &task_inventory,
2045 window,
2046 |editor, _, window, cx| {
2047 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2048 },
2049 ));
2050 };
2051
2052 project_subscriptions.push(cx.subscribe_in(
2053 &project.read(cx).breakpoint_store(),
2054 window,
2055 |editor, _, event, window, cx| match event {
2056 BreakpointStoreEvent::ClearDebugLines => {
2057 editor.clear_row_highlights::<ActiveDebugLine>();
2058 editor.refresh_inline_values(cx);
2059 }
2060 BreakpointStoreEvent::SetDebugLine => {
2061 if editor.go_to_active_debug_line(window, cx) {
2062 cx.stop_propagation();
2063 }
2064
2065 editor.refresh_inline_values(cx);
2066 }
2067 _ => {}
2068 },
2069 ));
2070 let git_store = project.read(cx).git_store().clone();
2071 let project = project.clone();
2072 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2073 if let GitStoreEvent::RepositoryAdded = event {
2074 this.load_diff_task = Some(
2075 update_uncommitted_diff_for_buffer(
2076 cx.entity(),
2077 &project,
2078 this.buffer.read(cx).all_buffers(),
2079 this.buffer.clone(),
2080 cx,
2081 )
2082 .shared(),
2083 );
2084 }
2085 }));
2086 }
2087
2088 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2089
2090 let inlay_hint_settings =
2091 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2092 let focus_handle = cx.focus_handle();
2093 if !is_minimap {
2094 cx.on_focus(&focus_handle, window, Self::handle_focus)
2095 .detach();
2096 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2097 .detach();
2098 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2099 .detach();
2100 cx.on_blur(&focus_handle, window, Self::handle_blur)
2101 .detach();
2102 cx.observe_pending_input(window, Self::observe_pending_input)
2103 .detach();
2104 }
2105
2106 let show_indent_guides =
2107 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2108 Some(false)
2109 } else {
2110 None
2111 };
2112
2113 let breakpoint_store = match (&mode, project.as_ref()) {
2114 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2115 _ => None,
2116 };
2117
2118 let mut code_action_providers = Vec::new();
2119 let mut load_uncommitted_diff = None;
2120 if let Some(project) = project.clone() {
2121 load_uncommitted_diff = Some(
2122 update_uncommitted_diff_for_buffer(
2123 cx.entity(),
2124 &project,
2125 multi_buffer.read(cx).all_buffers(),
2126 multi_buffer.clone(),
2127 cx,
2128 )
2129 .shared(),
2130 );
2131 code_action_providers.push(Rc::new(project) as Rc<_>);
2132 }
2133
2134 let mut editor = Self {
2135 focus_handle,
2136 show_cursor_when_unfocused: false,
2137 last_focused_descendant: None,
2138 buffer: multi_buffer.clone(),
2139 display_map: display_map.clone(),
2140 placeholder_display_map: None,
2141 selections,
2142 scroll_manager: ScrollManager::new(cx),
2143 columnar_selection_state: None,
2144 add_selections_state: None,
2145 select_next_state: None,
2146 select_prev_state: None,
2147 selection_history: SelectionHistory::default(),
2148 defer_selection_effects: false,
2149 deferred_selection_effects_state: None,
2150 autoclose_regions: Vec::new(),
2151 snippet_stack: InvalidationStack::default(),
2152 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2153 ime_transaction: None,
2154 active_diagnostics: ActiveDiagnostic::None,
2155 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2156 inline_diagnostics_update: Task::ready(()),
2157 inline_diagnostics: Vec::new(),
2158 soft_wrap_mode_override,
2159 diagnostics_max_severity,
2160 hard_wrap: None,
2161 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2162 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2163 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2164 project,
2165 blink_manager: blink_manager.clone(),
2166 show_local_selections: true,
2167 show_scrollbars: ScrollbarAxes {
2168 horizontal: full_mode,
2169 vertical: full_mode,
2170 },
2171 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2172 offset_content: !matches!(mode, EditorMode::SingleLine),
2173 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2174 show_gutter: full_mode,
2175 show_line_numbers: (!full_mode).then_some(false),
2176 use_relative_line_numbers: None,
2177 disable_expand_excerpt_buttons: !full_mode,
2178 show_git_diff_gutter: None,
2179 show_code_actions: None,
2180 show_runnables: None,
2181 show_breakpoints: None,
2182 show_wrap_guides: None,
2183 show_indent_guides,
2184 highlight_order: 0,
2185 highlighted_rows: HashMap::default(),
2186 background_highlights: HashMap::default(),
2187 gutter_highlights: HashMap::default(),
2188 scrollbar_marker_state: ScrollbarMarkerState::default(),
2189 active_indent_guides_state: ActiveIndentGuidesState::default(),
2190 nav_history: None,
2191 context_menu: RefCell::new(None),
2192 context_menu_options: None,
2193 mouse_context_menu: None,
2194 completion_tasks: Vec::new(),
2195 inline_blame_popover: None,
2196 inline_blame_popover_show_task: None,
2197 signature_help_state: SignatureHelpState::default(),
2198 auto_signature_help: None,
2199 find_all_references_task_sources: Vec::new(),
2200 next_completion_id: 0,
2201 next_inlay_id: 0,
2202 code_action_providers,
2203 available_code_actions: None,
2204 code_actions_task: None,
2205 quick_selection_highlight_task: None,
2206 debounced_selection_highlight_task: None,
2207 document_highlights_task: None,
2208 linked_editing_range_task: None,
2209 pending_rename: None,
2210 searchable: !is_minimap,
2211 cursor_shape: EditorSettings::get_global(cx)
2212 .cursor_shape
2213 .unwrap_or_default(),
2214 current_line_highlight: None,
2215 autoindent_mode: Some(AutoindentMode::EachLine),
2216
2217 workspace: None,
2218 input_enabled: !is_minimap,
2219 use_modal_editing: full_mode,
2220 read_only: is_minimap,
2221 use_autoclose: true,
2222 use_auto_surround: true,
2223 auto_replace_emoji_shortcode: false,
2224 jsx_tag_auto_close_enabled_in_any_buffer: false,
2225 leader_id: None,
2226 remote_id: None,
2227 hover_state: HoverState::default(),
2228 pending_mouse_down: None,
2229 hovered_link_state: None,
2230 edit_prediction_provider: None,
2231 active_edit_prediction: None,
2232 stale_edit_prediction_in_menu: None,
2233 edit_prediction_preview: EditPredictionPreview::Inactive {
2234 released_too_fast: false,
2235 },
2236 inline_diagnostics_enabled: full_mode,
2237 diagnostics_enabled: full_mode,
2238 word_completions_enabled: full_mode,
2239 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2240 gutter_hovered: false,
2241 pixel_position_of_newest_cursor: None,
2242 last_bounds: None,
2243 last_position_map: None,
2244 expect_bounds_change: None,
2245 gutter_dimensions: GutterDimensions::default(),
2246 style: None,
2247 show_cursor_names: false,
2248 hovered_cursors: HashMap::default(),
2249 next_editor_action_id: EditorActionId::default(),
2250 editor_actions: Rc::default(),
2251 edit_predictions_hidden_for_vim_mode: false,
2252 show_edit_predictions_override: None,
2253 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2254 edit_prediction_settings: EditPredictionSettings::Disabled,
2255 edit_prediction_indent_conflict: false,
2256 edit_prediction_requires_modifier_in_indent_conflict: true,
2257 custom_context_menu: None,
2258 show_git_blame_gutter: false,
2259 show_git_blame_inline: false,
2260 show_selection_menu: None,
2261 show_git_blame_inline_delay_task: None,
2262 git_blame_inline_enabled: full_mode
2263 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2264 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2265 buffer_serialization: is_minimap.not().then(|| {
2266 BufferSerialization::new(
2267 ProjectSettings::get_global(cx)
2268 .session
2269 .restore_unsaved_buffers,
2270 )
2271 }),
2272 blame: None,
2273 blame_subscription: None,
2274 tasks: BTreeMap::default(),
2275
2276 breakpoint_store,
2277 gutter_breakpoint_indicator: (None, None),
2278 hovered_diff_hunk_row: None,
2279 _subscriptions: (!is_minimap)
2280 .then(|| {
2281 vec![
2282 cx.observe(&multi_buffer, Self::on_buffer_changed),
2283 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2284 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2285 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2286 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2287 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2288 cx.observe_window_activation(window, |editor, window, cx| {
2289 let active = window.is_window_active();
2290 editor.blink_manager.update(cx, |blink_manager, cx| {
2291 if active {
2292 blink_manager.enable(cx);
2293 } else {
2294 blink_manager.disable(cx);
2295 }
2296 });
2297 if active {
2298 editor.show_mouse_cursor(cx);
2299 }
2300 }),
2301 ]
2302 })
2303 .unwrap_or_default(),
2304 tasks_update_task: None,
2305 pull_diagnostics_task: Task::ready(()),
2306 colors: None,
2307 refresh_colors_task: Task::ready(()),
2308 inlay_hints: None,
2309 next_color_inlay_id: 0,
2310 post_scroll_update: Task::ready(()),
2311 linked_edit_ranges: Default::default(),
2312 in_project_search: false,
2313 previous_search_ranges: None,
2314 breadcrumb_header: None,
2315 focused_block: None,
2316 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2317 addons: HashMap::default(),
2318 registered_buffers: HashMap::default(),
2319 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2320 selection_mark_mode: false,
2321 toggle_fold_multiple_buffers: Task::ready(()),
2322 serialize_selections: Task::ready(()),
2323 serialize_folds: Task::ready(()),
2324 text_style_refinement: None,
2325 load_diff_task: load_uncommitted_diff,
2326 temporary_diff_override: false,
2327 mouse_cursor_hidden: false,
2328 minimap: None,
2329 hide_mouse_mode: EditorSettings::get_global(cx)
2330 .hide_mouse
2331 .unwrap_or_default(),
2332 change_list: ChangeList::new(),
2333 mode,
2334 selection_drag_state: SelectionDragState::None,
2335 folding_newlines: Task::ready(()),
2336 lookup_key: None,
2337 select_next_is_case_sensitive: None,
2338 };
2339
2340 if is_minimap {
2341 return editor;
2342 }
2343
2344 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2345 editor
2346 ._subscriptions
2347 .push(cx.observe(breakpoints, |_, _, cx| {
2348 cx.notify();
2349 }));
2350 }
2351 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2352 editor._subscriptions.extend(project_subscriptions);
2353
2354 editor._subscriptions.push(cx.subscribe_in(
2355 &cx.entity(),
2356 window,
2357 |editor, _, e: &EditorEvent, window, cx| match e {
2358 EditorEvent::ScrollPositionChanged { local, .. } => {
2359 if *local {
2360 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2361 editor.inline_blame_popover.take();
2362 let new_anchor = editor.scroll_manager.anchor();
2363 let snapshot = editor.snapshot(window, cx);
2364 editor.update_restoration_data(cx, move |data| {
2365 data.scroll_position = (
2366 new_anchor.top_row(snapshot.buffer_snapshot()),
2367 new_anchor.offset,
2368 );
2369 });
2370
2371 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2372 cx.background_executor()
2373 .timer(Duration::from_millis(50))
2374 .await;
2375 editor
2376 .update_in(cx, |editor, window, cx| {
2377 editor.register_visible_buffers(cx);
2378 editor.refresh_colors_for_visible_range(None, window, cx);
2379 editor.refresh_inlay_hints(
2380 InlayHintRefreshReason::NewLinesShown,
2381 cx,
2382 );
2383 })
2384 .ok();
2385 });
2386 }
2387 }
2388 EditorEvent::Edited { .. } => {
2389 if vim_flavor(cx).is_none() {
2390 let display_map = editor.display_snapshot(cx);
2391 let selections = editor.selections.all_adjusted_display(&display_map);
2392 let pop_state = editor
2393 .change_list
2394 .last()
2395 .map(|previous| {
2396 previous.len() == selections.len()
2397 && previous.iter().enumerate().all(|(ix, p)| {
2398 p.to_display_point(&display_map).row()
2399 == selections[ix].head().row()
2400 })
2401 })
2402 .unwrap_or(false);
2403 let new_positions = selections
2404 .into_iter()
2405 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2406 .collect();
2407 editor
2408 .change_list
2409 .push_to_change_list(pop_state, new_positions);
2410 }
2411 }
2412 _ => (),
2413 },
2414 ));
2415
2416 if let Some(dap_store) = editor
2417 .project
2418 .as_ref()
2419 .map(|project| project.read(cx).dap_store())
2420 {
2421 let weak_editor = cx.weak_entity();
2422
2423 editor
2424 ._subscriptions
2425 .push(
2426 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2427 let session_entity = cx.entity();
2428 weak_editor
2429 .update(cx, |editor, cx| {
2430 editor._subscriptions.push(
2431 cx.subscribe(&session_entity, Self::on_debug_session_event),
2432 );
2433 })
2434 .ok();
2435 }),
2436 );
2437
2438 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2439 editor
2440 ._subscriptions
2441 .push(cx.subscribe(&session, Self::on_debug_session_event));
2442 }
2443 }
2444
2445 // skip adding the initial selection to selection history
2446 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2447 editor.end_selection(window, cx);
2448 editor.selection_history.mode = SelectionHistoryMode::Normal;
2449
2450 editor.scroll_manager.show_scrollbars(window, cx);
2451 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2452
2453 if full_mode {
2454 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2455 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2456
2457 if editor.git_blame_inline_enabled {
2458 editor.start_git_blame_inline(false, window, cx);
2459 }
2460
2461 editor.go_to_active_debug_line(window, cx);
2462
2463 editor.minimap =
2464 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2465 editor.colors = Some(LspColorData::new(cx));
2466 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2467
2468 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2469 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2470 }
2471 editor.update_lsp_data(None, window, cx);
2472 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2473 }
2474
2475 editor
2476 }
2477
2478 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2479 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2480 }
2481
2482 pub fn deploy_mouse_context_menu(
2483 &mut self,
2484 position: gpui::Point<Pixels>,
2485 context_menu: Entity<ContextMenu>,
2486 window: &mut Window,
2487 cx: &mut Context<Self>,
2488 ) {
2489 self.mouse_context_menu = Some(MouseContextMenu::new(
2490 self,
2491 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2492 context_menu,
2493 window,
2494 cx,
2495 ));
2496 }
2497
2498 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2499 self.mouse_context_menu
2500 .as_ref()
2501 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2502 }
2503
2504 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2505 if self
2506 .selections
2507 .pending_anchor()
2508 .is_some_and(|pending_selection| {
2509 let snapshot = self.buffer().read(cx).snapshot(cx);
2510 pending_selection.range().includes(range, &snapshot)
2511 })
2512 {
2513 return true;
2514 }
2515
2516 self.selections
2517 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2518 .into_iter()
2519 .any(|selection| {
2520 // This is needed to cover a corner case, if we just check for an existing
2521 // selection in the fold range, having a cursor at the start of the fold
2522 // marks it as selected. Non-empty selections don't cause this.
2523 let length = selection.end - selection.start;
2524 length > 0
2525 })
2526 }
2527
2528 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2529 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2530 }
2531
2532 fn key_context_internal(
2533 &self,
2534 has_active_edit_prediction: bool,
2535 window: &mut Window,
2536 cx: &mut App,
2537 ) -> KeyContext {
2538 let mut key_context = KeyContext::new_with_defaults();
2539 key_context.add("Editor");
2540 let mode = match self.mode {
2541 EditorMode::SingleLine => "single_line",
2542 EditorMode::AutoHeight { .. } => "auto_height",
2543 EditorMode::Minimap { .. } => "minimap",
2544 EditorMode::Full { .. } => "full",
2545 };
2546
2547 if EditorSettings::jupyter_enabled(cx) {
2548 key_context.add("jupyter");
2549 }
2550
2551 key_context.set("mode", mode);
2552 if self.pending_rename.is_some() {
2553 key_context.add("renaming");
2554 }
2555
2556 if let Some(snippet_stack) = self.snippet_stack.last() {
2557 key_context.add("in_snippet");
2558
2559 if snippet_stack.active_index > 0 {
2560 key_context.add("has_previous_tabstop");
2561 }
2562
2563 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2564 key_context.add("has_next_tabstop");
2565 }
2566 }
2567
2568 match self.context_menu.borrow().as_ref() {
2569 Some(CodeContextMenu::Completions(menu)) => {
2570 if menu.visible() {
2571 key_context.add("menu");
2572 key_context.add("showing_completions");
2573 }
2574 }
2575 Some(CodeContextMenu::CodeActions(menu)) => {
2576 if menu.visible() {
2577 key_context.add("menu");
2578 key_context.add("showing_code_actions")
2579 }
2580 }
2581 None => {}
2582 }
2583
2584 if self.signature_help_state.has_multiple_signatures() {
2585 key_context.add("showing_signature_help");
2586 }
2587
2588 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2589 if !self.focus_handle(cx).contains_focused(window, cx)
2590 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2591 {
2592 for addon in self.addons.values() {
2593 addon.extend_key_context(&mut key_context, cx)
2594 }
2595 }
2596
2597 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2598 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2599 Some(
2600 file.full_path(cx)
2601 .extension()?
2602 .to_string_lossy()
2603 .into_owned(),
2604 )
2605 }) {
2606 key_context.set("extension", extension);
2607 }
2608 } else {
2609 key_context.add("multibuffer");
2610 }
2611
2612 if has_active_edit_prediction {
2613 if self.edit_prediction_in_conflict() {
2614 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2615 } else {
2616 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2617 key_context.add("copilot_suggestion");
2618 }
2619 }
2620
2621 if self.selection_mark_mode {
2622 key_context.add("selection_mode");
2623 }
2624
2625 let disjoint = self.selections.disjoint_anchors();
2626 let snapshot = self.snapshot(window, cx);
2627 let snapshot = snapshot.buffer_snapshot();
2628 if self.mode == EditorMode::SingleLine
2629 && let [selection] = disjoint
2630 && selection.start == selection.end
2631 && selection.end.to_offset(snapshot) == snapshot.len()
2632 {
2633 key_context.add("end_of_input");
2634 }
2635
2636 key_context
2637 }
2638
2639 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2640 self.last_bounds.as_ref()
2641 }
2642
2643 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2644 if self.mouse_cursor_hidden {
2645 self.mouse_cursor_hidden = false;
2646 cx.notify();
2647 }
2648 }
2649
2650 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2651 let hide_mouse_cursor = match origin {
2652 HideMouseCursorOrigin::TypingAction => {
2653 matches!(
2654 self.hide_mouse_mode,
2655 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2656 )
2657 }
2658 HideMouseCursorOrigin::MovementAction => {
2659 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2660 }
2661 };
2662 if self.mouse_cursor_hidden != hide_mouse_cursor {
2663 self.mouse_cursor_hidden = hide_mouse_cursor;
2664 cx.notify();
2665 }
2666 }
2667
2668 pub fn edit_prediction_in_conflict(&self) -> bool {
2669 if !self.show_edit_predictions_in_menu() {
2670 return false;
2671 }
2672
2673 let showing_completions = self
2674 .context_menu
2675 .borrow()
2676 .as_ref()
2677 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2678
2679 showing_completions
2680 || self.edit_prediction_requires_modifier()
2681 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2682 // bindings to insert tab characters.
2683 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2684 }
2685
2686 pub fn accept_edit_prediction_keybind(
2687 &self,
2688 accept_partial: bool,
2689 window: &mut Window,
2690 cx: &mut App,
2691 ) -> AcceptEditPredictionBinding {
2692 let key_context = self.key_context_internal(true, window, cx);
2693 let in_conflict = self.edit_prediction_in_conflict();
2694
2695 let bindings = if accept_partial {
2696 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2697 } else {
2698 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2699 };
2700
2701 // TODO: if the binding contains multiple keystrokes, display all of them, not
2702 // just the first one.
2703 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2704 !in_conflict
2705 || binding
2706 .keystrokes()
2707 .first()
2708 .is_some_and(|keystroke| keystroke.modifiers().modified())
2709 }))
2710 }
2711
2712 pub fn new_file(
2713 workspace: &mut Workspace,
2714 _: &workspace::NewFile,
2715 window: &mut Window,
2716 cx: &mut Context<Workspace>,
2717 ) {
2718 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2719 "Failed to create buffer",
2720 window,
2721 cx,
2722 |e, _, _| match e.error_code() {
2723 ErrorCode::RemoteUpgradeRequired => Some(format!(
2724 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2725 e.error_tag("required").unwrap_or("the latest version")
2726 )),
2727 _ => None,
2728 },
2729 );
2730 }
2731
2732 pub fn new_in_workspace(
2733 workspace: &mut Workspace,
2734 window: &mut Window,
2735 cx: &mut Context<Workspace>,
2736 ) -> Task<Result<Entity<Editor>>> {
2737 let project = workspace.project().clone();
2738 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2739
2740 cx.spawn_in(window, async move |workspace, cx| {
2741 let buffer = create.await?;
2742 workspace.update_in(cx, |workspace, window, cx| {
2743 let editor =
2744 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2745 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2746 editor
2747 })
2748 })
2749 }
2750
2751 fn new_file_vertical(
2752 workspace: &mut Workspace,
2753 _: &workspace::NewFileSplitVertical,
2754 window: &mut Window,
2755 cx: &mut Context<Workspace>,
2756 ) {
2757 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2758 }
2759
2760 fn new_file_horizontal(
2761 workspace: &mut Workspace,
2762 _: &workspace::NewFileSplitHorizontal,
2763 window: &mut Window,
2764 cx: &mut Context<Workspace>,
2765 ) {
2766 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2767 }
2768
2769 fn new_file_split(
2770 workspace: &mut Workspace,
2771 action: &workspace::NewFileSplit,
2772 window: &mut Window,
2773 cx: &mut Context<Workspace>,
2774 ) {
2775 Self::new_file_in_direction(workspace, action.0, window, cx)
2776 }
2777
2778 fn new_file_in_direction(
2779 workspace: &mut Workspace,
2780 direction: SplitDirection,
2781 window: &mut Window,
2782 cx: &mut Context<Workspace>,
2783 ) {
2784 let project = workspace.project().clone();
2785 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2786
2787 cx.spawn_in(window, async move |workspace, cx| {
2788 let buffer = create.await?;
2789 workspace.update_in(cx, move |workspace, window, cx| {
2790 workspace.split_item(
2791 direction,
2792 Box::new(
2793 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2794 ),
2795 window,
2796 cx,
2797 )
2798 })?;
2799 anyhow::Ok(())
2800 })
2801 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2802 match e.error_code() {
2803 ErrorCode::RemoteUpgradeRequired => Some(format!(
2804 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2805 e.error_tag("required").unwrap_or("the latest version")
2806 )),
2807 _ => None,
2808 }
2809 });
2810 }
2811
2812 pub fn leader_id(&self) -> Option<CollaboratorId> {
2813 self.leader_id
2814 }
2815
2816 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2817 &self.buffer
2818 }
2819
2820 pub fn project(&self) -> Option<&Entity<Project>> {
2821 self.project.as_ref()
2822 }
2823
2824 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2825 self.workspace.as_ref()?.0.upgrade()
2826 }
2827
2828 /// Returns the workspace serialization ID if this editor should be serialized.
2829 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2830 self.workspace
2831 .as_ref()
2832 .filter(|_| self.should_serialize_buffer())
2833 .and_then(|workspace| workspace.1)
2834 }
2835
2836 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2837 self.buffer().read(cx).title(cx)
2838 }
2839
2840 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2841 let git_blame_gutter_max_author_length = self
2842 .render_git_blame_gutter(cx)
2843 .then(|| {
2844 if let Some(blame) = self.blame.as_ref() {
2845 let max_author_length =
2846 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2847 Some(max_author_length)
2848 } else {
2849 None
2850 }
2851 })
2852 .flatten();
2853
2854 EditorSnapshot {
2855 mode: self.mode.clone(),
2856 show_gutter: self.show_gutter,
2857 show_line_numbers: self.show_line_numbers,
2858 show_git_diff_gutter: self.show_git_diff_gutter,
2859 show_code_actions: self.show_code_actions,
2860 show_runnables: self.show_runnables,
2861 show_breakpoints: self.show_breakpoints,
2862 git_blame_gutter_max_author_length,
2863 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2864 placeholder_display_snapshot: self
2865 .placeholder_display_map
2866 .as_ref()
2867 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2868 scroll_anchor: self.scroll_manager.anchor(),
2869 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2870 is_focused: self.focus_handle.is_focused(window),
2871 current_line_highlight: self
2872 .current_line_highlight
2873 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2874 gutter_hovered: self.gutter_hovered,
2875 }
2876 }
2877
2878 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2879 self.buffer.read(cx).language_at(point, cx)
2880 }
2881
2882 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2883 self.buffer.read(cx).read(cx).file_at(point).cloned()
2884 }
2885
2886 pub fn active_excerpt(
2887 &self,
2888 cx: &App,
2889 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2890 self.buffer
2891 .read(cx)
2892 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2893 }
2894
2895 pub fn mode(&self) -> &EditorMode {
2896 &self.mode
2897 }
2898
2899 pub fn set_mode(&mut self, mode: EditorMode) {
2900 self.mode = mode;
2901 }
2902
2903 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2904 self.collaboration_hub.as_deref()
2905 }
2906
2907 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2908 self.collaboration_hub = Some(hub);
2909 }
2910
2911 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2912 self.in_project_search = in_project_search;
2913 }
2914
2915 pub fn set_custom_context_menu(
2916 &mut self,
2917 f: impl 'static
2918 + Fn(
2919 &mut Self,
2920 DisplayPoint,
2921 &mut Window,
2922 &mut Context<Self>,
2923 ) -> Option<Entity<ui::ContextMenu>>,
2924 ) {
2925 self.custom_context_menu = Some(Box::new(f))
2926 }
2927
2928 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2929 self.completion_provider = provider;
2930 }
2931
2932 #[cfg(any(test, feature = "test-support"))]
2933 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2934 self.completion_provider.clone()
2935 }
2936
2937 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2938 self.semantics_provider.clone()
2939 }
2940
2941 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2942 self.semantics_provider = provider;
2943 }
2944
2945 pub fn set_edit_prediction_provider<T>(
2946 &mut self,
2947 provider: Option<Entity<T>>,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) where
2951 T: EditPredictionProvider,
2952 {
2953 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2954 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2955 if this.focus_handle.is_focused(window) {
2956 this.update_visible_edit_prediction(window, cx);
2957 }
2958 }),
2959 provider: Arc::new(provider),
2960 });
2961 self.update_edit_prediction_settings(cx);
2962 self.refresh_edit_prediction(false, false, window, cx);
2963 }
2964
2965 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2966 self.placeholder_display_map
2967 .as_ref()
2968 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2969 }
2970
2971 pub fn set_placeholder_text(
2972 &mut self,
2973 placeholder_text: &str,
2974 window: &mut Window,
2975 cx: &mut Context<Self>,
2976 ) {
2977 let multibuffer = cx
2978 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2979
2980 let style = window.text_style();
2981
2982 self.placeholder_display_map = Some(cx.new(|cx| {
2983 DisplayMap::new(
2984 multibuffer,
2985 style.font(),
2986 style.font_size.to_pixels(window.rem_size()),
2987 None,
2988 FILE_HEADER_HEIGHT,
2989 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2990 Default::default(),
2991 DiagnosticSeverity::Off,
2992 cx,
2993 )
2994 }));
2995 cx.notify();
2996 }
2997
2998 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2999 self.cursor_shape = cursor_shape;
3000
3001 // Disrupt blink for immediate user feedback that the cursor shape has changed
3002 self.blink_manager.update(cx, BlinkManager::show_cursor);
3003
3004 cx.notify();
3005 }
3006
3007 pub fn set_current_line_highlight(
3008 &mut self,
3009 current_line_highlight: Option<CurrentLineHighlight>,
3010 ) {
3011 self.current_line_highlight = current_line_highlight;
3012 }
3013
3014 pub fn range_for_match<T: std::marker::Copy>(
3015 &self,
3016 range: &Range<T>,
3017 collapse: bool,
3018 ) -> Range<T> {
3019 if collapse {
3020 return range.start..range.start;
3021 }
3022 range.clone()
3023 }
3024
3025 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3026 if self.display_map.read(cx).clip_at_line_ends != clip {
3027 self.display_map
3028 .update(cx, |map, _| map.clip_at_line_ends = clip);
3029 }
3030 }
3031
3032 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3033 self.input_enabled = input_enabled;
3034 }
3035
3036 pub fn set_edit_predictions_hidden_for_vim_mode(
3037 &mut self,
3038 hidden: bool,
3039 window: &mut Window,
3040 cx: &mut Context<Self>,
3041 ) {
3042 if hidden != self.edit_predictions_hidden_for_vim_mode {
3043 self.edit_predictions_hidden_for_vim_mode = hidden;
3044 if hidden {
3045 self.update_visible_edit_prediction(window, cx);
3046 } else {
3047 self.refresh_edit_prediction(true, false, window, cx);
3048 }
3049 }
3050 }
3051
3052 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3053 self.menu_edit_predictions_policy = value;
3054 }
3055
3056 pub fn set_autoindent(&mut self, autoindent: bool) {
3057 if autoindent {
3058 self.autoindent_mode = Some(AutoindentMode::EachLine);
3059 } else {
3060 self.autoindent_mode = None;
3061 }
3062 }
3063
3064 pub fn read_only(&self, cx: &App) -> bool {
3065 self.read_only || self.buffer.read(cx).read_only()
3066 }
3067
3068 pub fn set_read_only(&mut self, read_only: bool) {
3069 self.read_only = read_only;
3070 }
3071
3072 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3073 self.use_autoclose = autoclose;
3074 }
3075
3076 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3077 self.use_auto_surround = auto_surround;
3078 }
3079
3080 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3081 self.auto_replace_emoji_shortcode = auto_replace;
3082 }
3083
3084 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3085 self.buffer_serialization = should_serialize.then(|| {
3086 BufferSerialization::new(
3087 ProjectSettings::get_global(cx)
3088 .session
3089 .restore_unsaved_buffers,
3090 )
3091 })
3092 }
3093
3094 fn should_serialize_buffer(&self) -> bool {
3095 self.buffer_serialization.is_some()
3096 }
3097
3098 pub fn toggle_edit_predictions(
3099 &mut self,
3100 _: &ToggleEditPrediction,
3101 window: &mut Window,
3102 cx: &mut Context<Self>,
3103 ) {
3104 if self.show_edit_predictions_override.is_some() {
3105 self.set_show_edit_predictions(None, window, cx);
3106 } else {
3107 let show_edit_predictions = !self.edit_predictions_enabled();
3108 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3109 }
3110 }
3111
3112 pub fn set_show_edit_predictions(
3113 &mut self,
3114 show_edit_predictions: Option<bool>,
3115 window: &mut Window,
3116 cx: &mut Context<Self>,
3117 ) {
3118 self.show_edit_predictions_override = show_edit_predictions;
3119 self.update_edit_prediction_settings(cx);
3120
3121 if let Some(false) = show_edit_predictions {
3122 self.discard_edit_prediction(false, cx);
3123 } else {
3124 self.refresh_edit_prediction(false, true, window, cx);
3125 }
3126 }
3127
3128 fn edit_predictions_disabled_in_scope(
3129 &self,
3130 buffer: &Entity<Buffer>,
3131 buffer_position: language::Anchor,
3132 cx: &App,
3133 ) -> bool {
3134 let snapshot = buffer.read(cx).snapshot();
3135 let settings = snapshot.settings_at(buffer_position, cx);
3136
3137 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3138 return false;
3139 };
3140
3141 scope.override_name().is_some_and(|scope_name| {
3142 settings
3143 .edit_predictions_disabled_in
3144 .iter()
3145 .any(|s| s == scope_name)
3146 })
3147 }
3148
3149 pub fn set_use_modal_editing(&mut self, to: bool) {
3150 self.use_modal_editing = to;
3151 }
3152
3153 pub fn use_modal_editing(&self) -> bool {
3154 self.use_modal_editing
3155 }
3156
3157 fn selections_did_change(
3158 &mut self,
3159 local: bool,
3160 old_cursor_position: &Anchor,
3161 effects: SelectionEffects,
3162 window: &mut Window,
3163 cx: &mut Context<Self>,
3164 ) {
3165 window.invalidate_character_coordinates();
3166
3167 // Copy selections to primary selection buffer
3168 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3169 if local {
3170 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3171 let buffer_handle = self.buffer.read(cx).read(cx);
3172
3173 let mut text = String::new();
3174 for (index, selection) in selections.iter().enumerate() {
3175 let text_for_selection = buffer_handle
3176 .text_for_range(selection.start..selection.end)
3177 .collect::<String>();
3178
3179 text.push_str(&text_for_selection);
3180 if index != selections.len() - 1 {
3181 text.push('\n');
3182 }
3183 }
3184
3185 if !text.is_empty() {
3186 cx.write_to_primary(ClipboardItem::new_string(text));
3187 }
3188 }
3189
3190 let selection_anchors = self.selections.disjoint_anchors_arc();
3191
3192 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3193 self.buffer.update(cx, |buffer, cx| {
3194 buffer.set_active_selections(
3195 &selection_anchors,
3196 self.selections.line_mode(),
3197 self.cursor_shape,
3198 cx,
3199 )
3200 });
3201 }
3202 let display_map = self
3203 .display_map
3204 .update(cx, |display_map, cx| display_map.snapshot(cx));
3205 let buffer = display_map.buffer_snapshot();
3206 if self.selections.count() == 1 {
3207 self.add_selections_state = None;
3208 }
3209 self.select_next_state = None;
3210 self.select_prev_state = None;
3211 self.select_syntax_node_history.try_clear();
3212 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3213 self.snippet_stack.invalidate(&selection_anchors, buffer);
3214 self.take_rename(false, window, cx);
3215
3216 let newest_selection = self.selections.newest_anchor();
3217 let new_cursor_position = newest_selection.head();
3218 let selection_start = newest_selection.start;
3219
3220 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3221 self.push_to_nav_history(
3222 *old_cursor_position,
3223 Some(new_cursor_position.to_point(buffer)),
3224 false,
3225 effects.nav_history == Some(true),
3226 cx,
3227 );
3228 }
3229
3230 if local {
3231 if let Some(buffer_id) = new_cursor_position.buffer_id {
3232 self.register_buffer(buffer_id, cx);
3233 }
3234
3235 let mut context_menu = self.context_menu.borrow_mut();
3236 let completion_menu = match context_menu.as_ref() {
3237 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3238 Some(CodeContextMenu::CodeActions(_)) => {
3239 *context_menu = None;
3240 None
3241 }
3242 None => None,
3243 };
3244 let completion_position = completion_menu.map(|menu| menu.initial_position);
3245 drop(context_menu);
3246
3247 if effects.completions
3248 && let Some(completion_position) = completion_position
3249 {
3250 let start_offset = selection_start.to_offset(buffer);
3251 let position_matches = start_offset == completion_position.to_offset(buffer);
3252 let continue_showing = if position_matches {
3253 if self.snippet_stack.is_empty() {
3254 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3255 == Some(CharKind::Word)
3256 } else {
3257 // Snippet choices can be shown even when the cursor is in whitespace.
3258 // Dismissing the menu with actions like backspace is handled by
3259 // invalidation regions.
3260 true
3261 }
3262 } else {
3263 false
3264 };
3265
3266 if continue_showing {
3267 self.open_or_update_completions_menu(None, None, false, window, cx);
3268 } else {
3269 self.hide_context_menu(window, cx);
3270 }
3271 }
3272
3273 hide_hover(self, cx);
3274
3275 if old_cursor_position.to_display_point(&display_map).row()
3276 != new_cursor_position.to_display_point(&display_map).row()
3277 {
3278 self.available_code_actions.take();
3279 }
3280 self.refresh_code_actions(window, cx);
3281 self.refresh_document_highlights(cx);
3282 refresh_linked_ranges(self, window, cx);
3283
3284 self.refresh_selected_text_highlights(false, window, cx);
3285 self.refresh_matching_bracket_highlights(window, cx);
3286 self.update_visible_edit_prediction(window, cx);
3287 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3288 self.inline_blame_popover.take();
3289 if self.git_blame_inline_enabled {
3290 self.start_inline_blame_timer(window, cx);
3291 }
3292 }
3293
3294 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3295 cx.emit(EditorEvent::SelectionsChanged { local });
3296
3297 let selections = &self.selections.disjoint_anchors_arc();
3298 if selections.len() == 1 {
3299 cx.emit(SearchEvent::ActiveMatchChanged)
3300 }
3301 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3302 let inmemory_selections = selections
3303 .iter()
3304 .map(|s| {
3305 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3306 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3307 })
3308 .collect();
3309 self.update_restoration_data(cx, |data| {
3310 data.selections = inmemory_selections;
3311 });
3312
3313 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3314 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3315 {
3316 let snapshot = self.buffer().read(cx).snapshot(cx);
3317 let selections = selections.clone();
3318 let background_executor = cx.background_executor().clone();
3319 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3320 self.serialize_selections = cx.background_spawn(async move {
3321 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3322 let db_selections = selections
3323 .iter()
3324 .map(|selection| {
3325 (
3326 selection.start.to_offset(&snapshot),
3327 selection.end.to_offset(&snapshot),
3328 )
3329 })
3330 .collect();
3331
3332 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3333 .await
3334 .with_context(|| {
3335 format!(
3336 "persisting editor selections for editor {editor_id}, \
3337 workspace {workspace_id:?}"
3338 )
3339 })
3340 .log_err();
3341 });
3342 }
3343 }
3344
3345 cx.notify();
3346 }
3347
3348 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3349 use text::ToOffset as _;
3350 use text::ToPoint as _;
3351
3352 if self.mode.is_minimap()
3353 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3354 {
3355 return;
3356 }
3357
3358 if !self.buffer().read(cx).is_singleton() {
3359 return;
3360 }
3361
3362 let display_snapshot = self
3363 .display_map
3364 .update(cx, |display_map, cx| display_map.snapshot(cx));
3365 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3366 return;
3367 };
3368 let inmemory_folds = display_snapshot
3369 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3370 .map(|fold| {
3371 fold.range.start.text_anchor.to_point(&snapshot)
3372 ..fold.range.end.text_anchor.to_point(&snapshot)
3373 })
3374 .collect();
3375 self.update_restoration_data(cx, |data| {
3376 data.folds = inmemory_folds;
3377 });
3378
3379 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3380 return;
3381 };
3382 let background_executor = cx.background_executor().clone();
3383 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3384 let db_folds = display_snapshot
3385 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3386 .map(|fold| {
3387 (
3388 fold.range.start.text_anchor.to_offset(&snapshot),
3389 fold.range.end.text_anchor.to_offset(&snapshot),
3390 )
3391 })
3392 .collect();
3393 self.serialize_folds = cx.background_spawn(async move {
3394 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3395 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3396 .await
3397 .with_context(|| {
3398 format!(
3399 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3400 )
3401 })
3402 .log_err();
3403 });
3404 }
3405
3406 pub fn sync_selections(
3407 &mut self,
3408 other: Entity<Editor>,
3409 cx: &mut Context<Self>,
3410 ) -> gpui::Subscription {
3411 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3412 if !other_selections.is_empty() {
3413 self.selections
3414 .change_with(&self.display_snapshot(cx), |selections| {
3415 selections.select_anchors(other_selections);
3416 });
3417 }
3418
3419 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3420 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3421 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3422 if other_selections.is_empty() {
3423 return;
3424 }
3425 let snapshot = this.display_snapshot(cx);
3426 this.selections.change_with(&snapshot, |selections| {
3427 selections.select_anchors(other_selections);
3428 });
3429 }
3430 });
3431
3432 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3433 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3434 let these_selections = this.selections.disjoint_anchors().to_vec();
3435 if these_selections.is_empty() {
3436 return;
3437 }
3438 other.update(cx, |other_editor, cx| {
3439 let snapshot = other_editor.display_snapshot(cx);
3440 other_editor
3441 .selections
3442 .change_with(&snapshot, |selections| {
3443 selections.select_anchors(these_selections);
3444 })
3445 });
3446 }
3447 });
3448
3449 Subscription::join(other_subscription, this_subscription)
3450 }
3451
3452 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3453 if self.buffer().read(cx).is_singleton() {
3454 return;
3455 }
3456 let snapshot = self.buffer.read(cx).snapshot(cx);
3457 let buffer_ids: HashSet<BufferId> = self
3458 .selections
3459 .disjoint_anchor_ranges()
3460 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3461 .collect();
3462 for buffer_id in buffer_ids {
3463 self.unfold_buffer(buffer_id, cx);
3464 }
3465 }
3466
3467 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3468 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3469 /// effects of selection change occur at the end of the transaction.
3470 pub fn change_selections<R>(
3471 &mut self,
3472 effects: SelectionEffects,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3476 ) -> R {
3477 let snapshot = self.display_snapshot(cx);
3478 if let Some(state) = &mut self.deferred_selection_effects_state {
3479 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3480 state.effects.completions = effects.completions;
3481 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3482 let (changed, result) = self.selections.change_with(&snapshot, change);
3483 state.changed |= changed;
3484 return result;
3485 }
3486 let mut state = DeferredSelectionEffectsState {
3487 changed: false,
3488 effects,
3489 old_cursor_position: self.selections.newest_anchor().head(),
3490 history_entry: SelectionHistoryEntry {
3491 selections: self.selections.disjoint_anchors_arc(),
3492 select_next_state: self.select_next_state.clone(),
3493 select_prev_state: self.select_prev_state.clone(),
3494 add_selections_state: self.add_selections_state.clone(),
3495 },
3496 };
3497 let (changed, result) = self.selections.change_with(&snapshot, change);
3498 state.changed = state.changed || changed;
3499 if self.defer_selection_effects {
3500 self.deferred_selection_effects_state = Some(state);
3501 } else {
3502 self.apply_selection_effects(state, window, cx);
3503 }
3504 result
3505 }
3506
3507 /// Defers the effects of selection change, so that the effects of multiple calls to
3508 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3509 /// to selection history and the state of popovers based on selection position aren't
3510 /// erroneously updated.
3511 pub fn with_selection_effects_deferred<R>(
3512 &mut self,
3513 window: &mut Window,
3514 cx: &mut Context<Self>,
3515 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3516 ) -> R {
3517 let already_deferred = self.defer_selection_effects;
3518 self.defer_selection_effects = true;
3519 let result = update(self, window, cx);
3520 if !already_deferred {
3521 self.defer_selection_effects = false;
3522 if let Some(state) = self.deferred_selection_effects_state.take() {
3523 self.apply_selection_effects(state, window, cx);
3524 }
3525 }
3526 result
3527 }
3528
3529 fn apply_selection_effects(
3530 &mut self,
3531 state: DeferredSelectionEffectsState,
3532 window: &mut Window,
3533 cx: &mut Context<Self>,
3534 ) {
3535 if state.changed {
3536 self.selection_history.push(state.history_entry);
3537
3538 if let Some(autoscroll) = state.effects.scroll {
3539 self.request_autoscroll(autoscroll, cx);
3540 }
3541
3542 let old_cursor_position = &state.old_cursor_position;
3543
3544 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3545
3546 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3547 self.show_signature_help(&ShowSignatureHelp, window, cx);
3548 }
3549 }
3550 }
3551
3552 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3553 where
3554 I: IntoIterator<Item = (Range<S>, T)>,
3555 S: ToOffset,
3556 T: Into<Arc<str>>,
3557 {
3558 if self.read_only(cx) {
3559 return;
3560 }
3561
3562 self.buffer
3563 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3564 }
3565
3566 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3567 where
3568 I: IntoIterator<Item = (Range<S>, T)>,
3569 S: ToOffset,
3570 T: Into<Arc<str>>,
3571 {
3572 if self.read_only(cx) {
3573 return;
3574 }
3575
3576 self.buffer.update(cx, |buffer, cx| {
3577 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3578 });
3579 }
3580
3581 pub fn edit_with_block_indent<I, S, T>(
3582 &mut self,
3583 edits: I,
3584 original_indent_columns: Vec<Option<u32>>,
3585 cx: &mut Context<Self>,
3586 ) where
3587 I: IntoIterator<Item = (Range<S>, T)>,
3588 S: ToOffset,
3589 T: Into<Arc<str>>,
3590 {
3591 if self.read_only(cx) {
3592 return;
3593 }
3594
3595 self.buffer.update(cx, |buffer, cx| {
3596 buffer.edit(
3597 edits,
3598 Some(AutoindentMode::Block {
3599 original_indent_columns,
3600 }),
3601 cx,
3602 )
3603 });
3604 }
3605
3606 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3607 self.hide_context_menu(window, cx);
3608
3609 match phase {
3610 SelectPhase::Begin {
3611 position,
3612 add,
3613 click_count,
3614 } => self.begin_selection(position, add, click_count, window, cx),
3615 SelectPhase::BeginColumnar {
3616 position,
3617 goal_column,
3618 reset,
3619 mode,
3620 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3621 SelectPhase::Extend {
3622 position,
3623 click_count,
3624 } => self.extend_selection(position, click_count, window, cx),
3625 SelectPhase::Update {
3626 position,
3627 goal_column,
3628 scroll_delta,
3629 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3630 SelectPhase::End => self.end_selection(window, cx),
3631 }
3632 }
3633
3634 fn extend_selection(
3635 &mut self,
3636 position: DisplayPoint,
3637 click_count: usize,
3638 window: &mut Window,
3639 cx: &mut Context<Self>,
3640 ) {
3641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3642 let tail = self.selections.newest::<usize>(&display_map).tail();
3643 let click_count = click_count.max(match self.selections.select_mode() {
3644 SelectMode::Character => 1,
3645 SelectMode::Word(_) => 2,
3646 SelectMode::Line(_) => 3,
3647 SelectMode::All => 4,
3648 });
3649 self.begin_selection(position, false, click_count, window, cx);
3650
3651 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3652
3653 let current_selection = match self.selections.select_mode() {
3654 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3655 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3656 };
3657
3658 let mut pending_selection = self
3659 .selections
3660 .pending_anchor()
3661 .cloned()
3662 .expect("extend_selection not called with pending selection");
3663
3664 if pending_selection
3665 .start
3666 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3667 == Ordering::Greater
3668 {
3669 pending_selection.start = current_selection.start;
3670 }
3671 if pending_selection
3672 .end
3673 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3674 == Ordering::Less
3675 {
3676 pending_selection.end = current_selection.end;
3677 pending_selection.reversed = true;
3678 }
3679
3680 let mut pending_mode = self.selections.pending_mode().unwrap();
3681 match &mut pending_mode {
3682 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3683 _ => {}
3684 }
3685
3686 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3687 SelectionEffects::scroll(Autoscroll::fit())
3688 } else {
3689 SelectionEffects::no_scroll()
3690 };
3691
3692 self.change_selections(effects, window, cx, |s| {
3693 s.set_pending(pending_selection.clone(), pending_mode);
3694 s.set_is_extending(true);
3695 });
3696 }
3697
3698 fn begin_selection(
3699 &mut self,
3700 position: DisplayPoint,
3701 add: bool,
3702 click_count: usize,
3703 window: &mut Window,
3704 cx: &mut Context<Self>,
3705 ) {
3706 if !self.focus_handle.is_focused(window) {
3707 self.last_focused_descendant = None;
3708 window.focus(&self.focus_handle);
3709 }
3710
3711 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3712 let buffer = display_map.buffer_snapshot();
3713 let position = display_map.clip_point(position, Bias::Left);
3714
3715 let start;
3716 let end;
3717 let mode;
3718 let mut auto_scroll;
3719 match click_count {
3720 1 => {
3721 start = buffer.anchor_before(position.to_point(&display_map));
3722 end = start;
3723 mode = SelectMode::Character;
3724 auto_scroll = true;
3725 }
3726 2 => {
3727 let position = display_map
3728 .clip_point(position, Bias::Left)
3729 .to_offset(&display_map, Bias::Left);
3730 let (range, _) = buffer.surrounding_word(position, None);
3731 start = buffer.anchor_before(range.start);
3732 end = buffer.anchor_before(range.end);
3733 mode = SelectMode::Word(start..end);
3734 auto_scroll = true;
3735 }
3736 3 => {
3737 let position = display_map
3738 .clip_point(position, Bias::Left)
3739 .to_point(&display_map);
3740 let line_start = display_map.prev_line_boundary(position).0;
3741 let next_line_start = buffer.clip_point(
3742 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3743 Bias::Left,
3744 );
3745 start = buffer.anchor_before(line_start);
3746 end = buffer.anchor_before(next_line_start);
3747 mode = SelectMode::Line(start..end);
3748 auto_scroll = true;
3749 }
3750 _ => {
3751 start = buffer.anchor_before(0);
3752 end = buffer.anchor_before(buffer.len());
3753 mode = SelectMode::All;
3754 auto_scroll = false;
3755 }
3756 }
3757 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3758
3759 let point_to_delete: Option<usize> = {
3760 let selected_points: Vec<Selection<Point>> =
3761 self.selections.disjoint_in_range(start..end, &display_map);
3762
3763 if !add || click_count > 1 {
3764 None
3765 } else if !selected_points.is_empty() {
3766 Some(selected_points[0].id)
3767 } else {
3768 let clicked_point_already_selected =
3769 self.selections.disjoint_anchors().iter().find(|selection| {
3770 selection.start.to_point(buffer) == start.to_point(buffer)
3771 || selection.end.to_point(buffer) == end.to_point(buffer)
3772 });
3773
3774 clicked_point_already_selected.map(|selection| selection.id)
3775 }
3776 };
3777
3778 let selections_count = self.selections.count();
3779 let effects = if auto_scroll {
3780 SelectionEffects::default()
3781 } else {
3782 SelectionEffects::no_scroll()
3783 };
3784
3785 self.change_selections(effects, window, cx, |s| {
3786 if let Some(point_to_delete) = point_to_delete {
3787 s.delete(point_to_delete);
3788
3789 if selections_count == 1 {
3790 s.set_pending_anchor_range(start..end, mode);
3791 }
3792 } else {
3793 if !add {
3794 s.clear_disjoint();
3795 }
3796
3797 s.set_pending_anchor_range(start..end, mode);
3798 }
3799 });
3800 }
3801
3802 fn begin_columnar_selection(
3803 &mut self,
3804 position: DisplayPoint,
3805 goal_column: u32,
3806 reset: bool,
3807 mode: ColumnarMode,
3808 window: &mut Window,
3809 cx: &mut Context<Self>,
3810 ) {
3811 if !self.focus_handle.is_focused(window) {
3812 self.last_focused_descendant = None;
3813 window.focus(&self.focus_handle);
3814 }
3815
3816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3817
3818 if reset {
3819 let pointer_position = display_map
3820 .buffer_snapshot()
3821 .anchor_before(position.to_point(&display_map));
3822
3823 self.change_selections(
3824 SelectionEffects::scroll(Autoscroll::newest()),
3825 window,
3826 cx,
3827 |s| {
3828 s.clear_disjoint();
3829 s.set_pending_anchor_range(
3830 pointer_position..pointer_position,
3831 SelectMode::Character,
3832 );
3833 },
3834 );
3835 };
3836
3837 let tail = self.selections.newest::<Point>(&display_map).tail();
3838 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3839 self.columnar_selection_state = match mode {
3840 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3841 selection_tail: selection_anchor,
3842 display_point: if reset {
3843 if position.column() != goal_column {
3844 Some(DisplayPoint::new(position.row(), goal_column))
3845 } else {
3846 None
3847 }
3848 } else {
3849 None
3850 },
3851 }),
3852 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3853 selection_tail: selection_anchor,
3854 }),
3855 };
3856
3857 if !reset {
3858 self.select_columns(position, goal_column, &display_map, window, cx);
3859 }
3860 }
3861
3862 fn update_selection(
3863 &mut self,
3864 position: DisplayPoint,
3865 goal_column: u32,
3866 scroll_delta: gpui::Point<f32>,
3867 window: &mut Window,
3868 cx: &mut Context<Self>,
3869 ) {
3870 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3871
3872 if self.columnar_selection_state.is_some() {
3873 self.select_columns(position, goal_column, &display_map, window, cx);
3874 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3875 let buffer = display_map.buffer_snapshot();
3876 let head;
3877 let tail;
3878 let mode = self.selections.pending_mode().unwrap();
3879 match &mode {
3880 SelectMode::Character => {
3881 head = position.to_point(&display_map);
3882 tail = pending.tail().to_point(buffer);
3883 }
3884 SelectMode::Word(original_range) => {
3885 let offset = display_map
3886 .clip_point(position, Bias::Left)
3887 .to_offset(&display_map, Bias::Left);
3888 let original_range = original_range.to_offset(buffer);
3889
3890 let head_offset = if buffer.is_inside_word(offset, None)
3891 || original_range.contains(&offset)
3892 {
3893 let (word_range, _) = buffer.surrounding_word(offset, None);
3894 if word_range.start < original_range.start {
3895 word_range.start
3896 } else {
3897 word_range.end
3898 }
3899 } else {
3900 offset
3901 };
3902
3903 head = head_offset.to_point(buffer);
3904 if head_offset <= original_range.start {
3905 tail = original_range.end.to_point(buffer);
3906 } else {
3907 tail = original_range.start.to_point(buffer);
3908 }
3909 }
3910 SelectMode::Line(original_range) => {
3911 let original_range = original_range.to_point(display_map.buffer_snapshot());
3912
3913 let position = display_map
3914 .clip_point(position, Bias::Left)
3915 .to_point(&display_map);
3916 let line_start = display_map.prev_line_boundary(position).0;
3917 let next_line_start = buffer.clip_point(
3918 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3919 Bias::Left,
3920 );
3921
3922 if line_start < original_range.start {
3923 head = line_start
3924 } else {
3925 head = next_line_start
3926 }
3927
3928 if head <= original_range.start {
3929 tail = original_range.end;
3930 } else {
3931 tail = original_range.start;
3932 }
3933 }
3934 SelectMode::All => {
3935 return;
3936 }
3937 };
3938
3939 if head < tail {
3940 pending.start = buffer.anchor_before(head);
3941 pending.end = buffer.anchor_before(tail);
3942 pending.reversed = true;
3943 } else {
3944 pending.start = buffer.anchor_before(tail);
3945 pending.end = buffer.anchor_before(head);
3946 pending.reversed = false;
3947 }
3948
3949 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3950 s.set_pending(pending.clone(), mode);
3951 });
3952 } else {
3953 log::error!("update_selection dispatched with no pending selection");
3954 return;
3955 }
3956
3957 self.apply_scroll_delta(scroll_delta, window, cx);
3958 cx.notify();
3959 }
3960
3961 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3962 self.columnar_selection_state.take();
3963 if let Some(pending_mode) = self.selections.pending_mode() {
3964 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3965 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3966 s.select(selections);
3967 s.clear_pending();
3968 if s.is_extending() {
3969 s.set_is_extending(false);
3970 } else {
3971 s.set_select_mode(pending_mode);
3972 }
3973 });
3974 }
3975 }
3976
3977 fn select_columns(
3978 &mut self,
3979 head: DisplayPoint,
3980 goal_column: u32,
3981 display_map: &DisplaySnapshot,
3982 window: &mut Window,
3983 cx: &mut Context<Self>,
3984 ) {
3985 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3986 return;
3987 };
3988
3989 let tail = match columnar_state {
3990 ColumnarSelectionState::FromMouse {
3991 selection_tail,
3992 display_point,
3993 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3994 ColumnarSelectionState::FromSelection { selection_tail } => {
3995 selection_tail.to_display_point(display_map)
3996 }
3997 };
3998
3999 let start_row = cmp::min(tail.row(), head.row());
4000 let end_row = cmp::max(tail.row(), head.row());
4001 let start_column = cmp::min(tail.column(), goal_column);
4002 let end_column = cmp::max(tail.column(), goal_column);
4003 let reversed = start_column < tail.column();
4004
4005 let selection_ranges = (start_row.0..=end_row.0)
4006 .map(DisplayRow)
4007 .filter_map(|row| {
4008 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4009 || start_column <= display_map.line_len(row))
4010 && !display_map.is_block_line(row)
4011 {
4012 let start = display_map
4013 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4014 .to_point(display_map);
4015 let end = display_map
4016 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4017 .to_point(display_map);
4018 if reversed {
4019 Some(end..start)
4020 } else {
4021 Some(start..end)
4022 }
4023 } else {
4024 None
4025 }
4026 })
4027 .collect::<Vec<_>>();
4028 if selection_ranges.is_empty() {
4029 return;
4030 }
4031
4032 let ranges = match columnar_state {
4033 ColumnarSelectionState::FromMouse { .. } => {
4034 let mut non_empty_ranges = selection_ranges
4035 .iter()
4036 .filter(|selection_range| selection_range.start != selection_range.end)
4037 .peekable();
4038 if non_empty_ranges.peek().is_some() {
4039 non_empty_ranges.cloned().collect()
4040 } else {
4041 selection_ranges
4042 }
4043 }
4044 _ => selection_ranges,
4045 };
4046
4047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4048 s.select_ranges(ranges);
4049 });
4050 cx.notify();
4051 }
4052
4053 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4054 self.selections
4055 .all_adjusted(snapshot)
4056 .iter()
4057 .any(|selection| !selection.is_empty())
4058 }
4059
4060 pub fn has_pending_nonempty_selection(&self) -> bool {
4061 let pending_nonempty_selection = match self.selections.pending_anchor() {
4062 Some(Selection { start, end, .. }) => start != end,
4063 None => false,
4064 };
4065
4066 pending_nonempty_selection
4067 || (self.columnar_selection_state.is_some()
4068 && self.selections.disjoint_anchors().len() > 1)
4069 }
4070
4071 pub fn has_pending_selection(&self) -> bool {
4072 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4073 }
4074
4075 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4076 self.selection_mark_mode = false;
4077 self.selection_drag_state = SelectionDragState::None;
4078
4079 if self.dismiss_menus_and_popups(true, window, cx) {
4080 cx.notify();
4081 return;
4082 }
4083 if self.clear_expanded_diff_hunks(cx) {
4084 cx.notify();
4085 return;
4086 }
4087 if self.show_git_blame_gutter {
4088 self.show_git_blame_gutter = false;
4089 cx.notify();
4090 return;
4091 }
4092
4093 if self.mode.is_full()
4094 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4095 {
4096 cx.notify();
4097 return;
4098 }
4099
4100 cx.propagate();
4101 }
4102
4103 pub fn dismiss_menus_and_popups(
4104 &mut self,
4105 is_user_requested: bool,
4106 window: &mut Window,
4107 cx: &mut Context<Self>,
4108 ) -> bool {
4109 if self.take_rename(false, window, cx).is_some() {
4110 return true;
4111 }
4112
4113 if self.hide_blame_popover(true, cx) {
4114 return true;
4115 }
4116
4117 if hide_hover(self, cx) {
4118 return true;
4119 }
4120
4121 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4122 return true;
4123 }
4124
4125 if self.hide_context_menu(window, cx).is_some() {
4126 return true;
4127 }
4128
4129 if self.mouse_context_menu.take().is_some() {
4130 return true;
4131 }
4132
4133 if is_user_requested && self.discard_edit_prediction(true, cx) {
4134 return true;
4135 }
4136
4137 if self.snippet_stack.pop().is_some() {
4138 return true;
4139 }
4140
4141 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4142 self.dismiss_diagnostics(cx);
4143 return true;
4144 }
4145
4146 false
4147 }
4148
4149 fn linked_editing_ranges_for(
4150 &self,
4151 selection: Range<text::Anchor>,
4152 cx: &App,
4153 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4154 if self.linked_edit_ranges.is_empty() {
4155 return None;
4156 }
4157 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4158 selection.end.buffer_id.and_then(|end_buffer_id| {
4159 if selection.start.buffer_id != Some(end_buffer_id) {
4160 return None;
4161 }
4162 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4163 let snapshot = buffer.read(cx).snapshot();
4164 self.linked_edit_ranges
4165 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4166 .map(|ranges| (ranges, snapshot, buffer))
4167 })?;
4168 use text::ToOffset as TO;
4169 // find offset from the start of current range to current cursor position
4170 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4171
4172 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4173 let start_difference = start_offset - start_byte_offset;
4174 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4175 let end_difference = end_offset - start_byte_offset;
4176 // Current range has associated linked ranges.
4177 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4178 for range in linked_ranges.iter() {
4179 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4180 let end_offset = start_offset + end_difference;
4181 let start_offset = start_offset + start_difference;
4182 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4183 continue;
4184 }
4185 if self.selections.disjoint_anchor_ranges().any(|s| {
4186 if s.start.buffer_id != selection.start.buffer_id
4187 || s.end.buffer_id != selection.end.buffer_id
4188 {
4189 return false;
4190 }
4191 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4192 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4193 }) {
4194 continue;
4195 }
4196 let start = buffer_snapshot.anchor_after(start_offset);
4197 let end = buffer_snapshot.anchor_after(end_offset);
4198 linked_edits
4199 .entry(buffer.clone())
4200 .or_default()
4201 .push(start..end);
4202 }
4203 Some(linked_edits)
4204 }
4205
4206 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4207 let text: Arc<str> = text.into();
4208
4209 if self.read_only(cx) {
4210 return;
4211 }
4212
4213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4214
4215 self.unfold_buffers_with_selections(cx);
4216
4217 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4218 let mut bracket_inserted = false;
4219 let mut edits = Vec::new();
4220 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4221 let mut new_selections = Vec::with_capacity(selections.len());
4222 let mut new_autoclose_regions = Vec::new();
4223 let snapshot = self.buffer.read(cx).read(cx);
4224 let mut clear_linked_edit_ranges = false;
4225
4226 for (selection, autoclose_region) in
4227 self.selections_with_autoclose_regions(selections, &snapshot)
4228 {
4229 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4230 // Determine if the inserted text matches the opening or closing
4231 // bracket of any of this language's bracket pairs.
4232 let mut bracket_pair = None;
4233 let mut is_bracket_pair_start = false;
4234 let mut is_bracket_pair_end = false;
4235 if !text.is_empty() {
4236 let mut bracket_pair_matching_end = None;
4237 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4238 // and they are removing the character that triggered IME popup.
4239 for (pair, enabled) in scope.brackets() {
4240 if !pair.close && !pair.surround {
4241 continue;
4242 }
4243
4244 if enabled && pair.start.ends_with(text.as_ref()) {
4245 let prefix_len = pair.start.len() - text.len();
4246 let preceding_text_matches_prefix = prefix_len == 0
4247 || (selection.start.column >= (prefix_len as u32)
4248 && snapshot.contains_str_at(
4249 Point::new(
4250 selection.start.row,
4251 selection.start.column - (prefix_len as u32),
4252 ),
4253 &pair.start[..prefix_len],
4254 ));
4255 if preceding_text_matches_prefix {
4256 bracket_pair = Some(pair.clone());
4257 is_bracket_pair_start = true;
4258 break;
4259 }
4260 }
4261 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4262 {
4263 // take first bracket pair matching end, but don't break in case a later bracket
4264 // pair matches start
4265 bracket_pair_matching_end = Some(pair.clone());
4266 }
4267 }
4268 if let Some(end) = bracket_pair_matching_end
4269 && bracket_pair.is_none()
4270 {
4271 bracket_pair = Some(end);
4272 is_bracket_pair_end = true;
4273 }
4274 }
4275
4276 if let Some(bracket_pair) = bracket_pair {
4277 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4278 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4279 let auto_surround =
4280 self.use_auto_surround && snapshot_settings.use_auto_surround;
4281 if selection.is_empty() {
4282 if is_bracket_pair_start {
4283 // If the inserted text is a suffix of an opening bracket and the
4284 // selection is preceded by the rest of the opening bracket, then
4285 // insert the closing bracket.
4286 let following_text_allows_autoclose = snapshot
4287 .chars_at(selection.start)
4288 .next()
4289 .is_none_or(|c| scope.should_autoclose_before(c));
4290
4291 let preceding_text_allows_autoclose = selection.start.column == 0
4292 || snapshot
4293 .reversed_chars_at(selection.start)
4294 .next()
4295 .is_none_or(|c| {
4296 bracket_pair.start != bracket_pair.end
4297 || !snapshot
4298 .char_classifier_at(selection.start)
4299 .is_word(c)
4300 });
4301
4302 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4303 && bracket_pair.start.len() == 1
4304 {
4305 let target = bracket_pair.start.chars().next().unwrap();
4306 let current_line_count = snapshot
4307 .reversed_chars_at(selection.start)
4308 .take_while(|&c| c != '\n')
4309 .filter(|&c| c == target)
4310 .count();
4311 current_line_count % 2 == 1
4312 } else {
4313 false
4314 };
4315
4316 if autoclose
4317 && bracket_pair.close
4318 && following_text_allows_autoclose
4319 && preceding_text_allows_autoclose
4320 && !is_closing_quote
4321 {
4322 let anchor = snapshot.anchor_before(selection.end);
4323 new_selections.push((selection.map(|_| anchor), text.len()));
4324 new_autoclose_regions.push((
4325 anchor,
4326 text.len(),
4327 selection.id,
4328 bracket_pair.clone(),
4329 ));
4330 edits.push((
4331 selection.range(),
4332 format!("{}{}", text, bracket_pair.end).into(),
4333 ));
4334 bracket_inserted = true;
4335 continue;
4336 }
4337 }
4338
4339 if let Some(region) = autoclose_region {
4340 // If the selection is followed by an auto-inserted closing bracket,
4341 // then don't insert that closing bracket again; just move the selection
4342 // past the closing bracket.
4343 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4344 && text.as_ref() == region.pair.end.as_str()
4345 && snapshot.contains_str_at(region.range.end, text.as_ref());
4346 if should_skip {
4347 let anchor = snapshot.anchor_after(selection.end);
4348 new_selections
4349 .push((selection.map(|_| anchor), region.pair.end.len()));
4350 continue;
4351 }
4352 }
4353
4354 let always_treat_brackets_as_autoclosed = snapshot
4355 .language_settings_at(selection.start, cx)
4356 .always_treat_brackets_as_autoclosed;
4357 if always_treat_brackets_as_autoclosed
4358 && is_bracket_pair_end
4359 && snapshot.contains_str_at(selection.end, text.as_ref())
4360 {
4361 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4362 // and the inserted text is a closing bracket and the selection is followed
4363 // by the closing bracket then move the selection past the closing bracket.
4364 let anchor = snapshot.anchor_after(selection.end);
4365 new_selections.push((selection.map(|_| anchor), text.len()));
4366 continue;
4367 }
4368 }
4369 // If an opening bracket is 1 character long and is typed while
4370 // text is selected, then surround that text with the bracket pair.
4371 else if auto_surround
4372 && bracket_pair.surround
4373 && is_bracket_pair_start
4374 && bracket_pair.start.chars().count() == 1
4375 {
4376 edits.push((selection.start..selection.start, text.clone()));
4377 edits.push((
4378 selection.end..selection.end,
4379 bracket_pair.end.as_str().into(),
4380 ));
4381 bracket_inserted = true;
4382 new_selections.push((
4383 Selection {
4384 id: selection.id,
4385 start: snapshot.anchor_after(selection.start),
4386 end: snapshot.anchor_before(selection.end),
4387 reversed: selection.reversed,
4388 goal: selection.goal,
4389 },
4390 0,
4391 ));
4392 continue;
4393 }
4394 }
4395 }
4396
4397 if self.auto_replace_emoji_shortcode
4398 && selection.is_empty()
4399 && text.as_ref().ends_with(':')
4400 && let Some(possible_emoji_short_code) =
4401 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4402 && !possible_emoji_short_code.is_empty()
4403 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4404 {
4405 let emoji_shortcode_start = Point::new(
4406 selection.start.row,
4407 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4408 );
4409
4410 // Remove shortcode from buffer
4411 edits.push((
4412 emoji_shortcode_start..selection.start,
4413 "".to_string().into(),
4414 ));
4415 new_selections.push((
4416 Selection {
4417 id: selection.id,
4418 start: snapshot.anchor_after(emoji_shortcode_start),
4419 end: snapshot.anchor_before(selection.start),
4420 reversed: selection.reversed,
4421 goal: selection.goal,
4422 },
4423 0,
4424 ));
4425
4426 // Insert emoji
4427 let selection_start_anchor = snapshot.anchor_after(selection.start);
4428 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4429 edits.push((selection.start..selection.end, emoji.to_string().into()));
4430
4431 continue;
4432 }
4433
4434 // If not handling any auto-close operation, then just replace the selected
4435 // text with the given input and move the selection to the end of the
4436 // newly inserted text.
4437 let anchor = snapshot.anchor_after(selection.end);
4438 if !self.linked_edit_ranges.is_empty() {
4439 let start_anchor = snapshot.anchor_before(selection.start);
4440
4441 let is_word_char = text.chars().next().is_none_or(|char| {
4442 let classifier = snapshot
4443 .char_classifier_at(start_anchor.to_offset(&snapshot))
4444 .scope_context(Some(CharScopeContext::LinkedEdit));
4445 classifier.is_word(char)
4446 });
4447
4448 if is_word_char {
4449 if let Some(ranges) = self
4450 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4451 {
4452 for (buffer, edits) in ranges {
4453 linked_edits
4454 .entry(buffer.clone())
4455 .or_default()
4456 .extend(edits.into_iter().map(|range| (range, text.clone())));
4457 }
4458 }
4459 } else {
4460 clear_linked_edit_ranges = true;
4461 }
4462 }
4463
4464 new_selections.push((selection.map(|_| anchor), 0));
4465 edits.push((selection.start..selection.end, text.clone()));
4466 }
4467
4468 drop(snapshot);
4469
4470 self.transact(window, cx, |this, window, cx| {
4471 if clear_linked_edit_ranges {
4472 this.linked_edit_ranges.clear();
4473 }
4474 let initial_buffer_versions =
4475 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4476
4477 this.buffer.update(cx, |buffer, cx| {
4478 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4479 });
4480 for (buffer, edits) in linked_edits {
4481 buffer.update(cx, |buffer, cx| {
4482 let snapshot = buffer.snapshot();
4483 let edits = edits
4484 .into_iter()
4485 .map(|(range, text)| {
4486 use text::ToPoint as TP;
4487 let end_point = TP::to_point(&range.end, &snapshot);
4488 let start_point = TP::to_point(&range.start, &snapshot);
4489 (start_point..end_point, text)
4490 })
4491 .sorted_by_key(|(range, _)| range.start);
4492 buffer.edit(edits, None, cx);
4493 })
4494 }
4495 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4496 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4497 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4498 let new_selections =
4499 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4500 .zip(new_selection_deltas)
4501 .map(|(selection, delta)| Selection {
4502 id: selection.id,
4503 start: selection.start + delta,
4504 end: selection.end + delta,
4505 reversed: selection.reversed,
4506 goal: SelectionGoal::None,
4507 })
4508 .collect::<Vec<_>>();
4509
4510 let mut i = 0;
4511 for (position, delta, selection_id, pair) in new_autoclose_regions {
4512 let position = position.to_offset(map.buffer_snapshot()) + delta;
4513 let start = map.buffer_snapshot().anchor_before(position);
4514 let end = map.buffer_snapshot().anchor_after(position);
4515 while let Some(existing_state) = this.autoclose_regions.get(i) {
4516 match existing_state
4517 .range
4518 .start
4519 .cmp(&start, map.buffer_snapshot())
4520 {
4521 Ordering::Less => i += 1,
4522 Ordering::Greater => break,
4523 Ordering::Equal => {
4524 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4525 Ordering::Less => i += 1,
4526 Ordering::Equal => break,
4527 Ordering::Greater => break,
4528 }
4529 }
4530 }
4531 }
4532 this.autoclose_regions.insert(
4533 i,
4534 AutocloseRegion {
4535 selection_id,
4536 range: start..end,
4537 pair,
4538 },
4539 );
4540 }
4541
4542 let had_active_edit_prediction = this.has_active_edit_prediction();
4543 this.change_selections(
4544 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4545 window,
4546 cx,
4547 |s| s.select(new_selections),
4548 );
4549
4550 if !bracket_inserted
4551 && let Some(on_type_format_task) =
4552 this.trigger_on_type_formatting(text.to_string(), window, cx)
4553 {
4554 on_type_format_task.detach_and_log_err(cx);
4555 }
4556
4557 let editor_settings = EditorSettings::get_global(cx);
4558 if bracket_inserted
4559 && (editor_settings.auto_signature_help
4560 || editor_settings.show_signature_help_after_edits)
4561 {
4562 this.show_signature_help(&ShowSignatureHelp, window, cx);
4563 }
4564
4565 let trigger_in_words =
4566 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4567 if this.hard_wrap.is_some() {
4568 let latest: Range<Point> = this.selections.newest(&map).range();
4569 if latest.is_empty()
4570 && this
4571 .buffer()
4572 .read(cx)
4573 .snapshot(cx)
4574 .line_len(MultiBufferRow(latest.start.row))
4575 == latest.start.column
4576 {
4577 this.rewrap_impl(
4578 RewrapOptions {
4579 override_language_settings: true,
4580 preserve_existing_whitespace: true,
4581 },
4582 cx,
4583 )
4584 }
4585 }
4586 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4587 refresh_linked_ranges(this, window, cx);
4588 this.refresh_edit_prediction(true, false, window, cx);
4589 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4590 });
4591 }
4592
4593 fn find_possible_emoji_shortcode_at_position(
4594 snapshot: &MultiBufferSnapshot,
4595 position: Point,
4596 ) -> Option<String> {
4597 let mut chars = Vec::new();
4598 let mut found_colon = false;
4599 for char in snapshot.reversed_chars_at(position).take(100) {
4600 // Found a possible emoji shortcode in the middle of the buffer
4601 if found_colon {
4602 if char.is_whitespace() {
4603 chars.reverse();
4604 return Some(chars.iter().collect());
4605 }
4606 // If the previous character is not a whitespace, we are in the middle of a word
4607 // and we only want to complete the shortcode if the word is made up of other emojis
4608 let mut containing_word = String::new();
4609 for ch in snapshot
4610 .reversed_chars_at(position)
4611 .skip(chars.len() + 1)
4612 .take(100)
4613 {
4614 if ch.is_whitespace() {
4615 break;
4616 }
4617 containing_word.push(ch);
4618 }
4619 let containing_word = containing_word.chars().rev().collect::<String>();
4620 if util::word_consists_of_emojis(containing_word.as_str()) {
4621 chars.reverse();
4622 return Some(chars.iter().collect());
4623 }
4624 }
4625
4626 if char.is_whitespace() || !char.is_ascii() {
4627 return None;
4628 }
4629 if char == ':' {
4630 found_colon = true;
4631 } else {
4632 chars.push(char);
4633 }
4634 }
4635 // Found a possible emoji shortcode at the beginning of the buffer
4636 chars.reverse();
4637 Some(chars.iter().collect())
4638 }
4639
4640 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4641 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4642 self.transact(window, cx, |this, window, cx| {
4643 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4644 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4645 let multi_buffer = this.buffer.read(cx);
4646 let buffer = multi_buffer.snapshot(cx);
4647 selections
4648 .iter()
4649 .map(|selection| {
4650 let start_point = selection.start.to_point(&buffer);
4651 let mut existing_indent =
4652 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4653 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4654 let start = selection.start;
4655 let end = selection.end;
4656 let selection_is_empty = start == end;
4657 let language_scope = buffer.language_scope_at(start);
4658 let (
4659 comment_delimiter,
4660 doc_delimiter,
4661 insert_extra_newline,
4662 indent_on_newline,
4663 indent_on_extra_newline,
4664 ) = if let Some(language) = &language_scope {
4665 let mut insert_extra_newline =
4666 insert_extra_newline_brackets(&buffer, start..end, language)
4667 || insert_extra_newline_tree_sitter(&buffer, start..end);
4668
4669 // Comment extension on newline is allowed only for cursor selections
4670 let comment_delimiter = maybe!({
4671 if !selection_is_empty {
4672 return None;
4673 }
4674
4675 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4676 return None;
4677 }
4678
4679 let delimiters = language.line_comment_prefixes();
4680 let max_len_of_delimiter =
4681 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4682 let (snapshot, range) =
4683 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4684
4685 let num_of_whitespaces = snapshot
4686 .chars_for_range(range.clone())
4687 .take_while(|c| c.is_whitespace())
4688 .count();
4689 let comment_candidate = snapshot
4690 .chars_for_range(range.clone())
4691 .skip(num_of_whitespaces)
4692 .take(max_len_of_delimiter)
4693 .collect::<String>();
4694 let (delimiter, trimmed_len) = delimiters
4695 .iter()
4696 .filter_map(|delimiter| {
4697 let prefix = delimiter.trim_end();
4698 if comment_candidate.starts_with(prefix) {
4699 Some((delimiter, prefix.len()))
4700 } else {
4701 None
4702 }
4703 })
4704 .max_by_key(|(_, len)| *len)?;
4705
4706 if let Some(BlockCommentConfig {
4707 start: block_start, ..
4708 }) = language.block_comment()
4709 {
4710 let block_start_trimmed = block_start.trim_end();
4711 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4712 let line_content = snapshot
4713 .chars_for_range(range)
4714 .skip(num_of_whitespaces)
4715 .take(block_start_trimmed.len())
4716 .collect::<String>();
4717
4718 if line_content.starts_with(block_start_trimmed) {
4719 return None;
4720 }
4721 }
4722 }
4723
4724 let cursor_is_placed_after_comment_marker =
4725 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4726 if cursor_is_placed_after_comment_marker {
4727 Some(delimiter.clone())
4728 } else {
4729 None
4730 }
4731 });
4732
4733 let mut indent_on_newline = IndentSize::spaces(0);
4734 let mut indent_on_extra_newline = IndentSize::spaces(0);
4735
4736 let doc_delimiter = maybe!({
4737 if !selection_is_empty {
4738 return None;
4739 }
4740
4741 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4742 return None;
4743 }
4744
4745 let BlockCommentConfig {
4746 start: start_tag,
4747 end: end_tag,
4748 prefix: delimiter,
4749 tab_size: len,
4750 } = language.documentation_comment()?;
4751 let is_within_block_comment = buffer
4752 .language_scope_at(start_point)
4753 .is_some_and(|scope| scope.override_name() == Some("comment"));
4754 if !is_within_block_comment {
4755 return None;
4756 }
4757
4758 let (snapshot, range) =
4759 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4760
4761 let num_of_whitespaces = snapshot
4762 .chars_for_range(range.clone())
4763 .take_while(|c| c.is_whitespace())
4764 .count();
4765
4766 // 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.
4767 let column = start_point.column;
4768 let cursor_is_after_start_tag = {
4769 let start_tag_len = start_tag.len();
4770 let start_tag_line = snapshot
4771 .chars_for_range(range.clone())
4772 .skip(num_of_whitespaces)
4773 .take(start_tag_len)
4774 .collect::<String>();
4775 if start_tag_line.starts_with(start_tag.as_ref()) {
4776 num_of_whitespaces + start_tag_len <= column as usize
4777 } else {
4778 false
4779 }
4780 };
4781
4782 let cursor_is_after_delimiter = {
4783 let delimiter_trim = delimiter.trim_end();
4784 let delimiter_line = snapshot
4785 .chars_for_range(range.clone())
4786 .skip(num_of_whitespaces)
4787 .take(delimiter_trim.len())
4788 .collect::<String>();
4789 if delimiter_line.starts_with(delimiter_trim) {
4790 num_of_whitespaces + delimiter_trim.len() <= column as usize
4791 } else {
4792 false
4793 }
4794 };
4795
4796 let cursor_is_before_end_tag_if_exists = {
4797 let mut char_position = 0u32;
4798 let mut end_tag_offset = None;
4799
4800 'outer: for chunk in snapshot.text_for_range(range) {
4801 if let Some(byte_pos) = chunk.find(&**end_tag) {
4802 let chars_before_match =
4803 chunk[..byte_pos].chars().count() as u32;
4804 end_tag_offset =
4805 Some(char_position + chars_before_match);
4806 break 'outer;
4807 }
4808 char_position += chunk.chars().count() as u32;
4809 }
4810
4811 if let Some(end_tag_offset) = end_tag_offset {
4812 let cursor_is_before_end_tag = column <= end_tag_offset;
4813 if cursor_is_after_start_tag {
4814 if cursor_is_before_end_tag {
4815 insert_extra_newline = true;
4816 }
4817 let cursor_is_at_start_of_end_tag =
4818 column == end_tag_offset;
4819 if cursor_is_at_start_of_end_tag {
4820 indent_on_extra_newline.len = *len;
4821 }
4822 }
4823 cursor_is_before_end_tag
4824 } else {
4825 true
4826 }
4827 };
4828
4829 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4830 && cursor_is_before_end_tag_if_exists
4831 {
4832 if cursor_is_after_start_tag {
4833 indent_on_newline.len = *len;
4834 }
4835 Some(delimiter.clone())
4836 } else {
4837 None
4838 }
4839 });
4840
4841 (
4842 comment_delimiter,
4843 doc_delimiter,
4844 insert_extra_newline,
4845 indent_on_newline,
4846 indent_on_extra_newline,
4847 )
4848 } else {
4849 (
4850 None,
4851 None,
4852 false,
4853 IndentSize::default(),
4854 IndentSize::default(),
4855 )
4856 };
4857
4858 let prevent_auto_indent = doc_delimiter.is_some();
4859 let delimiter = comment_delimiter.or(doc_delimiter);
4860
4861 let capacity_for_delimiter =
4862 delimiter.as_deref().map(str::len).unwrap_or_default();
4863 let mut new_text = String::with_capacity(
4864 1 + capacity_for_delimiter
4865 + existing_indent.len as usize
4866 + indent_on_newline.len as usize
4867 + indent_on_extra_newline.len as usize,
4868 );
4869 new_text.push('\n');
4870 new_text.extend(existing_indent.chars());
4871 new_text.extend(indent_on_newline.chars());
4872
4873 if let Some(delimiter) = &delimiter {
4874 new_text.push_str(delimiter);
4875 }
4876
4877 if insert_extra_newline {
4878 new_text.push('\n');
4879 new_text.extend(existing_indent.chars());
4880 new_text.extend(indent_on_extra_newline.chars());
4881 }
4882
4883 let anchor = buffer.anchor_after(end);
4884 let new_selection = selection.map(|_| anchor);
4885 (
4886 ((start..end, new_text), prevent_auto_indent),
4887 (insert_extra_newline, new_selection),
4888 )
4889 })
4890 .unzip()
4891 };
4892
4893 let mut auto_indent_edits = Vec::new();
4894 let mut edits = Vec::new();
4895 for (edit, prevent_auto_indent) in edits_with_flags {
4896 if prevent_auto_indent {
4897 edits.push(edit);
4898 } else {
4899 auto_indent_edits.push(edit);
4900 }
4901 }
4902 if !edits.is_empty() {
4903 this.edit(edits, cx);
4904 }
4905 if !auto_indent_edits.is_empty() {
4906 this.edit_with_autoindent(auto_indent_edits, cx);
4907 }
4908
4909 let buffer = this.buffer.read(cx).snapshot(cx);
4910 let new_selections = selection_info
4911 .into_iter()
4912 .map(|(extra_newline_inserted, new_selection)| {
4913 let mut cursor = new_selection.end.to_point(&buffer);
4914 if extra_newline_inserted {
4915 cursor.row -= 1;
4916 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4917 }
4918 new_selection.map(|_| cursor)
4919 })
4920 .collect();
4921
4922 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4923 this.refresh_edit_prediction(true, false, window, cx);
4924 });
4925 }
4926
4927 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4929
4930 let buffer = self.buffer.read(cx);
4931 let snapshot = buffer.snapshot(cx);
4932
4933 let mut edits = Vec::new();
4934 let mut rows = Vec::new();
4935
4936 for (rows_inserted, selection) in self
4937 .selections
4938 .all_adjusted(&self.display_snapshot(cx))
4939 .into_iter()
4940 .enumerate()
4941 {
4942 let cursor = selection.head();
4943 let row = cursor.row;
4944
4945 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4946
4947 let newline = "\n".to_string();
4948 edits.push((start_of_line..start_of_line, newline));
4949
4950 rows.push(row + rows_inserted as u32);
4951 }
4952
4953 self.transact(window, cx, |editor, window, cx| {
4954 editor.edit(edits, cx);
4955
4956 editor.change_selections(Default::default(), window, cx, |s| {
4957 let mut index = 0;
4958 s.move_cursors_with(|map, _, _| {
4959 let row = rows[index];
4960 index += 1;
4961
4962 let point = Point::new(row, 0);
4963 let boundary = map.next_line_boundary(point).1;
4964 let clipped = map.clip_point(boundary, Bias::Left);
4965
4966 (clipped, SelectionGoal::None)
4967 });
4968 });
4969
4970 let mut indent_edits = Vec::new();
4971 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4972 for row in rows {
4973 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4974 for (row, indent) in indents {
4975 if indent.len == 0 {
4976 continue;
4977 }
4978
4979 let text = match indent.kind {
4980 IndentKind::Space => " ".repeat(indent.len as usize),
4981 IndentKind::Tab => "\t".repeat(indent.len as usize),
4982 };
4983 let point = Point::new(row.0, 0);
4984 indent_edits.push((point..point, text));
4985 }
4986 }
4987 editor.edit(indent_edits, cx);
4988 });
4989 }
4990
4991 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4993
4994 let buffer = self.buffer.read(cx);
4995 let snapshot = buffer.snapshot(cx);
4996
4997 let mut edits = Vec::new();
4998 let mut rows = Vec::new();
4999 let mut rows_inserted = 0;
5000
5001 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5002 let cursor = selection.head();
5003 let row = cursor.row;
5004
5005 let point = Point::new(row + 1, 0);
5006 let start_of_line = snapshot.clip_point(point, Bias::Left);
5007
5008 let newline = "\n".to_string();
5009 edits.push((start_of_line..start_of_line, newline));
5010
5011 rows_inserted += 1;
5012 rows.push(row + rows_inserted);
5013 }
5014
5015 self.transact(window, cx, |editor, window, cx| {
5016 editor.edit(edits, cx);
5017
5018 editor.change_selections(Default::default(), window, cx, |s| {
5019 let mut index = 0;
5020 s.move_cursors_with(|map, _, _| {
5021 let row = rows[index];
5022 index += 1;
5023
5024 let point = Point::new(row, 0);
5025 let boundary = map.next_line_boundary(point).1;
5026 let clipped = map.clip_point(boundary, Bias::Left);
5027
5028 (clipped, SelectionGoal::None)
5029 });
5030 });
5031
5032 let mut indent_edits = Vec::new();
5033 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5034 for row in rows {
5035 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5036 for (row, indent) in indents {
5037 if indent.len == 0 {
5038 continue;
5039 }
5040
5041 let text = match indent.kind {
5042 IndentKind::Space => " ".repeat(indent.len as usize),
5043 IndentKind::Tab => "\t".repeat(indent.len as usize),
5044 };
5045 let point = Point::new(row.0, 0);
5046 indent_edits.push((point..point, text));
5047 }
5048 }
5049 editor.edit(indent_edits, cx);
5050 });
5051 }
5052
5053 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5054 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5055 original_indent_columns: Vec::new(),
5056 });
5057 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5058 }
5059
5060 fn insert_with_autoindent_mode(
5061 &mut self,
5062 text: &str,
5063 autoindent_mode: Option<AutoindentMode>,
5064 window: &mut Window,
5065 cx: &mut Context<Self>,
5066 ) {
5067 if self.read_only(cx) {
5068 return;
5069 }
5070
5071 let text: Arc<str> = text.into();
5072 self.transact(window, cx, |this, window, cx| {
5073 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5074 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5075 let anchors = {
5076 let snapshot = buffer.read(cx);
5077 old_selections
5078 .iter()
5079 .map(|s| {
5080 let anchor = snapshot.anchor_after(s.head());
5081 s.map(|_| anchor)
5082 })
5083 .collect::<Vec<_>>()
5084 };
5085 buffer.edit(
5086 old_selections
5087 .iter()
5088 .map(|s| (s.start..s.end, text.clone())),
5089 autoindent_mode,
5090 cx,
5091 );
5092 anchors
5093 });
5094
5095 this.change_selections(Default::default(), window, cx, |s| {
5096 s.select_anchors(selection_anchors);
5097 });
5098
5099 cx.notify();
5100 });
5101 }
5102
5103 fn trigger_completion_on_input(
5104 &mut self,
5105 text: &str,
5106 trigger_in_words: bool,
5107 window: &mut Window,
5108 cx: &mut Context<Self>,
5109 ) {
5110 let completions_source = self
5111 .context_menu
5112 .borrow()
5113 .as_ref()
5114 .and_then(|menu| match menu {
5115 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5116 CodeContextMenu::CodeActions(_) => None,
5117 });
5118
5119 match completions_source {
5120 Some(CompletionsMenuSource::Words { .. }) => {
5121 self.open_or_update_completions_menu(
5122 Some(CompletionsMenuSource::Words {
5123 ignore_threshold: false,
5124 }),
5125 None,
5126 trigger_in_words,
5127 window,
5128 cx,
5129 );
5130 }
5131 _ => self.open_or_update_completions_menu(
5132 None,
5133 Some(text.to_owned()).filter(|x| !x.is_empty()),
5134 true,
5135 window,
5136 cx,
5137 ),
5138 }
5139 }
5140
5141 /// If any empty selections is touching the start of its innermost containing autoclose
5142 /// region, expand it to select the brackets.
5143 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5144 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5145 let buffer = self.buffer.read(cx).read(cx);
5146 let new_selections = self
5147 .selections_with_autoclose_regions(selections, &buffer)
5148 .map(|(mut selection, region)| {
5149 if !selection.is_empty() {
5150 return selection;
5151 }
5152
5153 if let Some(region) = region {
5154 let mut range = region.range.to_offset(&buffer);
5155 if selection.start == range.start && range.start >= region.pair.start.len() {
5156 range.start -= region.pair.start.len();
5157 if buffer.contains_str_at(range.start, ®ion.pair.start)
5158 && buffer.contains_str_at(range.end, ®ion.pair.end)
5159 {
5160 range.end += region.pair.end.len();
5161 selection.start = range.start;
5162 selection.end = range.end;
5163
5164 return selection;
5165 }
5166 }
5167 }
5168
5169 let always_treat_brackets_as_autoclosed = buffer
5170 .language_settings_at(selection.start, cx)
5171 .always_treat_brackets_as_autoclosed;
5172
5173 if !always_treat_brackets_as_autoclosed {
5174 return selection;
5175 }
5176
5177 if let Some(scope) = buffer.language_scope_at(selection.start) {
5178 for (pair, enabled) in scope.brackets() {
5179 if !enabled || !pair.close {
5180 continue;
5181 }
5182
5183 if buffer.contains_str_at(selection.start, &pair.end) {
5184 let pair_start_len = pair.start.len();
5185 if buffer.contains_str_at(
5186 selection.start.saturating_sub(pair_start_len),
5187 &pair.start,
5188 ) {
5189 selection.start -= pair_start_len;
5190 selection.end += pair.end.len();
5191
5192 return selection;
5193 }
5194 }
5195 }
5196 }
5197
5198 selection
5199 })
5200 .collect();
5201
5202 drop(buffer);
5203 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5204 selections.select(new_selections)
5205 });
5206 }
5207
5208 /// Iterate the given selections, and for each one, find the smallest surrounding
5209 /// autoclose region. This uses the ordering of the selections and the autoclose
5210 /// regions to avoid repeated comparisons.
5211 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5212 &'a self,
5213 selections: impl IntoIterator<Item = Selection<D>>,
5214 buffer: &'a MultiBufferSnapshot,
5215 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5216 let mut i = 0;
5217 let mut regions = self.autoclose_regions.as_slice();
5218 selections.into_iter().map(move |selection| {
5219 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5220
5221 let mut enclosing = None;
5222 while let Some(pair_state) = regions.get(i) {
5223 if pair_state.range.end.to_offset(buffer) < range.start {
5224 regions = ®ions[i + 1..];
5225 i = 0;
5226 } else if pair_state.range.start.to_offset(buffer) > range.end {
5227 break;
5228 } else {
5229 if pair_state.selection_id == selection.id {
5230 enclosing = Some(pair_state);
5231 }
5232 i += 1;
5233 }
5234 }
5235
5236 (selection, enclosing)
5237 })
5238 }
5239
5240 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5241 fn invalidate_autoclose_regions(
5242 &mut self,
5243 mut selections: &[Selection<Anchor>],
5244 buffer: &MultiBufferSnapshot,
5245 ) {
5246 self.autoclose_regions.retain(|state| {
5247 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5248 return false;
5249 }
5250
5251 let mut i = 0;
5252 while let Some(selection) = selections.get(i) {
5253 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5254 selections = &selections[1..];
5255 continue;
5256 }
5257 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5258 break;
5259 }
5260 if selection.id == state.selection_id {
5261 return true;
5262 } else {
5263 i += 1;
5264 }
5265 }
5266 false
5267 });
5268 }
5269
5270 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5271 let offset = position.to_offset(buffer);
5272 let (word_range, kind) =
5273 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5274 if offset > word_range.start && kind == Some(CharKind::Word) {
5275 Some(
5276 buffer
5277 .text_for_range(word_range.start..offset)
5278 .collect::<String>(),
5279 )
5280 } else {
5281 None
5282 }
5283 }
5284
5285 pub fn visible_excerpts(
5286 &self,
5287 cx: &mut Context<Editor>,
5288 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5289 let Some(project) = self.project() else {
5290 return HashMap::default();
5291 };
5292 let project = project.read(cx);
5293 let multi_buffer = self.buffer().read(cx);
5294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5295 let multi_buffer_visible_start = self
5296 .scroll_manager
5297 .anchor()
5298 .anchor
5299 .to_point(&multi_buffer_snapshot);
5300 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5301 multi_buffer_visible_start
5302 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5303 Bias::Left,
5304 );
5305 multi_buffer_snapshot
5306 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5307 .into_iter()
5308 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5309 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5310 let buffer_file = project::File::from_dyn(buffer.file())?;
5311 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5312 let worktree_entry = buffer_worktree
5313 .read(cx)
5314 .entry_for_id(buffer_file.project_entry_id()?)?;
5315 if worktree_entry.is_ignored {
5316 None
5317 } else {
5318 Some((
5319 excerpt_id,
5320 (
5321 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5322 buffer.version().clone(),
5323 excerpt_visible_range,
5324 ),
5325 ))
5326 }
5327 })
5328 .collect()
5329 }
5330
5331 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5332 TextLayoutDetails {
5333 text_system: window.text_system().clone(),
5334 editor_style: self.style.clone().unwrap(),
5335 rem_size: window.rem_size(),
5336 scroll_anchor: self.scroll_manager.anchor(),
5337 visible_rows: self.visible_line_count(),
5338 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5339 }
5340 }
5341
5342 fn trigger_on_type_formatting(
5343 &self,
5344 input: String,
5345 window: &mut Window,
5346 cx: &mut Context<Self>,
5347 ) -> Option<Task<Result<()>>> {
5348 if input.len() != 1 {
5349 return None;
5350 }
5351
5352 let project = self.project()?;
5353 let position = self.selections.newest_anchor().head();
5354 let (buffer, buffer_position) = self
5355 .buffer
5356 .read(cx)
5357 .text_anchor_for_position(position, cx)?;
5358
5359 let settings = language_settings::language_settings(
5360 buffer
5361 .read(cx)
5362 .language_at(buffer_position)
5363 .map(|l| l.name()),
5364 buffer.read(cx).file(),
5365 cx,
5366 );
5367 if !settings.use_on_type_format {
5368 return None;
5369 }
5370
5371 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5372 // hence we do LSP request & edit on host side only — add formats to host's history.
5373 let push_to_lsp_host_history = true;
5374 // If this is not the host, append its history with new edits.
5375 let push_to_client_history = project.read(cx).is_via_collab();
5376
5377 let on_type_formatting = project.update(cx, |project, cx| {
5378 project.on_type_format(
5379 buffer.clone(),
5380 buffer_position,
5381 input,
5382 push_to_lsp_host_history,
5383 cx,
5384 )
5385 });
5386 Some(cx.spawn_in(window, async move |editor, cx| {
5387 if let Some(transaction) = on_type_formatting.await? {
5388 if push_to_client_history {
5389 buffer
5390 .update(cx, |buffer, _| {
5391 buffer.push_transaction(transaction, Instant::now());
5392 buffer.finalize_last_transaction();
5393 })
5394 .ok();
5395 }
5396 editor.update(cx, |editor, cx| {
5397 editor.refresh_document_highlights(cx);
5398 })?;
5399 }
5400 Ok(())
5401 }))
5402 }
5403
5404 pub fn show_word_completions(
5405 &mut self,
5406 _: &ShowWordCompletions,
5407 window: &mut Window,
5408 cx: &mut Context<Self>,
5409 ) {
5410 self.open_or_update_completions_menu(
5411 Some(CompletionsMenuSource::Words {
5412 ignore_threshold: true,
5413 }),
5414 None,
5415 false,
5416 window,
5417 cx,
5418 );
5419 }
5420
5421 pub fn show_completions(
5422 &mut self,
5423 _: &ShowCompletions,
5424 window: &mut Window,
5425 cx: &mut Context<Self>,
5426 ) {
5427 self.open_or_update_completions_menu(None, None, false, window, cx);
5428 }
5429
5430 fn open_or_update_completions_menu(
5431 &mut self,
5432 requested_source: Option<CompletionsMenuSource>,
5433 trigger: Option<String>,
5434 trigger_in_words: bool,
5435 window: &mut Window,
5436 cx: &mut Context<Self>,
5437 ) {
5438 if self.pending_rename.is_some() {
5439 return;
5440 }
5441
5442 let completions_source = self
5443 .context_menu
5444 .borrow()
5445 .as_ref()
5446 .and_then(|menu| match menu {
5447 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5448 CodeContextMenu::CodeActions(_) => None,
5449 });
5450
5451 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5452
5453 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5454 // inserted and selected. To handle that case, the start of the selection is used so that
5455 // the menu starts with all choices.
5456 let position = self
5457 .selections
5458 .newest_anchor()
5459 .start
5460 .bias_right(&multibuffer_snapshot);
5461 if position.diff_base_anchor.is_some() {
5462 return;
5463 }
5464 let buffer_position = multibuffer_snapshot.anchor_before(position);
5465 let Some(buffer) = buffer_position
5466 .buffer_id
5467 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5468 else {
5469 return;
5470 };
5471 let buffer_snapshot = buffer.read(cx).snapshot();
5472
5473 let query: Option<Arc<String>> =
5474 Self::completion_query(&multibuffer_snapshot, buffer_position)
5475 .map(|query| query.into());
5476
5477 drop(multibuffer_snapshot);
5478
5479 // Hide the current completions menu when query is empty. Without this, cached
5480 // completions from before the trigger char may be reused (#32774).
5481 if query.is_none() {
5482 let menu_is_open = matches!(
5483 self.context_menu.borrow().as_ref(),
5484 Some(CodeContextMenu::Completions(_))
5485 );
5486 if menu_is_open {
5487 self.hide_context_menu(window, cx);
5488 }
5489 }
5490
5491 let mut ignore_word_threshold = false;
5492 let provider = match requested_source {
5493 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5494 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5495 ignore_word_threshold = ignore_threshold;
5496 None
5497 }
5498 Some(CompletionsMenuSource::SnippetChoices)
5499 | Some(CompletionsMenuSource::SnippetsOnly) => {
5500 log::error!("bug: SnippetChoices requested_source is not handled");
5501 None
5502 }
5503 };
5504
5505 let sort_completions = provider
5506 .as_ref()
5507 .is_some_and(|provider| provider.sort_completions());
5508
5509 let filter_completions = provider
5510 .as_ref()
5511 .is_none_or(|provider| provider.filter_completions());
5512
5513 let was_snippets_only = matches!(
5514 completions_source,
5515 Some(CompletionsMenuSource::SnippetsOnly)
5516 );
5517
5518 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5519 if filter_completions {
5520 menu.filter(
5521 query.clone().unwrap_or_default(),
5522 buffer_position.text_anchor,
5523 &buffer,
5524 provider.clone(),
5525 window,
5526 cx,
5527 );
5528 }
5529 // When `is_incomplete` is false, no need to re-query completions when the current query
5530 // is a suffix of the initial query.
5531 let was_complete = !menu.is_incomplete;
5532 if was_complete && !was_snippets_only {
5533 // If the new query is a suffix of the old query (typing more characters) and
5534 // the previous result was complete, the existing completions can be filtered.
5535 //
5536 // Note that snippet completions are always complete.
5537 let query_matches = match (&menu.initial_query, &query) {
5538 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5539 (None, _) => true,
5540 _ => false,
5541 };
5542 if query_matches {
5543 let position_matches = if menu.initial_position == position {
5544 true
5545 } else {
5546 let snapshot = self.buffer.read(cx).read(cx);
5547 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5548 };
5549 if position_matches {
5550 return;
5551 }
5552 }
5553 }
5554 };
5555
5556 let Anchor {
5557 excerpt_id: buffer_excerpt_id,
5558 text_anchor: buffer_position,
5559 ..
5560 } = buffer_position;
5561
5562 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5563 buffer_snapshot.surrounding_word(buffer_position, None)
5564 {
5565 let word_to_exclude = buffer_snapshot
5566 .text_for_range(word_range.clone())
5567 .collect::<String>();
5568 (
5569 buffer_snapshot.anchor_before(word_range.start)
5570 ..buffer_snapshot.anchor_after(buffer_position),
5571 Some(word_to_exclude),
5572 )
5573 } else {
5574 (buffer_position..buffer_position, None)
5575 };
5576
5577 let language = buffer_snapshot
5578 .language_at(buffer_position)
5579 .map(|language| language.name());
5580
5581 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5582 .completions
5583 .clone();
5584
5585 let show_completion_documentation = buffer_snapshot
5586 .settings_at(buffer_position, cx)
5587 .show_completion_documentation;
5588
5589 // The document can be large, so stay in reasonable bounds when searching for words,
5590 // otherwise completion pop-up might be slow to appear.
5591 const WORD_LOOKUP_ROWS: u32 = 5_000;
5592 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5593 let min_word_search = buffer_snapshot.clip_point(
5594 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5595 Bias::Left,
5596 );
5597 let max_word_search = buffer_snapshot.clip_point(
5598 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5599 Bias::Right,
5600 );
5601 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5602 ..buffer_snapshot.point_to_offset(max_word_search);
5603
5604 let skip_digits = query
5605 .as_ref()
5606 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5607
5608 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5609 trigger.as_ref().is_none_or(|trigger| {
5610 provider.is_completion_trigger(
5611 &buffer,
5612 position.text_anchor,
5613 trigger,
5614 trigger_in_words,
5615 completions_source.is_some(),
5616 cx,
5617 )
5618 })
5619 });
5620
5621 let provider_responses = if let Some(provider) = &provider
5622 && load_provider_completions
5623 {
5624 let trigger_character =
5625 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5626 let completion_context = CompletionContext {
5627 trigger_kind: match &trigger_character {
5628 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5629 None => CompletionTriggerKind::INVOKED,
5630 },
5631 trigger_character,
5632 };
5633
5634 provider.completions(
5635 buffer_excerpt_id,
5636 &buffer,
5637 buffer_position,
5638 completion_context,
5639 window,
5640 cx,
5641 )
5642 } else {
5643 Task::ready(Ok(Vec::new()))
5644 };
5645
5646 let load_word_completions = if !self.word_completions_enabled {
5647 false
5648 } else if requested_source
5649 == Some(CompletionsMenuSource::Words {
5650 ignore_threshold: true,
5651 })
5652 {
5653 true
5654 } else {
5655 load_provider_completions
5656 && completion_settings.words != WordsCompletionMode::Disabled
5657 && (ignore_word_threshold || {
5658 let words_min_length = completion_settings.words_min_length;
5659 // check whether word has at least `words_min_length` characters
5660 let query_chars = query.iter().flat_map(|q| q.chars());
5661 query_chars.take(words_min_length).count() == words_min_length
5662 })
5663 };
5664
5665 let mut words = if load_word_completions {
5666 cx.background_spawn({
5667 let buffer_snapshot = buffer_snapshot.clone();
5668 async move {
5669 buffer_snapshot.words_in_range(WordsQuery {
5670 fuzzy_contents: None,
5671 range: word_search_range,
5672 skip_digits,
5673 })
5674 }
5675 })
5676 } else {
5677 Task::ready(BTreeMap::default())
5678 };
5679
5680 let snippets = if let Some(provider) = &provider
5681 && provider.show_snippets()
5682 && let Some(project) = self.project()
5683 {
5684 let char_classifier = buffer_snapshot
5685 .char_classifier_at(buffer_position)
5686 .scope_context(Some(CharScopeContext::Completion));
5687 project.update(cx, |project, cx| {
5688 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5689 })
5690 } else {
5691 Task::ready(Ok(CompletionResponse {
5692 completions: Vec::new(),
5693 display_options: Default::default(),
5694 is_incomplete: false,
5695 }))
5696 };
5697
5698 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5699
5700 let id = post_inc(&mut self.next_completion_id);
5701 let task = cx.spawn_in(window, async move |editor, cx| {
5702 let Ok(()) = editor.update(cx, |this, _| {
5703 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5704 }) else {
5705 return;
5706 };
5707
5708 // TODO: Ideally completions from different sources would be selectively re-queried, so
5709 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5710 let mut completions = Vec::new();
5711 let mut is_incomplete = false;
5712 let mut display_options: Option<CompletionDisplayOptions> = None;
5713 if let Some(provider_responses) = provider_responses.await.log_err()
5714 && !provider_responses.is_empty()
5715 {
5716 for response in provider_responses {
5717 completions.extend(response.completions);
5718 is_incomplete = is_incomplete || response.is_incomplete;
5719 match display_options.as_mut() {
5720 None => {
5721 display_options = Some(response.display_options);
5722 }
5723 Some(options) => options.merge(&response.display_options),
5724 }
5725 }
5726 if completion_settings.words == WordsCompletionMode::Fallback {
5727 words = Task::ready(BTreeMap::default());
5728 }
5729 }
5730 let display_options = display_options.unwrap_or_default();
5731
5732 let mut words = words.await;
5733 if let Some(word_to_exclude) = &word_to_exclude {
5734 words.remove(word_to_exclude);
5735 }
5736 for lsp_completion in &completions {
5737 words.remove(&lsp_completion.new_text);
5738 }
5739 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5740 replace_range: word_replace_range.clone(),
5741 new_text: word.clone(),
5742 label: CodeLabel::plain(word, None),
5743 match_start: None,
5744 snippet_deduplication_key: None,
5745 icon_path: None,
5746 documentation: None,
5747 source: CompletionSource::BufferWord {
5748 word_range,
5749 resolved: false,
5750 },
5751 insert_text_mode: Some(InsertTextMode::AS_IS),
5752 confirm: None,
5753 }));
5754
5755 completions.extend(
5756 snippets
5757 .await
5758 .into_iter()
5759 .flat_map(|response| response.completions),
5760 );
5761
5762 let menu = if completions.is_empty() {
5763 None
5764 } else {
5765 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5766 let languages = editor
5767 .workspace
5768 .as_ref()
5769 .and_then(|(workspace, _)| workspace.upgrade())
5770 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5771 let menu = CompletionsMenu::new(
5772 id,
5773 requested_source.unwrap_or(if load_provider_completions {
5774 CompletionsMenuSource::Normal
5775 } else {
5776 CompletionsMenuSource::SnippetsOnly
5777 }),
5778 sort_completions,
5779 show_completion_documentation,
5780 position,
5781 query.clone(),
5782 is_incomplete,
5783 buffer.clone(),
5784 completions.into(),
5785 display_options,
5786 snippet_sort_order,
5787 languages,
5788 language,
5789 cx,
5790 );
5791
5792 let query = if filter_completions { query } else { None };
5793 let matches_task = menu.do_async_filtering(
5794 query.unwrap_or_default(),
5795 buffer_position,
5796 &buffer,
5797 cx,
5798 );
5799 (menu, matches_task)
5800 }) else {
5801 return;
5802 };
5803
5804 let matches = matches_task.await;
5805
5806 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5807 // Newer menu already set, so exit.
5808 if let Some(CodeContextMenu::Completions(prev_menu)) =
5809 editor.context_menu.borrow().as_ref()
5810 && prev_menu.id > id
5811 {
5812 return;
5813 };
5814
5815 // Only valid to take prev_menu because either the new menu is immediately set
5816 // below, or the menu is hidden.
5817 if let Some(CodeContextMenu::Completions(prev_menu)) =
5818 editor.context_menu.borrow_mut().take()
5819 {
5820 let position_matches =
5821 if prev_menu.initial_position == menu.initial_position {
5822 true
5823 } else {
5824 let snapshot = editor.buffer.read(cx).read(cx);
5825 prev_menu.initial_position.to_offset(&snapshot)
5826 == menu.initial_position.to_offset(&snapshot)
5827 };
5828 if position_matches {
5829 // Preserve markdown cache before `set_filter_results` because it will
5830 // try to populate the documentation cache.
5831 menu.preserve_markdown_cache(prev_menu);
5832 }
5833 };
5834
5835 menu.set_filter_results(matches, provider, window, cx);
5836 }) else {
5837 return;
5838 };
5839
5840 menu.visible().then_some(menu)
5841 };
5842
5843 editor
5844 .update_in(cx, |editor, window, cx| {
5845 if editor.focus_handle.is_focused(window)
5846 && let Some(menu) = menu
5847 {
5848 *editor.context_menu.borrow_mut() =
5849 Some(CodeContextMenu::Completions(menu));
5850
5851 crate::hover_popover::hide_hover(editor, cx);
5852 if editor.show_edit_predictions_in_menu() {
5853 editor.update_visible_edit_prediction(window, cx);
5854 } else {
5855 editor.discard_edit_prediction(false, cx);
5856 }
5857
5858 cx.notify();
5859 return;
5860 }
5861
5862 if editor.completion_tasks.len() <= 1 {
5863 // If there are no more completion tasks and the last menu was empty, we should hide it.
5864 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5865 // If it was already hidden and we don't show edit predictions in the menu,
5866 // we should also show the edit prediction when available.
5867 if was_hidden && editor.show_edit_predictions_in_menu() {
5868 editor.update_visible_edit_prediction(window, cx);
5869 }
5870 }
5871 })
5872 .ok();
5873 });
5874
5875 self.completion_tasks.push((id, task));
5876 }
5877
5878 #[cfg(feature = "test-support")]
5879 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5880 let menu = self.context_menu.borrow();
5881 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5882 let completions = menu.completions.borrow();
5883 Some(completions.to_vec())
5884 } else {
5885 None
5886 }
5887 }
5888
5889 pub fn with_completions_menu_matching_id<R>(
5890 &self,
5891 id: CompletionId,
5892 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5893 ) -> R {
5894 let mut context_menu = self.context_menu.borrow_mut();
5895 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5896 return f(None);
5897 };
5898 if completions_menu.id != id {
5899 return f(None);
5900 }
5901 f(Some(completions_menu))
5902 }
5903
5904 pub fn confirm_completion(
5905 &mut self,
5906 action: &ConfirmCompletion,
5907 window: &mut Window,
5908 cx: &mut Context<Self>,
5909 ) -> Option<Task<Result<()>>> {
5910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5911 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5912 }
5913
5914 pub fn confirm_completion_insert(
5915 &mut self,
5916 _: &ConfirmCompletionInsert,
5917 window: &mut Window,
5918 cx: &mut Context<Self>,
5919 ) -> Option<Task<Result<()>>> {
5920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5921 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5922 }
5923
5924 pub fn confirm_completion_replace(
5925 &mut self,
5926 _: &ConfirmCompletionReplace,
5927 window: &mut Window,
5928 cx: &mut Context<Self>,
5929 ) -> Option<Task<Result<()>>> {
5930 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5931 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5932 }
5933
5934 pub fn compose_completion(
5935 &mut self,
5936 action: &ComposeCompletion,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) -> Option<Task<Result<()>>> {
5940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5941 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5942 }
5943
5944 fn do_completion(
5945 &mut self,
5946 item_ix: Option<usize>,
5947 intent: CompletionIntent,
5948 window: &mut Window,
5949 cx: &mut Context<Editor>,
5950 ) -> Option<Task<Result<()>>> {
5951 use language::ToOffset as _;
5952
5953 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5954 else {
5955 return None;
5956 };
5957
5958 let candidate_id = {
5959 let entries = completions_menu.entries.borrow();
5960 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5961 if self.show_edit_predictions_in_menu() {
5962 self.discard_edit_prediction(true, cx);
5963 }
5964 mat.candidate_id
5965 };
5966
5967 let completion = completions_menu
5968 .completions
5969 .borrow()
5970 .get(candidate_id)?
5971 .clone();
5972 cx.stop_propagation();
5973
5974 let buffer_handle = completions_menu.buffer.clone();
5975
5976 let CompletionEdit {
5977 new_text,
5978 snippet,
5979 replace_range,
5980 } = process_completion_for_edit(
5981 &completion,
5982 intent,
5983 &buffer_handle,
5984 &completions_menu.initial_position.text_anchor,
5985 cx,
5986 );
5987
5988 let buffer = buffer_handle.read(cx);
5989 let snapshot = self.buffer.read(cx).snapshot(cx);
5990 let newest_anchor = self.selections.newest_anchor();
5991 let replace_range_multibuffer = {
5992 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5993 excerpt.map_range_from_buffer(replace_range.clone())
5994 };
5995 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5996 return None;
5997 }
5998
5999 let old_text = buffer
6000 .text_for_range(replace_range.clone())
6001 .collect::<String>();
6002 let lookbehind = newest_anchor
6003 .start
6004 .text_anchor
6005 .to_offset(buffer)
6006 .saturating_sub(replace_range.start);
6007 let lookahead = replace_range
6008 .end
6009 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6010 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6011 let suffix = &old_text[lookbehind.min(old_text.len())..];
6012
6013 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6014 let mut ranges = Vec::new();
6015 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6016
6017 for selection in &selections {
6018 let range = if selection.id == newest_anchor.id {
6019 replace_range_multibuffer.clone()
6020 } else {
6021 let mut range = selection.range();
6022
6023 // if prefix is present, don't duplicate it
6024 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6025 range.start = range.start.saturating_sub(lookbehind);
6026
6027 // if suffix is also present, mimic the newest cursor and replace it
6028 if selection.id != newest_anchor.id
6029 && snapshot.contains_str_at(range.end, suffix)
6030 {
6031 range.end += lookahead;
6032 }
6033 }
6034 range
6035 };
6036
6037 ranges.push(range.clone());
6038
6039 if !self.linked_edit_ranges.is_empty() {
6040 let start_anchor = snapshot.anchor_before(range.start);
6041 let end_anchor = snapshot.anchor_after(range.end);
6042 if let Some(ranges) = self
6043 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6044 {
6045 for (buffer, edits) in ranges {
6046 linked_edits
6047 .entry(buffer.clone())
6048 .or_default()
6049 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6050 }
6051 }
6052 }
6053 }
6054
6055 let common_prefix_len = old_text
6056 .chars()
6057 .zip(new_text.chars())
6058 .take_while(|(a, b)| a == b)
6059 .map(|(a, _)| a.len_utf8())
6060 .sum::<usize>();
6061
6062 cx.emit(EditorEvent::InputHandled {
6063 utf16_range_to_replace: None,
6064 text: new_text[common_prefix_len..].into(),
6065 });
6066
6067 self.transact(window, cx, |editor, window, cx| {
6068 if let Some(mut snippet) = snippet {
6069 snippet.text = new_text.to_string();
6070 editor
6071 .insert_snippet(&ranges, snippet, window, cx)
6072 .log_err();
6073 } else {
6074 editor.buffer.update(cx, |multi_buffer, cx| {
6075 let auto_indent = match completion.insert_text_mode {
6076 Some(InsertTextMode::AS_IS) => None,
6077 _ => editor.autoindent_mode.clone(),
6078 };
6079 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6080 multi_buffer.edit(edits, auto_indent, cx);
6081 });
6082 }
6083 for (buffer, edits) in linked_edits {
6084 buffer.update(cx, |buffer, cx| {
6085 let snapshot = buffer.snapshot();
6086 let edits = edits
6087 .into_iter()
6088 .map(|(range, text)| {
6089 use text::ToPoint as TP;
6090 let end_point = TP::to_point(&range.end, &snapshot);
6091 let start_point = TP::to_point(&range.start, &snapshot);
6092 (start_point..end_point, text)
6093 })
6094 .sorted_by_key(|(range, _)| range.start);
6095 buffer.edit(edits, None, cx);
6096 })
6097 }
6098
6099 editor.refresh_edit_prediction(true, false, window, cx);
6100 });
6101 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6102
6103 let show_new_completions_on_confirm = completion
6104 .confirm
6105 .as_ref()
6106 .is_some_and(|confirm| confirm(intent, window, cx));
6107 if show_new_completions_on_confirm {
6108 self.open_or_update_completions_menu(None, None, false, window, cx);
6109 }
6110
6111 let provider = self.completion_provider.as_ref()?;
6112 drop(completion);
6113 let apply_edits = provider.apply_additional_edits_for_completion(
6114 buffer_handle,
6115 completions_menu.completions.clone(),
6116 candidate_id,
6117 true,
6118 cx,
6119 );
6120
6121 let editor_settings = EditorSettings::get_global(cx);
6122 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6123 // After the code completion is finished, users often want to know what signatures are needed.
6124 // so we should automatically call signature_help
6125 self.show_signature_help(&ShowSignatureHelp, window, cx);
6126 }
6127
6128 Some(cx.foreground_executor().spawn(async move {
6129 apply_edits.await?;
6130 Ok(())
6131 }))
6132 }
6133
6134 pub fn toggle_code_actions(
6135 &mut self,
6136 action: &ToggleCodeActions,
6137 window: &mut Window,
6138 cx: &mut Context<Self>,
6139 ) {
6140 let quick_launch = action.quick_launch;
6141 let mut context_menu = self.context_menu.borrow_mut();
6142 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6143 if code_actions.deployed_from == action.deployed_from {
6144 // Toggle if we're selecting the same one
6145 *context_menu = None;
6146 cx.notify();
6147 return;
6148 } else {
6149 // Otherwise, clear it and start a new one
6150 *context_menu = None;
6151 cx.notify();
6152 }
6153 }
6154 drop(context_menu);
6155 let snapshot = self.snapshot(window, cx);
6156 let deployed_from = action.deployed_from.clone();
6157 let action = action.clone();
6158 self.completion_tasks.clear();
6159 self.discard_edit_prediction(false, cx);
6160
6161 let multibuffer_point = match &action.deployed_from {
6162 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6163 DisplayPoint::new(*row, 0).to_point(&snapshot)
6164 }
6165 _ => self
6166 .selections
6167 .newest::<Point>(&snapshot.display_snapshot)
6168 .head(),
6169 };
6170 let Some((buffer, buffer_row)) = snapshot
6171 .buffer_snapshot()
6172 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6173 .and_then(|(buffer_snapshot, range)| {
6174 self.buffer()
6175 .read(cx)
6176 .buffer(buffer_snapshot.remote_id())
6177 .map(|buffer| (buffer, range.start.row))
6178 })
6179 else {
6180 return;
6181 };
6182 let buffer_id = buffer.read(cx).remote_id();
6183 let tasks = self
6184 .tasks
6185 .get(&(buffer_id, buffer_row))
6186 .map(|t| Arc::new(t.to_owned()));
6187
6188 if !self.focus_handle.is_focused(window) {
6189 return;
6190 }
6191 let project = self.project.clone();
6192
6193 let code_actions_task = match deployed_from {
6194 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6195 _ => self.code_actions(buffer_row, window, cx),
6196 };
6197
6198 let runnable_task = match deployed_from {
6199 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6200 _ => {
6201 let mut task_context_task = Task::ready(None);
6202 if let Some(tasks) = &tasks
6203 && let Some(project) = project
6204 {
6205 task_context_task =
6206 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6207 }
6208
6209 cx.spawn_in(window, {
6210 let buffer = buffer.clone();
6211 async move |editor, cx| {
6212 let task_context = task_context_task.await;
6213
6214 let resolved_tasks =
6215 tasks
6216 .zip(task_context.clone())
6217 .map(|(tasks, task_context)| ResolvedTasks {
6218 templates: tasks.resolve(&task_context).collect(),
6219 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6220 multibuffer_point.row,
6221 tasks.column,
6222 )),
6223 });
6224 let debug_scenarios = editor
6225 .update(cx, |editor, cx| {
6226 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6227 })?
6228 .await;
6229 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6230 }
6231 })
6232 }
6233 };
6234
6235 cx.spawn_in(window, async move |editor, cx| {
6236 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6237 let code_actions = code_actions_task.await;
6238 let spawn_straight_away = quick_launch
6239 && resolved_tasks
6240 .as_ref()
6241 .is_some_and(|tasks| tasks.templates.len() == 1)
6242 && code_actions
6243 .as_ref()
6244 .is_none_or(|actions| actions.is_empty())
6245 && debug_scenarios.is_empty();
6246
6247 editor.update_in(cx, |editor, window, cx| {
6248 crate::hover_popover::hide_hover(editor, cx);
6249 let actions = CodeActionContents::new(
6250 resolved_tasks,
6251 code_actions,
6252 debug_scenarios,
6253 task_context.unwrap_or_default(),
6254 );
6255
6256 // Don't show the menu if there are no actions available
6257 if actions.is_empty() {
6258 cx.notify();
6259 return Task::ready(Ok(()));
6260 }
6261
6262 *editor.context_menu.borrow_mut() =
6263 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6264 buffer,
6265 actions,
6266 selected_item: Default::default(),
6267 scroll_handle: UniformListScrollHandle::default(),
6268 deployed_from,
6269 }));
6270 cx.notify();
6271 if spawn_straight_away
6272 && let Some(task) = editor.confirm_code_action(
6273 &ConfirmCodeAction { item_ix: Some(0) },
6274 window,
6275 cx,
6276 )
6277 {
6278 return task;
6279 }
6280
6281 Task::ready(Ok(()))
6282 })
6283 })
6284 .detach_and_log_err(cx);
6285 }
6286
6287 fn debug_scenarios(
6288 &mut self,
6289 resolved_tasks: &Option<ResolvedTasks>,
6290 buffer: &Entity<Buffer>,
6291 cx: &mut App,
6292 ) -> Task<Vec<task::DebugScenario>> {
6293 maybe!({
6294 let project = self.project()?;
6295 let dap_store = project.read(cx).dap_store();
6296 let mut scenarios = vec![];
6297 let resolved_tasks = resolved_tasks.as_ref()?;
6298 let buffer = buffer.read(cx);
6299 let language = buffer.language()?;
6300 let file = buffer.file();
6301 let debug_adapter = language_settings(language.name().into(), file, cx)
6302 .debuggers
6303 .first()
6304 .map(SharedString::from)
6305 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6306
6307 dap_store.update(cx, |dap_store, cx| {
6308 for (_, task) in &resolved_tasks.templates {
6309 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6310 task.original_task().clone(),
6311 debug_adapter.clone().into(),
6312 task.display_label().to_owned().into(),
6313 cx,
6314 );
6315 scenarios.push(maybe_scenario);
6316 }
6317 });
6318 Some(cx.background_spawn(async move {
6319 futures::future::join_all(scenarios)
6320 .await
6321 .into_iter()
6322 .flatten()
6323 .collect::<Vec<_>>()
6324 }))
6325 })
6326 .unwrap_or_else(|| Task::ready(vec![]))
6327 }
6328
6329 fn code_actions(
6330 &mut self,
6331 buffer_row: u32,
6332 window: &mut Window,
6333 cx: &mut Context<Self>,
6334 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6335 let mut task = self.code_actions_task.take();
6336 cx.spawn_in(window, async move |editor, cx| {
6337 while let Some(prev_task) = task {
6338 prev_task.await.log_err();
6339 task = editor
6340 .update(cx, |this, _| this.code_actions_task.take())
6341 .ok()?;
6342 }
6343
6344 editor
6345 .update(cx, |editor, cx| {
6346 editor
6347 .available_code_actions
6348 .clone()
6349 .and_then(|(location, code_actions)| {
6350 let snapshot = location.buffer.read(cx).snapshot();
6351 let point_range = location.range.to_point(&snapshot);
6352 let point_range = point_range.start.row..=point_range.end.row;
6353 if point_range.contains(&buffer_row) {
6354 Some(code_actions)
6355 } else {
6356 None
6357 }
6358 })
6359 })
6360 .ok()
6361 .flatten()
6362 })
6363 }
6364
6365 pub fn confirm_code_action(
6366 &mut self,
6367 action: &ConfirmCodeAction,
6368 window: &mut Window,
6369 cx: &mut Context<Self>,
6370 ) -> Option<Task<Result<()>>> {
6371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6372
6373 let actions_menu =
6374 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6375 menu
6376 } else {
6377 return None;
6378 };
6379
6380 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6381 let action = actions_menu.actions.get(action_ix)?;
6382 let title = action.label();
6383 let buffer = actions_menu.buffer;
6384 let workspace = self.workspace()?;
6385
6386 match action {
6387 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6388 workspace.update(cx, |workspace, cx| {
6389 workspace.schedule_resolved_task(
6390 task_source_kind,
6391 resolved_task,
6392 false,
6393 window,
6394 cx,
6395 );
6396
6397 Some(Task::ready(Ok(())))
6398 })
6399 }
6400 CodeActionsItem::CodeAction {
6401 excerpt_id,
6402 action,
6403 provider,
6404 } => {
6405 let apply_code_action =
6406 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6407 let workspace = workspace.downgrade();
6408 Some(cx.spawn_in(window, async move |editor, cx| {
6409 let project_transaction = apply_code_action.await?;
6410 Self::open_project_transaction(
6411 &editor,
6412 workspace,
6413 project_transaction,
6414 title,
6415 cx,
6416 )
6417 .await
6418 }))
6419 }
6420 CodeActionsItem::DebugScenario(scenario) => {
6421 let context = actions_menu.actions.context;
6422
6423 workspace.update(cx, |workspace, cx| {
6424 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6425 workspace.start_debug_session(
6426 scenario,
6427 context,
6428 Some(buffer),
6429 None,
6430 window,
6431 cx,
6432 );
6433 });
6434 Some(Task::ready(Ok(())))
6435 }
6436 }
6437 }
6438
6439 pub async fn open_project_transaction(
6440 editor: &WeakEntity<Editor>,
6441 workspace: WeakEntity<Workspace>,
6442 transaction: ProjectTransaction,
6443 title: String,
6444 cx: &mut AsyncWindowContext,
6445 ) -> Result<()> {
6446 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6447 cx.update(|_, cx| {
6448 entries.sort_unstable_by_key(|(buffer, _)| {
6449 buffer.read(cx).file().map(|f| f.path().clone())
6450 });
6451 })?;
6452 if entries.is_empty() {
6453 return Ok(());
6454 }
6455
6456 // If the project transaction's edits are all contained within this editor, then
6457 // avoid opening a new editor to display them.
6458
6459 if let [(buffer, transaction)] = &*entries {
6460 let excerpt = editor.update(cx, |editor, cx| {
6461 editor
6462 .buffer()
6463 .read(cx)
6464 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6465 })?;
6466 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6467 && excerpted_buffer == *buffer
6468 {
6469 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6470 let excerpt_range = excerpt_range.to_offset(buffer);
6471 buffer
6472 .edited_ranges_for_transaction::<usize>(transaction)
6473 .all(|range| {
6474 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6475 })
6476 })?;
6477
6478 if all_edits_within_excerpt {
6479 return Ok(());
6480 }
6481 }
6482 }
6483
6484 let mut ranges_to_highlight = Vec::new();
6485 let excerpt_buffer = cx.new(|cx| {
6486 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6487 for (buffer_handle, transaction) in &entries {
6488 let edited_ranges = buffer_handle
6489 .read(cx)
6490 .edited_ranges_for_transaction::<Point>(transaction)
6491 .collect::<Vec<_>>();
6492 let (ranges, _) = multibuffer.set_excerpts_for_path(
6493 PathKey::for_buffer(buffer_handle, cx),
6494 buffer_handle.clone(),
6495 edited_ranges,
6496 multibuffer_context_lines(cx),
6497 cx,
6498 );
6499
6500 ranges_to_highlight.extend(ranges);
6501 }
6502 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6503 multibuffer
6504 })?;
6505
6506 workspace.update_in(cx, |workspace, window, cx| {
6507 let project = workspace.project().clone();
6508 let editor =
6509 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6510 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6511 editor.update(cx, |editor, cx| {
6512 editor.highlight_background::<Self>(
6513 &ranges_to_highlight,
6514 |theme| theme.colors().editor_highlighted_line_background,
6515 cx,
6516 );
6517 });
6518 })?;
6519
6520 Ok(())
6521 }
6522
6523 pub fn clear_code_action_providers(&mut self) {
6524 self.code_action_providers.clear();
6525 self.available_code_actions.take();
6526 }
6527
6528 pub fn add_code_action_provider(
6529 &mut self,
6530 provider: Rc<dyn CodeActionProvider>,
6531 window: &mut Window,
6532 cx: &mut Context<Self>,
6533 ) {
6534 if self
6535 .code_action_providers
6536 .iter()
6537 .any(|existing_provider| existing_provider.id() == provider.id())
6538 {
6539 return;
6540 }
6541
6542 self.code_action_providers.push(provider);
6543 self.refresh_code_actions(window, cx);
6544 }
6545
6546 pub fn remove_code_action_provider(
6547 &mut self,
6548 id: Arc<str>,
6549 window: &mut Window,
6550 cx: &mut Context<Self>,
6551 ) {
6552 self.code_action_providers
6553 .retain(|provider| provider.id() != id);
6554 self.refresh_code_actions(window, cx);
6555 }
6556
6557 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6558 !self.code_action_providers.is_empty()
6559 && EditorSettings::get_global(cx).toolbar.code_actions
6560 }
6561
6562 pub fn has_available_code_actions(&self) -> bool {
6563 self.available_code_actions
6564 .as_ref()
6565 .is_some_and(|(_, actions)| !actions.is_empty())
6566 }
6567
6568 fn render_inline_code_actions(
6569 &self,
6570 icon_size: ui::IconSize,
6571 display_row: DisplayRow,
6572 is_active: bool,
6573 cx: &mut Context<Self>,
6574 ) -> AnyElement {
6575 let show_tooltip = !self.context_menu_visible();
6576 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6577 .icon_size(icon_size)
6578 .shape(ui::IconButtonShape::Square)
6579 .icon_color(ui::Color::Hidden)
6580 .toggle_state(is_active)
6581 .when(show_tooltip, |this| {
6582 this.tooltip({
6583 let focus_handle = self.focus_handle.clone();
6584 move |_window, cx| {
6585 Tooltip::for_action_in(
6586 "Toggle Code Actions",
6587 &ToggleCodeActions {
6588 deployed_from: None,
6589 quick_launch: false,
6590 },
6591 &focus_handle,
6592 cx,
6593 )
6594 }
6595 })
6596 })
6597 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6598 window.focus(&editor.focus_handle(cx));
6599 editor.toggle_code_actions(
6600 &crate::actions::ToggleCodeActions {
6601 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6602 display_row,
6603 )),
6604 quick_launch: false,
6605 },
6606 window,
6607 cx,
6608 );
6609 }))
6610 .into_any_element()
6611 }
6612
6613 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6614 &self.context_menu
6615 }
6616
6617 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6618 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6619 cx.background_executor()
6620 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6621 .await;
6622
6623 let (start_buffer, start, _, end, newest_selection) = this
6624 .update(cx, |this, cx| {
6625 let newest_selection = this.selections.newest_anchor().clone();
6626 if newest_selection.head().diff_base_anchor.is_some() {
6627 return None;
6628 }
6629 let display_snapshot = this.display_snapshot(cx);
6630 let newest_selection_adjusted =
6631 this.selections.newest_adjusted(&display_snapshot);
6632 let buffer = this.buffer.read(cx);
6633
6634 let (start_buffer, start) =
6635 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6636 let (end_buffer, end) =
6637 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6638
6639 Some((start_buffer, start, end_buffer, end, newest_selection))
6640 })?
6641 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6642 .context(
6643 "Expected selection to lie in a single buffer when refreshing code actions",
6644 )?;
6645 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6646 let providers = this.code_action_providers.clone();
6647 let tasks = this
6648 .code_action_providers
6649 .iter()
6650 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6651 .collect::<Vec<_>>();
6652 (providers, tasks)
6653 })?;
6654
6655 let mut actions = Vec::new();
6656 for (provider, provider_actions) in
6657 providers.into_iter().zip(future::join_all(tasks).await)
6658 {
6659 if let Some(provider_actions) = provider_actions.log_err() {
6660 actions.extend(provider_actions.into_iter().map(|action| {
6661 AvailableCodeAction {
6662 excerpt_id: newest_selection.start.excerpt_id,
6663 action,
6664 provider: provider.clone(),
6665 }
6666 }));
6667 }
6668 }
6669
6670 this.update(cx, |this, cx| {
6671 this.available_code_actions = if actions.is_empty() {
6672 None
6673 } else {
6674 Some((
6675 Location {
6676 buffer: start_buffer,
6677 range: start..end,
6678 },
6679 actions.into(),
6680 ))
6681 };
6682 cx.notify();
6683 })
6684 }));
6685 }
6686
6687 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6688 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6689 self.show_git_blame_inline = false;
6690
6691 self.show_git_blame_inline_delay_task =
6692 Some(cx.spawn_in(window, async move |this, cx| {
6693 cx.background_executor().timer(delay).await;
6694
6695 this.update(cx, |this, cx| {
6696 this.show_git_blame_inline = true;
6697 cx.notify();
6698 })
6699 .log_err();
6700 }));
6701 }
6702 }
6703
6704 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6705 let snapshot = self.snapshot(window, cx);
6706 let cursor = self
6707 .selections
6708 .newest::<Point>(&snapshot.display_snapshot)
6709 .head();
6710 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6711 else {
6712 return;
6713 };
6714
6715 let Some(blame) = self.blame.as_ref() else {
6716 return;
6717 };
6718
6719 let row_info = RowInfo {
6720 buffer_id: Some(buffer.remote_id()),
6721 buffer_row: Some(point.row),
6722 ..Default::default()
6723 };
6724 let Some((buffer, blame_entry)) = blame
6725 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6726 .flatten()
6727 else {
6728 return;
6729 };
6730
6731 let anchor = self.selections.newest_anchor().head();
6732 let position = self.to_pixel_point(anchor, &snapshot, window);
6733 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6734 self.show_blame_popover(
6735 buffer,
6736 &blame_entry,
6737 position + last_bounds.origin,
6738 true,
6739 cx,
6740 );
6741 };
6742 }
6743
6744 fn show_blame_popover(
6745 &mut self,
6746 buffer: BufferId,
6747 blame_entry: &BlameEntry,
6748 position: gpui::Point<Pixels>,
6749 ignore_timeout: bool,
6750 cx: &mut Context<Self>,
6751 ) {
6752 if let Some(state) = &mut self.inline_blame_popover {
6753 state.hide_task.take();
6754 } else {
6755 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6756 let blame_entry = blame_entry.clone();
6757 let show_task = cx.spawn(async move |editor, cx| {
6758 if !ignore_timeout {
6759 cx.background_executor()
6760 .timer(std::time::Duration::from_millis(blame_popover_delay))
6761 .await;
6762 }
6763 editor
6764 .update(cx, |editor, cx| {
6765 editor.inline_blame_popover_show_task.take();
6766 let Some(blame) = editor.blame.as_ref() else {
6767 return;
6768 };
6769 let blame = blame.read(cx);
6770 let details = blame.details_for_entry(buffer, &blame_entry);
6771 let markdown = cx.new(|cx| {
6772 Markdown::new(
6773 details
6774 .as_ref()
6775 .map(|message| message.message.clone())
6776 .unwrap_or_default(),
6777 None,
6778 None,
6779 cx,
6780 )
6781 });
6782 editor.inline_blame_popover = Some(InlineBlamePopover {
6783 position,
6784 hide_task: None,
6785 popover_bounds: None,
6786 popover_state: InlineBlamePopoverState {
6787 scroll_handle: ScrollHandle::new(),
6788 commit_message: details,
6789 markdown,
6790 },
6791 keyboard_grace: ignore_timeout,
6792 });
6793 cx.notify();
6794 })
6795 .ok();
6796 });
6797 self.inline_blame_popover_show_task = Some(show_task);
6798 }
6799 }
6800
6801 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6802 self.inline_blame_popover_show_task.take();
6803 if let Some(state) = &mut self.inline_blame_popover {
6804 let hide_task = cx.spawn(async move |editor, cx| {
6805 if !ignore_timeout {
6806 cx.background_executor()
6807 .timer(std::time::Duration::from_millis(100))
6808 .await;
6809 }
6810 editor
6811 .update(cx, |editor, cx| {
6812 editor.inline_blame_popover.take();
6813 cx.notify();
6814 })
6815 .ok();
6816 });
6817 state.hide_task = Some(hide_task);
6818 true
6819 } else {
6820 false
6821 }
6822 }
6823
6824 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6825 if self.pending_rename.is_some() {
6826 return None;
6827 }
6828
6829 let provider = self.semantics_provider.clone()?;
6830 let buffer = self.buffer.read(cx);
6831 let newest_selection = self.selections.newest_anchor().clone();
6832 let cursor_position = newest_selection.head();
6833 let (cursor_buffer, cursor_buffer_position) =
6834 buffer.text_anchor_for_position(cursor_position, cx)?;
6835 let (tail_buffer, tail_buffer_position) =
6836 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6837 if cursor_buffer != tail_buffer {
6838 return None;
6839 }
6840
6841 let snapshot = cursor_buffer.read(cx).snapshot();
6842 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6843 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6844 if start_word_range != end_word_range {
6845 self.document_highlights_task.take();
6846 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6847 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6848 return None;
6849 }
6850
6851 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6852 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6853 cx.background_executor()
6854 .timer(Duration::from_millis(debounce))
6855 .await;
6856
6857 let highlights = if let Some(highlights) = cx
6858 .update(|cx| {
6859 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6860 })
6861 .ok()
6862 .flatten()
6863 {
6864 highlights.await.log_err()
6865 } else {
6866 None
6867 };
6868
6869 if let Some(highlights) = highlights {
6870 this.update(cx, |this, cx| {
6871 if this.pending_rename.is_some() {
6872 return;
6873 }
6874
6875 let buffer = this.buffer.read(cx);
6876 if buffer
6877 .text_anchor_for_position(cursor_position, cx)
6878 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6879 {
6880 return;
6881 }
6882
6883 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6884 let mut write_ranges = Vec::new();
6885 let mut read_ranges = Vec::new();
6886 for highlight in highlights {
6887 let buffer_id = cursor_buffer.read(cx).remote_id();
6888 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6889 {
6890 let start = highlight
6891 .range
6892 .start
6893 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6894 let end = highlight
6895 .range
6896 .end
6897 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6898 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6899 continue;
6900 }
6901
6902 let range =
6903 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6904 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6905 write_ranges.push(range);
6906 } else {
6907 read_ranges.push(range);
6908 }
6909 }
6910 }
6911
6912 this.highlight_background::<DocumentHighlightRead>(
6913 &read_ranges,
6914 |theme| theme.colors().editor_document_highlight_read_background,
6915 cx,
6916 );
6917 this.highlight_background::<DocumentHighlightWrite>(
6918 &write_ranges,
6919 |theme| theme.colors().editor_document_highlight_write_background,
6920 cx,
6921 );
6922 cx.notify();
6923 })
6924 .log_err();
6925 }
6926 }));
6927 None
6928 }
6929
6930 fn prepare_highlight_query_from_selection(
6931 &mut self,
6932 window: &Window,
6933 cx: &mut Context<Editor>,
6934 ) -> Option<(String, Range<Anchor>)> {
6935 if matches!(self.mode, EditorMode::SingleLine) {
6936 return None;
6937 }
6938 if !EditorSettings::get_global(cx).selection_highlight {
6939 return None;
6940 }
6941 if self.selections.count() != 1 || self.selections.line_mode() {
6942 return None;
6943 }
6944 let snapshot = self.snapshot(window, cx);
6945 let selection = self.selections.newest::<Point>(&snapshot);
6946 // If the selection spans multiple rows OR it is empty
6947 if selection.start.row != selection.end.row
6948 || selection.start.column == selection.end.column
6949 {
6950 return None;
6951 }
6952 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6953 let query = snapshot
6954 .buffer_snapshot()
6955 .text_for_range(selection_anchor_range.clone())
6956 .collect::<String>();
6957 if query.trim().is_empty() {
6958 return None;
6959 }
6960 Some((query, selection_anchor_range))
6961 }
6962
6963 fn update_selection_occurrence_highlights(
6964 &mut self,
6965 query_text: String,
6966 query_range: Range<Anchor>,
6967 multi_buffer_range_to_query: Range<Point>,
6968 use_debounce: bool,
6969 window: &mut Window,
6970 cx: &mut Context<Editor>,
6971 ) -> Task<()> {
6972 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6973 cx.spawn_in(window, async move |editor, cx| {
6974 if use_debounce {
6975 cx.background_executor()
6976 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6977 .await;
6978 }
6979 let match_task = cx.background_spawn(async move {
6980 let buffer_ranges = multi_buffer_snapshot
6981 .range_to_buffer_ranges(multi_buffer_range_to_query)
6982 .into_iter()
6983 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6984 let mut match_ranges = Vec::new();
6985 let Ok(regex) = project::search::SearchQuery::text(
6986 query_text.clone(),
6987 false,
6988 false,
6989 false,
6990 Default::default(),
6991 Default::default(),
6992 false,
6993 None,
6994 ) else {
6995 return Vec::default();
6996 };
6997 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6998 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6999 match_ranges.extend(
7000 regex
7001 .search(buffer_snapshot, Some(search_range.clone()))
7002 .await
7003 .into_iter()
7004 .filter_map(|match_range| {
7005 let match_start = buffer_snapshot
7006 .anchor_after(search_range.start + match_range.start);
7007 let match_end = buffer_snapshot
7008 .anchor_before(search_range.start + match_range.end);
7009 let match_anchor_range = Anchor::range_in_buffer(
7010 excerpt_id,
7011 buffer_snapshot.remote_id(),
7012 match_start..match_end,
7013 );
7014 (match_anchor_range != query_range).then_some(match_anchor_range)
7015 }),
7016 );
7017 }
7018 match_ranges
7019 });
7020 let match_ranges = match_task.await;
7021 editor
7022 .update_in(cx, |editor, _, cx| {
7023 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7024 if !match_ranges.is_empty() {
7025 editor.highlight_background::<SelectedTextHighlight>(
7026 &match_ranges,
7027 |theme| theme.colors().editor_document_highlight_bracket_background,
7028 cx,
7029 )
7030 }
7031 })
7032 .log_err();
7033 })
7034 }
7035
7036 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7037 struct NewlineFold;
7038 let type_id = std::any::TypeId::of::<NewlineFold>();
7039 if !self.mode.is_single_line() {
7040 return;
7041 }
7042 let snapshot = self.snapshot(window, cx);
7043 if snapshot.buffer_snapshot().max_point().row == 0 {
7044 return;
7045 }
7046 let task = cx.background_spawn(async move {
7047 let new_newlines = snapshot
7048 .buffer_chars_at(0)
7049 .filter_map(|(c, i)| {
7050 if c == '\n' {
7051 Some(
7052 snapshot.buffer_snapshot().anchor_after(i)
7053 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7054 )
7055 } else {
7056 None
7057 }
7058 })
7059 .collect::<Vec<_>>();
7060 let existing_newlines = snapshot
7061 .folds_in_range(0..snapshot.buffer_snapshot().len())
7062 .filter_map(|fold| {
7063 if fold.placeholder.type_tag == Some(type_id) {
7064 Some(fold.range.start..fold.range.end)
7065 } else {
7066 None
7067 }
7068 })
7069 .collect::<Vec<_>>();
7070
7071 (new_newlines, existing_newlines)
7072 });
7073 self.folding_newlines = cx.spawn(async move |this, cx| {
7074 let (new_newlines, existing_newlines) = task.await;
7075 if new_newlines == existing_newlines {
7076 return;
7077 }
7078 let placeholder = FoldPlaceholder {
7079 render: Arc::new(move |_, _, cx| {
7080 div()
7081 .bg(cx.theme().status().hint_background)
7082 .border_b_1()
7083 .size_full()
7084 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7085 .border_color(cx.theme().status().hint)
7086 .child("\\n")
7087 .into_any()
7088 }),
7089 constrain_width: false,
7090 merge_adjacent: false,
7091 type_tag: Some(type_id),
7092 };
7093 let creases = new_newlines
7094 .into_iter()
7095 .map(|range| Crease::simple(range, placeholder.clone()))
7096 .collect();
7097 this.update(cx, |this, cx| {
7098 this.display_map.update(cx, |display_map, cx| {
7099 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7100 display_map.fold(creases, cx);
7101 });
7102 })
7103 .ok();
7104 });
7105 }
7106
7107 fn refresh_selected_text_highlights(
7108 &mut self,
7109 on_buffer_edit: bool,
7110 window: &mut Window,
7111 cx: &mut Context<Editor>,
7112 ) {
7113 let Some((query_text, query_range)) =
7114 self.prepare_highlight_query_from_selection(window, cx)
7115 else {
7116 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7117 self.quick_selection_highlight_task.take();
7118 self.debounced_selection_highlight_task.take();
7119 return;
7120 };
7121 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7122 if on_buffer_edit
7123 || self
7124 .quick_selection_highlight_task
7125 .as_ref()
7126 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7127 {
7128 let multi_buffer_visible_start = self
7129 .scroll_manager
7130 .anchor()
7131 .anchor
7132 .to_point(&multi_buffer_snapshot);
7133 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7134 multi_buffer_visible_start
7135 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7136 Bias::Left,
7137 );
7138 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7139 self.quick_selection_highlight_task = Some((
7140 query_range.clone(),
7141 self.update_selection_occurrence_highlights(
7142 query_text.clone(),
7143 query_range.clone(),
7144 multi_buffer_visible_range,
7145 false,
7146 window,
7147 cx,
7148 ),
7149 ));
7150 }
7151 if on_buffer_edit
7152 || self
7153 .debounced_selection_highlight_task
7154 .as_ref()
7155 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7156 {
7157 let multi_buffer_start = multi_buffer_snapshot
7158 .anchor_before(0)
7159 .to_point(&multi_buffer_snapshot);
7160 let multi_buffer_end = multi_buffer_snapshot
7161 .anchor_after(multi_buffer_snapshot.len())
7162 .to_point(&multi_buffer_snapshot);
7163 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7164 self.debounced_selection_highlight_task = Some((
7165 query_range.clone(),
7166 self.update_selection_occurrence_highlights(
7167 query_text,
7168 query_range,
7169 multi_buffer_full_range,
7170 true,
7171 window,
7172 cx,
7173 ),
7174 ));
7175 }
7176 }
7177
7178 pub fn refresh_edit_prediction(
7179 &mut self,
7180 debounce: bool,
7181 user_requested: bool,
7182 window: &mut Window,
7183 cx: &mut Context<Self>,
7184 ) -> Option<()> {
7185 if DisableAiSettings::get_global(cx).disable_ai {
7186 return None;
7187 }
7188
7189 let provider = self.edit_prediction_provider()?;
7190 let cursor = self.selections.newest_anchor().head();
7191 let (buffer, cursor_buffer_position) =
7192 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7193
7194 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7195 self.discard_edit_prediction(false, cx);
7196 return None;
7197 }
7198
7199 self.update_visible_edit_prediction(window, cx);
7200
7201 if !user_requested
7202 && (!self.should_show_edit_predictions()
7203 || !self.is_focused(window)
7204 || buffer.read(cx).is_empty())
7205 {
7206 self.discard_edit_prediction(false, cx);
7207 return None;
7208 }
7209
7210 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7211 Some(())
7212 }
7213
7214 fn show_edit_predictions_in_menu(&self) -> bool {
7215 match self.edit_prediction_settings {
7216 EditPredictionSettings::Disabled => false,
7217 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7218 }
7219 }
7220
7221 pub fn edit_predictions_enabled(&self) -> bool {
7222 match self.edit_prediction_settings {
7223 EditPredictionSettings::Disabled => false,
7224 EditPredictionSettings::Enabled { .. } => true,
7225 }
7226 }
7227
7228 fn edit_prediction_requires_modifier(&self) -> bool {
7229 match self.edit_prediction_settings {
7230 EditPredictionSettings::Disabled => false,
7231 EditPredictionSettings::Enabled {
7232 preview_requires_modifier,
7233 ..
7234 } => preview_requires_modifier,
7235 }
7236 }
7237
7238 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7239 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7240 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7241 self.discard_edit_prediction(false, cx);
7242 } else {
7243 let selection = self.selections.newest_anchor();
7244 let cursor = selection.head();
7245
7246 if let Some((buffer, cursor_buffer_position)) =
7247 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7248 {
7249 self.edit_prediction_settings =
7250 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7251 }
7252 }
7253 }
7254
7255 fn edit_prediction_settings_at_position(
7256 &self,
7257 buffer: &Entity<Buffer>,
7258 buffer_position: language::Anchor,
7259 cx: &App,
7260 ) -> EditPredictionSettings {
7261 if !self.mode.is_full()
7262 || !self.show_edit_predictions_override.unwrap_or(true)
7263 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7264 {
7265 return EditPredictionSettings::Disabled;
7266 }
7267
7268 let buffer = buffer.read(cx);
7269
7270 let file = buffer.file();
7271
7272 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7273 return EditPredictionSettings::Disabled;
7274 };
7275
7276 let by_provider = matches!(
7277 self.menu_edit_predictions_policy,
7278 MenuEditPredictionsPolicy::ByProvider
7279 );
7280
7281 let show_in_menu = by_provider
7282 && self
7283 .edit_prediction_provider
7284 .as_ref()
7285 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7286
7287 let preview_requires_modifier =
7288 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7289
7290 EditPredictionSettings::Enabled {
7291 show_in_menu,
7292 preview_requires_modifier,
7293 }
7294 }
7295
7296 fn should_show_edit_predictions(&self) -> bool {
7297 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7298 }
7299
7300 pub fn edit_prediction_preview_is_active(&self) -> bool {
7301 matches!(
7302 self.edit_prediction_preview,
7303 EditPredictionPreview::Active { .. }
7304 )
7305 }
7306
7307 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7308 let cursor = self.selections.newest_anchor().head();
7309 if let Some((buffer, cursor_position)) =
7310 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7311 {
7312 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7313 } else {
7314 false
7315 }
7316 }
7317
7318 pub fn supports_minimap(&self, cx: &App) -> bool {
7319 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7320 }
7321
7322 fn edit_predictions_enabled_in_buffer(
7323 &self,
7324 buffer: &Entity<Buffer>,
7325 buffer_position: language::Anchor,
7326 cx: &App,
7327 ) -> bool {
7328 maybe!({
7329 if self.read_only(cx) {
7330 return Some(false);
7331 }
7332 let provider = self.edit_prediction_provider()?;
7333 if !provider.is_enabled(buffer, buffer_position, cx) {
7334 return Some(false);
7335 }
7336 let buffer = buffer.read(cx);
7337 let Some(file) = buffer.file() else {
7338 return Some(true);
7339 };
7340 let settings = all_language_settings(Some(file), cx);
7341 Some(settings.edit_predictions_enabled_for_file(file, cx))
7342 })
7343 .unwrap_or(false)
7344 }
7345
7346 fn cycle_edit_prediction(
7347 &mut self,
7348 direction: Direction,
7349 window: &mut Window,
7350 cx: &mut Context<Self>,
7351 ) -> Option<()> {
7352 let provider = self.edit_prediction_provider()?;
7353 let cursor = self.selections.newest_anchor().head();
7354 let (buffer, cursor_buffer_position) =
7355 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7356 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7357 return None;
7358 }
7359
7360 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7361 self.update_visible_edit_prediction(window, cx);
7362
7363 Some(())
7364 }
7365
7366 pub fn show_edit_prediction(
7367 &mut self,
7368 _: &ShowEditPrediction,
7369 window: &mut Window,
7370 cx: &mut Context<Self>,
7371 ) {
7372 if !self.has_active_edit_prediction() {
7373 self.refresh_edit_prediction(false, true, window, cx);
7374 return;
7375 }
7376
7377 self.update_visible_edit_prediction(window, cx);
7378 }
7379
7380 pub fn display_cursor_names(
7381 &mut self,
7382 _: &DisplayCursorNames,
7383 window: &mut Window,
7384 cx: &mut Context<Self>,
7385 ) {
7386 self.show_cursor_names(window, cx);
7387 }
7388
7389 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7390 self.show_cursor_names = true;
7391 cx.notify();
7392 cx.spawn_in(window, async move |this, cx| {
7393 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7394 this.update(cx, |this, cx| {
7395 this.show_cursor_names = false;
7396 cx.notify()
7397 })
7398 .ok()
7399 })
7400 .detach();
7401 }
7402
7403 pub fn next_edit_prediction(
7404 &mut self,
7405 _: &NextEditPrediction,
7406 window: &mut Window,
7407 cx: &mut Context<Self>,
7408 ) {
7409 if self.has_active_edit_prediction() {
7410 self.cycle_edit_prediction(Direction::Next, window, cx);
7411 } else {
7412 let is_copilot_disabled = self
7413 .refresh_edit_prediction(false, true, window, cx)
7414 .is_none();
7415 if is_copilot_disabled {
7416 cx.propagate();
7417 }
7418 }
7419 }
7420
7421 pub fn previous_edit_prediction(
7422 &mut self,
7423 _: &PreviousEditPrediction,
7424 window: &mut Window,
7425 cx: &mut Context<Self>,
7426 ) {
7427 if self.has_active_edit_prediction() {
7428 self.cycle_edit_prediction(Direction::Prev, window, cx);
7429 } else {
7430 let is_copilot_disabled = self
7431 .refresh_edit_prediction(false, true, window, cx)
7432 .is_none();
7433 if is_copilot_disabled {
7434 cx.propagate();
7435 }
7436 }
7437 }
7438
7439 pub fn accept_edit_prediction(
7440 &mut self,
7441 _: &AcceptEditPrediction,
7442 window: &mut Window,
7443 cx: &mut Context<Self>,
7444 ) {
7445 if self.show_edit_predictions_in_menu() {
7446 self.hide_context_menu(window, cx);
7447 }
7448
7449 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7450 return;
7451 };
7452
7453 match &active_edit_prediction.completion {
7454 EditPrediction::MoveWithin { target, .. } => {
7455 let target = *target;
7456
7457 if let Some(position_map) = &self.last_position_map {
7458 if position_map
7459 .visible_row_range
7460 .contains(&target.to_display_point(&position_map.snapshot).row())
7461 || !self.edit_prediction_requires_modifier()
7462 {
7463 self.unfold_ranges(&[target..target], true, false, cx);
7464 // Note that this is also done in vim's handler of the Tab action.
7465 self.change_selections(
7466 SelectionEffects::scroll(Autoscroll::newest()),
7467 window,
7468 cx,
7469 |selections| {
7470 selections.select_anchor_ranges([target..target]);
7471 },
7472 );
7473 self.clear_row_highlights::<EditPredictionPreview>();
7474
7475 self.edit_prediction_preview
7476 .set_previous_scroll_position(None);
7477 } else {
7478 self.edit_prediction_preview
7479 .set_previous_scroll_position(Some(
7480 position_map.snapshot.scroll_anchor,
7481 ));
7482
7483 self.highlight_rows::<EditPredictionPreview>(
7484 target..target,
7485 cx.theme().colors().editor_highlighted_line_background,
7486 RowHighlightOptions {
7487 autoscroll: true,
7488 ..Default::default()
7489 },
7490 cx,
7491 );
7492 self.request_autoscroll(Autoscroll::fit(), cx);
7493 }
7494 }
7495 }
7496 EditPrediction::MoveOutside { snapshot, target } => {
7497 if let Some(workspace) = self.workspace() {
7498 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7499 .detach_and_log_err(cx);
7500 }
7501 }
7502 EditPrediction::Edit { edits, .. } => {
7503 self.report_edit_prediction_event(
7504 active_edit_prediction.completion_id.clone(),
7505 true,
7506 cx,
7507 );
7508
7509 if let Some(provider) = self.edit_prediction_provider() {
7510 provider.accept(cx);
7511 }
7512
7513 // Store the transaction ID and selections before applying the edit
7514 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7515
7516 let snapshot = self.buffer.read(cx).snapshot(cx);
7517 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7518
7519 self.buffer.update(cx, |buffer, cx| {
7520 buffer.edit(edits.iter().cloned(), None, cx)
7521 });
7522
7523 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7524 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7525 });
7526
7527 let selections = self.selections.disjoint_anchors_arc();
7528 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7529 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7530 if has_new_transaction {
7531 self.selection_history
7532 .insert_transaction(transaction_id_now, selections);
7533 }
7534 }
7535
7536 self.update_visible_edit_prediction(window, cx);
7537 if self.active_edit_prediction.is_none() {
7538 self.refresh_edit_prediction(true, true, window, cx);
7539 }
7540
7541 cx.notify();
7542 }
7543 }
7544
7545 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7546 }
7547
7548 pub fn accept_partial_edit_prediction(
7549 &mut self,
7550 _: &AcceptPartialEditPrediction,
7551 window: &mut Window,
7552 cx: &mut Context<Self>,
7553 ) {
7554 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7555 return;
7556 };
7557 if self.selections.count() != 1 {
7558 return;
7559 }
7560
7561 match &active_edit_prediction.completion {
7562 EditPrediction::MoveWithin { target, .. } => {
7563 let target = *target;
7564 self.change_selections(
7565 SelectionEffects::scroll(Autoscroll::newest()),
7566 window,
7567 cx,
7568 |selections| {
7569 selections.select_anchor_ranges([target..target]);
7570 },
7571 );
7572 }
7573 EditPrediction::MoveOutside { snapshot, target } => {
7574 if let Some(workspace) = self.workspace() {
7575 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7576 .detach_and_log_err(cx);
7577 }
7578 }
7579 EditPrediction::Edit { edits, .. } => {
7580 self.report_edit_prediction_event(
7581 active_edit_prediction.completion_id.clone(),
7582 true,
7583 cx,
7584 );
7585
7586 // Find an insertion that starts at the cursor position.
7587 let snapshot = self.buffer.read(cx).snapshot(cx);
7588 let cursor_offset = self
7589 .selections
7590 .newest::<usize>(&self.display_snapshot(cx))
7591 .head();
7592 let insertion = edits.iter().find_map(|(range, text)| {
7593 let range = range.to_offset(&snapshot);
7594 if range.is_empty() && range.start == cursor_offset {
7595 Some(text)
7596 } else {
7597 None
7598 }
7599 });
7600
7601 if let Some(text) = insertion {
7602 let mut partial_completion = text
7603 .chars()
7604 .by_ref()
7605 .take_while(|c| c.is_alphabetic())
7606 .collect::<String>();
7607 if partial_completion.is_empty() {
7608 partial_completion = text
7609 .chars()
7610 .by_ref()
7611 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7612 .collect::<String>();
7613 }
7614
7615 cx.emit(EditorEvent::InputHandled {
7616 utf16_range_to_replace: None,
7617 text: partial_completion.clone().into(),
7618 });
7619
7620 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7621
7622 self.refresh_edit_prediction(true, true, window, cx);
7623 cx.notify();
7624 } else {
7625 self.accept_edit_prediction(&Default::default(), window, cx);
7626 }
7627 }
7628 }
7629 }
7630
7631 fn discard_edit_prediction(
7632 &mut self,
7633 should_report_edit_prediction_event: bool,
7634 cx: &mut Context<Self>,
7635 ) -> bool {
7636 if should_report_edit_prediction_event {
7637 let completion_id = self
7638 .active_edit_prediction
7639 .as_ref()
7640 .and_then(|active_completion| active_completion.completion_id.clone());
7641
7642 self.report_edit_prediction_event(completion_id, false, cx);
7643 }
7644
7645 if let Some(provider) = self.edit_prediction_provider() {
7646 provider.discard(cx);
7647 }
7648
7649 self.take_active_edit_prediction(cx)
7650 }
7651
7652 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7653 let Some(provider) = self.edit_prediction_provider() else {
7654 return;
7655 };
7656
7657 let Some((_, buffer, _)) = self
7658 .buffer
7659 .read(cx)
7660 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7661 else {
7662 return;
7663 };
7664
7665 let extension = buffer
7666 .read(cx)
7667 .file()
7668 .and_then(|file| Some(file.path().extension()?.to_string()));
7669
7670 let event_type = match accepted {
7671 true => "Edit Prediction Accepted",
7672 false => "Edit Prediction Discarded",
7673 };
7674 telemetry::event!(
7675 event_type,
7676 provider = provider.name(),
7677 prediction_id = id,
7678 suggestion_accepted = accepted,
7679 file_extension = extension,
7680 );
7681 }
7682
7683 fn open_editor_at_anchor(
7684 snapshot: &language::BufferSnapshot,
7685 target: language::Anchor,
7686 workspace: &Entity<Workspace>,
7687 window: &mut Window,
7688 cx: &mut App,
7689 ) -> Task<Result<()>> {
7690 workspace.update(cx, |workspace, cx| {
7691 let path = snapshot.file().map(|file| file.full_path(cx));
7692 let Some(path) =
7693 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7694 else {
7695 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7696 };
7697 let target = text::ToPoint::to_point(&target, snapshot);
7698 let item = workspace.open_path(path, None, true, window, cx);
7699 window.spawn(cx, async move |cx| {
7700 let Some(editor) = item.await?.downcast::<Editor>() else {
7701 return Ok(());
7702 };
7703 editor
7704 .update_in(cx, |editor, window, cx| {
7705 editor.go_to_singleton_buffer_point(target, window, cx);
7706 })
7707 .ok();
7708 anyhow::Ok(())
7709 })
7710 })
7711 }
7712
7713 pub fn has_active_edit_prediction(&self) -> bool {
7714 self.active_edit_prediction.is_some()
7715 }
7716
7717 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7718 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7719 return false;
7720 };
7721
7722 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7723 self.clear_highlights::<EditPredictionHighlight>(cx);
7724 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7725 true
7726 }
7727
7728 /// Returns true when we're displaying the edit prediction popover below the cursor
7729 /// like we are not previewing and the LSP autocomplete menu is visible
7730 /// or we are in `when_holding_modifier` mode.
7731 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7732 if self.edit_prediction_preview_is_active()
7733 || !self.show_edit_predictions_in_menu()
7734 || !self.edit_predictions_enabled()
7735 {
7736 return false;
7737 }
7738
7739 if self.has_visible_completions_menu() {
7740 return true;
7741 }
7742
7743 has_completion && self.edit_prediction_requires_modifier()
7744 }
7745
7746 fn handle_modifiers_changed(
7747 &mut self,
7748 modifiers: Modifiers,
7749 position_map: &PositionMap,
7750 window: &mut Window,
7751 cx: &mut Context<Self>,
7752 ) {
7753 // Ensure that the edit prediction preview is updated, even when not
7754 // enabled, if there's an active edit prediction preview.
7755 if self.show_edit_predictions_in_menu()
7756 || matches!(
7757 self.edit_prediction_preview,
7758 EditPredictionPreview::Active { .. }
7759 )
7760 {
7761 self.update_edit_prediction_preview(&modifiers, window, cx);
7762 }
7763
7764 self.update_selection_mode(&modifiers, position_map, window, cx);
7765
7766 let mouse_position = window.mouse_position();
7767 if !position_map.text_hitbox.is_hovered(window) {
7768 return;
7769 }
7770
7771 self.update_hovered_link(
7772 position_map.point_for_position(mouse_position),
7773 &position_map.snapshot,
7774 modifiers,
7775 window,
7776 cx,
7777 )
7778 }
7779
7780 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7781 match EditorSettings::get_global(cx).multi_cursor_modifier {
7782 MultiCursorModifier::Alt => modifiers.secondary(),
7783 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7784 }
7785 }
7786
7787 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7788 match EditorSettings::get_global(cx).multi_cursor_modifier {
7789 MultiCursorModifier::Alt => modifiers.alt,
7790 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7791 }
7792 }
7793
7794 fn columnar_selection_mode(
7795 modifiers: &Modifiers,
7796 cx: &mut Context<Self>,
7797 ) -> Option<ColumnarMode> {
7798 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7799 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7800 Some(ColumnarMode::FromMouse)
7801 } else if Self::is_alt_pressed(modifiers, cx) {
7802 Some(ColumnarMode::FromSelection)
7803 } else {
7804 None
7805 }
7806 } else {
7807 None
7808 }
7809 }
7810
7811 fn update_selection_mode(
7812 &mut self,
7813 modifiers: &Modifiers,
7814 position_map: &PositionMap,
7815 window: &mut Window,
7816 cx: &mut Context<Self>,
7817 ) {
7818 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7819 return;
7820 };
7821 if self.selections.pending_anchor().is_none() {
7822 return;
7823 }
7824
7825 let mouse_position = window.mouse_position();
7826 let point_for_position = position_map.point_for_position(mouse_position);
7827 let position = point_for_position.previous_valid;
7828
7829 self.select(
7830 SelectPhase::BeginColumnar {
7831 position,
7832 reset: false,
7833 mode,
7834 goal_column: point_for_position.exact_unclipped.column(),
7835 },
7836 window,
7837 cx,
7838 );
7839 }
7840
7841 fn update_edit_prediction_preview(
7842 &mut self,
7843 modifiers: &Modifiers,
7844 window: &mut Window,
7845 cx: &mut Context<Self>,
7846 ) {
7847 let mut modifiers_held = false;
7848 if let Some(accept_keystroke) = self
7849 .accept_edit_prediction_keybind(false, window, cx)
7850 .keystroke()
7851 {
7852 modifiers_held = modifiers_held
7853 || (accept_keystroke.modifiers() == modifiers
7854 && accept_keystroke.modifiers().modified());
7855 };
7856 if let Some(accept_partial_keystroke) = self
7857 .accept_edit_prediction_keybind(true, window, cx)
7858 .keystroke()
7859 {
7860 modifiers_held = modifiers_held
7861 || (accept_partial_keystroke.modifiers() == modifiers
7862 && accept_partial_keystroke.modifiers().modified());
7863 }
7864
7865 if modifiers_held {
7866 if matches!(
7867 self.edit_prediction_preview,
7868 EditPredictionPreview::Inactive { .. }
7869 ) {
7870 self.edit_prediction_preview = EditPredictionPreview::Active {
7871 previous_scroll_position: None,
7872 since: Instant::now(),
7873 };
7874
7875 self.update_visible_edit_prediction(window, cx);
7876 cx.notify();
7877 }
7878 } else if let EditPredictionPreview::Active {
7879 previous_scroll_position,
7880 since,
7881 } = self.edit_prediction_preview
7882 {
7883 if let (Some(previous_scroll_position), Some(position_map)) =
7884 (previous_scroll_position, self.last_position_map.as_ref())
7885 {
7886 self.set_scroll_position(
7887 previous_scroll_position
7888 .scroll_position(&position_map.snapshot.display_snapshot),
7889 window,
7890 cx,
7891 );
7892 }
7893
7894 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7895 released_too_fast: since.elapsed() < Duration::from_millis(200),
7896 };
7897 self.clear_row_highlights::<EditPredictionPreview>();
7898 self.update_visible_edit_prediction(window, cx);
7899 cx.notify();
7900 }
7901 }
7902
7903 fn update_visible_edit_prediction(
7904 &mut self,
7905 _window: &mut Window,
7906 cx: &mut Context<Self>,
7907 ) -> Option<()> {
7908 if DisableAiSettings::get_global(cx).disable_ai {
7909 return None;
7910 }
7911
7912 if self.ime_transaction.is_some() {
7913 self.discard_edit_prediction(false, cx);
7914 return None;
7915 }
7916
7917 let selection = self.selections.newest_anchor();
7918 let cursor = selection.head();
7919 let multibuffer = self.buffer.read(cx).snapshot(cx);
7920 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7921 let excerpt_id = cursor.excerpt_id;
7922
7923 let show_in_menu = self.show_edit_predictions_in_menu();
7924 let completions_menu_has_precedence = !show_in_menu
7925 && (self.context_menu.borrow().is_some()
7926 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7927
7928 if completions_menu_has_precedence
7929 || !offset_selection.is_empty()
7930 || self
7931 .active_edit_prediction
7932 .as_ref()
7933 .is_some_and(|completion| {
7934 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7935 return false;
7936 };
7937 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7938 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7939 !invalidation_range.contains(&offset_selection.head())
7940 })
7941 {
7942 self.discard_edit_prediction(false, cx);
7943 return None;
7944 }
7945
7946 self.take_active_edit_prediction(cx);
7947 let Some(provider) = self.edit_prediction_provider() else {
7948 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7949 return None;
7950 };
7951
7952 let (buffer, cursor_buffer_position) =
7953 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7954
7955 self.edit_prediction_settings =
7956 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7957
7958 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7959
7960 if self.edit_prediction_indent_conflict {
7961 let cursor_point = cursor.to_point(&multibuffer);
7962
7963 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7964
7965 if let Some((_, indent)) = indents.iter().next()
7966 && indent.len == cursor_point.column
7967 {
7968 self.edit_prediction_indent_conflict = false;
7969 }
7970 }
7971
7972 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7973
7974 let (completion_id, edits, edit_preview) = match edit_prediction {
7975 edit_prediction::EditPrediction::Local {
7976 id,
7977 edits,
7978 edit_preview,
7979 } => (id, edits, edit_preview),
7980 edit_prediction::EditPrediction::Jump {
7981 id,
7982 snapshot,
7983 target,
7984 } => {
7985 self.stale_edit_prediction_in_menu = None;
7986 self.active_edit_prediction = Some(EditPredictionState {
7987 inlay_ids: vec![],
7988 completion: EditPrediction::MoveOutside { snapshot, target },
7989 completion_id: id,
7990 invalidation_range: None,
7991 });
7992 cx.notify();
7993 return Some(());
7994 }
7995 };
7996
7997 let edits = edits
7998 .into_iter()
7999 .flat_map(|(range, new_text)| {
8000 Some((
8001 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8002 new_text,
8003 ))
8004 })
8005 .collect::<Vec<_>>();
8006 if edits.is_empty() {
8007 return None;
8008 }
8009
8010 let first_edit_start = edits.first().unwrap().0.start;
8011 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8012 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8013
8014 let last_edit_end = edits.last().unwrap().0.end;
8015 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8016 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8017
8018 let cursor_row = cursor.to_point(&multibuffer).row;
8019
8020 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8021
8022 let mut inlay_ids = Vec::new();
8023 let invalidation_row_range;
8024 let move_invalidation_row_range = if cursor_row < edit_start_row {
8025 Some(cursor_row..edit_end_row)
8026 } else if cursor_row > edit_end_row {
8027 Some(edit_start_row..cursor_row)
8028 } else {
8029 None
8030 };
8031 let supports_jump = self
8032 .edit_prediction_provider
8033 .as_ref()
8034 .map(|provider| provider.provider.supports_jump_to_edit())
8035 .unwrap_or(true);
8036
8037 let is_move = supports_jump
8038 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8039 let completion = if is_move {
8040 invalidation_row_range =
8041 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8042 let target = first_edit_start;
8043 EditPrediction::MoveWithin { target, snapshot }
8044 } else {
8045 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8046 && !self.edit_predictions_hidden_for_vim_mode;
8047
8048 if show_completions_in_buffer {
8049 if edits
8050 .iter()
8051 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8052 {
8053 let mut inlays = Vec::new();
8054 for (range, new_text) in &edits {
8055 let inlay = Inlay::edit_prediction(
8056 post_inc(&mut self.next_inlay_id),
8057 range.start,
8058 new_text.as_ref(),
8059 );
8060 inlay_ids.push(inlay.id);
8061 inlays.push(inlay);
8062 }
8063
8064 self.splice_inlays(&[], inlays, cx);
8065 } else {
8066 let background_color = cx.theme().status().deleted_background;
8067 self.highlight_text::<EditPredictionHighlight>(
8068 edits.iter().map(|(range, _)| range.clone()).collect(),
8069 HighlightStyle {
8070 background_color: Some(background_color),
8071 ..Default::default()
8072 },
8073 cx,
8074 );
8075 }
8076 }
8077
8078 invalidation_row_range = edit_start_row..edit_end_row;
8079
8080 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8081 if provider.show_tab_accept_marker() {
8082 EditDisplayMode::TabAccept
8083 } else {
8084 EditDisplayMode::Inline
8085 }
8086 } else {
8087 EditDisplayMode::DiffPopover
8088 };
8089
8090 EditPrediction::Edit {
8091 edits,
8092 edit_preview,
8093 display_mode,
8094 snapshot,
8095 }
8096 };
8097
8098 let invalidation_range = multibuffer
8099 .anchor_before(Point::new(invalidation_row_range.start, 0))
8100 ..multibuffer.anchor_after(Point::new(
8101 invalidation_row_range.end,
8102 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8103 ));
8104
8105 self.stale_edit_prediction_in_menu = None;
8106 self.active_edit_prediction = Some(EditPredictionState {
8107 inlay_ids,
8108 completion,
8109 completion_id,
8110 invalidation_range: Some(invalidation_range),
8111 });
8112
8113 cx.notify();
8114
8115 Some(())
8116 }
8117
8118 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8119 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8120 }
8121
8122 fn clear_tasks(&mut self) {
8123 self.tasks.clear()
8124 }
8125
8126 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8127 if self.tasks.insert(key, value).is_some() {
8128 // This case should hopefully be rare, but just in case...
8129 log::error!(
8130 "multiple different run targets found on a single line, only the last target will be rendered"
8131 )
8132 }
8133 }
8134
8135 /// Get all display points of breakpoints that will be rendered within editor
8136 ///
8137 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8138 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8139 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8140 fn active_breakpoints(
8141 &self,
8142 range: Range<DisplayRow>,
8143 window: &mut Window,
8144 cx: &mut Context<Self>,
8145 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8146 let mut breakpoint_display_points = HashMap::default();
8147
8148 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8149 return breakpoint_display_points;
8150 };
8151
8152 let snapshot = self.snapshot(window, cx);
8153
8154 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8155 let Some(project) = self.project() else {
8156 return breakpoint_display_points;
8157 };
8158
8159 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8160 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8161
8162 for (buffer_snapshot, range, excerpt_id) in
8163 multi_buffer_snapshot.range_to_buffer_ranges(range)
8164 {
8165 let Some(buffer) = project
8166 .read(cx)
8167 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8168 else {
8169 continue;
8170 };
8171 let breakpoints = breakpoint_store.read(cx).breakpoints(
8172 &buffer,
8173 Some(
8174 buffer_snapshot.anchor_before(range.start)
8175 ..buffer_snapshot.anchor_after(range.end),
8176 ),
8177 buffer_snapshot,
8178 cx,
8179 );
8180 for (breakpoint, state) in breakpoints {
8181 let multi_buffer_anchor =
8182 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8183 let position = multi_buffer_anchor
8184 .to_point(&multi_buffer_snapshot)
8185 .to_display_point(&snapshot);
8186
8187 breakpoint_display_points.insert(
8188 position.row(),
8189 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8190 );
8191 }
8192 }
8193
8194 breakpoint_display_points
8195 }
8196
8197 fn breakpoint_context_menu(
8198 &self,
8199 anchor: Anchor,
8200 window: &mut Window,
8201 cx: &mut Context<Self>,
8202 ) -> Entity<ui::ContextMenu> {
8203 let weak_editor = cx.weak_entity();
8204 let focus_handle = self.focus_handle(cx);
8205
8206 let row = self
8207 .buffer
8208 .read(cx)
8209 .snapshot(cx)
8210 .summary_for_anchor::<Point>(&anchor)
8211 .row;
8212
8213 let breakpoint = self
8214 .breakpoint_at_row(row, window, cx)
8215 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8216
8217 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8218 "Edit Log Breakpoint"
8219 } else {
8220 "Set Log Breakpoint"
8221 };
8222
8223 let condition_breakpoint_msg = if breakpoint
8224 .as_ref()
8225 .is_some_and(|bp| bp.1.condition.is_some())
8226 {
8227 "Edit Condition Breakpoint"
8228 } else {
8229 "Set Condition Breakpoint"
8230 };
8231
8232 let hit_condition_breakpoint_msg = if breakpoint
8233 .as_ref()
8234 .is_some_and(|bp| bp.1.hit_condition.is_some())
8235 {
8236 "Edit Hit Condition Breakpoint"
8237 } else {
8238 "Set Hit Condition Breakpoint"
8239 };
8240
8241 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8242 "Unset Breakpoint"
8243 } else {
8244 "Set Breakpoint"
8245 };
8246
8247 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8248
8249 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8250 BreakpointState::Enabled => Some("Disable"),
8251 BreakpointState::Disabled => Some("Enable"),
8252 });
8253
8254 let (anchor, breakpoint) =
8255 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8256
8257 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8258 menu.on_blur_subscription(Subscription::new(|| {}))
8259 .context(focus_handle)
8260 .when(run_to_cursor, |this| {
8261 let weak_editor = weak_editor.clone();
8262 this.entry("Run to cursor", None, move |window, cx| {
8263 weak_editor
8264 .update(cx, |editor, cx| {
8265 editor.change_selections(
8266 SelectionEffects::no_scroll(),
8267 window,
8268 cx,
8269 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8270 );
8271 })
8272 .ok();
8273
8274 window.dispatch_action(Box::new(RunToCursor), cx);
8275 })
8276 .separator()
8277 })
8278 .when_some(toggle_state_msg, |this, msg| {
8279 this.entry(msg, None, {
8280 let weak_editor = weak_editor.clone();
8281 let breakpoint = breakpoint.clone();
8282 move |_window, cx| {
8283 weak_editor
8284 .update(cx, |this, cx| {
8285 this.edit_breakpoint_at_anchor(
8286 anchor,
8287 breakpoint.as_ref().clone(),
8288 BreakpointEditAction::InvertState,
8289 cx,
8290 );
8291 })
8292 .log_err();
8293 }
8294 })
8295 })
8296 .entry(set_breakpoint_msg, None, {
8297 let weak_editor = weak_editor.clone();
8298 let breakpoint = breakpoint.clone();
8299 move |_window, cx| {
8300 weak_editor
8301 .update(cx, |this, cx| {
8302 this.edit_breakpoint_at_anchor(
8303 anchor,
8304 breakpoint.as_ref().clone(),
8305 BreakpointEditAction::Toggle,
8306 cx,
8307 );
8308 })
8309 .log_err();
8310 }
8311 })
8312 .entry(log_breakpoint_msg, None, {
8313 let breakpoint = breakpoint.clone();
8314 let weak_editor = weak_editor.clone();
8315 move |window, cx| {
8316 weak_editor
8317 .update(cx, |this, cx| {
8318 this.add_edit_breakpoint_block(
8319 anchor,
8320 breakpoint.as_ref(),
8321 BreakpointPromptEditAction::Log,
8322 window,
8323 cx,
8324 );
8325 })
8326 .log_err();
8327 }
8328 })
8329 .entry(condition_breakpoint_msg, None, {
8330 let breakpoint = breakpoint.clone();
8331 let weak_editor = weak_editor.clone();
8332 move |window, cx| {
8333 weak_editor
8334 .update(cx, |this, cx| {
8335 this.add_edit_breakpoint_block(
8336 anchor,
8337 breakpoint.as_ref(),
8338 BreakpointPromptEditAction::Condition,
8339 window,
8340 cx,
8341 );
8342 })
8343 .log_err();
8344 }
8345 })
8346 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8347 weak_editor
8348 .update(cx, |this, cx| {
8349 this.add_edit_breakpoint_block(
8350 anchor,
8351 breakpoint.as_ref(),
8352 BreakpointPromptEditAction::HitCondition,
8353 window,
8354 cx,
8355 );
8356 })
8357 .log_err();
8358 })
8359 })
8360 }
8361
8362 fn render_breakpoint(
8363 &self,
8364 position: Anchor,
8365 row: DisplayRow,
8366 breakpoint: &Breakpoint,
8367 state: Option<BreakpointSessionState>,
8368 cx: &mut Context<Self>,
8369 ) -> IconButton {
8370 let is_rejected = state.is_some_and(|s| !s.verified);
8371 // Is it a breakpoint that shows up when hovering over gutter?
8372 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8373 (false, false),
8374 |PhantomBreakpointIndicator {
8375 is_active,
8376 display_row,
8377 collides_with_existing_breakpoint,
8378 }| {
8379 (
8380 is_active && display_row == row,
8381 collides_with_existing_breakpoint,
8382 )
8383 },
8384 );
8385
8386 let (color, icon) = {
8387 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8388 (false, false) => ui::IconName::DebugBreakpoint,
8389 (true, false) => ui::IconName::DebugLogBreakpoint,
8390 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8391 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8392 };
8393
8394 let color = if is_phantom {
8395 Color::Hint
8396 } else if is_rejected {
8397 Color::Disabled
8398 } else {
8399 Color::Debugger
8400 };
8401
8402 (color, icon)
8403 };
8404
8405 let breakpoint = Arc::from(breakpoint.clone());
8406
8407 let alt_as_text = gpui::Keystroke {
8408 modifiers: Modifiers::secondary_key(),
8409 ..Default::default()
8410 };
8411 let primary_action_text = if breakpoint.is_disabled() {
8412 "Enable breakpoint"
8413 } else if is_phantom && !collides_with_existing {
8414 "Set breakpoint"
8415 } else {
8416 "Unset breakpoint"
8417 };
8418 let focus_handle = self.focus_handle.clone();
8419
8420 let meta = if is_rejected {
8421 SharedString::from("No executable code is associated with this line.")
8422 } else if collides_with_existing && !breakpoint.is_disabled() {
8423 SharedString::from(format!(
8424 "{alt_as_text}-click to disable,\nright-click for more options."
8425 ))
8426 } else {
8427 SharedString::from("Right-click for more options.")
8428 };
8429 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8430 .icon_size(IconSize::XSmall)
8431 .size(ui::ButtonSize::None)
8432 .when(is_rejected, |this| {
8433 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8434 })
8435 .icon_color(color)
8436 .style(ButtonStyle::Transparent)
8437 .on_click(cx.listener({
8438 move |editor, event: &ClickEvent, window, cx| {
8439 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8440 BreakpointEditAction::InvertState
8441 } else {
8442 BreakpointEditAction::Toggle
8443 };
8444
8445 window.focus(&editor.focus_handle(cx));
8446 editor.edit_breakpoint_at_anchor(
8447 position,
8448 breakpoint.as_ref().clone(),
8449 edit_action,
8450 cx,
8451 );
8452 }
8453 }))
8454 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8455 editor.set_breakpoint_context_menu(
8456 row,
8457 Some(position),
8458 event.position(),
8459 window,
8460 cx,
8461 );
8462 }))
8463 .tooltip(move |_window, cx| {
8464 Tooltip::with_meta_in(
8465 primary_action_text,
8466 Some(&ToggleBreakpoint),
8467 meta.clone(),
8468 &focus_handle,
8469 cx,
8470 )
8471 })
8472 }
8473
8474 fn build_tasks_context(
8475 project: &Entity<Project>,
8476 buffer: &Entity<Buffer>,
8477 buffer_row: u32,
8478 tasks: &Arc<RunnableTasks>,
8479 cx: &mut Context<Self>,
8480 ) -> Task<Option<task::TaskContext>> {
8481 let position = Point::new(buffer_row, tasks.column);
8482 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8483 let location = Location {
8484 buffer: buffer.clone(),
8485 range: range_start..range_start,
8486 };
8487 // Fill in the environmental variables from the tree-sitter captures
8488 let mut captured_task_variables = TaskVariables::default();
8489 for (capture_name, value) in tasks.extra_variables.clone() {
8490 captured_task_variables.insert(
8491 task::VariableName::Custom(capture_name.into()),
8492 value.clone(),
8493 );
8494 }
8495 project.update(cx, |project, cx| {
8496 project.task_store().update(cx, |task_store, cx| {
8497 task_store.task_context_for_location(captured_task_variables, location, cx)
8498 })
8499 })
8500 }
8501
8502 pub fn spawn_nearest_task(
8503 &mut self,
8504 action: &SpawnNearestTask,
8505 window: &mut Window,
8506 cx: &mut Context<Self>,
8507 ) {
8508 let Some((workspace, _)) = self.workspace.clone() else {
8509 return;
8510 };
8511 let Some(project) = self.project.clone() else {
8512 return;
8513 };
8514
8515 // Try to find a closest, enclosing node using tree-sitter that has a task
8516 let Some((buffer, buffer_row, tasks)) = self
8517 .find_enclosing_node_task(cx)
8518 // Or find the task that's closest in row-distance.
8519 .or_else(|| self.find_closest_task(cx))
8520 else {
8521 return;
8522 };
8523
8524 let reveal_strategy = action.reveal;
8525 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8526 cx.spawn_in(window, async move |_, cx| {
8527 let context = task_context.await?;
8528 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8529
8530 let resolved = &mut resolved_task.resolved;
8531 resolved.reveal = reveal_strategy;
8532
8533 workspace
8534 .update_in(cx, |workspace, window, cx| {
8535 workspace.schedule_resolved_task(
8536 task_source_kind,
8537 resolved_task,
8538 false,
8539 window,
8540 cx,
8541 );
8542 })
8543 .ok()
8544 })
8545 .detach();
8546 }
8547
8548 fn find_closest_task(
8549 &mut self,
8550 cx: &mut Context<Self>,
8551 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8552 let cursor_row = self
8553 .selections
8554 .newest_adjusted(&self.display_snapshot(cx))
8555 .head()
8556 .row;
8557
8558 let ((buffer_id, row), tasks) = self
8559 .tasks
8560 .iter()
8561 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8562
8563 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8564 let tasks = Arc::new(tasks.to_owned());
8565 Some((buffer, *row, tasks))
8566 }
8567
8568 fn find_enclosing_node_task(
8569 &mut self,
8570 cx: &mut Context<Self>,
8571 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8572 let snapshot = self.buffer.read(cx).snapshot(cx);
8573 let offset = self
8574 .selections
8575 .newest::<usize>(&self.display_snapshot(cx))
8576 .head();
8577 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8578 let buffer_id = excerpt.buffer().remote_id();
8579
8580 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8581 let mut cursor = layer.node().walk();
8582
8583 while cursor.goto_first_child_for_byte(offset).is_some() {
8584 if cursor.node().end_byte() == offset {
8585 cursor.goto_next_sibling();
8586 }
8587 }
8588
8589 // Ascend to the smallest ancestor that contains the range and has a task.
8590 loop {
8591 let node = cursor.node();
8592 let node_range = node.byte_range();
8593 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8594
8595 // Check if this node contains our offset
8596 if node_range.start <= offset && node_range.end >= offset {
8597 // If it contains offset, check for task
8598 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8599 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8600 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8601 }
8602 }
8603
8604 if !cursor.goto_parent() {
8605 break;
8606 }
8607 }
8608 None
8609 }
8610
8611 fn render_run_indicator(
8612 &self,
8613 _style: &EditorStyle,
8614 is_active: bool,
8615 row: DisplayRow,
8616 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8617 cx: &mut Context<Self>,
8618 ) -> IconButton {
8619 let color = Color::Muted;
8620 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8621
8622 IconButton::new(
8623 ("run_indicator", row.0 as usize),
8624 ui::IconName::PlayOutlined,
8625 )
8626 .shape(ui::IconButtonShape::Square)
8627 .icon_size(IconSize::XSmall)
8628 .icon_color(color)
8629 .toggle_state(is_active)
8630 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8631 let quick_launch = match e {
8632 ClickEvent::Keyboard(_) => true,
8633 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8634 };
8635
8636 window.focus(&editor.focus_handle(cx));
8637 editor.toggle_code_actions(
8638 &ToggleCodeActions {
8639 deployed_from: Some(CodeActionSource::RunMenu(row)),
8640 quick_launch,
8641 },
8642 window,
8643 cx,
8644 );
8645 }))
8646 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8647 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8648 }))
8649 }
8650
8651 pub fn context_menu_visible(&self) -> bool {
8652 !self.edit_prediction_preview_is_active()
8653 && self
8654 .context_menu
8655 .borrow()
8656 .as_ref()
8657 .is_some_and(|menu| menu.visible())
8658 }
8659
8660 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8661 self.context_menu
8662 .borrow()
8663 .as_ref()
8664 .map(|menu| menu.origin())
8665 }
8666
8667 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8668 self.context_menu_options = Some(options);
8669 }
8670
8671 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8672 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8673
8674 fn render_edit_prediction_popover(
8675 &mut self,
8676 text_bounds: &Bounds<Pixels>,
8677 content_origin: gpui::Point<Pixels>,
8678 right_margin: Pixels,
8679 editor_snapshot: &EditorSnapshot,
8680 visible_row_range: Range<DisplayRow>,
8681 scroll_top: ScrollOffset,
8682 scroll_bottom: ScrollOffset,
8683 line_layouts: &[LineWithInvisibles],
8684 line_height: Pixels,
8685 scroll_position: gpui::Point<ScrollOffset>,
8686 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8687 newest_selection_head: Option<DisplayPoint>,
8688 editor_width: Pixels,
8689 style: &EditorStyle,
8690 window: &mut Window,
8691 cx: &mut App,
8692 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8693 if self.mode().is_minimap() {
8694 return None;
8695 }
8696 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8697
8698 if self.edit_prediction_visible_in_cursor_popover(true) {
8699 return None;
8700 }
8701
8702 match &active_edit_prediction.completion {
8703 EditPrediction::MoveWithin { target, .. } => {
8704 let target_display_point = target.to_display_point(editor_snapshot);
8705
8706 if self.edit_prediction_requires_modifier() {
8707 if !self.edit_prediction_preview_is_active() {
8708 return None;
8709 }
8710
8711 self.render_edit_prediction_modifier_jump_popover(
8712 text_bounds,
8713 content_origin,
8714 visible_row_range,
8715 line_layouts,
8716 line_height,
8717 scroll_pixel_position,
8718 newest_selection_head,
8719 target_display_point,
8720 window,
8721 cx,
8722 )
8723 } else {
8724 self.render_edit_prediction_eager_jump_popover(
8725 text_bounds,
8726 content_origin,
8727 editor_snapshot,
8728 visible_row_range,
8729 scroll_top,
8730 scroll_bottom,
8731 line_height,
8732 scroll_pixel_position,
8733 target_display_point,
8734 editor_width,
8735 window,
8736 cx,
8737 )
8738 }
8739 }
8740 EditPrediction::Edit {
8741 display_mode: EditDisplayMode::Inline,
8742 ..
8743 } => None,
8744 EditPrediction::Edit {
8745 display_mode: EditDisplayMode::TabAccept,
8746 edits,
8747 ..
8748 } => {
8749 let range = &edits.first()?.0;
8750 let target_display_point = range.end.to_display_point(editor_snapshot);
8751
8752 self.render_edit_prediction_end_of_line_popover(
8753 "Accept",
8754 editor_snapshot,
8755 visible_row_range,
8756 target_display_point,
8757 line_height,
8758 scroll_pixel_position,
8759 content_origin,
8760 editor_width,
8761 window,
8762 cx,
8763 )
8764 }
8765 EditPrediction::Edit {
8766 edits,
8767 edit_preview,
8768 display_mode: EditDisplayMode::DiffPopover,
8769 snapshot,
8770 } => self.render_edit_prediction_diff_popover(
8771 text_bounds,
8772 content_origin,
8773 right_margin,
8774 editor_snapshot,
8775 visible_row_range,
8776 line_layouts,
8777 line_height,
8778 scroll_position,
8779 scroll_pixel_position,
8780 newest_selection_head,
8781 editor_width,
8782 style,
8783 edits,
8784 edit_preview,
8785 snapshot,
8786 window,
8787 cx,
8788 ),
8789 EditPrediction::MoveOutside { snapshot, .. } => {
8790 let file_name = snapshot
8791 .file()
8792 .map(|file| file.file_name(cx))
8793 .unwrap_or("untitled");
8794 let mut element = self
8795 .render_edit_prediction_line_popover(
8796 format!("Jump to {file_name}"),
8797 Some(IconName::ZedPredict),
8798 window,
8799 cx,
8800 )
8801 .into_any();
8802
8803 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8804 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8805 let origin_y = text_bounds.size.height - size.height - px(30.);
8806 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8807 element.prepaint_at(origin, window, cx);
8808
8809 Some((element, origin))
8810 }
8811 }
8812 }
8813
8814 fn render_edit_prediction_modifier_jump_popover(
8815 &mut self,
8816 text_bounds: &Bounds<Pixels>,
8817 content_origin: gpui::Point<Pixels>,
8818 visible_row_range: Range<DisplayRow>,
8819 line_layouts: &[LineWithInvisibles],
8820 line_height: Pixels,
8821 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8822 newest_selection_head: Option<DisplayPoint>,
8823 target_display_point: DisplayPoint,
8824 window: &mut Window,
8825 cx: &mut App,
8826 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8827 let scrolled_content_origin =
8828 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8829
8830 const SCROLL_PADDING_Y: Pixels = px(12.);
8831
8832 if target_display_point.row() < visible_row_range.start {
8833 return self.render_edit_prediction_scroll_popover(
8834 |_| SCROLL_PADDING_Y,
8835 IconName::ArrowUp,
8836 visible_row_range,
8837 line_layouts,
8838 newest_selection_head,
8839 scrolled_content_origin,
8840 window,
8841 cx,
8842 );
8843 } else if target_display_point.row() >= visible_row_range.end {
8844 return self.render_edit_prediction_scroll_popover(
8845 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8846 IconName::ArrowDown,
8847 visible_row_range,
8848 line_layouts,
8849 newest_selection_head,
8850 scrolled_content_origin,
8851 window,
8852 cx,
8853 );
8854 }
8855
8856 const POLE_WIDTH: Pixels = px(2.);
8857
8858 let line_layout =
8859 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8860 let target_column = target_display_point.column() as usize;
8861
8862 let target_x = line_layout.x_for_index(target_column);
8863 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8864 - scroll_pixel_position.y;
8865
8866 let flag_on_right = target_x < text_bounds.size.width / 2.;
8867
8868 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8869 border_color.l += 0.001;
8870
8871 let mut element = v_flex()
8872 .items_end()
8873 .when(flag_on_right, |el| el.items_start())
8874 .child(if flag_on_right {
8875 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8876 .rounded_bl(px(0.))
8877 .rounded_tl(px(0.))
8878 .border_l_2()
8879 .border_color(border_color)
8880 } else {
8881 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8882 .rounded_br(px(0.))
8883 .rounded_tr(px(0.))
8884 .border_r_2()
8885 .border_color(border_color)
8886 })
8887 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8888 .into_any();
8889
8890 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8891
8892 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8893 - point(
8894 if flag_on_right {
8895 POLE_WIDTH
8896 } else {
8897 size.width - POLE_WIDTH
8898 },
8899 size.height - line_height,
8900 );
8901
8902 origin.x = origin.x.max(content_origin.x);
8903
8904 element.prepaint_at(origin, window, cx);
8905
8906 Some((element, origin))
8907 }
8908
8909 fn render_edit_prediction_scroll_popover(
8910 &mut self,
8911 to_y: impl Fn(Size<Pixels>) -> Pixels,
8912 scroll_icon: IconName,
8913 visible_row_range: Range<DisplayRow>,
8914 line_layouts: &[LineWithInvisibles],
8915 newest_selection_head: Option<DisplayPoint>,
8916 scrolled_content_origin: gpui::Point<Pixels>,
8917 window: &mut Window,
8918 cx: &mut App,
8919 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8920 let mut element = self
8921 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8922 .into_any();
8923
8924 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8925
8926 let cursor = newest_selection_head?;
8927 let cursor_row_layout =
8928 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8929 let cursor_column = cursor.column() as usize;
8930
8931 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8932
8933 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8934
8935 element.prepaint_at(origin, window, cx);
8936 Some((element, origin))
8937 }
8938
8939 fn render_edit_prediction_eager_jump_popover(
8940 &mut self,
8941 text_bounds: &Bounds<Pixels>,
8942 content_origin: gpui::Point<Pixels>,
8943 editor_snapshot: &EditorSnapshot,
8944 visible_row_range: Range<DisplayRow>,
8945 scroll_top: ScrollOffset,
8946 scroll_bottom: ScrollOffset,
8947 line_height: Pixels,
8948 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8949 target_display_point: DisplayPoint,
8950 editor_width: Pixels,
8951 window: &mut Window,
8952 cx: &mut App,
8953 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8954 if target_display_point.row().as_f64() < scroll_top {
8955 let mut element = self
8956 .render_edit_prediction_line_popover(
8957 "Jump to Edit",
8958 Some(IconName::ArrowUp),
8959 window,
8960 cx,
8961 )
8962 .into_any();
8963
8964 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8965 let offset = point(
8966 (text_bounds.size.width - size.width) / 2.,
8967 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8968 );
8969
8970 let origin = text_bounds.origin + offset;
8971 element.prepaint_at(origin, window, cx);
8972 Some((element, origin))
8973 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8974 let mut element = self
8975 .render_edit_prediction_line_popover(
8976 "Jump to Edit",
8977 Some(IconName::ArrowDown),
8978 window,
8979 cx,
8980 )
8981 .into_any();
8982
8983 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8984 let offset = point(
8985 (text_bounds.size.width - size.width) / 2.,
8986 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8987 );
8988
8989 let origin = text_bounds.origin + offset;
8990 element.prepaint_at(origin, window, cx);
8991 Some((element, origin))
8992 } else {
8993 self.render_edit_prediction_end_of_line_popover(
8994 "Jump to Edit",
8995 editor_snapshot,
8996 visible_row_range,
8997 target_display_point,
8998 line_height,
8999 scroll_pixel_position,
9000 content_origin,
9001 editor_width,
9002 window,
9003 cx,
9004 )
9005 }
9006 }
9007
9008 fn render_edit_prediction_end_of_line_popover(
9009 self: &mut Editor,
9010 label: &'static str,
9011 editor_snapshot: &EditorSnapshot,
9012 visible_row_range: Range<DisplayRow>,
9013 target_display_point: DisplayPoint,
9014 line_height: Pixels,
9015 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9016 content_origin: gpui::Point<Pixels>,
9017 editor_width: Pixels,
9018 window: &mut Window,
9019 cx: &mut App,
9020 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9021 let target_line_end = DisplayPoint::new(
9022 target_display_point.row(),
9023 editor_snapshot.line_len(target_display_point.row()),
9024 );
9025
9026 let mut element = self
9027 .render_edit_prediction_line_popover(label, None, window, cx)
9028 .into_any();
9029
9030 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9031
9032 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9033
9034 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9035 let mut origin = start_point
9036 + line_origin
9037 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9038 origin.x = origin.x.max(content_origin.x);
9039
9040 let max_x = content_origin.x + editor_width - size.width;
9041
9042 if origin.x > max_x {
9043 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9044
9045 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9046 origin.y += offset;
9047 IconName::ArrowUp
9048 } else {
9049 origin.y -= offset;
9050 IconName::ArrowDown
9051 };
9052
9053 element = self
9054 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9055 .into_any();
9056
9057 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9058
9059 origin.x = content_origin.x + editor_width - size.width - px(2.);
9060 }
9061
9062 element.prepaint_at(origin, window, cx);
9063 Some((element, origin))
9064 }
9065
9066 fn render_edit_prediction_diff_popover(
9067 self: &Editor,
9068 text_bounds: &Bounds<Pixels>,
9069 content_origin: gpui::Point<Pixels>,
9070 right_margin: Pixels,
9071 editor_snapshot: &EditorSnapshot,
9072 visible_row_range: Range<DisplayRow>,
9073 line_layouts: &[LineWithInvisibles],
9074 line_height: Pixels,
9075 scroll_position: gpui::Point<ScrollOffset>,
9076 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9077 newest_selection_head: Option<DisplayPoint>,
9078 editor_width: Pixels,
9079 style: &EditorStyle,
9080 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9081 edit_preview: &Option<language::EditPreview>,
9082 snapshot: &language::BufferSnapshot,
9083 window: &mut Window,
9084 cx: &mut App,
9085 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9086 let edit_start = edits
9087 .first()
9088 .unwrap()
9089 .0
9090 .start
9091 .to_display_point(editor_snapshot);
9092 let edit_end = edits
9093 .last()
9094 .unwrap()
9095 .0
9096 .end
9097 .to_display_point(editor_snapshot);
9098
9099 let is_visible = visible_row_range.contains(&edit_start.row())
9100 || visible_row_range.contains(&edit_end.row());
9101 if !is_visible {
9102 return None;
9103 }
9104
9105 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9106 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9107 } else {
9108 // Fallback for providers without edit_preview
9109 crate::edit_prediction_fallback_text(edits, cx)
9110 };
9111
9112 let styled_text = highlighted_edits.to_styled_text(&style.text);
9113 let line_count = highlighted_edits.text.lines().count();
9114
9115 const BORDER_WIDTH: Pixels = px(1.);
9116
9117 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9118 let has_keybind = keybind.is_some();
9119
9120 let mut element = h_flex()
9121 .items_start()
9122 .child(
9123 h_flex()
9124 .bg(cx.theme().colors().editor_background)
9125 .border(BORDER_WIDTH)
9126 .shadow_xs()
9127 .border_color(cx.theme().colors().border)
9128 .rounded_l_lg()
9129 .when(line_count > 1, |el| el.rounded_br_lg())
9130 .pr_1()
9131 .child(styled_text),
9132 )
9133 .child(
9134 h_flex()
9135 .h(line_height + BORDER_WIDTH * 2.)
9136 .px_1p5()
9137 .gap_1()
9138 // Workaround: For some reason, there's a gap if we don't do this
9139 .ml(-BORDER_WIDTH)
9140 .shadow(vec![gpui::BoxShadow {
9141 color: gpui::black().opacity(0.05),
9142 offset: point(px(1.), px(1.)),
9143 blur_radius: px(2.),
9144 spread_radius: px(0.),
9145 }])
9146 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9147 .border(BORDER_WIDTH)
9148 .border_color(cx.theme().colors().border)
9149 .rounded_r_lg()
9150 .id("edit_prediction_diff_popover_keybind")
9151 .when(!has_keybind, |el| {
9152 let status_colors = cx.theme().status();
9153
9154 el.bg(status_colors.error_background)
9155 .border_color(status_colors.error.opacity(0.6))
9156 .child(Icon::new(IconName::Info).color(Color::Error))
9157 .cursor_default()
9158 .hoverable_tooltip(move |_window, cx| {
9159 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9160 })
9161 })
9162 .children(keybind),
9163 )
9164 .into_any();
9165
9166 let longest_row =
9167 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9168 let longest_line_width = if visible_row_range.contains(&longest_row) {
9169 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9170 } else {
9171 layout_line(
9172 longest_row,
9173 editor_snapshot,
9174 style,
9175 editor_width,
9176 |_| false,
9177 window,
9178 cx,
9179 )
9180 .width
9181 };
9182
9183 let viewport_bounds =
9184 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9185 right: -right_margin,
9186 ..Default::default()
9187 });
9188
9189 let x_after_longest = Pixels::from(
9190 ScrollPixelOffset::from(
9191 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9192 ) - scroll_pixel_position.x,
9193 );
9194
9195 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9196
9197 // Fully visible if it can be displayed within the window (allow overlapping other
9198 // panes). However, this is only allowed if the popover starts within text_bounds.
9199 let can_position_to_the_right = x_after_longest < text_bounds.right()
9200 && x_after_longest + element_bounds.width < viewport_bounds.right();
9201
9202 let mut origin = if can_position_to_the_right {
9203 point(
9204 x_after_longest,
9205 text_bounds.origin.y
9206 + Pixels::from(
9207 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9208 - scroll_pixel_position.y,
9209 ),
9210 )
9211 } else {
9212 let cursor_row = newest_selection_head.map(|head| head.row());
9213 let above_edit = edit_start
9214 .row()
9215 .0
9216 .checked_sub(line_count as u32)
9217 .map(DisplayRow);
9218 let below_edit = Some(edit_end.row() + 1);
9219 let above_cursor =
9220 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9221 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9222
9223 // Place the edit popover adjacent to the edit if there is a location
9224 // available that is onscreen and does not obscure the cursor. Otherwise,
9225 // place it adjacent to the cursor.
9226 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9227 .into_iter()
9228 .flatten()
9229 .find(|&start_row| {
9230 let end_row = start_row + line_count as u32;
9231 visible_row_range.contains(&start_row)
9232 && visible_row_range.contains(&end_row)
9233 && cursor_row
9234 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9235 })?;
9236
9237 content_origin
9238 + point(
9239 Pixels::from(-scroll_pixel_position.x),
9240 Pixels::from(
9241 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9242 ),
9243 )
9244 };
9245
9246 origin.x -= BORDER_WIDTH;
9247
9248 window.defer_draw(element, origin, 1);
9249
9250 // Do not return an element, since it will already be drawn due to defer_draw.
9251 None
9252 }
9253
9254 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9255 px(30.)
9256 }
9257
9258 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9259 if self.read_only(cx) {
9260 cx.theme().players().read_only()
9261 } else {
9262 self.style.as_ref().unwrap().local_player
9263 }
9264 }
9265
9266 fn render_edit_prediction_accept_keybind(
9267 &self,
9268 window: &mut Window,
9269 cx: &mut App,
9270 ) -> Option<AnyElement> {
9271 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9272 let accept_keystroke = accept_binding.keystroke()?;
9273
9274 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9275
9276 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9277 Color::Accent
9278 } else {
9279 Color::Muted
9280 };
9281
9282 h_flex()
9283 .px_0p5()
9284 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9285 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9286 .text_size(TextSize::XSmall.rems(cx))
9287 .child(h_flex().children(ui::render_modifiers(
9288 accept_keystroke.modifiers(),
9289 PlatformStyle::platform(),
9290 Some(modifiers_color),
9291 Some(IconSize::XSmall.rems().into()),
9292 true,
9293 )))
9294 .when(is_platform_style_mac, |parent| {
9295 parent.child(accept_keystroke.key().to_string())
9296 })
9297 .when(!is_platform_style_mac, |parent| {
9298 parent.child(
9299 Key::new(
9300 util::capitalize(accept_keystroke.key()),
9301 Some(Color::Default),
9302 )
9303 .size(Some(IconSize::XSmall.rems().into())),
9304 )
9305 })
9306 .into_any()
9307 .into()
9308 }
9309
9310 fn render_edit_prediction_line_popover(
9311 &self,
9312 label: impl Into<SharedString>,
9313 icon: Option<IconName>,
9314 window: &mut Window,
9315 cx: &mut App,
9316 ) -> Stateful<Div> {
9317 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9318
9319 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9320 let has_keybind = keybind.is_some();
9321
9322 h_flex()
9323 .id("ep-line-popover")
9324 .py_0p5()
9325 .pl_1()
9326 .pr(padding_right)
9327 .gap_1()
9328 .rounded_md()
9329 .border_1()
9330 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9331 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9332 .shadow_xs()
9333 .when(!has_keybind, |el| {
9334 let status_colors = cx.theme().status();
9335
9336 el.bg(status_colors.error_background)
9337 .border_color(status_colors.error.opacity(0.6))
9338 .pl_2()
9339 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9340 .cursor_default()
9341 .hoverable_tooltip(move |_window, cx| {
9342 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9343 })
9344 })
9345 .children(keybind)
9346 .child(
9347 Label::new(label)
9348 .size(LabelSize::Small)
9349 .when(!has_keybind, |el| {
9350 el.color(cx.theme().status().error.into()).strikethrough()
9351 }),
9352 )
9353 .when(!has_keybind, |el| {
9354 el.child(
9355 h_flex().ml_1().child(
9356 Icon::new(IconName::Info)
9357 .size(IconSize::Small)
9358 .color(cx.theme().status().error.into()),
9359 ),
9360 )
9361 })
9362 .when_some(icon, |element, icon| {
9363 element.child(
9364 div()
9365 .mt(px(1.5))
9366 .child(Icon::new(icon).size(IconSize::Small)),
9367 )
9368 })
9369 }
9370
9371 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9372 let accent_color = cx.theme().colors().text_accent;
9373 let editor_bg_color = cx.theme().colors().editor_background;
9374 editor_bg_color.blend(accent_color.opacity(0.1))
9375 }
9376
9377 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9378 let accent_color = cx.theme().colors().text_accent;
9379 let editor_bg_color = cx.theme().colors().editor_background;
9380 editor_bg_color.blend(accent_color.opacity(0.6))
9381 }
9382 fn get_prediction_provider_icon_name(
9383 provider: &Option<RegisteredEditPredictionProvider>,
9384 ) -> IconName {
9385 match provider {
9386 Some(provider) => match provider.provider.name() {
9387 "copilot" => IconName::Copilot,
9388 "supermaven" => IconName::Supermaven,
9389 _ => IconName::ZedPredict,
9390 },
9391 None => IconName::ZedPredict,
9392 }
9393 }
9394
9395 fn render_edit_prediction_cursor_popover(
9396 &self,
9397 min_width: Pixels,
9398 max_width: Pixels,
9399 cursor_point: Point,
9400 style: &EditorStyle,
9401 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9402 _window: &Window,
9403 cx: &mut Context<Editor>,
9404 ) -> Option<AnyElement> {
9405 let provider = self.edit_prediction_provider.as_ref()?;
9406 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9407
9408 let is_refreshing = provider.provider.is_refreshing(cx);
9409
9410 fn pending_completion_container(icon: IconName) -> Div {
9411 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9412 }
9413
9414 let completion = match &self.active_edit_prediction {
9415 Some(prediction) => {
9416 if !self.has_visible_completions_menu() {
9417 const RADIUS: Pixels = px(6.);
9418 const BORDER_WIDTH: Pixels = px(1.);
9419
9420 return Some(
9421 h_flex()
9422 .elevation_2(cx)
9423 .border(BORDER_WIDTH)
9424 .border_color(cx.theme().colors().border)
9425 .when(accept_keystroke.is_none(), |el| {
9426 el.border_color(cx.theme().status().error)
9427 })
9428 .rounded(RADIUS)
9429 .rounded_tl(px(0.))
9430 .overflow_hidden()
9431 .child(div().px_1p5().child(match &prediction.completion {
9432 EditPrediction::MoveWithin { target, snapshot } => {
9433 use text::ToPoint as _;
9434 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9435 {
9436 Icon::new(IconName::ZedPredictDown)
9437 } else {
9438 Icon::new(IconName::ZedPredictUp)
9439 }
9440 }
9441 EditPrediction::MoveOutside { .. } => {
9442 // TODO [zeta2] custom icon for external jump?
9443 Icon::new(provider_icon)
9444 }
9445 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9446 }))
9447 .child(
9448 h_flex()
9449 .gap_1()
9450 .py_1()
9451 .px_2()
9452 .rounded_r(RADIUS - BORDER_WIDTH)
9453 .border_l_1()
9454 .border_color(cx.theme().colors().border)
9455 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9456 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9457 el.child(
9458 Label::new("Hold")
9459 .size(LabelSize::Small)
9460 .when(accept_keystroke.is_none(), |el| {
9461 el.strikethrough()
9462 })
9463 .line_height_style(LineHeightStyle::UiLabel),
9464 )
9465 })
9466 .id("edit_prediction_cursor_popover_keybind")
9467 .when(accept_keystroke.is_none(), |el| {
9468 let status_colors = cx.theme().status();
9469
9470 el.bg(status_colors.error_background)
9471 .border_color(status_colors.error.opacity(0.6))
9472 .child(Icon::new(IconName::Info).color(Color::Error))
9473 .cursor_default()
9474 .hoverable_tooltip(move |_window, cx| {
9475 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9476 .into()
9477 })
9478 })
9479 .when_some(
9480 accept_keystroke.as_ref(),
9481 |el, accept_keystroke| {
9482 el.child(h_flex().children(ui::render_modifiers(
9483 accept_keystroke.modifiers(),
9484 PlatformStyle::platform(),
9485 Some(Color::Default),
9486 Some(IconSize::XSmall.rems().into()),
9487 false,
9488 )))
9489 },
9490 ),
9491 )
9492 .into_any(),
9493 );
9494 }
9495
9496 self.render_edit_prediction_cursor_popover_preview(
9497 prediction,
9498 cursor_point,
9499 style,
9500 cx,
9501 )?
9502 }
9503
9504 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9505 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9506 stale_completion,
9507 cursor_point,
9508 style,
9509 cx,
9510 )?,
9511
9512 None => pending_completion_container(provider_icon)
9513 .child(Label::new("...").size(LabelSize::Small)),
9514 },
9515
9516 None => pending_completion_container(provider_icon)
9517 .child(Label::new("...").size(LabelSize::Small)),
9518 };
9519
9520 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9521 completion
9522 .with_animation(
9523 "loading-completion",
9524 Animation::new(Duration::from_secs(2))
9525 .repeat()
9526 .with_easing(pulsating_between(0.4, 0.8)),
9527 |label, delta| label.opacity(delta),
9528 )
9529 .into_any_element()
9530 } else {
9531 completion.into_any_element()
9532 };
9533
9534 let has_completion = self.active_edit_prediction.is_some();
9535
9536 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9537 Some(
9538 h_flex()
9539 .min_w(min_width)
9540 .max_w(max_width)
9541 .flex_1()
9542 .elevation_2(cx)
9543 .border_color(cx.theme().colors().border)
9544 .child(
9545 div()
9546 .flex_1()
9547 .py_1()
9548 .px_2()
9549 .overflow_hidden()
9550 .child(completion),
9551 )
9552 .when_some(accept_keystroke, |el, accept_keystroke| {
9553 if !accept_keystroke.modifiers().modified() {
9554 return el;
9555 }
9556
9557 el.child(
9558 h_flex()
9559 .h_full()
9560 .border_l_1()
9561 .rounded_r_lg()
9562 .border_color(cx.theme().colors().border)
9563 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9564 .gap_1()
9565 .py_1()
9566 .px_2()
9567 .child(
9568 h_flex()
9569 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9570 .when(is_platform_style_mac, |parent| parent.gap_1())
9571 .child(h_flex().children(ui::render_modifiers(
9572 accept_keystroke.modifiers(),
9573 PlatformStyle::platform(),
9574 Some(if !has_completion {
9575 Color::Muted
9576 } else {
9577 Color::Default
9578 }),
9579 None,
9580 false,
9581 ))),
9582 )
9583 .child(Label::new("Preview").into_any_element())
9584 .opacity(if has_completion { 1.0 } else { 0.4 }),
9585 )
9586 })
9587 .into_any(),
9588 )
9589 }
9590
9591 fn render_edit_prediction_cursor_popover_preview(
9592 &self,
9593 completion: &EditPredictionState,
9594 cursor_point: Point,
9595 style: &EditorStyle,
9596 cx: &mut Context<Editor>,
9597 ) -> Option<Div> {
9598 use text::ToPoint as _;
9599
9600 fn render_relative_row_jump(
9601 prefix: impl Into<String>,
9602 current_row: u32,
9603 target_row: u32,
9604 ) -> Div {
9605 let (row_diff, arrow) = if target_row < current_row {
9606 (current_row - target_row, IconName::ArrowUp)
9607 } else {
9608 (target_row - current_row, IconName::ArrowDown)
9609 };
9610
9611 h_flex()
9612 .child(
9613 Label::new(format!("{}{}", prefix.into(), row_diff))
9614 .color(Color::Muted)
9615 .size(LabelSize::Small),
9616 )
9617 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9618 }
9619
9620 let supports_jump = self
9621 .edit_prediction_provider
9622 .as_ref()
9623 .map(|provider| provider.provider.supports_jump_to_edit())
9624 .unwrap_or(true);
9625
9626 match &completion.completion {
9627 EditPrediction::MoveWithin {
9628 target, snapshot, ..
9629 } => {
9630 if !supports_jump {
9631 return None;
9632 }
9633
9634 Some(
9635 h_flex()
9636 .px_2()
9637 .gap_2()
9638 .flex_1()
9639 .child(
9640 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9641 Icon::new(IconName::ZedPredictDown)
9642 } else {
9643 Icon::new(IconName::ZedPredictUp)
9644 },
9645 )
9646 .child(Label::new("Jump to Edit")),
9647 )
9648 }
9649 EditPrediction::MoveOutside { snapshot, .. } => {
9650 let file_name = snapshot
9651 .file()
9652 .map(|file| file.file_name(cx))
9653 .unwrap_or("untitled");
9654 Some(
9655 h_flex()
9656 .px_2()
9657 .gap_2()
9658 .flex_1()
9659 .child(Icon::new(IconName::ZedPredict))
9660 .child(Label::new(format!("Jump to {file_name}"))),
9661 )
9662 }
9663 EditPrediction::Edit {
9664 edits,
9665 edit_preview,
9666 snapshot,
9667 display_mode: _,
9668 } => {
9669 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9670
9671 let (highlighted_edits, has_more_lines) =
9672 if let Some(edit_preview) = edit_preview.as_ref() {
9673 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9674 .first_line_preview()
9675 } else {
9676 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9677 };
9678
9679 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9680 .with_default_highlights(&style.text, highlighted_edits.highlights);
9681
9682 let preview = h_flex()
9683 .gap_1()
9684 .min_w_16()
9685 .child(styled_text)
9686 .when(has_more_lines, |parent| parent.child("…"));
9687
9688 let left = if supports_jump && first_edit_row != cursor_point.row {
9689 render_relative_row_jump("", cursor_point.row, first_edit_row)
9690 .into_any_element()
9691 } else {
9692 let icon_name =
9693 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9694 Icon::new(icon_name).into_any_element()
9695 };
9696
9697 Some(
9698 h_flex()
9699 .h_full()
9700 .flex_1()
9701 .gap_2()
9702 .pr_1()
9703 .overflow_x_hidden()
9704 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9705 .child(left)
9706 .child(preview),
9707 )
9708 }
9709 }
9710 }
9711
9712 pub fn render_context_menu(
9713 &self,
9714 style: &EditorStyle,
9715 max_height_in_lines: u32,
9716 window: &mut Window,
9717 cx: &mut Context<Editor>,
9718 ) -> Option<AnyElement> {
9719 let menu = self.context_menu.borrow();
9720 let menu = menu.as_ref()?;
9721 if !menu.visible() {
9722 return None;
9723 };
9724 Some(menu.render(style, max_height_in_lines, window, cx))
9725 }
9726
9727 fn render_context_menu_aside(
9728 &mut self,
9729 max_size: Size<Pixels>,
9730 window: &mut Window,
9731 cx: &mut Context<Editor>,
9732 ) -> Option<AnyElement> {
9733 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9734 if menu.visible() {
9735 menu.render_aside(max_size, window, cx)
9736 } else {
9737 None
9738 }
9739 })
9740 }
9741
9742 fn hide_context_menu(
9743 &mut self,
9744 window: &mut Window,
9745 cx: &mut Context<Self>,
9746 ) -> Option<CodeContextMenu> {
9747 cx.notify();
9748 self.completion_tasks.clear();
9749 let context_menu = self.context_menu.borrow_mut().take();
9750 self.stale_edit_prediction_in_menu.take();
9751 self.update_visible_edit_prediction(window, cx);
9752 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9753 && let Some(completion_provider) = &self.completion_provider
9754 {
9755 completion_provider.selection_changed(None, window, cx);
9756 }
9757 context_menu
9758 }
9759
9760 fn show_snippet_choices(
9761 &mut self,
9762 choices: &Vec<String>,
9763 selection: Range<Anchor>,
9764 cx: &mut Context<Self>,
9765 ) {
9766 let Some((_, buffer, _)) = self
9767 .buffer()
9768 .read(cx)
9769 .excerpt_containing(selection.start, cx)
9770 else {
9771 return;
9772 };
9773 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9774 else {
9775 return;
9776 };
9777 if buffer != end_buffer {
9778 log::error!("expected anchor range to have matching buffer IDs");
9779 return;
9780 }
9781
9782 let id = post_inc(&mut self.next_completion_id);
9783 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9784 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9785 CompletionsMenu::new_snippet_choices(
9786 id,
9787 true,
9788 choices,
9789 selection,
9790 buffer,
9791 snippet_sort_order,
9792 ),
9793 ));
9794 }
9795
9796 pub fn insert_snippet(
9797 &mut self,
9798 insertion_ranges: &[Range<usize>],
9799 snippet: Snippet,
9800 window: &mut Window,
9801 cx: &mut Context<Self>,
9802 ) -> Result<()> {
9803 struct Tabstop<T> {
9804 is_end_tabstop: bool,
9805 ranges: Vec<Range<T>>,
9806 choices: Option<Vec<String>>,
9807 }
9808
9809 let tabstops = self.buffer.update(cx, |buffer, cx| {
9810 let snippet_text: Arc<str> = snippet.text.clone().into();
9811 let edits = insertion_ranges
9812 .iter()
9813 .cloned()
9814 .map(|range| (range, snippet_text.clone()));
9815 let autoindent_mode = AutoindentMode::Block {
9816 original_indent_columns: Vec::new(),
9817 };
9818 buffer.edit(edits, Some(autoindent_mode), cx);
9819
9820 let snapshot = &*buffer.read(cx);
9821 let snippet = &snippet;
9822 snippet
9823 .tabstops
9824 .iter()
9825 .map(|tabstop| {
9826 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9827 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9828 });
9829 let mut tabstop_ranges = tabstop
9830 .ranges
9831 .iter()
9832 .flat_map(|tabstop_range| {
9833 let mut delta = 0_isize;
9834 insertion_ranges.iter().map(move |insertion_range| {
9835 let insertion_start = insertion_range.start as isize + delta;
9836 delta +=
9837 snippet.text.len() as isize - insertion_range.len() as isize;
9838
9839 let start = ((insertion_start + tabstop_range.start) as usize)
9840 .min(snapshot.len());
9841 let end = ((insertion_start + tabstop_range.end) as usize)
9842 .min(snapshot.len());
9843 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9844 })
9845 })
9846 .collect::<Vec<_>>();
9847 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9848
9849 Tabstop {
9850 is_end_tabstop,
9851 ranges: tabstop_ranges,
9852 choices: tabstop.choices.clone(),
9853 }
9854 })
9855 .collect::<Vec<_>>()
9856 });
9857 if let Some(tabstop) = tabstops.first() {
9858 self.change_selections(Default::default(), window, cx, |s| {
9859 // Reverse order so that the first range is the newest created selection.
9860 // Completions will use it and autoscroll will prioritize it.
9861 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9862 });
9863
9864 if let Some(choices) = &tabstop.choices
9865 && let Some(selection) = tabstop.ranges.first()
9866 {
9867 self.show_snippet_choices(choices, selection.clone(), cx)
9868 }
9869
9870 // If we're already at the last tabstop and it's at the end of the snippet,
9871 // we're done, we don't need to keep the state around.
9872 if !tabstop.is_end_tabstop {
9873 let choices = tabstops
9874 .iter()
9875 .map(|tabstop| tabstop.choices.clone())
9876 .collect();
9877
9878 let ranges = tabstops
9879 .into_iter()
9880 .map(|tabstop| tabstop.ranges)
9881 .collect::<Vec<_>>();
9882
9883 self.snippet_stack.push(SnippetState {
9884 active_index: 0,
9885 ranges,
9886 choices,
9887 });
9888 }
9889
9890 // Check whether the just-entered snippet ends with an auto-closable bracket.
9891 if self.autoclose_regions.is_empty() {
9892 let snapshot = self.buffer.read(cx).snapshot(cx);
9893 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9894 let selection_head = selection.head();
9895 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9896 continue;
9897 };
9898
9899 let mut bracket_pair = None;
9900 let max_lookup_length = scope
9901 .brackets()
9902 .map(|(pair, _)| {
9903 pair.start
9904 .as_str()
9905 .chars()
9906 .count()
9907 .max(pair.end.as_str().chars().count())
9908 })
9909 .max();
9910 if let Some(max_lookup_length) = max_lookup_length {
9911 let next_text = snapshot
9912 .chars_at(selection_head)
9913 .take(max_lookup_length)
9914 .collect::<String>();
9915 let prev_text = snapshot
9916 .reversed_chars_at(selection_head)
9917 .take(max_lookup_length)
9918 .collect::<String>();
9919
9920 for (pair, enabled) in scope.brackets() {
9921 if enabled
9922 && pair.close
9923 && prev_text.starts_with(pair.start.as_str())
9924 && next_text.starts_with(pair.end.as_str())
9925 {
9926 bracket_pair = Some(pair.clone());
9927 break;
9928 }
9929 }
9930 }
9931
9932 if let Some(pair) = bracket_pair {
9933 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9934 let autoclose_enabled =
9935 self.use_autoclose && snapshot_settings.use_autoclose;
9936 if autoclose_enabled {
9937 let start = snapshot.anchor_after(selection_head);
9938 let end = snapshot.anchor_after(selection_head);
9939 self.autoclose_regions.push(AutocloseRegion {
9940 selection_id: selection.id,
9941 range: start..end,
9942 pair,
9943 });
9944 }
9945 }
9946 }
9947 }
9948 }
9949 Ok(())
9950 }
9951
9952 pub fn move_to_next_snippet_tabstop(
9953 &mut self,
9954 window: &mut Window,
9955 cx: &mut Context<Self>,
9956 ) -> bool {
9957 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9958 }
9959
9960 pub fn move_to_prev_snippet_tabstop(
9961 &mut self,
9962 window: &mut Window,
9963 cx: &mut Context<Self>,
9964 ) -> bool {
9965 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9966 }
9967
9968 pub fn move_to_snippet_tabstop(
9969 &mut self,
9970 bias: Bias,
9971 window: &mut Window,
9972 cx: &mut Context<Self>,
9973 ) -> bool {
9974 if let Some(mut snippet) = self.snippet_stack.pop() {
9975 match bias {
9976 Bias::Left => {
9977 if snippet.active_index > 0 {
9978 snippet.active_index -= 1;
9979 } else {
9980 self.snippet_stack.push(snippet);
9981 return false;
9982 }
9983 }
9984 Bias::Right => {
9985 if snippet.active_index + 1 < snippet.ranges.len() {
9986 snippet.active_index += 1;
9987 } else {
9988 self.snippet_stack.push(snippet);
9989 return false;
9990 }
9991 }
9992 }
9993 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9994 self.change_selections(Default::default(), window, cx, |s| {
9995 // Reverse order so that the first range is the newest created selection.
9996 // Completions will use it and autoscroll will prioritize it.
9997 s.select_ranges(current_ranges.iter().rev().cloned())
9998 });
9999
10000 if let Some(choices) = &snippet.choices[snippet.active_index]
10001 && let Some(selection) = current_ranges.first()
10002 {
10003 self.show_snippet_choices(choices, selection.clone(), cx);
10004 }
10005
10006 // If snippet state is not at the last tabstop, push it back on the stack
10007 if snippet.active_index + 1 < snippet.ranges.len() {
10008 self.snippet_stack.push(snippet);
10009 }
10010 return true;
10011 }
10012 }
10013
10014 false
10015 }
10016
10017 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10018 self.transact(window, cx, |this, window, cx| {
10019 this.select_all(&SelectAll, window, cx);
10020 this.insert("", window, cx);
10021 });
10022 }
10023
10024 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10025 if self.read_only(cx) {
10026 return;
10027 }
10028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10029 self.transact(window, cx, |this, window, cx| {
10030 this.select_autoclose_pair(window, cx);
10031
10032 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10033
10034 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10035 if !this.linked_edit_ranges.is_empty() {
10036 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10037 let snapshot = this.buffer.read(cx).snapshot(cx);
10038
10039 for selection in selections.iter() {
10040 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10041 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10042 if selection_start.buffer_id != selection_end.buffer_id {
10043 continue;
10044 }
10045 if let Some(ranges) =
10046 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10047 {
10048 for (buffer, entries) in ranges {
10049 linked_ranges.entry(buffer).or_default().extend(entries);
10050 }
10051 }
10052 }
10053 }
10054
10055 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10056 for selection in &mut selections {
10057 if selection.is_empty() {
10058 let old_head = selection.head();
10059 let mut new_head =
10060 movement::left(&display_map, old_head.to_display_point(&display_map))
10061 .to_point(&display_map);
10062 if let Some((buffer, line_buffer_range)) = display_map
10063 .buffer_snapshot()
10064 .buffer_line_for_row(MultiBufferRow(old_head.row))
10065 {
10066 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10067 let indent_len = match indent_size.kind {
10068 IndentKind::Space => {
10069 buffer.settings_at(line_buffer_range.start, cx).tab_size
10070 }
10071 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10072 };
10073 if old_head.column <= indent_size.len && old_head.column > 0 {
10074 let indent_len = indent_len.get();
10075 new_head = cmp::min(
10076 new_head,
10077 MultiBufferPoint::new(
10078 old_head.row,
10079 ((old_head.column - 1) / indent_len) * indent_len,
10080 ),
10081 );
10082 }
10083 }
10084
10085 selection.set_head(new_head, SelectionGoal::None);
10086 }
10087 }
10088
10089 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10090 this.insert("", window, cx);
10091 let empty_str: Arc<str> = Arc::from("");
10092 for (buffer, edits) in linked_ranges {
10093 let snapshot = buffer.read(cx).snapshot();
10094 use text::ToPoint as TP;
10095
10096 let edits = edits
10097 .into_iter()
10098 .map(|range| {
10099 let end_point = TP::to_point(&range.end, &snapshot);
10100 let mut start_point = TP::to_point(&range.start, &snapshot);
10101
10102 if end_point == start_point {
10103 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10104 .saturating_sub(1);
10105 start_point =
10106 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10107 };
10108
10109 (start_point..end_point, empty_str.clone())
10110 })
10111 .sorted_by_key(|(range, _)| range.start)
10112 .collect::<Vec<_>>();
10113 buffer.update(cx, |this, cx| {
10114 this.edit(edits, None, cx);
10115 })
10116 }
10117 this.refresh_edit_prediction(true, false, window, cx);
10118 refresh_linked_ranges(this, window, cx);
10119 });
10120 }
10121
10122 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10123 if self.read_only(cx) {
10124 return;
10125 }
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 self.transact(window, cx, |this, window, cx| {
10128 this.change_selections(Default::default(), window, cx, |s| {
10129 s.move_with(|map, selection| {
10130 if selection.is_empty() {
10131 let cursor = movement::right(map, selection.head());
10132 selection.end = cursor;
10133 selection.reversed = true;
10134 selection.goal = SelectionGoal::None;
10135 }
10136 })
10137 });
10138 this.insert("", window, cx);
10139 this.refresh_edit_prediction(true, false, window, cx);
10140 });
10141 }
10142
10143 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10144 if self.mode.is_single_line() {
10145 cx.propagate();
10146 return;
10147 }
10148
10149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10150 if self.move_to_prev_snippet_tabstop(window, cx) {
10151 return;
10152 }
10153 self.outdent(&Outdent, window, cx);
10154 }
10155
10156 pub fn next_snippet_tabstop(
10157 &mut self,
10158 _: &NextSnippetTabstop,
10159 window: &mut Window,
10160 cx: &mut Context<Self>,
10161 ) {
10162 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10163 cx.propagate();
10164 return;
10165 }
10166
10167 if self.move_to_next_snippet_tabstop(window, cx) {
10168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10169 return;
10170 }
10171 cx.propagate();
10172 }
10173
10174 pub fn previous_snippet_tabstop(
10175 &mut self,
10176 _: &PreviousSnippetTabstop,
10177 window: &mut Window,
10178 cx: &mut Context<Self>,
10179 ) {
10180 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10181 cx.propagate();
10182 return;
10183 }
10184
10185 if self.move_to_prev_snippet_tabstop(window, cx) {
10186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10187 return;
10188 }
10189 cx.propagate();
10190 }
10191
10192 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10193 if self.mode.is_single_line() {
10194 cx.propagate();
10195 return;
10196 }
10197
10198 if self.move_to_next_snippet_tabstop(window, cx) {
10199 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10200 return;
10201 }
10202 if self.read_only(cx) {
10203 return;
10204 }
10205 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10206 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10207 let buffer = self.buffer.read(cx);
10208 let snapshot = buffer.snapshot(cx);
10209 let rows_iter = selections.iter().map(|s| s.head().row);
10210 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10211
10212 let has_some_cursor_in_whitespace = selections
10213 .iter()
10214 .filter(|selection| selection.is_empty())
10215 .any(|selection| {
10216 let cursor = selection.head();
10217 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10218 cursor.column < current_indent.len
10219 });
10220
10221 let mut edits = Vec::new();
10222 let mut prev_edited_row = 0;
10223 let mut row_delta = 0;
10224 for selection in &mut selections {
10225 if selection.start.row != prev_edited_row {
10226 row_delta = 0;
10227 }
10228 prev_edited_row = selection.end.row;
10229
10230 // If the selection is non-empty, then increase the indentation of the selected lines.
10231 if !selection.is_empty() {
10232 row_delta =
10233 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10234 continue;
10235 }
10236
10237 let cursor = selection.head();
10238 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10239 if let Some(suggested_indent) =
10240 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10241 {
10242 // Don't do anything if already at suggested indent
10243 // and there is any other cursor which is not
10244 if has_some_cursor_in_whitespace
10245 && cursor.column == current_indent.len
10246 && current_indent.len == suggested_indent.len
10247 {
10248 continue;
10249 }
10250
10251 // Adjust line and move cursor to suggested indent
10252 // if cursor is not at suggested indent
10253 if cursor.column < suggested_indent.len
10254 && cursor.column <= current_indent.len
10255 && current_indent.len <= suggested_indent.len
10256 {
10257 selection.start = Point::new(cursor.row, suggested_indent.len);
10258 selection.end = selection.start;
10259 if row_delta == 0 {
10260 edits.extend(Buffer::edit_for_indent_size_adjustment(
10261 cursor.row,
10262 current_indent,
10263 suggested_indent,
10264 ));
10265 row_delta = suggested_indent.len - current_indent.len;
10266 }
10267 continue;
10268 }
10269
10270 // If current indent is more than suggested indent
10271 // only move cursor to current indent and skip indent
10272 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10273 selection.start = Point::new(cursor.row, current_indent.len);
10274 selection.end = selection.start;
10275 continue;
10276 }
10277 }
10278
10279 // Otherwise, insert a hard or soft tab.
10280 let settings = buffer.language_settings_at(cursor, cx);
10281 let tab_size = if settings.hard_tabs {
10282 IndentSize::tab()
10283 } else {
10284 let tab_size = settings.tab_size.get();
10285 let indent_remainder = snapshot
10286 .text_for_range(Point::new(cursor.row, 0)..cursor)
10287 .flat_map(str::chars)
10288 .fold(row_delta % tab_size, |counter: u32, c| {
10289 if c == '\t' {
10290 0
10291 } else {
10292 (counter + 1) % tab_size
10293 }
10294 });
10295
10296 let chars_to_next_tab_stop = tab_size - indent_remainder;
10297 IndentSize::spaces(chars_to_next_tab_stop)
10298 };
10299 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10300 selection.end = selection.start;
10301 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10302 row_delta += tab_size.len;
10303 }
10304
10305 self.transact(window, cx, |this, window, cx| {
10306 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10307 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10308 this.refresh_edit_prediction(true, false, window, cx);
10309 });
10310 }
10311
10312 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10313 if self.read_only(cx) {
10314 return;
10315 }
10316 if self.mode.is_single_line() {
10317 cx.propagate();
10318 return;
10319 }
10320
10321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10322 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10323 let mut prev_edited_row = 0;
10324 let mut row_delta = 0;
10325 let mut edits = Vec::new();
10326 let buffer = self.buffer.read(cx);
10327 let snapshot = buffer.snapshot(cx);
10328 for selection in &mut selections {
10329 if selection.start.row != prev_edited_row {
10330 row_delta = 0;
10331 }
10332 prev_edited_row = selection.end.row;
10333
10334 row_delta =
10335 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10336 }
10337
10338 self.transact(window, cx, |this, window, cx| {
10339 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10340 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10341 });
10342 }
10343
10344 fn indent_selection(
10345 buffer: &MultiBuffer,
10346 snapshot: &MultiBufferSnapshot,
10347 selection: &mut Selection<Point>,
10348 edits: &mut Vec<(Range<Point>, String)>,
10349 delta_for_start_row: u32,
10350 cx: &App,
10351 ) -> u32 {
10352 let settings = buffer.language_settings_at(selection.start, cx);
10353 let tab_size = settings.tab_size.get();
10354 let indent_kind = if settings.hard_tabs {
10355 IndentKind::Tab
10356 } else {
10357 IndentKind::Space
10358 };
10359 let mut start_row = selection.start.row;
10360 let mut end_row = selection.end.row + 1;
10361
10362 // If a selection ends at the beginning of a line, don't indent
10363 // that last line.
10364 if selection.end.column == 0 && selection.end.row > selection.start.row {
10365 end_row -= 1;
10366 }
10367
10368 // Avoid re-indenting a row that has already been indented by a
10369 // previous selection, but still update this selection's column
10370 // to reflect that indentation.
10371 if delta_for_start_row > 0 {
10372 start_row += 1;
10373 selection.start.column += delta_for_start_row;
10374 if selection.end.row == selection.start.row {
10375 selection.end.column += delta_for_start_row;
10376 }
10377 }
10378
10379 let mut delta_for_end_row = 0;
10380 let has_multiple_rows = start_row + 1 != end_row;
10381 for row in start_row..end_row {
10382 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10383 let indent_delta = match (current_indent.kind, indent_kind) {
10384 (IndentKind::Space, IndentKind::Space) => {
10385 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10386 IndentSize::spaces(columns_to_next_tab_stop)
10387 }
10388 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10389 (_, IndentKind::Tab) => IndentSize::tab(),
10390 };
10391
10392 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10393 0
10394 } else {
10395 selection.start.column
10396 };
10397 let row_start = Point::new(row, start);
10398 edits.push((
10399 row_start..row_start,
10400 indent_delta.chars().collect::<String>(),
10401 ));
10402
10403 // Update this selection's endpoints to reflect the indentation.
10404 if row == selection.start.row {
10405 selection.start.column += indent_delta.len;
10406 }
10407 if row == selection.end.row {
10408 selection.end.column += indent_delta.len;
10409 delta_for_end_row = indent_delta.len;
10410 }
10411 }
10412
10413 if selection.start.row == selection.end.row {
10414 delta_for_start_row + delta_for_end_row
10415 } else {
10416 delta_for_end_row
10417 }
10418 }
10419
10420 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10421 if self.read_only(cx) {
10422 return;
10423 }
10424 if self.mode.is_single_line() {
10425 cx.propagate();
10426 return;
10427 }
10428
10429 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10431 let selections = self.selections.all::<Point>(&display_map);
10432 let mut deletion_ranges = Vec::new();
10433 let mut last_outdent = None;
10434 {
10435 let buffer = self.buffer.read(cx);
10436 let snapshot = buffer.snapshot(cx);
10437 for selection in &selections {
10438 let settings = buffer.language_settings_at(selection.start, cx);
10439 let tab_size = settings.tab_size.get();
10440 let mut rows = selection.spanned_rows(false, &display_map);
10441
10442 // Avoid re-outdenting a row that has already been outdented by a
10443 // previous selection.
10444 if let Some(last_row) = last_outdent
10445 && last_row == rows.start
10446 {
10447 rows.start = rows.start.next_row();
10448 }
10449 let has_multiple_rows = rows.len() > 1;
10450 for row in rows.iter_rows() {
10451 let indent_size = snapshot.indent_size_for_line(row);
10452 if indent_size.len > 0 {
10453 let deletion_len = match indent_size.kind {
10454 IndentKind::Space => {
10455 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10456 if columns_to_prev_tab_stop == 0 {
10457 tab_size
10458 } else {
10459 columns_to_prev_tab_stop
10460 }
10461 }
10462 IndentKind::Tab => 1,
10463 };
10464 let start = if has_multiple_rows
10465 || deletion_len > selection.start.column
10466 || indent_size.len < selection.start.column
10467 {
10468 0
10469 } else {
10470 selection.start.column - deletion_len
10471 };
10472 deletion_ranges.push(
10473 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10474 );
10475 last_outdent = Some(row);
10476 }
10477 }
10478 }
10479 }
10480
10481 self.transact(window, cx, |this, window, cx| {
10482 this.buffer.update(cx, |buffer, cx| {
10483 let empty_str: Arc<str> = Arc::default();
10484 buffer.edit(
10485 deletion_ranges
10486 .into_iter()
10487 .map(|range| (range, empty_str.clone())),
10488 None,
10489 cx,
10490 );
10491 });
10492 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10493 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10494 });
10495 }
10496
10497 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10498 if self.read_only(cx) {
10499 return;
10500 }
10501 if self.mode.is_single_line() {
10502 cx.propagate();
10503 return;
10504 }
10505
10506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10507 let selections = self
10508 .selections
10509 .all::<usize>(&self.display_snapshot(cx))
10510 .into_iter()
10511 .map(|s| s.range());
10512
10513 self.transact(window, cx, |this, window, cx| {
10514 this.buffer.update(cx, |buffer, cx| {
10515 buffer.autoindent_ranges(selections, cx);
10516 });
10517 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10518 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10519 });
10520 }
10521
10522 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10524 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10525 let selections = self.selections.all::<Point>(&display_map);
10526
10527 let mut new_cursors = Vec::new();
10528 let mut edit_ranges = Vec::new();
10529 let mut selections = selections.iter().peekable();
10530 while let Some(selection) = selections.next() {
10531 let mut rows = selection.spanned_rows(false, &display_map);
10532
10533 // Accumulate contiguous regions of rows that we want to delete.
10534 while let Some(next_selection) = selections.peek() {
10535 let next_rows = next_selection.spanned_rows(false, &display_map);
10536 if next_rows.start <= rows.end {
10537 rows.end = next_rows.end;
10538 selections.next().unwrap();
10539 } else {
10540 break;
10541 }
10542 }
10543
10544 let buffer = display_map.buffer_snapshot();
10545 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10546 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10547 // If there's a line after the range, delete the \n from the end of the row range
10548 (
10549 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10550 rows.end,
10551 )
10552 } else {
10553 // If there isn't a line after the range, delete the \n from the line before the
10554 // start of the row range
10555 edit_start = edit_start.saturating_sub(1);
10556 (buffer.len(), rows.start.previous_row())
10557 };
10558
10559 let text_layout_details = self.text_layout_details(window);
10560 let x = display_map.x_for_display_point(
10561 selection.head().to_display_point(&display_map),
10562 &text_layout_details,
10563 );
10564 let row = Point::new(target_row.0, 0)
10565 .to_display_point(&display_map)
10566 .row();
10567 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10568
10569 new_cursors.push((
10570 selection.id,
10571 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10572 SelectionGoal::None,
10573 ));
10574 edit_ranges.push(edit_start..edit_end);
10575 }
10576
10577 self.transact(window, cx, |this, window, cx| {
10578 let buffer = this.buffer.update(cx, |buffer, cx| {
10579 let empty_str: Arc<str> = Arc::default();
10580 buffer.edit(
10581 edit_ranges
10582 .into_iter()
10583 .map(|range| (range, empty_str.clone())),
10584 None,
10585 cx,
10586 );
10587 buffer.snapshot(cx)
10588 });
10589 let new_selections = new_cursors
10590 .into_iter()
10591 .map(|(id, cursor, goal)| {
10592 let cursor = cursor.to_point(&buffer);
10593 Selection {
10594 id,
10595 start: cursor,
10596 end: cursor,
10597 reversed: false,
10598 goal,
10599 }
10600 })
10601 .collect();
10602
10603 this.change_selections(Default::default(), window, cx, |s| {
10604 s.select(new_selections);
10605 });
10606 });
10607 }
10608
10609 pub fn join_lines_impl(
10610 &mut self,
10611 insert_whitespace: bool,
10612 window: &mut Window,
10613 cx: &mut Context<Self>,
10614 ) {
10615 if self.read_only(cx) {
10616 return;
10617 }
10618 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10619 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10620 let start = MultiBufferRow(selection.start.row);
10621 // Treat single line selections as if they include the next line. Otherwise this action
10622 // would do nothing for single line selections individual cursors.
10623 let end = if selection.start.row == selection.end.row {
10624 MultiBufferRow(selection.start.row + 1)
10625 } else {
10626 MultiBufferRow(selection.end.row)
10627 };
10628
10629 if let Some(last_row_range) = row_ranges.last_mut()
10630 && start <= last_row_range.end
10631 {
10632 last_row_range.end = end;
10633 continue;
10634 }
10635 row_ranges.push(start..end);
10636 }
10637
10638 let snapshot = self.buffer.read(cx).snapshot(cx);
10639 let mut cursor_positions = Vec::new();
10640 for row_range in &row_ranges {
10641 let anchor = snapshot.anchor_before(Point::new(
10642 row_range.end.previous_row().0,
10643 snapshot.line_len(row_range.end.previous_row()),
10644 ));
10645 cursor_positions.push(anchor..anchor);
10646 }
10647
10648 self.transact(window, cx, |this, window, cx| {
10649 for row_range in row_ranges.into_iter().rev() {
10650 for row in row_range.iter_rows().rev() {
10651 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10652 let next_line_row = row.next_row();
10653 let indent = snapshot.indent_size_for_line(next_line_row);
10654 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10655
10656 let replace =
10657 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10658 " "
10659 } else {
10660 ""
10661 };
10662
10663 this.buffer.update(cx, |buffer, cx| {
10664 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10665 });
10666 }
10667 }
10668
10669 this.change_selections(Default::default(), window, cx, |s| {
10670 s.select_anchor_ranges(cursor_positions)
10671 });
10672 });
10673 }
10674
10675 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10676 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10677 self.join_lines_impl(true, window, cx);
10678 }
10679
10680 pub fn sort_lines_case_sensitive(
10681 &mut self,
10682 _: &SortLinesCaseSensitive,
10683 window: &mut Window,
10684 cx: &mut Context<Self>,
10685 ) {
10686 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10687 }
10688
10689 pub fn sort_lines_by_length(
10690 &mut self,
10691 _: &SortLinesByLength,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 self.manipulate_immutable_lines(window, cx, |lines| {
10696 lines.sort_by_key(|&line| line.chars().count())
10697 })
10698 }
10699
10700 pub fn sort_lines_case_insensitive(
10701 &mut self,
10702 _: &SortLinesCaseInsensitive,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 self.manipulate_immutable_lines(window, cx, |lines| {
10707 lines.sort_by_key(|line| line.to_lowercase())
10708 })
10709 }
10710
10711 pub fn unique_lines_case_insensitive(
10712 &mut self,
10713 _: &UniqueLinesCaseInsensitive,
10714 window: &mut Window,
10715 cx: &mut Context<Self>,
10716 ) {
10717 self.manipulate_immutable_lines(window, cx, |lines| {
10718 let mut seen = HashSet::default();
10719 lines.retain(|line| seen.insert(line.to_lowercase()));
10720 })
10721 }
10722
10723 pub fn unique_lines_case_sensitive(
10724 &mut self,
10725 _: &UniqueLinesCaseSensitive,
10726 window: &mut Window,
10727 cx: &mut Context<Self>,
10728 ) {
10729 self.manipulate_immutable_lines(window, cx, |lines| {
10730 let mut seen = HashSet::default();
10731 lines.retain(|line| seen.insert(*line));
10732 })
10733 }
10734
10735 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10736 let snapshot = self.buffer.read(cx).snapshot(cx);
10737 for selection in self.selections.disjoint_anchors_arc().iter() {
10738 if snapshot
10739 .language_at(selection.start)
10740 .and_then(|lang| lang.config().wrap_characters.as_ref())
10741 .is_some()
10742 {
10743 return true;
10744 }
10745 }
10746 false
10747 }
10748
10749 fn wrap_selections_in_tag(
10750 &mut self,
10751 _: &WrapSelectionsInTag,
10752 window: &mut Window,
10753 cx: &mut Context<Self>,
10754 ) {
10755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10756
10757 let snapshot = self.buffer.read(cx).snapshot(cx);
10758
10759 let mut edits = Vec::new();
10760 let mut boundaries = Vec::new();
10761
10762 for selection in self
10763 .selections
10764 .all_adjusted(&self.display_snapshot(cx))
10765 .iter()
10766 {
10767 let Some(wrap_config) = snapshot
10768 .language_at(selection.start)
10769 .and_then(|lang| lang.config().wrap_characters.clone())
10770 else {
10771 continue;
10772 };
10773
10774 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10775 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10776
10777 let start_before = snapshot.anchor_before(selection.start);
10778 let end_after = snapshot.anchor_after(selection.end);
10779
10780 edits.push((start_before..start_before, open_tag));
10781 edits.push((end_after..end_after, close_tag));
10782
10783 boundaries.push((
10784 start_before,
10785 end_after,
10786 wrap_config.start_prefix.len(),
10787 wrap_config.end_suffix.len(),
10788 ));
10789 }
10790
10791 if edits.is_empty() {
10792 return;
10793 }
10794
10795 self.transact(window, cx, |this, window, cx| {
10796 let buffer = this.buffer.update(cx, |buffer, cx| {
10797 buffer.edit(edits, None, cx);
10798 buffer.snapshot(cx)
10799 });
10800
10801 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10802 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10803 boundaries.into_iter()
10804 {
10805 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10806 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10807 new_selections.push(open_offset..open_offset);
10808 new_selections.push(close_offset..close_offset);
10809 }
10810
10811 this.change_selections(Default::default(), window, cx, |s| {
10812 s.select_ranges(new_selections);
10813 });
10814
10815 this.request_autoscroll(Autoscroll::fit(), cx);
10816 });
10817 }
10818
10819 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10820 let Some(project) = self.project.clone() else {
10821 return;
10822 };
10823 self.reload(project, window, cx)
10824 .detach_and_notify_err(window, cx);
10825 }
10826
10827 pub fn restore_file(
10828 &mut self,
10829 _: &::git::RestoreFile,
10830 window: &mut Window,
10831 cx: &mut Context<Self>,
10832 ) {
10833 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10834 let mut buffer_ids = HashSet::default();
10835 let snapshot = self.buffer().read(cx).snapshot(cx);
10836 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10837 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10838 }
10839
10840 let buffer = self.buffer().read(cx);
10841 let ranges = buffer_ids
10842 .into_iter()
10843 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10844 .collect::<Vec<_>>();
10845
10846 self.restore_hunks_in_ranges(ranges, window, cx);
10847 }
10848
10849 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10850 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10851 let selections = self
10852 .selections
10853 .all(&self.display_snapshot(cx))
10854 .into_iter()
10855 .map(|s| s.range())
10856 .collect();
10857 self.restore_hunks_in_ranges(selections, window, cx);
10858 }
10859
10860 pub fn restore_hunks_in_ranges(
10861 &mut self,
10862 ranges: Vec<Range<Point>>,
10863 window: &mut Window,
10864 cx: &mut Context<Editor>,
10865 ) {
10866 let mut revert_changes = HashMap::default();
10867 let chunk_by = self
10868 .snapshot(window, cx)
10869 .hunks_for_ranges(ranges)
10870 .into_iter()
10871 .chunk_by(|hunk| hunk.buffer_id);
10872 for (buffer_id, hunks) in &chunk_by {
10873 let hunks = hunks.collect::<Vec<_>>();
10874 for hunk in &hunks {
10875 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10876 }
10877 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10878 }
10879 drop(chunk_by);
10880 if !revert_changes.is_empty() {
10881 self.transact(window, cx, |editor, window, cx| {
10882 editor.restore(revert_changes, window, cx);
10883 });
10884 }
10885 }
10886
10887 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10888 if let Some(status) = self
10889 .addons
10890 .iter()
10891 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10892 {
10893 return Some(status);
10894 }
10895 self.project
10896 .as_ref()?
10897 .read(cx)
10898 .status_for_buffer_id(buffer_id, cx)
10899 }
10900
10901 pub fn open_active_item_in_terminal(
10902 &mut self,
10903 _: &OpenInTerminal,
10904 window: &mut Window,
10905 cx: &mut Context<Self>,
10906 ) {
10907 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10908 let project_path = buffer.read(cx).project_path(cx)?;
10909 let project = self.project()?.read(cx);
10910 let entry = project.entry_for_path(&project_path, cx)?;
10911 let parent = match &entry.canonical_path {
10912 Some(canonical_path) => canonical_path.to_path_buf(),
10913 None => project.absolute_path(&project_path, cx)?,
10914 }
10915 .parent()?
10916 .to_path_buf();
10917 Some(parent)
10918 }) {
10919 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10920 }
10921 }
10922
10923 fn set_breakpoint_context_menu(
10924 &mut self,
10925 display_row: DisplayRow,
10926 position: Option<Anchor>,
10927 clicked_point: gpui::Point<Pixels>,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 ) {
10931 let source = self
10932 .buffer
10933 .read(cx)
10934 .snapshot(cx)
10935 .anchor_before(Point::new(display_row.0, 0u32));
10936
10937 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10938
10939 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10940 self,
10941 source,
10942 clicked_point,
10943 context_menu,
10944 window,
10945 cx,
10946 );
10947 }
10948
10949 fn add_edit_breakpoint_block(
10950 &mut self,
10951 anchor: Anchor,
10952 breakpoint: &Breakpoint,
10953 edit_action: BreakpointPromptEditAction,
10954 window: &mut Window,
10955 cx: &mut Context<Self>,
10956 ) {
10957 let weak_editor = cx.weak_entity();
10958 let bp_prompt = cx.new(|cx| {
10959 BreakpointPromptEditor::new(
10960 weak_editor,
10961 anchor,
10962 breakpoint.clone(),
10963 edit_action,
10964 window,
10965 cx,
10966 )
10967 });
10968
10969 let height = bp_prompt.update(cx, |this, cx| {
10970 this.prompt
10971 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10972 });
10973 let cloned_prompt = bp_prompt.clone();
10974 let blocks = vec![BlockProperties {
10975 style: BlockStyle::Sticky,
10976 placement: BlockPlacement::Above(anchor),
10977 height: Some(height),
10978 render: Arc::new(move |cx| {
10979 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10980 cloned_prompt.clone().into_any_element()
10981 }),
10982 priority: 0,
10983 }];
10984
10985 let focus_handle = bp_prompt.focus_handle(cx);
10986 window.focus(&focus_handle);
10987
10988 let block_ids = self.insert_blocks(blocks, None, cx);
10989 bp_prompt.update(cx, |prompt, _| {
10990 prompt.add_block_ids(block_ids);
10991 });
10992 }
10993
10994 pub(crate) fn breakpoint_at_row(
10995 &self,
10996 row: u32,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 ) -> Option<(Anchor, Breakpoint)> {
11000 let snapshot = self.snapshot(window, cx);
11001 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11002
11003 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11004 }
11005
11006 pub(crate) fn breakpoint_at_anchor(
11007 &self,
11008 breakpoint_position: Anchor,
11009 snapshot: &EditorSnapshot,
11010 cx: &mut Context<Self>,
11011 ) -> Option<(Anchor, Breakpoint)> {
11012 let buffer = self
11013 .buffer
11014 .read(cx)
11015 .buffer_for_anchor(breakpoint_position, cx)?;
11016
11017 let enclosing_excerpt = breakpoint_position.excerpt_id;
11018 let buffer_snapshot = buffer.read(cx).snapshot();
11019
11020 let row = buffer_snapshot
11021 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11022 .row;
11023
11024 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11025 let anchor_end = snapshot
11026 .buffer_snapshot()
11027 .anchor_after(Point::new(row, line_len));
11028
11029 self.breakpoint_store
11030 .as_ref()?
11031 .read_with(cx, |breakpoint_store, cx| {
11032 breakpoint_store
11033 .breakpoints(
11034 &buffer,
11035 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11036 &buffer_snapshot,
11037 cx,
11038 )
11039 .next()
11040 .and_then(|(bp, _)| {
11041 let breakpoint_row = buffer_snapshot
11042 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11043 .row;
11044
11045 if breakpoint_row == row {
11046 snapshot
11047 .buffer_snapshot()
11048 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11049 .map(|position| (position, bp.bp.clone()))
11050 } else {
11051 None
11052 }
11053 })
11054 })
11055 }
11056
11057 pub fn edit_log_breakpoint(
11058 &mut self,
11059 _: &EditLogBreakpoint,
11060 window: &mut Window,
11061 cx: &mut Context<Self>,
11062 ) {
11063 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11064 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11065 message: None,
11066 state: BreakpointState::Enabled,
11067 condition: None,
11068 hit_condition: None,
11069 });
11070
11071 self.add_edit_breakpoint_block(
11072 anchor,
11073 &breakpoint,
11074 BreakpointPromptEditAction::Log,
11075 window,
11076 cx,
11077 );
11078 }
11079 }
11080
11081 fn breakpoints_at_cursors(
11082 &self,
11083 window: &mut Window,
11084 cx: &mut Context<Self>,
11085 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11086 let snapshot = self.snapshot(window, cx);
11087 let cursors = self
11088 .selections
11089 .disjoint_anchors_arc()
11090 .iter()
11091 .map(|selection| {
11092 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11093
11094 let breakpoint_position = self
11095 .breakpoint_at_row(cursor_position.row, window, cx)
11096 .map(|bp| bp.0)
11097 .unwrap_or_else(|| {
11098 snapshot
11099 .display_snapshot
11100 .buffer_snapshot()
11101 .anchor_after(Point::new(cursor_position.row, 0))
11102 });
11103
11104 let breakpoint = self
11105 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11106 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11107
11108 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11109 })
11110 // 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.
11111 .collect::<HashMap<Anchor, _>>();
11112
11113 cursors.into_iter().collect()
11114 }
11115
11116 pub fn enable_breakpoint(
11117 &mut self,
11118 _: &crate::actions::EnableBreakpoint,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11123 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11124 continue;
11125 };
11126 self.edit_breakpoint_at_anchor(
11127 anchor,
11128 breakpoint,
11129 BreakpointEditAction::InvertState,
11130 cx,
11131 );
11132 }
11133 }
11134
11135 pub fn disable_breakpoint(
11136 &mut self,
11137 _: &crate::actions::DisableBreakpoint,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 ) {
11141 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11142 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11143 continue;
11144 };
11145 self.edit_breakpoint_at_anchor(
11146 anchor,
11147 breakpoint,
11148 BreakpointEditAction::InvertState,
11149 cx,
11150 );
11151 }
11152 }
11153
11154 pub fn toggle_breakpoint(
11155 &mut self,
11156 _: &crate::actions::ToggleBreakpoint,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11161 if let Some(breakpoint) = breakpoint {
11162 self.edit_breakpoint_at_anchor(
11163 anchor,
11164 breakpoint,
11165 BreakpointEditAction::Toggle,
11166 cx,
11167 );
11168 } else {
11169 self.edit_breakpoint_at_anchor(
11170 anchor,
11171 Breakpoint::new_standard(),
11172 BreakpointEditAction::Toggle,
11173 cx,
11174 );
11175 }
11176 }
11177 }
11178
11179 pub fn edit_breakpoint_at_anchor(
11180 &mut self,
11181 breakpoint_position: Anchor,
11182 breakpoint: Breakpoint,
11183 edit_action: BreakpointEditAction,
11184 cx: &mut Context<Self>,
11185 ) {
11186 let Some(breakpoint_store) = &self.breakpoint_store else {
11187 return;
11188 };
11189
11190 let Some(buffer) = self
11191 .buffer
11192 .read(cx)
11193 .buffer_for_anchor(breakpoint_position, cx)
11194 else {
11195 return;
11196 };
11197
11198 breakpoint_store.update(cx, |breakpoint_store, cx| {
11199 breakpoint_store.toggle_breakpoint(
11200 buffer,
11201 BreakpointWithPosition {
11202 position: breakpoint_position.text_anchor,
11203 bp: breakpoint,
11204 },
11205 edit_action,
11206 cx,
11207 );
11208 });
11209
11210 cx.notify();
11211 }
11212
11213 #[cfg(any(test, feature = "test-support"))]
11214 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11215 self.breakpoint_store.clone()
11216 }
11217
11218 pub fn prepare_restore_change(
11219 &self,
11220 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11221 hunk: &MultiBufferDiffHunk,
11222 cx: &mut App,
11223 ) -> Option<()> {
11224 if hunk.is_created_file() {
11225 return None;
11226 }
11227 let buffer = self.buffer.read(cx);
11228 let diff = buffer.diff_for(hunk.buffer_id)?;
11229 let buffer = buffer.buffer(hunk.buffer_id)?;
11230 let buffer = buffer.read(cx);
11231 let original_text = diff
11232 .read(cx)
11233 .base_text()
11234 .as_rope()
11235 .slice(hunk.diff_base_byte_range.clone());
11236 let buffer_snapshot = buffer.snapshot();
11237 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11238 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11239 probe
11240 .0
11241 .start
11242 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11243 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11244 }) {
11245 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11246 Some(())
11247 } else {
11248 None
11249 }
11250 }
11251
11252 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11253 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11254 }
11255
11256 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11257 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11258 }
11259
11260 fn manipulate_lines<M>(
11261 &mut self,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 mut manipulate: M,
11265 ) where
11266 M: FnMut(&str) -> LineManipulationResult,
11267 {
11268 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11269
11270 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11271 let buffer = self.buffer.read(cx).snapshot(cx);
11272
11273 let mut edits = Vec::new();
11274
11275 let selections = self.selections.all::<Point>(&display_map);
11276 let mut selections = selections.iter().peekable();
11277 let mut contiguous_row_selections = Vec::new();
11278 let mut new_selections = Vec::new();
11279 let mut added_lines = 0;
11280 let mut removed_lines = 0;
11281
11282 while let Some(selection) = selections.next() {
11283 let (start_row, end_row) = consume_contiguous_rows(
11284 &mut contiguous_row_selections,
11285 selection,
11286 &display_map,
11287 &mut selections,
11288 );
11289
11290 let start_point = Point::new(start_row.0, 0);
11291 let end_point = Point::new(
11292 end_row.previous_row().0,
11293 buffer.line_len(end_row.previous_row()),
11294 );
11295 let text = buffer
11296 .text_for_range(start_point..end_point)
11297 .collect::<String>();
11298
11299 let LineManipulationResult {
11300 new_text,
11301 line_count_before,
11302 line_count_after,
11303 } = manipulate(&text);
11304
11305 edits.push((start_point..end_point, new_text));
11306
11307 // Selections must change based on added and removed line count
11308 let start_row =
11309 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11310 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11311 new_selections.push(Selection {
11312 id: selection.id,
11313 start: start_row,
11314 end: end_row,
11315 goal: SelectionGoal::None,
11316 reversed: selection.reversed,
11317 });
11318
11319 if line_count_after > line_count_before {
11320 added_lines += line_count_after - line_count_before;
11321 } else if line_count_before > line_count_after {
11322 removed_lines += line_count_before - line_count_after;
11323 }
11324 }
11325
11326 self.transact(window, cx, |this, window, cx| {
11327 let buffer = this.buffer.update(cx, |buffer, cx| {
11328 buffer.edit(edits, None, cx);
11329 buffer.snapshot(cx)
11330 });
11331
11332 // Recalculate offsets on newly edited buffer
11333 let new_selections = new_selections
11334 .iter()
11335 .map(|s| {
11336 let start_point = Point::new(s.start.0, 0);
11337 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11338 Selection {
11339 id: s.id,
11340 start: buffer.point_to_offset(start_point),
11341 end: buffer.point_to_offset(end_point),
11342 goal: s.goal,
11343 reversed: s.reversed,
11344 }
11345 })
11346 .collect();
11347
11348 this.change_selections(Default::default(), window, cx, |s| {
11349 s.select(new_selections);
11350 });
11351
11352 this.request_autoscroll(Autoscroll::fit(), cx);
11353 });
11354 }
11355
11356 fn manipulate_immutable_lines<Fn>(
11357 &mut self,
11358 window: &mut Window,
11359 cx: &mut Context<Self>,
11360 mut callback: Fn,
11361 ) where
11362 Fn: FnMut(&mut Vec<&str>),
11363 {
11364 self.manipulate_lines(window, cx, |text| {
11365 let mut lines: Vec<&str> = text.split('\n').collect();
11366 let line_count_before = lines.len();
11367
11368 callback(&mut lines);
11369
11370 LineManipulationResult {
11371 new_text: lines.join("\n"),
11372 line_count_before,
11373 line_count_after: lines.len(),
11374 }
11375 });
11376 }
11377
11378 fn manipulate_mutable_lines<Fn>(
11379 &mut self,
11380 window: &mut Window,
11381 cx: &mut Context<Self>,
11382 mut callback: Fn,
11383 ) where
11384 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11385 {
11386 self.manipulate_lines(window, cx, |text| {
11387 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11388 let line_count_before = lines.len();
11389
11390 callback(&mut lines);
11391
11392 LineManipulationResult {
11393 new_text: lines.join("\n"),
11394 line_count_before,
11395 line_count_after: lines.len(),
11396 }
11397 });
11398 }
11399
11400 pub fn convert_indentation_to_spaces(
11401 &mut self,
11402 _: &ConvertIndentationToSpaces,
11403 window: &mut Window,
11404 cx: &mut Context<Self>,
11405 ) {
11406 let settings = self.buffer.read(cx).language_settings(cx);
11407 let tab_size = settings.tab_size.get() as usize;
11408
11409 self.manipulate_mutable_lines(window, cx, |lines| {
11410 // Allocates a reasonably sized scratch buffer once for the whole loop
11411 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11412 // Avoids recomputing spaces that could be inserted many times
11413 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11414 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11415 .collect();
11416
11417 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11418 let mut chars = line.as_ref().chars();
11419 let mut col = 0;
11420 let mut changed = false;
11421
11422 for ch in chars.by_ref() {
11423 match ch {
11424 ' ' => {
11425 reindented_line.push(' ');
11426 col += 1;
11427 }
11428 '\t' => {
11429 // \t are converted to spaces depending on the current column
11430 let spaces_len = tab_size - (col % tab_size);
11431 reindented_line.extend(&space_cache[spaces_len - 1]);
11432 col += spaces_len;
11433 changed = true;
11434 }
11435 _ => {
11436 // If we dont append before break, the character is consumed
11437 reindented_line.push(ch);
11438 break;
11439 }
11440 }
11441 }
11442
11443 if !changed {
11444 reindented_line.clear();
11445 continue;
11446 }
11447 // Append the rest of the line and replace old reference with new one
11448 reindented_line.extend(chars);
11449 *line = Cow::Owned(reindented_line.clone());
11450 reindented_line.clear();
11451 }
11452 });
11453 }
11454
11455 pub fn convert_indentation_to_tabs(
11456 &mut self,
11457 _: &ConvertIndentationToTabs,
11458 window: &mut Window,
11459 cx: &mut Context<Self>,
11460 ) {
11461 let settings = self.buffer.read(cx).language_settings(cx);
11462 let tab_size = settings.tab_size.get() as usize;
11463
11464 self.manipulate_mutable_lines(window, cx, |lines| {
11465 // Allocates a reasonably sized buffer once for the whole loop
11466 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11467 // Avoids recomputing spaces that could be inserted many times
11468 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11469 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11470 .collect();
11471
11472 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11473 let mut chars = line.chars();
11474 let mut spaces_count = 0;
11475 let mut first_non_indent_char = None;
11476 let mut changed = false;
11477
11478 for ch in chars.by_ref() {
11479 match ch {
11480 ' ' => {
11481 // Keep track of spaces. Append \t when we reach tab_size
11482 spaces_count += 1;
11483 changed = true;
11484 if spaces_count == tab_size {
11485 reindented_line.push('\t');
11486 spaces_count = 0;
11487 }
11488 }
11489 '\t' => {
11490 reindented_line.push('\t');
11491 spaces_count = 0;
11492 }
11493 _ => {
11494 // Dont append it yet, we might have remaining spaces
11495 first_non_indent_char = Some(ch);
11496 break;
11497 }
11498 }
11499 }
11500
11501 if !changed {
11502 reindented_line.clear();
11503 continue;
11504 }
11505 // Remaining spaces that didn't make a full tab stop
11506 if spaces_count > 0 {
11507 reindented_line.extend(&space_cache[spaces_count - 1]);
11508 }
11509 // If we consume an extra character that was not indentation, add it back
11510 if let Some(extra_char) = first_non_indent_char {
11511 reindented_line.push(extra_char);
11512 }
11513 // Append the rest of the line and replace old reference with new one
11514 reindented_line.extend(chars);
11515 *line = Cow::Owned(reindented_line.clone());
11516 reindented_line.clear();
11517 }
11518 });
11519 }
11520
11521 pub fn convert_to_upper_case(
11522 &mut self,
11523 _: &ConvertToUpperCase,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_text(window, cx, |text| text.to_uppercase())
11528 }
11529
11530 pub fn convert_to_lower_case(
11531 &mut self,
11532 _: &ConvertToLowerCase,
11533 window: &mut Window,
11534 cx: &mut Context<Self>,
11535 ) {
11536 self.manipulate_text(window, cx, |text| text.to_lowercase())
11537 }
11538
11539 pub fn convert_to_title_case(
11540 &mut self,
11541 _: &ConvertToTitleCase,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 ) {
11545 self.manipulate_text(window, cx, |text| {
11546 text.split('\n')
11547 .map(|line| line.to_case(Case::Title))
11548 .join("\n")
11549 })
11550 }
11551
11552 pub fn convert_to_snake_case(
11553 &mut self,
11554 _: &ConvertToSnakeCase,
11555 window: &mut Window,
11556 cx: &mut Context<Self>,
11557 ) {
11558 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11559 }
11560
11561 pub fn convert_to_kebab_case(
11562 &mut self,
11563 _: &ConvertToKebabCase,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 ) {
11567 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11568 }
11569
11570 pub fn convert_to_upper_camel_case(
11571 &mut self,
11572 _: &ConvertToUpperCamelCase,
11573 window: &mut Window,
11574 cx: &mut Context<Self>,
11575 ) {
11576 self.manipulate_text(window, cx, |text| {
11577 text.split('\n')
11578 .map(|line| line.to_case(Case::UpperCamel))
11579 .join("\n")
11580 })
11581 }
11582
11583 pub fn convert_to_lower_camel_case(
11584 &mut self,
11585 _: &ConvertToLowerCamelCase,
11586 window: &mut Window,
11587 cx: &mut Context<Self>,
11588 ) {
11589 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11590 }
11591
11592 pub fn convert_to_opposite_case(
11593 &mut self,
11594 _: &ConvertToOppositeCase,
11595 window: &mut Window,
11596 cx: &mut Context<Self>,
11597 ) {
11598 self.manipulate_text(window, cx, |text| {
11599 text.chars()
11600 .fold(String::with_capacity(text.len()), |mut t, c| {
11601 if c.is_uppercase() {
11602 t.extend(c.to_lowercase());
11603 } else {
11604 t.extend(c.to_uppercase());
11605 }
11606 t
11607 })
11608 })
11609 }
11610
11611 pub fn convert_to_sentence_case(
11612 &mut self,
11613 _: &ConvertToSentenceCase,
11614 window: &mut Window,
11615 cx: &mut Context<Self>,
11616 ) {
11617 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11618 }
11619
11620 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11621 self.manipulate_text(window, cx, |text| {
11622 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11623 if has_upper_case_characters {
11624 text.to_lowercase()
11625 } else {
11626 text.to_uppercase()
11627 }
11628 })
11629 }
11630
11631 pub fn convert_to_rot13(
11632 &mut self,
11633 _: &ConvertToRot13,
11634 window: &mut Window,
11635 cx: &mut Context<Self>,
11636 ) {
11637 self.manipulate_text(window, cx, |text| {
11638 text.chars()
11639 .map(|c| match c {
11640 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11641 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11642 _ => c,
11643 })
11644 .collect()
11645 })
11646 }
11647
11648 pub fn convert_to_rot47(
11649 &mut self,
11650 _: &ConvertToRot47,
11651 window: &mut Window,
11652 cx: &mut Context<Self>,
11653 ) {
11654 self.manipulate_text(window, cx, |text| {
11655 text.chars()
11656 .map(|c| {
11657 let code_point = c as u32;
11658 if code_point >= 33 && code_point <= 126 {
11659 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11660 }
11661 c
11662 })
11663 .collect()
11664 })
11665 }
11666
11667 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11668 where
11669 Fn: FnMut(&str) -> String,
11670 {
11671 let buffer = self.buffer.read(cx).snapshot(cx);
11672
11673 let mut new_selections = Vec::new();
11674 let mut edits = Vec::new();
11675 let mut selection_adjustment = 0i32;
11676
11677 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11678 let selection_is_empty = selection.is_empty();
11679
11680 let (start, end) = if selection_is_empty {
11681 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11682 (word_range.start, word_range.end)
11683 } else {
11684 (
11685 buffer.point_to_offset(selection.start),
11686 buffer.point_to_offset(selection.end),
11687 )
11688 };
11689
11690 let text = buffer.text_for_range(start..end).collect::<String>();
11691 let old_length = text.len() as i32;
11692 let text = callback(&text);
11693
11694 new_selections.push(Selection {
11695 start: (start as i32 - selection_adjustment) as usize,
11696 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11697 goal: SelectionGoal::None,
11698 id: selection.id,
11699 reversed: selection.reversed,
11700 });
11701
11702 selection_adjustment += old_length - text.len() as i32;
11703
11704 edits.push((start..end, text));
11705 }
11706
11707 self.transact(window, cx, |this, window, cx| {
11708 this.buffer.update(cx, |buffer, cx| {
11709 buffer.edit(edits, None, cx);
11710 });
11711
11712 this.change_selections(Default::default(), window, cx, |s| {
11713 s.select(new_selections);
11714 });
11715
11716 this.request_autoscroll(Autoscroll::fit(), cx);
11717 });
11718 }
11719
11720 pub fn move_selection_on_drop(
11721 &mut self,
11722 selection: &Selection<Anchor>,
11723 target: DisplayPoint,
11724 is_cut: bool,
11725 window: &mut Window,
11726 cx: &mut Context<Self>,
11727 ) {
11728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11729 let buffer = display_map.buffer_snapshot();
11730 let mut edits = Vec::new();
11731 let insert_point = display_map
11732 .clip_point(target, Bias::Left)
11733 .to_point(&display_map);
11734 let text = buffer
11735 .text_for_range(selection.start..selection.end)
11736 .collect::<String>();
11737 if is_cut {
11738 edits.push(((selection.start..selection.end), String::new()));
11739 }
11740 let insert_anchor = buffer.anchor_before(insert_point);
11741 edits.push(((insert_anchor..insert_anchor), text));
11742 let last_edit_start = insert_anchor.bias_left(buffer);
11743 let last_edit_end = insert_anchor.bias_right(buffer);
11744 self.transact(window, cx, |this, window, cx| {
11745 this.buffer.update(cx, |buffer, cx| {
11746 buffer.edit(edits, None, cx);
11747 });
11748 this.change_selections(Default::default(), window, cx, |s| {
11749 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11750 });
11751 });
11752 }
11753
11754 pub fn clear_selection_drag_state(&mut self) {
11755 self.selection_drag_state = SelectionDragState::None;
11756 }
11757
11758 pub fn duplicate(
11759 &mut self,
11760 upwards: bool,
11761 whole_lines: bool,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11766
11767 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11768 let buffer = display_map.buffer_snapshot();
11769 let selections = self.selections.all::<Point>(&display_map);
11770
11771 let mut edits = Vec::new();
11772 let mut selections_iter = selections.iter().peekable();
11773 while let Some(selection) = selections_iter.next() {
11774 let mut rows = selection.spanned_rows(false, &display_map);
11775 // duplicate line-wise
11776 if whole_lines || selection.start == selection.end {
11777 // Avoid duplicating the same lines twice.
11778 while let Some(next_selection) = selections_iter.peek() {
11779 let next_rows = next_selection.spanned_rows(false, &display_map);
11780 if next_rows.start < rows.end {
11781 rows.end = next_rows.end;
11782 selections_iter.next().unwrap();
11783 } else {
11784 break;
11785 }
11786 }
11787
11788 // Copy the text from the selected row region and splice it either at the start
11789 // or end of the region.
11790 let start = Point::new(rows.start.0, 0);
11791 let end = Point::new(
11792 rows.end.previous_row().0,
11793 buffer.line_len(rows.end.previous_row()),
11794 );
11795
11796 let mut text = buffer.text_for_range(start..end).collect::<String>();
11797
11798 let insert_location = if upwards {
11799 // When duplicating upward, we need to insert before the current line.
11800 // If we're on the last line and it doesn't end with a newline,
11801 // we need to add a newline before the duplicated content.
11802 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11803 && buffer.max_point().column > 0
11804 && !text.ends_with('\n');
11805
11806 if needs_leading_newline {
11807 text.insert(0, '\n');
11808 end
11809 } else {
11810 text.push('\n');
11811 Point::new(rows.start.0, 0)
11812 }
11813 } else {
11814 text.push('\n');
11815 start
11816 };
11817 edits.push((insert_location..insert_location, text));
11818 } else {
11819 // duplicate character-wise
11820 let start = selection.start;
11821 let end = selection.end;
11822 let text = buffer.text_for_range(start..end).collect::<String>();
11823 edits.push((selection.end..selection.end, text));
11824 }
11825 }
11826
11827 self.transact(window, cx, |this, window, cx| {
11828 this.buffer.update(cx, |buffer, cx| {
11829 buffer.edit(edits, None, cx);
11830 });
11831
11832 // When duplicating upward with whole lines, move the cursor to the duplicated line
11833 if upwards && whole_lines {
11834 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11835
11836 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11837 let mut new_ranges = Vec::new();
11838 let selections = s.all::<Point>(&display_map);
11839 let mut selections_iter = selections.iter().peekable();
11840
11841 while let Some(first_selection) = selections_iter.next() {
11842 // Group contiguous selections together to find the total row span
11843 let mut group_selections = vec![first_selection];
11844 let mut rows = first_selection.spanned_rows(false, &display_map);
11845
11846 while let Some(next_selection) = selections_iter.peek() {
11847 let next_rows = next_selection.spanned_rows(false, &display_map);
11848 if next_rows.start < rows.end {
11849 rows.end = next_rows.end;
11850 group_selections.push(selections_iter.next().unwrap());
11851 } else {
11852 break;
11853 }
11854 }
11855
11856 let row_count = rows.end.0 - rows.start.0;
11857
11858 // Move all selections in this group up by the total number of duplicated rows
11859 for selection in group_selections {
11860 let new_start = Point::new(
11861 selection.start.row.saturating_sub(row_count),
11862 selection.start.column,
11863 );
11864
11865 let new_end = Point::new(
11866 selection.end.row.saturating_sub(row_count),
11867 selection.end.column,
11868 );
11869
11870 new_ranges.push(new_start..new_end);
11871 }
11872 }
11873
11874 s.select_ranges(new_ranges);
11875 });
11876 }
11877
11878 this.request_autoscroll(Autoscroll::fit(), cx);
11879 });
11880 }
11881
11882 pub fn duplicate_line_up(
11883 &mut self,
11884 _: &DuplicateLineUp,
11885 window: &mut Window,
11886 cx: &mut Context<Self>,
11887 ) {
11888 self.duplicate(true, true, window, cx);
11889 }
11890
11891 pub fn duplicate_line_down(
11892 &mut self,
11893 _: &DuplicateLineDown,
11894 window: &mut Window,
11895 cx: &mut Context<Self>,
11896 ) {
11897 self.duplicate(false, true, window, cx);
11898 }
11899
11900 pub fn duplicate_selection(
11901 &mut self,
11902 _: &DuplicateSelection,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 ) {
11906 self.duplicate(false, false, window, cx);
11907 }
11908
11909 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11910 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11911 if self.mode.is_single_line() {
11912 cx.propagate();
11913 return;
11914 }
11915
11916 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11917 let buffer = self.buffer.read(cx).snapshot(cx);
11918
11919 let mut edits = Vec::new();
11920 let mut unfold_ranges = Vec::new();
11921 let mut refold_creases = Vec::new();
11922
11923 let selections = self.selections.all::<Point>(&display_map);
11924 let mut selections = selections.iter().peekable();
11925 let mut contiguous_row_selections = Vec::new();
11926 let mut new_selections = Vec::new();
11927
11928 while let Some(selection) = selections.next() {
11929 // Find all the selections that span a contiguous row range
11930 let (start_row, end_row) = consume_contiguous_rows(
11931 &mut contiguous_row_selections,
11932 selection,
11933 &display_map,
11934 &mut selections,
11935 );
11936
11937 // Move the text spanned by the row range to be before the line preceding the row range
11938 if start_row.0 > 0 {
11939 let range_to_move = Point::new(
11940 start_row.previous_row().0,
11941 buffer.line_len(start_row.previous_row()),
11942 )
11943 ..Point::new(
11944 end_row.previous_row().0,
11945 buffer.line_len(end_row.previous_row()),
11946 );
11947 let insertion_point = display_map
11948 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11949 .0;
11950
11951 // Don't move lines across excerpts
11952 if buffer
11953 .excerpt_containing(insertion_point..range_to_move.end)
11954 .is_some()
11955 {
11956 let text = buffer
11957 .text_for_range(range_to_move.clone())
11958 .flat_map(|s| s.chars())
11959 .skip(1)
11960 .chain(['\n'])
11961 .collect::<String>();
11962
11963 edits.push((
11964 buffer.anchor_after(range_to_move.start)
11965 ..buffer.anchor_before(range_to_move.end),
11966 String::new(),
11967 ));
11968 let insertion_anchor = buffer.anchor_after(insertion_point);
11969 edits.push((insertion_anchor..insertion_anchor, text));
11970
11971 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11972
11973 // Move selections up
11974 new_selections.extend(contiguous_row_selections.drain(..).map(
11975 |mut selection| {
11976 selection.start.row -= row_delta;
11977 selection.end.row -= row_delta;
11978 selection
11979 },
11980 ));
11981
11982 // Move folds up
11983 unfold_ranges.push(range_to_move.clone());
11984 for fold in display_map.folds_in_range(
11985 buffer.anchor_before(range_to_move.start)
11986 ..buffer.anchor_after(range_to_move.end),
11987 ) {
11988 let mut start = fold.range.start.to_point(&buffer);
11989 let mut end = fold.range.end.to_point(&buffer);
11990 start.row -= row_delta;
11991 end.row -= row_delta;
11992 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11993 }
11994 }
11995 }
11996
11997 // If we didn't move line(s), preserve the existing selections
11998 new_selections.append(&mut contiguous_row_selections);
11999 }
12000
12001 self.transact(window, cx, |this, window, cx| {
12002 this.unfold_ranges(&unfold_ranges, true, true, cx);
12003 this.buffer.update(cx, |buffer, cx| {
12004 for (range, text) in edits {
12005 buffer.edit([(range, text)], None, cx);
12006 }
12007 });
12008 this.fold_creases(refold_creases, true, window, cx);
12009 this.change_selections(Default::default(), window, cx, |s| {
12010 s.select(new_selections);
12011 })
12012 });
12013 }
12014
12015 pub fn move_line_down(
12016 &mut self,
12017 _: &MoveLineDown,
12018 window: &mut Window,
12019 cx: &mut Context<Self>,
12020 ) {
12021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12022 if self.mode.is_single_line() {
12023 cx.propagate();
12024 return;
12025 }
12026
12027 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12028 let buffer = self.buffer.read(cx).snapshot(cx);
12029
12030 let mut edits = Vec::new();
12031 let mut unfold_ranges = Vec::new();
12032 let mut refold_creases = Vec::new();
12033
12034 let selections = self.selections.all::<Point>(&display_map);
12035 let mut selections = selections.iter().peekable();
12036 let mut contiguous_row_selections = Vec::new();
12037 let mut new_selections = Vec::new();
12038
12039 while let Some(selection) = selections.next() {
12040 // Find all the selections that span a contiguous row range
12041 let (start_row, end_row) = consume_contiguous_rows(
12042 &mut contiguous_row_selections,
12043 selection,
12044 &display_map,
12045 &mut selections,
12046 );
12047
12048 // Move the text spanned by the row range to be after the last line of the row range
12049 if end_row.0 <= buffer.max_point().row {
12050 let range_to_move =
12051 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12052 let insertion_point = display_map
12053 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12054 .0;
12055
12056 // Don't move lines across excerpt boundaries
12057 if buffer
12058 .excerpt_containing(range_to_move.start..insertion_point)
12059 .is_some()
12060 {
12061 let mut text = String::from("\n");
12062 text.extend(buffer.text_for_range(range_to_move.clone()));
12063 text.pop(); // Drop trailing newline
12064 edits.push((
12065 buffer.anchor_after(range_to_move.start)
12066 ..buffer.anchor_before(range_to_move.end),
12067 String::new(),
12068 ));
12069 let insertion_anchor = buffer.anchor_after(insertion_point);
12070 edits.push((insertion_anchor..insertion_anchor, text));
12071
12072 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12073
12074 // Move selections down
12075 new_selections.extend(contiguous_row_selections.drain(..).map(
12076 |mut selection| {
12077 selection.start.row += row_delta;
12078 selection.end.row += row_delta;
12079 selection
12080 },
12081 ));
12082
12083 // Move folds down
12084 unfold_ranges.push(range_to_move.clone());
12085 for fold in display_map.folds_in_range(
12086 buffer.anchor_before(range_to_move.start)
12087 ..buffer.anchor_after(range_to_move.end),
12088 ) {
12089 let mut start = fold.range.start.to_point(&buffer);
12090 let mut end = fold.range.end.to_point(&buffer);
12091 start.row += row_delta;
12092 end.row += row_delta;
12093 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12094 }
12095 }
12096 }
12097
12098 // If we didn't move line(s), preserve the existing selections
12099 new_selections.append(&mut contiguous_row_selections);
12100 }
12101
12102 self.transact(window, cx, |this, window, cx| {
12103 this.unfold_ranges(&unfold_ranges, true, true, cx);
12104 this.buffer.update(cx, |buffer, cx| {
12105 for (range, text) in edits {
12106 buffer.edit([(range, text)], None, cx);
12107 }
12108 });
12109 this.fold_creases(refold_creases, true, window, cx);
12110 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12111 });
12112 }
12113
12114 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12116 let text_layout_details = &self.text_layout_details(window);
12117 self.transact(window, cx, |this, window, cx| {
12118 let edits = this.change_selections(Default::default(), window, cx, |s| {
12119 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12120 s.move_with(|display_map, selection| {
12121 if !selection.is_empty() {
12122 return;
12123 }
12124
12125 let mut head = selection.head();
12126 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12127 if head.column() == display_map.line_len(head.row()) {
12128 transpose_offset = display_map
12129 .buffer_snapshot()
12130 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12131 }
12132
12133 if transpose_offset == 0 {
12134 return;
12135 }
12136
12137 *head.column_mut() += 1;
12138 head = display_map.clip_point(head, Bias::Right);
12139 let goal = SelectionGoal::HorizontalPosition(
12140 display_map
12141 .x_for_display_point(head, text_layout_details)
12142 .into(),
12143 );
12144 selection.collapse_to(head, goal);
12145
12146 let transpose_start = display_map
12147 .buffer_snapshot()
12148 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12149 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12150 let transpose_end = display_map
12151 .buffer_snapshot()
12152 .clip_offset(transpose_offset + 1, Bias::Right);
12153 if let Some(ch) = display_map
12154 .buffer_snapshot()
12155 .chars_at(transpose_start)
12156 .next()
12157 {
12158 edits.push((transpose_start..transpose_offset, String::new()));
12159 edits.push((transpose_end..transpose_end, ch.to_string()));
12160 }
12161 }
12162 });
12163 edits
12164 });
12165 this.buffer
12166 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12167 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12168 this.change_selections(Default::default(), window, cx, |s| {
12169 s.select(selections);
12170 });
12171 });
12172 }
12173
12174 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12176 if self.mode.is_single_line() {
12177 cx.propagate();
12178 return;
12179 }
12180
12181 self.rewrap_impl(RewrapOptions::default(), cx)
12182 }
12183
12184 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12185 let buffer = self.buffer.read(cx).snapshot(cx);
12186 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12187
12188 #[derive(Clone, Debug, PartialEq)]
12189 enum CommentFormat {
12190 /// single line comment, with prefix for line
12191 Line(String),
12192 /// single line within a block comment, with prefix for line
12193 BlockLine(String),
12194 /// a single line of a block comment that includes the initial delimiter
12195 BlockCommentWithStart(BlockCommentConfig),
12196 /// a single line of a block comment that includes the ending delimiter
12197 BlockCommentWithEnd(BlockCommentConfig),
12198 }
12199
12200 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12201 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12202 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12203 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12204 .peekable();
12205
12206 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12207 row
12208 } else {
12209 return Vec::new();
12210 };
12211
12212 let language_settings = buffer.language_settings_at(selection.head(), cx);
12213 let language_scope = buffer.language_scope_at(selection.head());
12214
12215 let indent_and_prefix_for_row =
12216 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12217 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12218 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12219 &language_scope
12220 {
12221 let indent_end = Point::new(row, indent.len);
12222 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12223 let line_text_after_indent = buffer
12224 .text_for_range(indent_end..line_end)
12225 .collect::<String>();
12226
12227 let is_within_comment_override = buffer
12228 .language_scope_at(indent_end)
12229 .is_some_and(|scope| scope.override_name() == Some("comment"));
12230 let comment_delimiters = if is_within_comment_override {
12231 // we are within a comment syntax node, but we don't
12232 // yet know what kind of comment: block, doc or line
12233 match (
12234 language_scope.documentation_comment(),
12235 language_scope.block_comment(),
12236 ) {
12237 (Some(config), _) | (_, Some(config))
12238 if buffer.contains_str_at(indent_end, &config.start) =>
12239 {
12240 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12241 }
12242 (Some(config), _) | (_, Some(config))
12243 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12244 {
12245 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12246 }
12247 (Some(config), _) | (_, Some(config))
12248 if buffer.contains_str_at(indent_end, &config.prefix) =>
12249 {
12250 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12251 }
12252 (_, _) => language_scope
12253 .line_comment_prefixes()
12254 .iter()
12255 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12256 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12257 }
12258 } else {
12259 // we not in an overridden comment node, but we may
12260 // be within a non-overridden line comment node
12261 language_scope
12262 .line_comment_prefixes()
12263 .iter()
12264 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12265 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12266 };
12267
12268 let rewrap_prefix = language_scope
12269 .rewrap_prefixes()
12270 .iter()
12271 .find_map(|prefix_regex| {
12272 prefix_regex.find(&line_text_after_indent).map(|mat| {
12273 if mat.start() == 0 {
12274 Some(mat.as_str().to_string())
12275 } else {
12276 None
12277 }
12278 })
12279 })
12280 .flatten();
12281 (comment_delimiters, rewrap_prefix)
12282 } else {
12283 (None, None)
12284 };
12285 (indent, comment_prefix, rewrap_prefix)
12286 };
12287
12288 let mut ranges = Vec::new();
12289 let from_empty_selection = selection.is_empty();
12290
12291 let mut current_range_start = first_row;
12292 let mut prev_row = first_row;
12293 let (
12294 mut current_range_indent,
12295 mut current_range_comment_delimiters,
12296 mut current_range_rewrap_prefix,
12297 ) = indent_and_prefix_for_row(first_row);
12298
12299 for row in non_blank_rows_iter.skip(1) {
12300 let has_paragraph_break = row > prev_row + 1;
12301
12302 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12303 indent_and_prefix_for_row(row);
12304
12305 let has_indent_change = row_indent != current_range_indent;
12306 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12307
12308 let has_boundary_change = has_comment_change
12309 || row_rewrap_prefix.is_some()
12310 || (has_indent_change && current_range_comment_delimiters.is_some());
12311
12312 if has_paragraph_break || has_boundary_change {
12313 ranges.push((
12314 language_settings.clone(),
12315 Point::new(current_range_start, 0)
12316 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12317 current_range_indent,
12318 current_range_comment_delimiters.clone(),
12319 current_range_rewrap_prefix.clone(),
12320 from_empty_selection,
12321 ));
12322 current_range_start = row;
12323 current_range_indent = row_indent;
12324 current_range_comment_delimiters = row_comment_delimiters;
12325 current_range_rewrap_prefix = row_rewrap_prefix;
12326 }
12327 prev_row = row;
12328 }
12329
12330 ranges.push((
12331 language_settings.clone(),
12332 Point::new(current_range_start, 0)
12333 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12334 current_range_indent,
12335 current_range_comment_delimiters,
12336 current_range_rewrap_prefix,
12337 from_empty_selection,
12338 ));
12339
12340 ranges
12341 });
12342
12343 let mut edits = Vec::new();
12344 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12345
12346 for (
12347 language_settings,
12348 wrap_range,
12349 mut indent_size,
12350 comment_prefix,
12351 rewrap_prefix,
12352 from_empty_selection,
12353 ) in wrap_ranges
12354 {
12355 let mut start_row = wrap_range.start.row;
12356 let mut end_row = wrap_range.end.row;
12357
12358 // Skip selections that overlap with a range that has already been rewrapped.
12359 let selection_range = start_row..end_row;
12360 if rewrapped_row_ranges
12361 .iter()
12362 .any(|range| range.overlaps(&selection_range))
12363 {
12364 continue;
12365 }
12366
12367 let tab_size = language_settings.tab_size;
12368
12369 let (line_prefix, inside_comment) = match &comment_prefix {
12370 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12371 (Some(prefix.as_str()), true)
12372 }
12373 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12374 (Some(prefix.as_ref()), true)
12375 }
12376 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12377 start: _,
12378 end: _,
12379 prefix,
12380 tab_size,
12381 })) => {
12382 indent_size.len += tab_size;
12383 (Some(prefix.as_ref()), true)
12384 }
12385 None => (None, false),
12386 };
12387 let indent_prefix = indent_size.chars().collect::<String>();
12388 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12389
12390 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12391 RewrapBehavior::InComments => inside_comment,
12392 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12393 RewrapBehavior::Anywhere => true,
12394 };
12395
12396 let should_rewrap = options.override_language_settings
12397 || allow_rewrap_based_on_language
12398 || self.hard_wrap.is_some();
12399 if !should_rewrap {
12400 continue;
12401 }
12402
12403 if from_empty_selection {
12404 'expand_upwards: while start_row > 0 {
12405 let prev_row = start_row - 1;
12406 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12407 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12408 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12409 {
12410 start_row = prev_row;
12411 } else {
12412 break 'expand_upwards;
12413 }
12414 }
12415
12416 'expand_downwards: while end_row < buffer.max_point().row {
12417 let next_row = end_row + 1;
12418 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12419 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12420 && !buffer.is_line_blank(MultiBufferRow(next_row))
12421 {
12422 end_row = next_row;
12423 } else {
12424 break 'expand_downwards;
12425 }
12426 }
12427 }
12428
12429 let start = Point::new(start_row, 0);
12430 let start_offset = ToOffset::to_offset(&start, &buffer);
12431 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12432 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12433 let mut first_line_delimiter = None;
12434 let mut last_line_delimiter = None;
12435 let Some(lines_without_prefixes) = selection_text
12436 .lines()
12437 .enumerate()
12438 .map(|(ix, line)| {
12439 let line_trimmed = line.trim_start();
12440 if rewrap_prefix.is_some() && ix > 0 {
12441 Ok(line_trimmed)
12442 } else if let Some(
12443 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12444 start,
12445 prefix,
12446 end,
12447 tab_size,
12448 })
12449 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12450 start,
12451 prefix,
12452 end,
12453 tab_size,
12454 }),
12455 ) = &comment_prefix
12456 {
12457 let line_trimmed = line_trimmed
12458 .strip_prefix(start.as_ref())
12459 .map(|s| {
12460 let mut indent_size = indent_size;
12461 indent_size.len -= tab_size;
12462 let indent_prefix: String = indent_size.chars().collect();
12463 first_line_delimiter = Some((indent_prefix, start));
12464 s.trim_start()
12465 })
12466 .unwrap_or(line_trimmed);
12467 let line_trimmed = line_trimmed
12468 .strip_suffix(end.as_ref())
12469 .map(|s| {
12470 last_line_delimiter = Some(end);
12471 s.trim_end()
12472 })
12473 .unwrap_or(line_trimmed);
12474 let line_trimmed = line_trimmed
12475 .strip_prefix(prefix.as_ref())
12476 .unwrap_or(line_trimmed);
12477 Ok(line_trimmed)
12478 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12479 line_trimmed.strip_prefix(prefix).with_context(|| {
12480 format!("line did not start with prefix {prefix:?}: {line:?}")
12481 })
12482 } else {
12483 line_trimmed
12484 .strip_prefix(&line_prefix.trim_start())
12485 .with_context(|| {
12486 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12487 })
12488 }
12489 })
12490 .collect::<Result<Vec<_>, _>>()
12491 .log_err()
12492 else {
12493 continue;
12494 };
12495
12496 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12497 buffer
12498 .language_settings_at(Point::new(start_row, 0), cx)
12499 .preferred_line_length as usize
12500 });
12501
12502 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12503 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12504 } else {
12505 line_prefix.clone()
12506 };
12507
12508 let wrapped_text = {
12509 let mut wrapped_text = wrap_with_prefix(
12510 line_prefix,
12511 subsequent_lines_prefix,
12512 lines_without_prefixes.join("\n"),
12513 wrap_column,
12514 tab_size,
12515 options.preserve_existing_whitespace,
12516 );
12517
12518 if let Some((indent, delimiter)) = first_line_delimiter {
12519 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12520 }
12521 if let Some(last_line) = last_line_delimiter {
12522 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12523 }
12524
12525 wrapped_text
12526 };
12527
12528 // TODO: should always use char-based diff while still supporting cursor behavior that
12529 // matches vim.
12530 let mut diff_options = DiffOptions::default();
12531 if options.override_language_settings {
12532 diff_options.max_word_diff_len = 0;
12533 diff_options.max_word_diff_line_count = 0;
12534 } else {
12535 diff_options.max_word_diff_len = usize::MAX;
12536 diff_options.max_word_diff_line_count = usize::MAX;
12537 }
12538
12539 for (old_range, new_text) in
12540 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12541 {
12542 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12543 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12544 edits.push((edit_start..edit_end, new_text));
12545 }
12546
12547 rewrapped_row_ranges.push(start_row..=end_row);
12548 }
12549
12550 self.buffer
12551 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12552 }
12553
12554 pub fn cut_common(
12555 &mut self,
12556 cut_no_selection_line: bool,
12557 window: &mut Window,
12558 cx: &mut Context<Self>,
12559 ) -> ClipboardItem {
12560 let mut text = String::new();
12561 let buffer = self.buffer.read(cx).snapshot(cx);
12562 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12563 let mut clipboard_selections = Vec::with_capacity(selections.len());
12564 {
12565 let max_point = buffer.max_point();
12566 let mut is_first = true;
12567 for selection in &mut selections {
12568 let is_entire_line =
12569 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12570 if is_entire_line {
12571 selection.start = Point::new(selection.start.row, 0);
12572 if !selection.is_empty() && selection.end.column == 0 {
12573 selection.end = cmp::min(max_point, selection.end);
12574 } else {
12575 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12576 }
12577 selection.goal = SelectionGoal::None;
12578 }
12579 if is_first {
12580 is_first = false;
12581 } else {
12582 text += "\n";
12583 }
12584 let mut len = 0;
12585 for chunk in buffer.text_for_range(selection.start..selection.end) {
12586 text.push_str(chunk);
12587 len += chunk.len();
12588 }
12589 clipboard_selections.push(ClipboardSelection {
12590 len,
12591 is_entire_line,
12592 first_line_indent: buffer
12593 .indent_size_for_line(MultiBufferRow(selection.start.row))
12594 .len,
12595 });
12596 }
12597 }
12598
12599 self.transact(window, cx, |this, window, cx| {
12600 this.change_selections(Default::default(), window, cx, |s| {
12601 s.select(selections);
12602 });
12603 this.insert("", window, cx);
12604 });
12605 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12606 }
12607
12608 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12609 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12610 let item = self.cut_common(true, window, cx);
12611 cx.write_to_clipboard(item);
12612 }
12613
12614 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12616 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12617 s.move_with(|snapshot, sel| {
12618 if sel.is_empty() {
12619 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12620 }
12621 if sel.is_empty() {
12622 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12623 }
12624 });
12625 });
12626 let item = self.cut_common(false, window, cx);
12627 cx.set_global(KillRing(item))
12628 }
12629
12630 pub fn kill_ring_yank(
12631 &mut self,
12632 _: &KillRingYank,
12633 window: &mut Window,
12634 cx: &mut Context<Self>,
12635 ) {
12636 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12637 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12638 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12639 (kill_ring.text().to_string(), kill_ring.metadata_json())
12640 } else {
12641 return;
12642 }
12643 } else {
12644 return;
12645 };
12646 self.do_paste(&text, metadata, false, window, cx);
12647 }
12648
12649 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12650 self.do_copy(true, cx);
12651 }
12652
12653 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12654 self.do_copy(false, cx);
12655 }
12656
12657 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12658 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12659 let buffer = self.buffer.read(cx).read(cx);
12660 let mut text = String::new();
12661
12662 let mut clipboard_selections = Vec::with_capacity(selections.len());
12663 {
12664 let max_point = buffer.max_point();
12665 let mut is_first = true;
12666 for selection in &selections {
12667 let mut start = selection.start;
12668 let mut end = selection.end;
12669 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12670 let mut add_trailing_newline = false;
12671 if is_entire_line {
12672 start = Point::new(start.row, 0);
12673 let next_line_start = Point::new(end.row + 1, 0);
12674 if next_line_start <= max_point {
12675 end = next_line_start;
12676 } else {
12677 // We're on the last line without a trailing newline.
12678 // Copy to the end of the line and add a newline afterwards.
12679 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12680 add_trailing_newline = true;
12681 }
12682 }
12683
12684 let mut trimmed_selections = Vec::new();
12685 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12686 let row = MultiBufferRow(start.row);
12687 let first_indent = buffer.indent_size_for_line(row);
12688 if first_indent.len == 0 || start.column > first_indent.len {
12689 trimmed_selections.push(start..end);
12690 } else {
12691 trimmed_selections.push(
12692 Point::new(row.0, first_indent.len)
12693 ..Point::new(row.0, buffer.line_len(row)),
12694 );
12695 for row in start.row + 1..=end.row {
12696 let mut line_len = buffer.line_len(MultiBufferRow(row));
12697 if row == end.row {
12698 line_len = end.column;
12699 }
12700 if line_len == 0 {
12701 trimmed_selections
12702 .push(Point::new(row, 0)..Point::new(row, line_len));
12703 continue;
12704 }
12705 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12706 if row_indent_size.len >= first_indent.len {
12707 trimmed_selections.push(
12708 Point::new(row, first_indent.len)..Point::new(row, line_len),
12709 );
12710 } else {
12711 trimmed_selections.clear();
12712 trimmed_selections.push(start..end);
12713 break;
12714 }
12715 }
12716 }
12717 } else {
12718 trimmed_selections.push(start..end);
12719 }
12720
12721 for trimmed_range in trimmed_selections {
12722 if is_first {
12723 is_first = false;
12724 } else {
12725 text += "\n";
12726 }
12727 let mut len = 0;
12728 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12729 text.push_str(chunk);
12730 len += chunk.len();
12731 }
12732 if add_trailing_newline {
12733 text.push('\n');
12734 len += 1;
12735 }
12736 clipboard_selections.push(ClipboardSelection {
12737 len,
12738 is_entire_line,
12739 first_line_indent: buffer
12740 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12741 .len,
12742 });
12743 }
12744 }
12745 }
12746
12747 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12748 text,
12749 clipboard_selections,
12750 ));
12751 }
12752
12753 pub fn do_paste(
12754 &mut self,
12755 text: &String,
12756 clipboard_selections: Option<Vec<ClipboardSelection>>,
12757 handle_entire_lines: bool,
12758 window: &mut Window,
12759 cx: &mut Context<Self>,
12760 ) {
12761 if self.read_only(cx) {
12762 return;
12763 }
12764
12765 let clipboard_text = Cow::Borrowed(text.as_str());
12766
12767 self.transact(window, cx, |this, window, cx| {
12768 let had_active_edit_prediction = this.has_active_edit_prediction();
12769 let display_map = this.display_snapshot(cx);
12770 let old_selections = this.selections.all::<usize>(&display_map);
12771 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12772
12773 if let Some(mut clipboard_selections) = clipboard_selections {
12774 let all_selections_were_entire_line =
12775 clipboard_selections.iter().all(|s| s.is_entire_line);
12776 let first_selection_indent_column =
12777 clipboard_selections.first().map(|s| s.first_line_indent);
12778 if clipboard_selections.len() != old_selections.len() {
12779 clipboard_selections.drain(..);
12780 }
12781 let mut auto_indent_on_paste = true;
12782
12783 this.buffer.update(cx, |buffer, cx| {
12784 let snapshot = buffer.read(cx);
12785 auto_indent_on_paste = snapshot
12786 .language_settings_at(cursor_offset, cx)
12787 .auto_indent_on_paste;
12788
12789 let mut start_offset = 0;
12790 let mut edits = Vec::new();
12791 let mut original_indent_columns = Vec::new();
12792 for (ix, selection) in old_selections.iter().enumerate() {
12793 let to_insert;
12794 let entire_line;
12795 let original_indent_column;
12796 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12797 let end_offset = start_offset + clipboard_selection.len;
12798 to_insert = &clipboard_text[start_offset..end_offset];
12799 entire_line = clipboard_selection.is_entire_line;
12800 start_offset = end_offset + 1;
12801 original_indent_column = Some(clipboard_selection.first_line_indent);
12802 } else {
12803 to_insert = &*clipboard_text;
12804 entire_line = all_selections_were_entire_line;
12805 original_indent_column = first_selection_indent_column
12806 }
12807
12808 let (range, to_insert) =
12809 if selection.is_empty() && handle_entire_lines && entire_line {
12810 // If the corresponding selection was empty when this slice of the
12811 // clipboard text was written, then the entire line containing the
12812 // selection was copied. If this selection is also currently empty,
12813 // then paste the line before the current line of the buffer.
12814 let column = selection.start.to_point(&snapshot).column as usize;
12815 let line_start = selection.start - column;
12816 (line_start..line_start, Cow::Borrowed(to_insert))
12817 } else {
12818 let language = snapshot.language_at(selection.head());
12819 let range = selection.range();
12820 if let Some(language) = language
12821 && language.name() == "Markdown".into()
12822 {
12823 edit_for_markdown_paste(
12824 &snapshot,
12825 range,
12826 to_insert,
12827 url::Url::parse(to_insert).ok(),
12828 )
12829 } else {
12830 (range, Cow::Borrowed(to_insert))
12831 }
12832 };
12833
12834 edits.push((range, to_insert));
12835 original_indent_columns.push(original_indent_column);
12836 }
12837 drop(snapshot);
12838
12839 buffer.edit(
12840 edits,
12841 if auto_indent_on_paste {
12842 Some(AutoindentMode::Block {
12843 original_indent_columns,
12844 })
12845 } else {
12846 None
12847 },
12848 cx,
12849 );
12850 });
12851
12852 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12853 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12854 } else {
12855 let url = url::Url::parse(&clipboard_text).ok();
12856
12857 let auto_indent_mode = if !clipboard_text.is_empty() {
12858 Some(AutoindentMode::Block {
12859 original_indent_columns: Vec::new(),
12860 })
12861 } else {
12862 None
12863 };
12864
12865 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12866 let snapshot = buffer.snapshot(cx);
12867
12868 let anchors = old_selections
12869 .iter()
12870 .map(|s| {
12871 let anchor = snapshot.anchor_after(s.head());
12872 s.map(|_| anchor)
12873 })
12874 .collect::<Vec<_>>();
12875
12876 let mut edits = Vec::new();
12877
12878 for selection in old_selections.iter() {
12879 let language = snapshot.language_at(selection.head());
12880 let range = selection.range();
12881
12882 let (edit_range, edit_text) = if let Some(language) = language
12883 && language.name() == "Markdown".into()
12884 {
12885 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12886 } else {
12887 (range, clipboard_text.clone())
12888 };
12889
12890 edits.push((edit_range, edit_text));
12891 }
12892
12893 drop(snapshot);
12894 buffer.edit(edits, auto_indent_mode, cx);
12895
12896 anchors
12897 });
12898
12899 this.change_selections(Default::default(), window, cx, |s| {
12900 s.select_anchors(selection_anchors);
12901 });
12902 }
12903
12904 // 🤔 | .. | show_in_menu |
12905 // | .. | true true
12906 // | had_edit_prediction | false true
12907
12908 let trigger_in_words =
12909 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12910
12911 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12912 });
12913 }
12914
12915 pub fn diff_clipboard_with_selection(
12916 &mut self,
12917 _: &DiffClipboardWithSelection,
12918 window: &mut Window,
12919 cx: &mut Context<Self>,
12920 ) {
12921 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12922
12923 if selections.is_empty() {
12924 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12925 return;
12926 };
12927
12928 let clipboard_text = match cx.read_from_clipboard() {
12929 Some(item) => match item.entries().first() {
12930 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12931 _ => None,
12932 },
12933 None => None,
12934 };
12935
12936 let Some(clipboard_text) = clipboard_text else {
12937 log::warn!("Clipboard doesn't contain text.");
12938 return;
12939 };
12940
12941 window.dispatch_action(
12942 Box::new(DiffClipboardWithSelectionData {
12943 clipboard_text,
12944 editor: cx.entity(),
12945 }),
12946 cx,
12947 );
12948 }
12949
12950 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12952 if let Some(item) = cx.read_from_clipboard() {
12953 let entries = item.entries();
12954
12955 match entries.first() {
12956 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12957 // of all the pasted entries.
12958 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12959 .do_paste(
12960 clipboard_string.text(),
12961 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12962 true,
12963 window,
12964 cx,
12965 ),
12966 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12967 }
12968 }
12969 }
12970
12971 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12972 if self.read_only(cx) {
12973 return;
12974 }
12975
12976 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12977
12978 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12979 if let Some((selections, _)) =
12980 self.selection_history.transaction(transaction_id).cloned()
12981 {
12982 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12983 s.select_anchors(selections.to_vec());
12984 });
12985 } else {
12986 log::error!(
12987 "No entry in selection_history found for undo. \
12988 This may correspond to a bug where undo does not update the selection. \
12989 If this is occurring, please add details to \
12990 https://github.com/zed-industries/zed/issues/22692"
12991 );
12992 }
12993 self.request_autoscroll(Autoscroll::fit(), cx);
12994 self.unmark_text(window, cx);
12995 self.refresh_edit_prediction(true, false, window, cx);
12996 cx.emit(EditorEvent::Edited { transaction_id });
12997 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12998 }
12999 }
13000
13001 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13002 if self.read_only(cx) {
13003 return;
13004 }
13005
13006 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13007
13008 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13009 if let Some((_, Some(selections))) =
13010 self.selection_history.transaction(transaction_id).cloned()
13011 {
13012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13013 s.select_anchors(selections.to_vec());
13014 });
13015 } else {
13016 log::error!(
13017 "No entry in selection_history found for redo. \
13018 This may correspond to a bug where undo does not update the selection. \
13019 If this is occurring, please add details to \
13020 https://github.com/zed-industries/zed/issues/22692"
13021 );
13022 }
13023 self.request_autoscroll(Autoscroll::fit(), cx);
13024 self.unmark_text(window, cx);
13025 self.refresh_edit_prediction(true, false, window, cx);
13026 cx.emit(EditorEvent::Edited { transaction_id });
13027 }
13028 }
13029
13030 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13031 self.buffer
13032 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13033 }
13034
13035 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13036 self.buffer
13037 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13038 }
13039
13040 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13042 self.change_selections(Default::default(), window, cx, |s| {
13043 s.move_with(|map, selection| {
13044 let cursor = if selection.is_empty() {
13045 movement::left(map, selection.start)
13046 } else {
13047 selection.start
13048 };
13049 selection.collapse_to(cursor, SelectionGoal::None);
13050 });
13051 })
13052 }
13053
13054 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13056 self.change_selections(Default::default(), window, cx, |s| {
13057 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13058 })
13059 }
13060
13061 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13062 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13063 self.change_selections(Default::default(), window, cx, |s| {
13064 s.move_with(|map, selection| {
13065 let cursor = if selection.is_empty() {
13066 movement::right(map, selection.end)
13067 } else {
13068 selection.end
13069 };
13070 selection.collapse_to(cursor, SelectionGoal::None)
13071 });
13072 })
13073 }
13074
13075 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13077 self.change_selections(Default::default(), window, cx, |s| {
13078 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13079 });
13080 }
13081
13082 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13083 if self.take_rename(true, window, cx).is_some() {
13084 return;
13085 }
13086
13087 if self.mode.is_single_line() {
13088 cx.propagate();
13089 return;
13090 }
13091
13092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13093
13094 let text_layout_details = &self.text_layout_details(window);
13095 let selection_count = self.selections.count();
13096 let first_selection = self.selections.first_anchor();
13097
13098 self.change_selections(Default::default(), window, cx, |s| {
13099 s.move_with(|map, selection| {
13100 if !selection.is_empty() {
13101 selection.goal = SelectionGoal::None;
13102 }
13103 let (cursor, goal) = movement::up(
13104 map,
13105 selection.start,
13106 selection.goal,
13107 false,
13108 text_layout_details,
13109 );
13110 selection.collapse_to(cursor, goal);
13111 });
13112 });
13113
13114 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13115 {
13116 cx.propagate();
13117 }
13118 }
13119
13120 pub fn move_up_by_lines(
13121 &mut self,
13122 action: &MoveUpByLines,
13123 window: &mut Window,
13124 cx: &mut Context<Self>,
13125 ) {
13126 if self.take_rename(true, window, cx).is_some() {
13127 return;
13128 }
13129
13130 if self.mode.is_single_line() {
13131 cx.propagate();
13132 return;
13133 }
13134
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13136
13137 let text_layout_details = &self.text_layout_details(window);
13138
13139 self.change_selections(Default::default(), window, cx, |s| {
13140 s.move_with(|map, selection| {
13141 if !selection.is_empty() {
13142 selection.goal = SelectionGoal::None;
13143 }
13144 let (cursor, goal) = movement::up_by_rows(
13145 map,
13146 selection.start,
13147 action.lines,
13148 selection.goal,
13149 false,
13150 text_layout_details,
13151 );
13152 selection.collapse_to(cursor, goal);
13153 });
13154 })
13155 }
13156
13157 pub fn move_down_by_lines(
13158 &mut self,
13159 action: &MoveDownByLines,
13160 window: &mut Window,
13161 cx: &mut Context<Self>,
13162 ) {
13163 if self.take_rename(true, window, cx).is_some() {
13164 return;
13165 }
13166
13167 if self.mode.is_single_line() {
13168 cx.propagate();
13169 return;
13170 }
13171
13172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13173
13174 let text_layout_details = &self.text_layout_details(window);
13175
13176 self.change_selections(Default::default(), window, cx, |s| {
13177 s.move_with(|map, selection| {
13178 if !selection.is_empty() {
13179 selection.goal = SelectionGoal::None;
13180 }
13181 let (cursor, goal) = movement::down_by_rows(
13182 map,
13183 selection.start,
13184 action.lines,
13185 selection.goal,
13186 false,
13187 text_layout_details,
13188 );
13189 selection.collapse_to(cursor, goal);
13190 });
13191 })
13192 }
13193
13194 pub fn select_down_by_lines(
13195 &mut self,
13196 action: &SelectDownByLines,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13201 let text_layout_details = &self.text_layout_details(window);
13202 self.change_selections(Default::default(), window, cx, |s| {
13203 s.move_heads_with(|map, head, goal| {
13204 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13205 })
13206 })
13207 }
13208
13209 pub fn select_up_by_lines(
13210 &mut self,
13211 action: &SelectUpByLines,
13212 window: &mut Window,
13213 cx: &mut Context<Self>,
13214 ) {
13215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13216 let text_layout_details = &self.text_layout_details(window);
13217 self.change_selections(Default::default(), window, cx, |s| {
13218 s.move_heads_with(|map, head, goal| {
13219 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13220 })
13221 })
13222 }
13223
13224 pub fn select_page_up(
13225 &mut self,
13226 _: &SelectPageUp,
13227 window: &mut Window,
13228 cx: &mut Context<Self>,
13229 ) {
13230 let Some(row_count) = self.visible_row_count() else {
13231 return;
13232 };
13233
13234 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13235
13236 let text_layout_details = &self.text_layout_details(window);
13237
13238 self.change_selections(Default::default(), window, cx, |s| {
13239 s.move_heads_with(|map, head, goal| {
13240 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13241 })
13242 })
13243 }
13244
13245 pub fn move_page_up(
13246 &mut self,
13247 action: &MovePageUp,
13248 window: &mut Window,
13249 cx: &mut Context<Self>,
13250 ) {
13251 if self.take_rename(true, window, cx).is_some() {
13252 return;
13253 }
13254
13255 if self
13256 .context_menu
13257 .borrow_mut()
13258 .as_mut()
13259 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13260 .unwrap_or(false)
13261 {
13262 return;
13263 }
13264
13265 if matches!(self.mode, EditorMode::SingleLine) {
13266 cx.propagate();
13267 return;
13268 }
13269
13270 let Some(row_count) = self.visible_row_count() else {
13271 return;
13272 };
13273
13274 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13275
13276 let effects = if action.center_cursor {
13277 SelectionEffects::scroll(Autoscroll::center())
13278 } else {
13279 SelectionEffects::default()
13280 };
13281
13282 let text_layout_details = &self.text_layout_details(window);
13283
13284 self.change_selections(effects, window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 if !selection.is_empty() {
13287 selection.goal = SelectionGoal::None;
13288 }
13289 let (cursor, goal) = movement::up_by_rows(
13290 map,
13291 selection.end,
13292 row_count,
13293 selection.goal,
13294 false,
13295 text_layout_details,
13296 );
13297 selection.collapse_to(cursor, goal);
13298 });
13299 });
13300 }
13301
13302 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13304 let text_layout_details = &self.text_layout_details(window);
13305 self.change_selections(Default::default(), window, cx, |s| {
13306 s.move_heads_with(|map, head, goal| {
13307 movement::up(map, head, goal, false, text_layout_details)
13308 })
13309 })
13310 }
13311
13312 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13313 self.take_rename(true, window, cx);
13314
13315 if self.mode.is_single_line() {
13316 cx.propagate();
13317 return;
13318 }
13319
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13321
13322 let text_layout_details = &self.text_layout_details(window);
13323 let selection_count = self.selections.count();
13324 let first_selection = self.selections.first_anchor();
13325
13326 self.change_selections(Default::default(), window, cx, |s| {
13327 s.move_with(|map, selection| {
13328 if !selection.is_empty() {
13329 selection.goal = SelectionGoal::None;
13330 }
13331 let (cursor, goal) = movement::down(
13332 map,
13333 selection.end,
13334 selection.goal,
13335 false,
13336 text_layout_details,
13337 );
13338 selection.collapse_to(cursor, goal);
13339 });
13340 });
13341
13342 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13343 {
13344 cx.propagate();
13345 }
13346 }
13347
13348 pub fn select_page_down(
13349 &mut self,
13350 _: &SelectPageDown,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 let Some(row_count) = self.visible_row_count() else {
13355 return;
13356 };
13357
13358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13359
13360 let text_layout_details = &self.text_layout_details(window);
13361
13362 self.change_selections(Default::default(), window, cx, |s| {
13363 s.move_heads_with(|map, head, goal| {
13364 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13365 })
13366 })
13367 }
13368
13369 pub fn move_page_down(
13370 &mut self,
13371 action: &MovePageDown,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if self.take_rename(true, window, cx).is_some() {
13376 return;
13377 }
13378
13379 if self
13380 .context_menu
13381 .borrow_mut()
13382 .as_mut()
13383 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13384 .unwrap_or(false)
13385 {
13386 return;
13387 }
13388
13389 if matches!(self.mode, EditorMode::SingleLine) {
13390 cx.propagate();
13391 return;
13392 }
13393
13394 let Some(row_count) = self.visible_row_count() else {
13395 return;
13396 };
13397
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399
13400 let effects = if action.center_cursor {
13401 SelectionEffects::scroll(Autoscroll::center())
13402 } else {
13403 SelectionEffects::default()
13404 };
13405
13406 let text_layout_details = &self.text_layout_details(window);
13407 self.change_selections(effects, window, cx, |s| {
13408 s.move_with(|map, selection| {
13409 if !selection.is_empty() {
13410 selection.goal = SelectionGoal::None;
13411 }
13412 let (cursor, goal) = movement::down_by_rows(
13413 map,
13414 selection.end,
13415 row_count,
13416 selection.goal,
13417 false,
13418 text_layout_details,
13419 );
13420 selection.collapse_to(cursor, goal);
13421 });
13422 });
13423 }
13424
13425 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 let text_layout_details = &self.text_layout_details(window);
13428 self.change_selections(Default::default(), window, cx, |s| {
13429 s.move_heads_with(|map, head, goal| {
13430 movement::down(map, head, goal, false, text_layout_details)
13431 })
13432 });
13433 }
13434
13435 pub fn context_menu_first(
13436 &mut self,
13437 _: &ContextMenuFirst,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13442 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13443 }
13444 }
13445
13446 pub fn context_menu_prev(
13447 &mut self,
13448 _: &ContextMenuPrevious,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13453 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13454 }
13455 }
13456
13457 pub fn context_menu_next(
13458 &mut self,
13459 _: &ContextMenuNext,
13460 window: &mut Window,
13461 cx: &mut Context<Self>,
13462 ) {
13463 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13464 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13465 }
13466 }
13467
13468 pub fn context_menu_last(
13469 &mut self,
13470 _: &ContextMenuLast,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13475 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13476 }
13477 }
13478
13479 pub fn signature_help_prev(
13480 &mut self,
13481 _: &SignatureHelpPrevious,
13482 _: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 if let Some(popover) = self.signature_help_state.popover_mut() {
13486 if popover.current_signature == 0 {
13487 popover.current_signature = popover.signatures.len() - 1;
13488 } else {
13489 popover.current_signature -= 1;
13490 }
13491 cx.notify();
13492 }
13493 }
13494
13495 pub fn signature_help_next(
13496 &mut self,
13497 _: &SignatureHelpNext,
13498 _: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 if let Some(popover) = self.signature_help_state.popover_mut() {
13502 if popover.current_signature + 1 == popover.signatures.len() {
13503 popover.current_signature = 0;
13504 } else {
13505 popover.current_signature += 1;
13506 }
13507 cx.notify();
13508 }
13509 }
13510
13511 pub fn move_to_previous_word_start(
13512 &mut self,
13513 _: &MoveToPreviousWordStart,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13518 self.change_selections(Default::default(), window, cx, |s| {
13519 s.move_cursors_with(|map, head, _| {
13520 (
13521 movement::previous_word_start(map, head),
13522 SelectionGoal::None,
13523 )
13524 });
13525 })
13526 }
13527
13528 pub fn move_to_previous_subword_start(
13529 &mut self,
13530 _: &MoveToPreviousSubwordStart,
13531 window: &mut Window,
13532 cx: &mut Context<Self>,
13533 ) {
13534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13535 self.change_selections(Default::default(), window, cx, |s| {
13536 s.move_cursors_with(|map, head, _| {
13537 (
13538 movement::previous_subword_start(map, head),
13539 SelectionGoal::None,
13540 )
13541 });
13542 })
13543 }
13544
13545 pub fn select_to_previous_word_start(
13546 &mut self,
13547 _: &SelectToPreviousWordStart,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, _| {
13554 (
13555 movement::previous_word_start(map, head),
13556 SelectionGoal::None,
13557 )
13558 });
13559 })
13560 }
13561
13562 pub fn select_to_previous_subword_start(
13563 &mut self,
13564 _: &SelectToPreviousSubwordStart,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (
13572 movement::previous_subword_start(map, head),
13573 SelectionGoal::None,
13574 )
13575 });
13576 })
13577 }
13578
13579 pub fn delete_to_previous_word_start(
13580 &mut self,
13581 action: &DeleteToPreviousWordStart,
13582 window: &mut Window,
13583 cx: &mut Context<Self>,
13584 ) {
13585 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13586 self.transact(window, cx, |this, window, cx| {
13587 this.select_autoclose_pair(window, cx);
13588 this.change_selections(Default::default(), window, cx, |s| {
13589 s.move_with(|map, selection| {
13590 if selection.is_empty() {
13591 let mut cursor = if action.ignore_newlines {
13592 movement::previous_word_start(map, selection.head())
13593 } else {
13594 movement::previous_word_start_or_newline(map, selection.head())
13595 };
13596 cursor = movement::adjust_greedy_deletion(
13597 map,
13598 selection.head(),
13599 cursor,
13600 action.ignore_brackets,
13601 );
13602 selection.set_head(cursor, SelectionGoal::None);
13603 }
13604 });
13605 });
13606 this.insert("", window, cx);
13607 });
13608 }
13609
13610 pub fn delete_to_previous_subword_start(
13611 &mut self,
13612 _: &DeleteToPreviousSubwordStart,
13613 window: &mut Window,
13614 cx: &mut Context<Self>,
13615 ) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13617 self.transact(window, cx, |this, window, cx| {
13618 this.select_autoclose_pair(window, cx);
13619 this.change_selections(Default::default(), window, cx, |s| {
13620 s.move_with(|map, selection| {
13621 if selection.is_empty() {
13622 let mut cursor = movement::previous_subword_start(map, selection.head());
13623 cursor =
13624 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13625 selection.set_head(cursor, SelectionGoal::None);
13626 }
13627 });
13628 });
13629 this.insert("", window, cx);
13630 });
13631 }
13632
13633 pub fn move_to_next_word_end(
13634 &mut self,
13635 _: &MoveToNextWordEnd,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13640 self.change_selections(Default::default(), window, cx, |s| {
13641 s.move_cursors_with(|map, head, _| {
13642 (movement::next_word_end(map, head), SelectionGoal::None)
13643 });
13644 })
13645 }
13646
13647 pub fn move_to_next_subword_end(
13648 &mut self,
13649 _: &MoveToNextSubwordEnd,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.move_cursors_with(|map, head, _| {
13656 (movement::next_subword_end(map, head), SelectionGoal::None)
13657 });
13658 })
13659 }
13660
13661 pub fn select_to_next_word_end(
13662 &mut self,
13663 _: &SelectToNextWordEnd,
13664 window: &mut Window,
13665 cx: &mut Context<Self>,
13666 ) {
13667 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13668 self.change_selections(Default::default(), window, cx, |s| {
13669 s.move_heads_with(|map, head, _| {
13670 (movement::next_word_end(map, head), SelectionGoal::None)
13671 });
13672 })
13673 }
13674
13675 pub fn select_to_next_subword_end(
13676 &mut self,
13677 _: &SelectToNextSubwordEnd,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13682 self.change_selections(Default::default(), window, cx, |s| {
13683 s.move_heads_with(|map, head, _| {
13684 (movement::next_subword_end(map, head), SelectionGoal::None)
13685 });
13686 })
13687 }
13688
13689 pub fn delete_to_next_word_end(
13690 &mut self,
13691 action: &DeleteToNextWordEnd,
13692 window: &mut Window,
13693 cx: &mut Context<Self>,
13694 ) {
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13696 self.transact(window, cx, |this, window, cx| {
13697 this.change_selections(Default::default(), window, cx, |s| {
13698 s.move_with(|map, selection| {
13699 if selection.is_empty() {
13700 let mut cursor = if action.ignore_newlines {
13701 movement::next_word_end(map, selection.head())
13702 } else {
13703 movement::next_word_end_or_newline(map, selection.head())
13704 };
13705 cursor = movement::adjust_greedy_deletion(
13706 map,
13707 selection.head(),
13708 cursor,
13709 action.ignore_brackets,
13710 );
13711 selection.set_head(cursor, SelectionGoal::None);
13712 }
13713 });
13714 });
13715 this.insert("", window, cx);
13716 });
13717 }
13718
13719 pub fn delete_to_next_subword_end(
13720 &mut self,
13721 _: &DeleteToNextSubwordEnd,
13722 window: &mut Window,
13723 cx: &mut Context<Self>,
13724 ) {
13725 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13726 self.transact(window, cx, |this, window, cx| {
13727 this.change_selections(Default::default(), window, cx, |s| {
13728 s.move_with(|map, selection| {
13729 if selection.is_empty() {
13730 let mut cursor = movement::next_subword_end(map, selection.head());
13731 cursor =
13732 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13733 selection.set_head(cursor, SelectionGoal::None);
13734 }
13735 });
13736 });
13737 this.insert("", window, cx);
13738 });
13739 }
13740
13741 pub fn move_to_beginning_of_line(
13742 &mut self,
13743 action: &MoveToBeginningOfLine,
13744 window: &mut Window,
13745 cx: &mut Context<Self>,
13746 ) {
13747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13748 self.change_selections(Default::default(), window, cx, |s| {
13749 s.move_cursors_with(|map, head, _| {
13750 (
13751 movement::indented_line_beginning(
13752 map,
13753 head,
13754 action.stop_at_soft_wraps,
13755 action.stop_at_indent,
13756 ),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn select_to_beginning_of_line(
13764 &mut self,
13765 action: &SelectToBeginningOfLine,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_heads_with(|map, head, _| {
13772 (
13773 movement::indented_line_beginning(
13774 map,
13775 head,
13776 action.stop_at_soft_wraps,
13777 action.stop_at_indent,
13778 ),
13779 SelectionGoal::None,
13780 )
13781 });
13782 });
13783 }
13784
13785 pub fn delete_to_beginning_of_line(
13786 &mut self,
13787 action: &DeleteToBeginningOfLine,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13792 self.transact(window, cx, |this, window, cx| {
13793 this.change_selections(Default::default(), window, cx, |s| {
13794 s.move_with(|_, selection| {
13795 selection.reversed = true;
13796 });
13797 });
13798
13799 this.select_to_beginning_of_line(
13800 &SelectToBeginningOfLine {
13801 stop_at_soft_wraps: false,
13802 stop_at_indent: action.stop_at_indent,
13803 },
13804 window,
13805 cx,
13806 );
13807 this.backspace(&Backspace, window, cx);
13808 });
13809 }
13810
13811 pub fn move_to_end_of_line(
13812 &mut self,
13813 action: &MoveToEndOfLine,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_cursors_with(|map, head, _| {
13820 (
13821 movement::line_end(map, head, action.stop_at_soft_wraps),
13822 SelectionGoal::None,
13823 )
13824 });
13825 })
13826 }
13827
13828 pub fn select_to_end_of_line(
13829 &mut self,
13830 action: &SelectToEndOfLine,
13831 window: &mut Window,
13832 cx: &mut Context<Self>,
13833 ) {
13834 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13835 self.change_selections(Default::default(), window, cx, |s| {
13836 s.move_heads_with(|map, head, _| {
13837 (
13838 movement::line_end(map, head, action.stop_at_soft_wraps),
13839 SelectionGoal::None,
13840 )
13841 });
13842 })
13843 }
13844
13845 pub fn delete_to_end_of_line(
13846 &mut self,
13847 _: &DeleteToEndOfLine,
13848 window: &mut Window,
13849 cx: &mut Context<Self>,
13850 ) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13852 self.transact(window, cx, |this, window, cx| {
13853 this.select_to_end_of_line(
13854 &SelectToEndOfLine {
13855 stop_at_soft_wraps: false,
13856 },
13857 window,
13858 cx,
13859 );
13860 this.delete(&Delete, window, cx);
13861 });
13862 }
13863
13864 pub fn cut_to_end_of_line(
13865 &mut self,
13866 action: &CutToEndOfLine,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13871 self.transact(window, cx, |this, window, cx| {
13872 this.select_to_end_of_line(
13873 &SelectToEndOfLine {
13874 stop_at_soft_wraps: false,
13875 },
13876 window,
13877 cx,
13878 );
13879 if !action.stop_at_newlines {
13880 this.change_selections(Default::default(), window, cx, |s| {
13881 s.move_with(|_, sel| {
13882 if sel.is_empty() {
13883 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13884 }
13885 });
13886 });
13887 }
13888 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13889 let item = this.cut_common(false, window, cx);
13890 cx.write_to_clipboard(item);
13891 });
13892 }
13893
13894 pub fn move_to_start_of_paragraph(
13895 &mut self,
13896 _: &MoveToStartOfParagraph,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 if matches!(self.mode, EditorMode::SingleLine) {
13901 cx.propagate();
13902 return;
13903 }
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 self.change_selections(Default::default(), window, cx, |s| {
13906 s.move_with(|map, selection| {
13907 selection.collapse_to(
13908 movement::start_of_paragraph(map, selection.head(), 1),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn move_to_end_of_paragraph(
13916 &mut self,
13917 _: &MoveToEndOfParagraph,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 if matches!(self.mode, EditorMode::SingleLine) {
13922 cx.propagate();
13923 return;
13924 }
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926 self.change_selections(Default::default(), window, cx, |s| {
13927 s.move_with(|map, selection| {
13928 selection.collapse_to(
13929 movement::end_of_paragraph(map, selection.head(), 1),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn select_to_start_of_paragraph(
13937 &mut self,
13938 _: &SelectToStartOfParagraph,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_heads_with(|map, head, _| {
13949 (
13950 movement::start_of_paragraph(map, head, 1),
13951 SelectionGoal::None,
13952 )
13953 });
13954 })
13955 }
13956
13957 pub fn select_to_end_of_paragraph(
13958 &mut self,
13959 _: &SelectToEndOfParagraph,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) {
13963 if matches!(self.mode, EditorMode::SingleLine) {
13964 cx.propagate();
13965 return;
13966 }
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.move_heads_with(|map, head, _| {
13970 (
13971 movement::end_of_paragraph(map, head, 1),
13972 SelectionGoal::None,
13973 )
13974 });
13975 })
13976 }
13977
13978 pub fn move_to_start_of_excerpt(
13979 &mut self,
13980 _: &MoveToStartOfExcerpt,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 if matches!(self.mode, EditorMode::SingleLine) {
13985 cx.propagate();
13986 return;
13987 }
13988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13989 self.change_selections(Default::default(), window, cx, |s| {
13990 s.move_with(|map, selection| {
13991 selection.collapse_to(
13992 movement::start_of_excerpt(
13993 map,
13994 selection.head(),
13995 workspace::searchable::Direction::Prev,
13996 ),
13997 SelectionGoal::None,
13998 )
13999 });
14000 })
14001 }
14002
14003 pub fn move_to_start_of_next_excerpt(
14004 &mut self,
14005 _: &MoveToStartOfNextExcerpt,
14006 window: &mut Window,
14007 cx: &mut Context<Self>,
14008 ) {
14009 if matches!(self.mode, EditorMode::SingleLine) {
14010 cx.propagate();
14011 return;
14012 }
14013
14014 self.change_selections(Default::default(), window, cx, |s| {
14015 s.move_with(|map, selection| {
14016 selection.collapse_to(
14017 movement::start_of_excerpt(
14018 map,
14019 selection.head(),
14020 workspace::searchable::Direction::Next,
14021 ),
14022 SelectionGoal::None,
14023 )
14024 });
14025 })
14026 }
14027
14028 pub fn move_to_end_of_excerpt(
14029 &mut self,
14030 _: &MoveToEndOfExcerpt,
14031 window: &mut Window,
14032 cx: &mut Context<Self>,
14033 ) {
14034 if matches!(self.mode, EditorMode::SingleLine) {
14035 cx.propagate();
14036 return;
14037 }
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.move_with(|map, selection| {
14041 selection.collapse_to(
14042 movement::end_of_excerpt(
14043 map,
14044 selection.head(),
14045 workspace::searchable::Direction::Next,
14046 ),
14047 SelectionGoal::None,
14048 )
14049 });
14050 })
14051 }
14052
14053 pub fn move_to_end_of_previous_excerpt(
14054 &mut self,
14055 _: &MoveToEndOfPreviousExcerpt,
14056 window: &mut Window,
14057 cx: &mut Context<Self>,
14058 ) {
14059 if matches!(self.mode, EditorMode::SingleLine) {
14060 cx.propagate();
14061 return;
14062 }
14063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14064 self.change_selections(Default::default(), window, cx, |s| {
14065 s.move_with(|map, selection| {
14066 selection.collapse_to(
14067 movement::end_of_excerpt(
14068 map,
14069 selection.head(),
14070 workspace::searchable::Direction::Prev,
14071 ),
14072 SelectionGoal::None,
14073 )
14074 });
14075 })
14076 }
14077
14078 pub fn select_to_start_of_excerpt(
14079 &mut self,
14080 _: &SelectToStartOfExcerpt,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) {
14084 if matches!(self.mode, EditorMode::SingleLine) {
14085 cx.propagate();
14086 return;
14087 }
14088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14089 self.change_selections(Default::default(), window, cx, |s| {
14090 s.move_heads_with(|map, head, _| {
14091 (
14092 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14093 SelectionGoal::None,
14094 )
14095 });
14096 })
14097 }
14098
14099 pub fn select_to_start_of_next_excerpt(
14100 &mut self,
14101 _: &SelectToStartOfNextExcerpt,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 if matches!(self.mode, EditorMode::SingleLine) {
14106 cx.propagate();
14107 return;
14108 }
14109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14110 self.change_selections(Default::default(), window, cx, |s| {
14111 s.move_heads_with(|map, head, _| {
14112 (
14113 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14114 SelectionGoal::None,
14115 )
14116 });
14117 })
14118 }
14119
14120 pub fn select_to_end_of_excerpt(
14121 &mut self,
14122 _: &SelectToEndOfExcerpt,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) {
14126 if matches!(self.mode, EditorMode::SingleLine) {
14127 cx.propagate();
14128 return;
14129 }
14130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14131 self.change_selections(Default::default(), window, cx, |s| {
14132 s.move_heads_with(|map, head, _| {
14133 (
14134 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14135 SelectionGoal::None,
14136 )
14137 });
14138 })
14139 }
14140
14141 pub fn select_to_end_of_previous_excerpt(
14142 &mut self,
14143 _: &SelectToEndOfPreviousExcerpt,
14144 window: &mut Window,
14145 cx: &mut Context<Self>,
14146 ) {
14147 if matches!(self.mode, EditorMode::SingleLine) {
14148 cx.propagate();
14149 return;
14150 }
14151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14152 self.change_selections(Default::default(), window, cx, |s| {
14153 s.move_heads_with(|map, head, _| {
14154 (
14155 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14156 SelectionGoal::None,
14157 )
14158 });
14159 })
14160 }
14161
14162 pub fn move_to_beginning(
14163 &mut self,
14164 _: &MoveToBeginning,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 if matches!(self.mode, EditorMode::SingleLine) {
14169 cx.propagate();
14170 return;
14171 }
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.select_ranges(vec![0..0]);
14175 });
14176 }
14177
14178 pub fn select_to_beginning(
14179 &mut self,
14180 _: &SelectToBeginning,
14181 window: &mut Window,
14182 cx: &mut Context<Self>,
14183 ) {
14184 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14185 selection.set_head(Point::zero(), SelectionGoal::None);
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187 self.change_selections(Default::default(), window, cx, |s| {
14188 s.select(vec![selection]);
14189 });
14190 }
14191
14192 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14193 if matches!(self.mode, EditorMode::SingleLine) {
14194 cx.propagate();
14195 return;
14196 }
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14198 let cursor = self.buffer.read(cx).read(cx).len();
14199 self.change_selections(Default::default(), window, cx, |s| {
14200 s.select_ranges(vec![cursor..cursor])
14201 });
14202 }
14203
14204 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14205 self.nav_history = nav_history;
14206 }
14207
14208 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14209 self.nav_history.as_ref()
14210 }
14211
14212 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14213 self.push_to_nav_history(
14214 self.selections.newest_anchor().head(),
14215 None,
14216 false,
14217 true,
14218 cx,
14219 );
14220 }
14221
14222 fn push_to_nav_history(
14223 &mut self,
14224 cursor_anchor: Anchor,
14225 new_position: Option<Point>,
14226 is_deactivate: bool,
14227 always: bool,
14228 cx: &mut Context<Self>,
14229 ) {
14230 if let Some(nav_history) = self.nav_history.as_mut() {
14231 let buffer = self.buffer.read(cx).read(cx);
14232 let cursor_position = cursor_anchor.to_point(&buffer);
14233 let scroll_state = self.scroll_manager.anchor();
14234 let scroll_top_row = scroll_state.top_row(&buffer);
14235 drop(buffer);
14236
14237 if let Some(new_position) = new_position {
14238 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14239 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14240 return;
14241 }
14242 }
14243
14244 nav_history.push(
14245 Some(NavigationData {
14246 cursor_anchor,
14247 cursor_position,
14248 scroll_anchor: scroll_state,
14249 scroll_top_row,
14250 }),
14251 cx,
14252 );
14253 cx.emit(EditorEvent::PushedToNavHistory {
14254 anchor: cursor_anchor,
14255 is_deactivate,
14256 })
14257 }
14258 }
14259
14260 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14262 let buffer = self.buffer.read(cx).snapshot(cx);
14263 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14264 selection.set_head(buffer.len(), SelectionGoal::None);
14265 self.change_selections(Default::default(), window, cx, |s| {
14266 s.select(vec![selection]);
14267 });
14268 }
14269
14270 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14272 let end = self.buffer.read(cx).read(cx).len();
14273 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14274 s.select_ranges(vec![0..end]);
14275 });
14276 }
14277
14278 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14280 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14281 let mut selections = self.selections.all::<Point>(&display_map);
14282 let max_point = display_map.buffer_snapshot().max_point();
14283 for selection in &mut selections {
14284 let rows = selection.spanned_rows(true, &display_map);
14285 selection.start = Point::new(rows.start.0, 0);
14286 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14287 selection.reversed = false;
14288 }
14289 self.change_selections(Default::default(), window, cx, |s| {
14290 s.select(selections);
14291 });
14292 }
14293
14294 pub fn split_selection_into_lines(
14295 &mut self,
14296 action: &SplitSelectionIntoLines,
14297 window: &mut Window,
14298 cx: &mut Context<Self>,
14299 ) {
14300 let selections = self
14301 .selections
14302 .all::<Point>(&self.display_snapshot(cx))
14303 .into_iter()
14304 .map(|selection| selection.start..selection.end)
14305 .collect::<Vec<_>>();
14306 self.unfold_ranges(&selections, true, true, cx);
14307
14308 let mut new_selection_ranges = Vec::new();
14309 {
14310 let buffer = self.buffer.read(cx).read(cx);
14311 for selection in selections {
14312 for row in selection.start.row..selection.end.row {
14313 let line_start = Point::new(row, 0);
14314 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14315
14316 if action.keep_selections {
14317 // Keep the selection range for each line
14318 let selection_start = if row == selection.start.row {
14319 selection.start
14320 } else {
14321 line_start
14322 };
14323 new_selection_ranges.push(selection_start..line_end);
14324 } else {
14325 // Collapse to cursor at end of line
14326 new_selection_ranges.push(line_end..line_end);
14327 }
14328 }
14329
14330 let is_multiline_selection = selection.start.row != selection.end.row;
14331 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14332 // so this action feels more ergonomic when paired with other selection operations
14333 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14334 if !should_skip_last {
14335 if action.keep_selections {
14336 if is_multiline_selection {
14337 let line_start = Point::new(selection.end.row, 0);
14338 new_selection_ranges.push(line_start..selection.end);
14339 } else {
14340 new_selection_ranges.push(selection.start..selection.end);
14341 }
14342 } else {
14343 new_selection_ranges.push(selection.end..selection.end);
14344 }
14345 }
14346 }
14347 }
14348 self.change_selections(Default::default(), window, cx, |s| {
14349 s.select_ranges(new_selection_ranges);
14350 });
14351 }
14352
14353 pub fn add_selection_above(
14354 &mut self,
14355 action: &AddSelectionAbove,
14356 window: &mut Window,
14357 cx: &mut Context<Self>,
14358 ) {
14359 self.add_selection(true, action.skip_soft_wrap, window, cx);
14360 }
14361
14362 pub fn add_selection_below(
14363 &mut self,
14364 action: &AddSelectionBelow,
14365 window: &mut Window,
14366 cx: &mut Context<Self>,
14367 ) {
14368 self.add_selection(false, action.skip_soft_wrap, window, cx);
14369 }
14370
14371 fn add_selection(
14372 &mut self,
14373 above: bool,
14374 skip_soft_wrap: bool,
14375 window: &mut Window,
14376 cx: &mut Context<Self>,
14377 ) {
14378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14379
14380 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14381 let all_selections = self.selections.all::<Point>(&display_map);
14382 let text_layout_details = self.text_layout_details(window);
14383
14384 let (mut columnar_selections, new_selections_to_columnarize) = {
14385 if let Some(state) = self.add_selections_state.as_ref() {
14386 let columnar_selection_ids: HashSet<_> = state
14387 .groups
14388 .iter()
14389 .flat_map(|group| group.stack.iter())
14390 .copied()
14391 .collect();
14392
14393 all_selections
14394 .into_iter()
14395 .partition(|s| columnar_selection_ids.contains(&s.id))
14396 } else {
14397 (Vec::new(), all_selections)
14398 }
14399 };
14400
14401 let mut state = self
14402 .add_selections_state
14403 .take()
14404 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14405
14406 for selection in new_selections_to_columnarize {
14407 let range = selection.display_range(&display_map).sorted();
14408 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14409 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14410 let positions = start_x.min(end_x)..start_x.max(end_x);
14411 let mut stack = Vec::new();
14412 for row in range.start.row().0..=range.end.row().0 {
14413 if let Some(selection) = self.selections.build_columnar_selection(
14414 &display_map,
14415 DisplayRow(row),
14416 &positions,
14417 selection.reversed,
14418 &text_layout_details,
14419 ) {
14420 stack.push(selection.id);
14421 columnar_selections.push(selection);
14422 }
14423 }
14424 if !stack.is_empty() {
14425 if above {
14426 stack.reverse();
14427 }
14428 state.groups.push(AddSelectionsGroup { above, stack });
14429 }
14430 }
14431
14432 let mut final_selections = Vec::new();
14433 let end_row = if above {
14434 DisplayRow(0)
14435 } else {
14436 display_map.max_point().row()
14437 };
14438
14439 let mut last_added_item_per_group = HashMap::default();
14440 for group in state.groups.iter_mut() {
14441 if let Some(last_id) = group.stack.last() {
14442 last_added_item_per_group.insert(*last_id, group);
14443 }
14444 }
14445
14446 for selection in columnar_selections {
14447 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14448 if above == group.above {
14449 let range = selection.display_range(&display_map).sorted();
14450 debug_assert_eq!(range.start.row(), range.end.row());
14451 let mut row = range.start.row();
14452 let positions =
14453 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14454 Pixels::from(start)..Pixels::from(end)
14455 } else {
14456 let start_x =
14457 display_map.x_for_display_point(range.start, &text_layout_details);
14458 let end_x =
14459 display_map.x_for_display_point(range.end, &text_layout_details);
14460 start_x.min(end_x)..start_x.max(end_x)
14461 };
14462
14463 let mut maybe_new_selection = None;
14464 let direction = if above { -1 } else { 1 };
14465
14466 while row != end_row {
14467 if skip_soft_wrap {
14468 row = display_map
14469 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14470 .row();
14471 } else if above {
14472 row.0 -= 1;
14473 } else {
14474 row.0 += 1;
14475 }
14476
14477 if let Some(new_selection) = self.selections.build_columnar_selection(
14478 &display_map,
14479 row,
14480 &positions,
14481 selection.reversed,
14482 &text_layout_details,
14483 ) {
14484 maybe_new_selection = Some(new_selection);
14485 break;
14486 }
14487 }
14488
14489 if let Some(new_selection) = maybe_new_selection {
14490 group.stack.push(new_selection.id);
14491 if above {
14492 final_selections.push(new_selection);
14493 final_selections.push(selection);
14494 } else {
14495 final_selections.push(selection);
14496 final_selections.push(new_selection);
14497 }
14498 } else {
14499 final_selections.push(selection);
14500 }
14501 } else {
14502 group.stack.pop();
14503 }
14504 } else {
14505 final_selections.push(selection);
14506 }
14507 }
14508
14509 self.change_selections(Default::default(), window, cx, |s| {
14510 s.select(final_selections);
14511 });
14512
14513 let final_selection_ids: HashSet<_> = self
14514 .selections
14515 .all::<Point>(&display_map)
14516 .iter()
14517 .map(|s| s.id)
14518 .collect();
14519 state.groups.retain_mut(|group| {
14520 // selections might get merged above so we remove invalid items from stacks
14521 group.stack.retain(|id| final_selection_ids.contains(id));
14522
14523 // single selection in stack can be treated as initial state
14524 group.stack.len() > 1
14525 });
14526
14527 if !state.groups.is_empty() {
14528 self.add_selections_state = Some(state);
14529 }
14530 }
14531
14532 fn select_match_ranges(
14533 &mut self,
14534 range: Range<usize>,
14535 reversed: bool,
14536 replace_newest: bool,
14537 auto_scroll: Option<Autoscroll>,
14538 window: &mut Window,
14539 cx: &mut Context<Editor>,
14540 ) {
14541 self.unfold_ranges(
14542 std::slice::from_ref(&range),
14543 false,
14544 auto_scroll.is_some(),
14545 cx,
14546 );
14547 let effects = if let Some(scroll) = auto_scroll {
14548 SelectionEffects::scroll(scroll)
14549 } else {
14550 SelectionEffects::no_scroll()
14551 };
14552 self.change_selections(effects, window, cx, |s| {
14553 if replace_newest {
14554 s.delete(s.newest_anchor().id);
14555 }
14556 if reversed {
14557 s.insert_range(range.end..range.start);
14558 } else {
14559 s.insert_range(range);
14560 }
14561 });
14562 }
14563
14564 pub fn select_next_match_internal(
14565 &mut self,
14566 display_map: &DisplaySnapshot,
14567 replace_newest: bool,
14568 autoscroll: Option<Autoscroll>,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) -> Result<()> {
14572 let buffer = display_map.buffer_snapshot();
14573 let mut selections = self.selections.all::<usize>(&display_map);
14574 if let Some(mut select_next_state) = self.select_next_state.take() {
14575 let query = &select_next_state.query;
14576 if !select_next_state.done {
14577 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14578 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14579 let mut next_selected_range = None;
14580
14581 let bytes_after_last_selection =
14582 buffer.bytes_in_range(last_selection.end..buffer.len());
14583 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14584 let query_matches = query
14585 .stream_find_iter(bytes_after_last_selection)
14586 .map(|result| (last_selection.end, result))
14587 .chain(
14588 query
14589 .stream_find_iter(bytes_before_first_selection)
14590 .map(|result| (0, result)),
14591 );
14592
14593 for (start_offset, query_match) in query_matches {
14594 let query_match = query_match.unwrap(); // can only fail due to I/O
14595 let offset_range =
14596 start_offset + query_match.start()..start_offset + query_match.end();
14597
14598 if !select_next_state.wordwise
14599 || (!buffer.is_inside_word(offset_range.start, None)
14600 && !buffer.is_inside_word(offset_range.end, None))
14601 {
14602 let idx = selections
14603 .partition_point(|selection| selection.end <= offset_range.start);
14604 let overlaps = selections
14605 .get(idx)
14606 .map_or(false, |selection| selection.start < offset_range.end);
14607
14608 if !overlaps {
14609 next_selected_range = Some(offset_range);
14610 break;
14611 }
14612 }
14613 }
14614
14615 if let Some(next_selected_range) = next_selected_range {
14616 self.select_match_ranges(
14617 next_selected_range,
14618 last_selection.reversed,
14619 replace_newest,
14620 autoscroll,
14621 window,
14622 cx,
14623 );
14624 } else {
14625 select_next_state.done = true;
14626 }
14627 }
14628
14629 self.select_next_state = Some(select_next_state);
14630 } else {
14631 let mut only_carets = true;
14632 let mut same_text_selected = true;
14633 let mut selected_text = None;
14634
14635 let mut selections_iter = selections.iter().peekable();
14636 while let Some(selection) = selections_iter.next() {
14637 if selection.start != selection.end {
14638 only_carets = false;
14639 }
14640
14641 if same_text_selected {
14642 if selected_text.is_none() {
14643 selected_text =
14644 Some(buffer.text_for_range(selection.range()).collect::<String>());
14645 }
14646
14647 if let Some(next_selection) = selections_iter.peek() {
14648 if next_selection.range().len() == selection.range().len() {
14649 let next_selected_text = buffer
14650 .text_for_range(next_selection.range())
14651 .collect::<String>();
14652 if Some(next_selected_text) != selected_text {
14653 same_text_selected = false;
14654 selected_text = None;
14655 }
14656 } else {
14657 same_text_selected = false;
14658 selected_text = None;
14659 }
14660 }
14661 }
14662 }
14663
14664 if only_carets {
14665 for selection in &mut selections {
14666 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14667 selection.start = word_range.start;
14668 selection.end = word_range.end;
14669 selection.goal = SelectionGoal::None;
14670 selection.reversed = false;
14671 self.select_match_ranges(
14672 selection.start..selection.end,
14673 selection.reversed,
14674 replace_newest,
14675 autoscroll,
14676 window,
14677 cx,
14678 );
14679 }
14680
14681 if selections.len() == 1 {
14682 let selection = selections
14683 .last()
14684 .expect("ensured that there's only one selection");
14685 let query = buffer
14686 .text_for_range(selection.start..selection.end)
14687 .collect::<String>();
14688 let is_empty = query.is_empty();
14689 let select_state = SelectNextState {
14690 query: self.build_query(&[query], cx)?,
14691 wordwise: true,
14692 done: is_empty,
14693 };
14694 self.select_next_state = Some(select_state);
14695 } else {
14696 self.select_next_state = None;
14697 }
14698 } else if let Some(selected_text) = selected_text {
14699 self.select_next_state = Some(SelectNextState {
14700 query: self.build_query(&[selected_text], cx)?,
14701 wordwise: false,
14702 done: false,
14703 });
14704 self.select_next_match_internal(
14705 display_map,
14706 replace_newest,
14707 autoscroll,
14708 window,
14709 cx,
14710 )?;
14711 }
14712 }
14713 Ok(())
14714 }
14715
14716 pub fn select_all_matches(
14717 &mut self,
14718 _action: &SelectAllMatches,
14719 window: &mut Window,
14720 cx: &mut Context<Self>,
14721 ) -> Result<()> {
14722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14723
14724 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14725
14726 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14727 let Some(select_next_state) = self.select_next_state.as_mut() else {
14728 return Ok(());
14729 };
14730 if select_next_state.done {
14731 return Ok(());
14732 }
14733
14734 let mut new_selections = Vec::new();
14735
14736 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14737 let buffer = display_map.buffer_snapshot();
14738 let query_matches = select_next_state
14739 .query
14740 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14741
14742 for query_match in query_matches.into_iter() {
14743 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14744 let offset_range = if reversed {
14745 query_match.end()..query_match.start()
14746 } else {
14747 query_match.start()..query_match.end()
14748 };
14749
14750 if !select_next_state.wordwise
14751 || (!buffer.is_inside_word(offset_range.start, None)
14752 && !buffer.is_inside_word(offset_range.end, None))
14753 {
14754 new_selections.push(offset_range.start..offset_range.end);
14755 }
14756 }
14757
14758 select_next_state.done = true;
14759
14760 if new_selections.is_empty() {
14761 log::error!("bug: new_selections is empty in select_all_matches");
14762 return Ok(());
14763 }
14764
14765 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14767 selections.select_ranges(new_selections)
14768 });
14769
14770 Ok(())
14771 }
14772
14773 pub fn select_next(
14774 &mut self,
14775 action: &SelectNext,
14776 window: &mut Window,
14777 cx: &mut Context<Self>,
14778 ) -> Result<()> {
14779 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14781 self.select_next_match_internal(
14782 &display_map,
14783 action.replace_newest,
14784 Some(Autoscroll::newest()),
14785 window,
14786 cx,
14787 )?;
14788 Ok(())
14789 }
14790
14791 pub fn select_previous(
14792 &mut self,
14793 action: &SelectPrevious,
14794 window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) -> Result<()> {
14797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14798 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14799 let buffer = display_map.buffer_snapshot();
14800 let mut selections = self.selections.all::<usize>(&display_map);
14801 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14802 let query = &select_prev_state.query;
14803 if !select_prev_state.done {
14804 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14805 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14806 let mut next_selected_range = None;
14807 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14808 let bytes_before_last_selection =
14809 buffer.reversed_bytes_in_range(0..last_selection.start);
14810 let bytes_after_first_selection =
14811 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14812 let query_matches = query
14813 .stream_find_iter(bytes_before_last_selection)
14814 .map(|result| (last_selection.start, result))
14815 .chain(
14816 query
14817 .stream_find_iter(bytes_after_first_selection)
14818 .map(|result| (buffer.len(), result)),
14819 );
14820 for (end_offset, query_match) in query_matches {
14821 let query_match = query_match.unwrap(); // can only fail due to I/O
14822 let offset_range =
14823 end_offset - query_match.end()..end_offset - query_match.start();
14824
14825 if !select_prev_state.wordwise
14826 || (!buffer.is_inside_word(offset_range.start, None)
14827 && !buffer.is_inside_word(offset_range.end, None))
14828 {
14829 next_selected_range = Some(offset_range);
14830 break;
14831 }
14832 }
14833
14834 if let Some(next_selected_range) = next_selected_range {
14835 self.select_match_ranges(
14836 next_selected_range,
14837 last_selection.reversed,
14838 action.replace_newest,
14839 Some(Autoscroll::newest()),
14840 window,
14841 cx,
14842 );
14843 } else {
14844 select_prev_state.done = true;
14845 }
14846 }
14847
14848 self.select_prev_state = Some(select_prev_state);
14849 } else {
14850 let mut only_carets = true;
14851 let mut same_text_selected = true;
14852 let mut selected_text = None;
14853
14854 let mut selections_iter = selections.iter().peekable();
14855 while let Some(selection) = selections_iter.next() {
14856 if selection.start != selection.end {
14857 only_carets = false;
14858 }
14859
14860 if same_text_selected {
14861 if selected_text.is_none() {
14862 selected_text =
14863 Some(buffer.text_for_range(selection.range()).collect::<String>());
14864 }
14865
14866 if let Some(next_selection) = selections_iter.peek() {
14867 if next_selection.range().len() == selection.range().len() {
14868 let next_selected_text = buffer
14869 .text_for_range(next_selection.range())
14870 .collect::<String>();
14871 if Some(next_selected_text) != selected_text {
14872 same_text_selected = false;
14873 selected_text = None;
14874 }
14875 } else {
14876 same_text_selected = false;
14877 selected_text = None;
14878 }
14879 }
14880 }
14881 }
14882
14883 if only_carets {
14884 for selection in &mut selections {
14885 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14886 selection.start = word_range.start;
14887 selection.end = word_range.end;
14888 selection.goal = SelectionGoal::None;
14889 selection.reversed = false;
14890 self.select_match_ranges(
14891 selection.start..selection.end,
14892 selection.reversed,
14893 action.replace_newest,
14894 Some(Autoscroll::newest()),
14895 window,
14896 cx,
14897 );
14898 }
14899 if selections.len() == 1 {
14900 let selection = selections
14901 .last()
14902 .expect("ensured that there's only one selection");
14903 let query = buffer
14904 .text_for_range(selection.start..selection.end)
14905 .collect::<String>();
14906 let is_empty = query.is_empty();
14907 let select_state = SelectNextState {
14908 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14909 wordwise: true,
14910 done: is_empty,
14911 };
14912 self.select_prev_state = Some(select_state);
14913 } else {
14914 self.select_prev_state = None;
14915 }
14916 } else if let Some(selected_text) = selected_text {
14917 self.select_prev_state = Some(SelectNextState {
14918 query: self
14919 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14920 wordwise: false,
14921 done: false,
14922 });
14923 self.select_previous(action, window, cx)?;
14924 }
14925 }
14926 Ok(())
14927 }
14928
14929 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14930 /// setting the case sensitivity based on the global
14931 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14932 /// editor's settings.
14933 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14934 where
14935 I: IntoIterator<Item = P>,
14936 P: AsRef<[u8]>,
14937 {
14938 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14939 || EditorSettings::get_global(cx).search.case_sensitive,
14940 |value| value,
14941 );
14942
14943 let mut builder = AhoCorasickBuilder::new();
14944 builder.ascii_case_insensitive(!case_sensitive);
14945 builder.build(patterns)
14946 }
14947
14948 pub fn find_next_match(
14949 &mut self,
14950 _: &FindNextMatch,
14951 window: &mut Window,
14952 cx: &mut Context<Self>,
14953 ) -> Result<()> {
14954 let selections = self.selections.disjoint_anchors_arc();
14955 match selections.first() {
14956 Some(first) if selections.len() >= 2 => {
14957 self.change_selections(Default::default(), window, cx, |s| {
14958 s.select_ranges([first.range()]);
14959 });
14960 }
14961 _ => self.select_next(
14962 &SelectNext {
14963 replace_newest: true,
14964 },
14965 window,
14966 cx,
14967 )?,
14968 }
14969 Ok(())
14970 }
14971
14972 pub fn find_previous_match(
14973 &mut self,
14974 _: &FindPreviousMatch,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) -> Result<()> {
14978 let selections = self.selections.disjoint_anchors_arc();
14979 match selections.last() {
14980 Some(last) if selections.len() >= 2 => {
14981 self.change_selections(Default::default(), window, cx, |s| {
14982 s.select_ranges([last.range()]);
14983 });
14984 }
14985 _ => self.select_previous(
14986 &SelectPrevious {
14987 replace_newest: true,
14988 },
14989 window,
14990 cx,
14991 )?,
14992 }
14993 Ok(())
14994 }
14995
14996 pub fn toggle_comments(
14997 &mut self,
14998 action: &ToggleComments,
14999 window: &mut Window,
15000 cx: &mut Context<Self>,
15001 ) {
15002 if self.read_only(cx) {
15003 return;
15004 }
15005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15006 let text_layout_details = &self.text_layout_details(window);
15007 self.transact(window, cx, |this, window, cx| {
15008 let mut selections = this
15009 .selections
15010 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15011 let mut edits = Vec::new();
15012 let mut selection_edit_ranges = Vec::new();
15013 let mut last_toggled_row = None;
15014 let snapshot = this.buffer.read(cx).read(cx);
15015 let empty_str: Arc<str> = Arc::default();
15016 let mut suffixes_inserted = Vec::new();
15017 let ignore_indent = action.ignore_indent;
15018
15019 fn comment_prefix_range(
15020 snapshot: &MultiBufferSnapshot,
15021 row: MultiBufferRow,
15022 comment_prefix: &str,
15023 comment_prefix_whitespace: &str,
15024 ignore_indent: bool,
15025 ) -> Range<Point> {
15026 let indent_size = if ignore_indent {
15027 0
15028 } else {
15029 snapshot.indent_size_for_line(row).len
15030 };
15031
15032 let start = Point::new(row.0, indent_size);
15033
15034 let mut line_bytes = snapshot
15035 .bytes_in_range(start..snapshot.max_point())
15036 .flatten()
15037 .copied();
15038
15039 // If this line currently begins with the line comment prefix, then record
15040 // the range containing the prefix.
15041 if line_bytes
15042 .by_ref()
15043 .take(comment_prefix.len())
15044 .eq(comment_prefix.bytes())
15045 {
15046 // Include any whitespace that matches the comment prefix.
15047 let matching_whitespace_len = line_bytes
15048 .zip(comment_prefix_whitespace.bytes())
15049 .take_while(|(a, b)| a == b)
15050 .count() as u32;
15051 let end = Point::new(
15052 start.row,
15053 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15054 );
15055 start..end
15056 } else {
15057 start..start
15058 }
15059 }
15060
15061 fn comment_suffix_range(
15062 snapshot: &MultiBufferSnapshot,
15063 row: MultiBufferRow,
15064 comment_suffix: &str,
15065 comment_suffix_has_leading_space: bool,
15066 ) -> Range<Point> {
15067 let end = Point::new(row.0, snapshot.line_len(row));
15068 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15069
15070 let mut line_end_bytes = snapshot
15071 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15072 .flatten()
15073 .copied();
15074
15075 let leading_space_len = if suffix_start_column > 0
15076 && line_end_bytes.next() == Some(b' ')
15077 && comment_suffix_has_leading_space
15078 {
15079 1
15080 } else {
15081 0
15082 };
15083
15084 // If this line currently begins with the line comment prefix, then record
15085 // the range containing the prefix.
15086 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15087 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15088 start..end
15089 } else {
15090 end..end
15091 }
15092 }
15093
15094 // TODO: Handle selections that cross excerpts
15095 for selection in &mut selections {
15096 let start_column = snapshot
15097 .indent_size_for_line(MultiBufferRow(selection.start.row))
15098 .len;
15099 let language = if let Some(language) =
15100 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15101 {
15102 language
15103 } else {
15104 continue;
15105 };
15106
15107 selection_edit_ranges.clear();
15108
15109 // If multiple selections contain a given row, avoid processing that
15110 // row more than once.
15111 let mut start_row = MultiBufferRow(selection.start.row);
15112 if last_toggled_row == Some(start_row) {
15113 start_row = start_row.next_row();
15114 }
15115 let end_row =
15116 if selection.end.row > selection.start.row && selection.end.column == 0 {
15117 MultiBufferRow(selection.end.row - 1)
15118 } else {
15119 MultiBufferRow(selection.end.row)
15120 };
15121 last_toggled_row = Some(end_row);
15122
15123 if start_row > end_row {
15124 continue;
15125 }
15126
15127 // If the language has line comments, toggle those.
15128 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15129
15130 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15131 if ignore_indent {
15132 full_comment_prefixes = full_comment_prefixes
15133 .into_iter()
15134 .map(|s| Arc::from(s.trim_end()))
15135 .collect();
15136 }
15137
15138 if !full_comment_prefixes.is_empty() {
15139 let first_prefix = full_comment_prefixes
15140 .first()
15141 .expect("prefixes is non-empty");
15142 let prefix_trimmed_lengths = full_comment_prefixes
15143 .iter()
15144 .map(|p| p.trim_end_matches(' ').len())
15145 .collect::<SmallVec<[usize; 4]>>();
15146
15147 let mut all_selection_lines_are_comments = true;
15148
15149 for row in start_row.0..=end_row.0 {
15150 let row = MultiBufferRow(row);
15151 if start_row < end_row && snapshot.is_line_blank(row) {
15152 continue;
15153 }
15154
15155 let prefix_range = full_comment_prefixes
15156 .iter()
15157 .zip(prefix_trimmed_lengths.iter().copied())
15158 .map(|(prefix, trimmed_prefix_len)| {
15159 comment_prefix_range(
15160 snapshot.deref(),
15161 row,
15162 &prefix[..trimmed_prefix_len],
15163 &prefix[trimmed_prefix_len..],
15164 ignore_indent,
15165 )
15166 })
15167 .max_by_key(|range| range.end.column - range.start.column)
15168 .expect("prefixes is non-empty");
15169
15170 if prefix_range.is_empty() {
15171 all_selection_lines_are_comments = false;
15172 }
15173
15174 selection_edit_ranges.push(prefix_range);
15175 }
15176
15177 if all_selection_lines_are_comments {
15178 edits.extend(
15179 selection_edit_ranges
15180 .iter()
15181 .cloned()
15182 .map(|range| (range, empty_str.clone())),
15183 );
15184 } else {
15185 let min_column = selection_edit_ranges
15186 .iter()
15187 .map(|range| range.start.column)
15188 .min()
15189 .unwrap_or(0);
15190 edits.extend(selection_edit_ranges.iter().map(|range| {
15191 let position = Point::new(range.start.row, min_column);
15192 (position..position, first_prefix.clone())
15193 }));
15194 }
15195 } else if let Some(BlockCommentConfig {
15196 start: full_comment_prefix,
15197 end: comment_suffix,
15198 ..
15199 }) = language.block_comment()
15200 {
15201 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15202 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15203 let prefix_range = comment_prefix_range(
15204 snapshot.deref(),
15205 start_row,
15206 comment_prefix,
15207 comment_prefix_whitespace,
15208 ignore_indent,
15209 );
15210 let suffix_range = comment_suffix_range(
15211 snapshot.deref(),
15212 end_row,
15213 comment_suffix.trim_start_matches(' '),
15214 comment_suffix.starts_with(' '),
15215 );
15216
15217 if prefix_range.is_empty() || suffix_range.is_empty() {
15218 edits.push((
15219 prefix_range.start..prefix_range.start,
15220 full_comment_prefix.clone(),
15221 ));
15222 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15223 suffixes_inserted.push((end_row, comment_suffix.len()));
15224 } else {
15225 edits.push((prefix_range, empty_str.clone()));
15226 edits.push((suffix_range, empty_str.clone()));
15227 }
15228 } else {
15229 continue;
15230 }
15231 }
15232
15233 drop(snapshot);
15234 this.buffer.update(cx, |buffer, cx| {
15235 buffer.edit(edits, None, cx);
15236 });
15237
15238 // Adjust selections so that they end before any comment suffixes that
15239 // were inserted.
15240 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15241 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15242 let snapshot = this.buffer.read(cx).read(cx);
15243 for selection in &mut selections {
15244 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15245 match row.cmp(&MultiBufferRow(selection.end.row)) {
15246 Ordering::Less => {
15247 suffixes_inserted.next();
15248 continue;
15249 }
15250 Ordering::Greater => break,
15251 Ordering::Equal => {
15252 if selection.end.column == snapshot.line_len(row) {
15253 if selection.is_empty() {
15254 selection.start.column -= suffix_len as u32;
15255 }
15256 selection.end.column -= suffix_len as u32;
15257 }
15258 break;
15259 }
15260 }
15261 }
15262 }
15263
15264 drop(snapshot);
15265 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15266
15267 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15268 let selections_on_single_row = selections.windows(2).all(|selections| {
15269 selections[0].start.row == selections[1].start.row
15270 && selections[0].end.row == selections[1].end.row
15271 && selections[0].start.row == selections[0].end.row
15272 });
15273 let selections_selecting = selections
15274 .iter()
15275 .any(|selection| selection.start != selection.end);
15276 let advance_downwards = action.advance_downwards
15277 && selections_on_single_row
15278 && !selections_selecting
15279 && !matches!(this.mode, EditorMode::SingleLine);
15280
15281 if advance_downwards {
15282 let snapshot = this.buffer.read(cx).snapshot(cx);
15283
15284 this.change_selections(Default::default(), window, cx, |s| {
15285 s.move_cursors_with(|display_snapshot, display_point, _| {
15286 let mut point = display_point.to_point(display_snapshot);
15287 point.row += 1;
15288 point = snapshot.clip_point(point, Bias::Left);
15289 let display_point = point.to_display_point(display_snapshot);
15290 let goal = SelectionGoal::HorizontalPosition(
15291 display_snapshot
15292 .x_for_display_point(display_point, text_layout_details)
15293 .into(),
15294 );
15295 (display_point, goal)
15296 })
15297 });
15298 }
15299 });
15300 }
15301
15302 pub fn select_enclosing_symbol(
15303 &mut self,
15304 _: &SelectEnclosingSymbol,
15305 window: &mut Window,
15306 cx: &mut Context<Self>,
15307 ) {
15308 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15309
15310 let buffer = self.buffer.read(cx).snapshot(cx);
15311 let old_selections = self
15312 .selections
15313 .all::<usize>(&self.display_snapshot(cx))
15314 .into_boxed_slice();
15315
15316 fn update_selection(
15317 selection: &Selection<usize>,
15318 buffer_snap: &MultiBufferSnapshot,
15319 ) -> Option<Selection<usize>> {
15320 let cursor = selection.head();
15321 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15322 for symbol in symbols.iter().rev() {
15323 let start = symbol.range.start.to_offset(buffer_snap);
15324 let end = symbol.range.end.to_offset(buffer_snap);
15325 let new_range = start..end;
15326 if start < selection.start || end > selection.end {
15327 return Some(Selection {
15328 id: selection.id,
15329 start: new_range.start,
15330 end: new_range.end,
15331 goal: SelectionGoal::None,
15332 reversed: selection.reversed,
15333 });
15334 }
15335 }
15336 None
15337 }
15338
15339 let mut selected_larger_symbol = false;
15340 let new_selections = old_selections
15341 .iter()
15342 .map(|selection| match update_selection(selection, &buffer) {
15343 Some(new_selection) => {
15344 if new_selection.range() != selection.range() {
15345 selected_larger_symbol = true;
15346 }
15347 new_selection
15348 }
15349 None => selection.clone(),
15350 })
15351 .collect::<Vec<_>>();
15352
15353 if selected_larger_symbol {
15354 self.change_selections(Default::default(), window, cx, |s| {
15355 s.select(new_selections);
15356 });
15357 }
15358 }
15359
15360 pub fn select_larger_syntax_node(
15361 &mut self,
15362 _: &SelectLargerSyntaxNode,
15363 window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) {
15366 let Some(visible_row_count) = self.visible_row_count() else {
15367 return;
15368 };
15369 let old_selections: Box<[_]> = self
15370 .selections
15371 .all::<usize>(&self.display_snapshot(cx))
15372 .into();
15373 if old_selections.is_empty() {
15374 return;
15375 }
15376
15377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15378
15379 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15380 let buffer = self.buffer.read(cx).snapshot(cx);
15381
15382 let mut selected_larger_node = false;
15383 let mut new_selections = old_selections
15384 .iter()
15385 .map(|selection| {
15386 let old_range = selection.start..selection.end;
15387
15388 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15389 // manually select word at selection
15390 if ["string_content", "inline"].contains(&node.kind()) {
15391 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15392 // ignore if word is already selected
15393 if !word_range.is_empty() && old_range != word_range {
15394 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15395 // only select word if start and end point belongs to same word
15396 if word_range == last_word_range {
15397 selected_larger_node = true;
15398 return Selection {
15399 id: selection.id,
15400 start: word_range.start,
15401 end: word_range.end,
15402 goal: SelectionGoal::None,
15403 reversed: selection.reversed,
15404 };
15405 }
15406 }
15407 }
15408 }
15409
15410 let mut new_range = old_range.clone();
15411 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15412 new_range = range;
15413 if !node.is_named() {
15414 continue;
15415 }
15416 if !display_map.intersects_fold(new_range.start)
15417 && !display_map.intersects_fold(new_range.end)
15418 {
15419 break;
15420 }
15421 }
15422
15423 selected_larger_node |= new_range != old_range;
15424 Selection {
15425 id: selection.id,
15426 start: new_range.start,
15427 end: new_range.end,
15428 goal: SelectionGoal::None,
15429 reversed: selection.reversed,
15430 }
15431 })
15432 .collect::<Vec<_>>();
15433
15434 if !selected_larger_node {
15435 return; // don't put this call in the history
15436 }
15437
15438 // scroll based on transformation done to the last selection created by the user
15439 let (last_old, last_new) = old_selections
15440 .last()
15441 .zip(new_selections.last().cloned())
15442 .expect("old_selections isn't empty");
15443
15444 // revert selection
15445 let is_selection_reversed = {
15446 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15447 new_selections.last_mut().expect("checked above").reversed =
15448 should_newest_selection_be_reversed;
15449 should_newest_selection_be_reversed
15450 };
15451
15452 if selected_larger_node {
15453 self.select_syntax_node_history.disable_clearing = true;
15454 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15455 s.select(new_selections.clone());
15456 });
15457 self.select_syntax_node_history.disable_clearing = false;
15458 }
15459
15460 let start_row = last_new.start.to_display_point(&display_map).row().0;
15461 let end_row = last_new.end.to_display_point(&display_map).row().0;
15462 let selection_height = end_row - start_row + 1;
15463 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15464
15465 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15466 let scroll_behavior = if fits_on_the_screen {
15467 self.request_autoscroll(Autoscroll::fit(), cx);
15468 SelectSyntaxNodeScrollBehavior::FitSelection
15469 } else if is_selection_reversed {
15470 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15471 SelectSyntaxNodeScrollBehavior::CursorTop
15472 } else {
15473 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15474 SelectSyntaxNodeScrollBehavior::CursorBottom
15475 };
15476
15477 self.select_syntax_node_history.push((
15478 old_selections,
15479 scroll_behavior,
15480 is_selection_reversed,
15481 ));
15482 }
15483
15484 pub fn select_smaller_syntax_node(
15485 &mut self,
15486 _: &SelectSmallerSyntaxNode,
15487 window: &mut Window,
15488 cx: &mut Context<Self>,
15489 ) {
15490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15491
15492 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15493 self.select_syntax_node_history.pop()
15494 {
15495 if let Some(selection) = selections.last_mut() {
15496 selection.reversed = is_selection_reversed;
15497 }
15498
15499 self.select_syntax_node_history.disable_clearing = true;
15500 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15501 s.select(selections.to_vec());
15502 });
15503 self.select_syntax_node_history.disable_clearing = false;
15504
15505 match scroll_behavior {
15506 SelectSyntaxNodeScrollBehavior::CursorTop => {
15507 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15508 }
15509 SelectSyntaxNodeScrollBehavior::FitSelection => {
15510 self.request_autoscroll(Autoscroll::fit(), cx);
15511 }
15512 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15513 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15514 }
15515 }
15516 }
15517 }
15518
15519 pub fn unwrap_syntax_node(
15520 &mut self,
15521 _: &UnwrapSyntaxNode,
15522 window: &mut Window,
15523 cx: &mut Context<Self>,
15524 ) {
15525 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15526
15527 let buffer = self.buffer.read(cx).snapshot(cx);
15528 let selections = self
15529 .selections
15530 .all::<usize>(&self.display_snapshot(cx))
15531 .into_iter()
15532 // subtracting the offset requires sorting
15533 .sorted_by_key(|i| i.start);
15534
15535 let full_edits = selections
15536 .into_iter()
15537 .filter_map(|selection| {
15538 let child = if selection.is_empty()
15539 && let Some((_, ancestor_range)) =
15540 buffer.syntax_ancestor(selection.start..selection.end)
15541 {
15542 ancestor_range
15543 } else {
15544 selection.range()
15545 };
15546
15547 let mut parent = child.clone();
15548 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15549 parent = ancestor_range;
15550 if parent.start < child.start || parent.end > child.end {
15551 break;
15552 }
15553 }
15554
15555 if parent == child {
15556 return None;
15557 }
15558 let text = buffer.text_for_range(child).collect::<String>();
15559 Some((selection.id, parent, text))
15560 })
15561 .collect::<Vec<_>>();
15562 if full_edits.is_empty() {
15563 return;
15564 }
15565
15566 self.transact(window, cx, |this, window, cx| {
15567 this.buffer.update(cx, |buffer, cx| {
15568 buffer.edit(
15569 full_edits
15570 .iter()
15571 .map(|(_, p, t)| (p.clone(), t.clone()))
15572 .collect::<Vec<_>>(),
15573 None,
15574 cx,
15575 );
15576 });
15577 this.change_selections(Default::default(), window, cx, |s| {
15578 let mut offset = 0;
15579 let mut selections = vec![];
15580 for (id, parent, text) in full_edits {
15581 let start = parent.start - offset;
15582 offset += parent.len() - text.len();
15583 selections.push(Selection {
15584 id,
15585 start,
15586 end: start + text.len(),
15587 reversed: false,
15588 goal: Default::default(),
15589 });
15590 }
15591 s.select(selections);
15592 });
15593 });
15594 }
15595
15596 pub fn select_next_syntax_node(
15597 &mut self,
15598 _: &SelectNextSyntaxNode,
15599 window: &mut Window,
15600 cx: &mut Context<Self>,
15601 ) {
15602 let old_selections: Box<[_]> = self
15603 .selections
15604 .all::<usize>(&self.display_snapshot(cx))
15605 .into();
15606 if old_selections.is_empty() {
15607 return;
15608 }
15609
15610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15611
15612 let buffer = self.buffer.read(cx).snapshot(cx);
15613 let mut selected_sibling = false;
15614
15615 let new_selections = old_selections
15616 .iter()
15617 .map(|selection| {
15618 let old_range = selection.start..selection.end;
15619
15620 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15621 let new_range = node.byte_range();
15622 selected_sibling = true;
15623 Selection {
15624 id: selection.id,
15625 start: new_range.start,
15626 end: new_range.end,
15627 goal: SelectionGoal::None,
15628 reversed: selection.reversed,
15629 }
15630 } else {
15631 selection.clone()
15632 }
15633 })
15634 .collect::<Vec<_>>();
15635
15636 if selected_sibling {
15637 self.change_selections(
15638 SelectionEffects::scroll(Autoscroll::fit()),
15639 window,
15640 cx,
15641 |s| {
15642 s.select(new_selections);
15643 },
15644 );
15645 }
15646 }
15647
15648 pub fn select_prev_syntax_node(
15649 &mut self,
15650 _: &SelectPreviousSyntaxNode,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 let old_selections: Box<[_]> = self
15655 .selections
15656 .all::<usize>(&self.display_snapshot(cx))
15657 .into();
15658 if old_selections.is_empty() {
15659 return;
15660 }
15661
15662 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15663
15664 let buffer = self.buffer.read(cx).snapshot(cx);
15665 let mut selected_sibling = false;
15666
15667 let new_selections = old_selections
15668 .iter()
15669 .map(|selection| {
15670 let old_range = selection.start..selection.end;
15671
15672 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15673 let new_range = node.byte_range();
15674 selected_sibling = true;
15675 Selection {
15676 id: selection.id,
15677 start: new_range.start,
15678 end: new_range.end,
15679 goal: SelectionGoal::None,
15680 reversed: selection.reversed,
15681 }
15682 } else {
15683 selection.clone()
15684 }
15685 })
15686 .collect::<Vec<_>>();
15687
15688 if selected_sibling {
15689 self.change_selections(
15690 SelectionEffects::scroll(Autoscroll::fit()),
15691 window,
15692 cx,
15693 |s| {
15694 s.select(new_selections);
15695 },
15696 );
15697 }
15698 }
15699
15700 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15701 if !EditorSettings::get_global(cx).gutter.runnables {
15702 self.clear_tasks();
15703 return Task::ready(());
15704 }
15705 let project = self.project().map(Entity::downgrade);
15706 let task_sources = self.lsp_task_sources(cx);
15707 let multi_buffer = self.buffer.downgrade();
15708 cx.spawn_in(window, async move |editor, cx| {
15709 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15710 let Some(project) = project.and_then(|p| p.upgrade()) else {
15711 return;
15712 };
15713 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15714 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15715 }) else {
15716 return;
15717 };
15718
15719 let hide_runnables = project
15720 .update(cx, |project, _| project.is_via_collab())
15721 .unwrap_or(true);
15722 if hide_runnables {
15723 return;
15724 }
15725 let new_rows =
15726 cx.background_spawn({
15727 let snapshot = display_snapshot.clone();
15728 async move {
15729 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15730 }
15731 })
15732 .await;
15733 let Ok(lsp_tasks) =
15734 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15735 else {
15736 return;
15737 };
15738 let lsp_tasks = lsp_tasks.await;
15739
15740 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15741 lsp_tasks
15742 .into_iter()
15743 .flat_map(|(kind, tasks)| {
15744 tasks.into_iter().filter_map(move |(location, task)| {
15745 Some((kind.clone(), location?, task))
15746 })
15747 })
15748 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15749 let buffer = location.target.buffer;
15750 let buffer_snapshot = buffer.read(cx).snapshot();
15751 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15752 |(excerpt_id, snapshot, _)| {
15753 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15754 display_snapshot
15755 .buffer_snapshot()
15756 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15757 } else {
15758 None
15759 }
15760 },
15761 );
15762 if let Some(offset) = offset {
15763 let task_buffer_range =
15764 location.target.range.to_point(&buffer_snapshot);
15765 let context_buffer_range =
15766 task_buffer_range.to_offset(&buffer_snapshot);
15767 let context_range = BufferOffset(context_buffer_range.start)
15768 ..BufferOffset(context_buffer_range.end);
15769
15770 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15771 .or_insert_with(|| RunnableTasks {
15772 templates: Vec::new(),
15773 offset,
15774 column: task_buffer_range.start.column,
15775 extra_variables: HashMap::default(),
15776 context_range,
15777 })
15778 .templates
15779 .push((kind, task.original_task().clone()));
15780 }
15781
15782 acc
15783 })
15784 }) else {
15785 return;
15786 };
15787
15788 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15789 buffer.language_settings(cx).tasks.prefer_lsp
15790 }) else {
15791 return;
15792 };
15793
15794 let rows = Self::runnable_rows(
15795 project,
15796 display_snapshot,
15797 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15798 new_rows,
15799 cx.clone(),
15800 )
15801 .await;
15802 editor
15803 .update(cx, |editor, _| {
15804 editor.clear_tasks();
15805 for (key, mut value) in rows {
15806 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15807 value.templates.extend(lsp_tasks.templates);
15808 }
15809
15810 editor.insert_tasks(key, value);
15811 }
15812 for (key, value) in lsp_tasks_by_rows {
15813 editor.insert_tasks(key, value);
15814 }
15815 })
15816 .ok();
15817 })
15818 }
15819 fn fetch_runnable_ranges(
15820 snapshot: &DisplaySnapshot,
15821 range: Range<Anchor>,
15822 ) -> Vec<language::RunnableRange> {
15823 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15824 }
15825
15826 fn runnable_rows(
15827 project: Entity<Project>,
15828 snapshot: DisplaySnapshot,
15829 prefer_lsp: bool,
15830 runnable_ranges: Vec<RunnableRange>,
15831 cx: AsyncWindowContext,
15832 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15833 cx.spawn(async move |cx| {
15834 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15835 for mut runnable in runnable_ranges {
15836 let Some(tasks) = cx
15837 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15838 .ok()
15839 else {
15840 continue;
15841 };
15842 let mut tasks = tasks.await;
15843
15844 if prefer_lsp {
15845 tasks.retain(|(task_kind, _)| {
15846 !matches!(task_kind, TaskSourceKind::Language { .. })
15847 });
15848 }
15849 if tasks.is_empty() {
15850 continue;
15851 }
15852
15853 let point = runnable
15854 .run_range
15855 .start
15856 .to_point(&snapshot.buffer_snapshot());
15857 let Some(row) = snapshot
15858 .buffer_snapshot()
15859 .buffer_line_for_row(MultiBufferRow(point.row))
15860 .map(|(_, range)| range.start.row)
15861 else {
15862 continue;
15863 };
15864
15865 let context_range =
15866 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15867 runnable_rows.push((
15868 (runnable.buffer_id, row),
15869 RunnableTasks {
15870 templates: tasks,
15871 offset: snapshot
15872 .buffer_snapshot()
15873 .anchor_before(runnable.run_range.start),
15874 context_range,
15875 column: point.column,
15876 extra_variables: runnable.extra_captures,
15877 },
15878 ));
15879 }
15880 runnable_rows
15881 })
15882 }
15883
15884 fn templates_with_tags(
15885 project: &Entity<Project>,
15886 runnable: &mut Runnable,
15887 cx: &mut App,
15888 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15889 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15890 let (worktree_id, file) = project
15891 .buffer_for_id(runnable.buffer, cx)
15892 .and_then(|buffer| buffer.read(cx).file())
15893 .map(|file| (file.worktree_id(cx), file.clone()))
15894 .unzip();
15895
15896 (
15897 project.task_store().read(cx).task_inventory().cloned(),
15898 worktree_id,
15899 file,
15900 )
15901 });
15902
15903 let tags = mem::take(&mut runnable.tags);
15904 let language = runnable.language.clone();
15905 cx.spawn(async move |cx| {
15906 let mut templates_with_tags = Vec::new();
15907 if let Some(inventory) = inventory {
15908 for RunnableTag(tag) in tags {
15909 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15910 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15911 }) else {
15912 return templates_with_tags;
15913 };
15914 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15915 move |(_, template)| {
15916 template.tags.iter().any(|source_tag| source_tag == &tag)
15917 },
15918 ));
15919 }
15920 }
15921 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15922
15923 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15924 // Strongest source wins; if we have worktree tag binding, prefer that to
15925 // global and language bindings;
15926 // if we have a global binding, prefer that to language binding.
15927 let first_mismatch = templates_with_tags
15928 .iter()
15929 .position(|(tag_source, _)| tag_source != leading_tag_source);
15930 if let Some(index) = first_mismatch {
15931 templates_with_tags.truncate(index);
15932 }
15933 }
15934
15935 templates_with_tags
15936 })
15937 }
15938
15939 pub fn move_to_enclosing_bracket(
15940 &mut self,
15941 _: &MoveToEnclosingBracket,
15942 window: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) {
15945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15946 self.change_selections(Default::default(), window, cx, |s| {
15947 s.move_offsets_with(|snapshot, selection| {
15948 let Some(enclosing_bracket_ranges) =
15949 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15950 else {
15951 return;
15952 };
15953
15954 let mut best_length = usize::MAX;
15955 let mut best_inside = false;
15956 let mut best_in_bracket_range = false;
15957 let mut best_destination = None;
15958 for (open, close) in enclosing_bracket_ranges {
15959 let close = close.to_inclusive();
15960 let length = close.end() - open.start;
15961 let inside = selection.start >= open.end && selection.end <= *close.start();
15962 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15963 || close.contains(&selection.head());
15964
15965 // If best is next to a bracket and current isn't, skip
15966 if !in_bracket_range && best_in_bracket_range {
15967 continue;
15968 }
15969
15970 // Prefer smaller lengths unless best is inside and current isn't
15971 if length > best_length && (best_inside || !inside) {
15972 continue;
15973 }
15974
15975 best_length = length;
15976 best_inside = inside;
15977 best_in_bracket_range = in_bracket_range;
15978 best_destination = Some(
15979 if close.contains(&selection.start) && close.contains(&selection.end) {
15980 if inside { open.end } else { open.start }
15981 } else if inside {
15982 *close.start()
15983 } else {
15984 *close.end()
15985 },
15986 );
15987 }
15988
15989 if let Some(destination) = best_destination {
15990 selection.collapse_to(destination, SelectionGoal::None);
15991 }
15992 })
15993 });
15994 }
15995
15996 pub fn undo_selection(
15997 &mut self,
15998 _: &UndoSelection,
15999 window: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16003 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16004 self.selection_history.mode = SelectionHistoryMode::Undoing;
16005 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16006 this.end_selection(window, cx);
16007 this.change_selections(
16008 SelectionEffects::scroll(Autoscroll::newest()),
16009 window,
16010 cx,
16011 |s| s.select_anchors(entry.selections.to_vec()),
16012 );
16013 });
16014 self.selection_history.mode = SelectionHistoryMode::Normal;
16015
16016 self.select_next_state = entry.select_next_state;
16017 self.select_prev_state = entry.select_prev_state;
16018 self.add_selections_state = entry.add_selections_state;
16019 }
16020 }
16021
16022 pub fn redo_selection(
16023 &mut self,
16024 _: &RedoSelection,
16025 window: &mut Window,
16026 cx: &mut Context<Self>,
16027 ) {
16028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16029 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16030 self.selection_history.mode = SelectionHistoryMode::Redoing;
16031 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16032 this.end_selection(window, cx);
16033 this.change_selections(
16034 SelectionEffects::scroll(Autoscroll::newest()),
16035 window,
16036 cx,
16037 |s| s.select_anchors(entry.selections.to_vec()),
16038 );
16039 });
16040 self.selection_history.mode = SelectionHistoryMode::Normal;
16041
16042 self.select_next_state = entry.select_next_state;
16043 self.select_prev_state = entry.select_prev_state;
16044 self.add_selections_state = entry.add_selections_state;
16045 }
16046 }
16047
16048 pub fn expand_excerpts(
16049 &mut self,
16050 action: &ExpandExcerpts,
16051 _: &mut Window,
16052 cx: &mut Context<Self>,
16053 ) {
16054 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16055 }
16056
16057 pub fn expand_excerpts_down(
16058 &mut self,
16059 action: &ExpandExcerptsDown,
16060 _: &mut Window,
16061 cx: &mut Context<Self>,
16062 ) {
16063 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16064 }
16065
16066 pub fn expand_excerpts_up(
16067 &mut self,
16068 action: &ExpandExcerptsUp,
16069 _: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) {
16072 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16073 }
16074
16075 pub fn expand_excerpts_for_direction(
16076 &mut self,
16077 lines: u32,
16078 direction: ExpandExcerptDirection,
16079
16080 cx: &mut Context<Self>,
16081 ) {
16082 let selections = self.selections.disjoint_anchors_arc();
16083
16084 let lines = if lines == 0 {
16085 EditorSettings::get_global(cx).expand_excerpt_lines
16086 } else {
16087 lines
16088 };
16089
16090 self.buffer.update(cx, |buffer, cx| {
16091 let snapshot = buffer.snapshot(cx);
16092 let mut excerpt_ids = selections
16093 .iter()
16094 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16095 .collect::<Vec<_>>();
16096 excerpt_ids.sort();
16097 excerpt_ids.dedup();
16098 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16099 })
16100 }
16101
16102 pub fn expand_excerpt(
16103 &mut self,
16104 excerpt: ExcerptId,
16105 direction: ExpandExcerptDirection,
16106 window: &mut Window,
16107 cx: &mut Context<Self>,
16108 ) {
16109 let current_scroll_position = self.scroll_position(cx);
16110 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16111 let mut scroll = None;
16112
16113 if direction == ExpandExcerptDirection::Down {
16114 let multi_buffer = self.buffer.read(cx);
16115 let snapshot = multi_buffer.snapshot(cx);
16116 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16117 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16118 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16119 {
16120 let buffer_snapshot = buffer.read(cx).snapshot();
16121 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16122 let last_row = buffer_snapshot.max_point().row;
16123 let lines_below = last_row.saturating_sub(excerpt_end_row);
16124 if lines_below >= lines_to_expand {
16125 scroll = Some(
16126 current_scroll_position
16127 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16128 );
16129 }
16130 }
16131 }
16132 if direction == ExpandExcerptDirection::Up
16133 && self
16134 .buffer
16135 .read(cx)
16136 .snapshot(cx)
16137 .excerpt_before(excerpt)
16138 .is_none()
16139 {
16140 scroll = Some(current_scroll_position);
16141 }
16142
16143 self.buffer.update(cx, |buffer, cx| {
16144 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16145 });
16146
16147 if let Some(new_scroll_position) = scroll {
16148 self.set_scroll_position(new_scroll_position, window, cx);
16149 }
16150 }
16151
16152 pub fn go_to_singleton_buffer_point(
16153 &mut self,
16154 point: Point,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) {
16158 self.go_to_singleton_buffer_range(point..point, window, cx);
16159 }
16160
16161 pub fn go_to_singleton_buffer_range(
16162 &mut self,
16163 range: Range<Point>,
16164 window: &mut Window,
16165 cx: &mut Context<Self>,
16166 ) {
16167 let multibuffer = self.buffer().read(cx);
16168 let Some(buffer) = multibuffer.as_singleton() else {
16169 return;
16170 };
16171 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16172 return;
16173 };
16174 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16175 return;
16176 };
16177 self.change_selections(
16178 SelectionEffects::default().nav_history(true),
16179 window,
16180 cx,
16181 |s| s.select_anchor_ranges([start..end]),
16182 );
16183 }
16184
16185 pub fn go_to_diagnostic(
16186 &mut self,
16187 action: &GoToDiagnostic,
16188 window: &mut Window,
16189 cx: &mut Context<Self>,
16190 ) {
16191 if !self.diagnostics_enabled() {
16192 return;
16193 }
16194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16195 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16196 }
16197
16198 pub fn go_to_prev_diagnostic(
16199 &mut self,
16200 action: &GoToPreviousDiagnostic,
16201 window: &mut Window,
16202 cx: &mut Context<Self>,
16203 ) {
16204 if !self.diagnostics_enabled() {
16205 return;
16206 }
16207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16208 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16209 }
16210
16211 pub fn go_to_diagnostic_impl(
16212 &mut self,
16213 direction: Direction,
16214 severity: GoToDiagnosticSeverityFilter,
16215 window: &mut Window,
16216 cx: &mut Context<Self>,
16217 ) {
16218 let buffer = self.buffer.read(cx).snapshot(cx);
16219 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16220
16221 let mut active_group_id = None;
16222 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16223 && active_group.active_range.start.to_offset(&buffer) == selection.start
16224 {
16225 active_group_id = Some(active_group.group_id);
16226 }
16227
16228 fn filtered<'a>(
16229 severity: GoToDiagnosticSeverityFilter,
16230 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16231 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16232 diagnostics
16233 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16234 .filter(|entry| entry.range.start != entry.range.end)
16235 .filter(|entry| !entry.diagnostic.is_unnecessary)
16236 }
16237
16238 let before = filtered(
16239 severity,
16240 buffer
16241 .diagnostics_in_range(0..selection.start)
16242 .filter(|entry| entry.range.start <= selection.start),
16243 );
16244 let after = filtered(
16245 severity,
16246 buffer
16247 .diagnostics_in_range(selection.start..buffer.len())
16248 .filter(|entry| entry.range.start >= selection.start),
16249 );
16250
16251 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16252 if direction == Direction::Prev {
16253 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16254 {
16255 for diagnostic in prev_diagnostics.into_iter().rev() {
16256 if diagnostic.range.start != selection.start
16257 || active_group_id
16258 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16259 {
16260 found = Some(diagnostic);
16261 break 'outer;
16262 }
16263 }
16264 }
16265 } else {
16266 for diagnostic in after.chain(before) {
16267 if diagnostic.range.start != selection.start
16268 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16269 {
16270 found = Some(diagnostic);
16271 break;
16272 }
16273 }
16274 }
16275 let Some(next_diagnostic) = found else {
16276 return;
16277 };
16278
16279 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16280 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16281 return;
16282 };
16283 let snapshot = self.snapshot(window, cx);
16284 if snapshot.intersects_fold(next_diagnostic.range.start) {
16285 self.unfold_ranges(
16286 std::slice::from_ref(&next_diagnostic.range),
16287 true,
16288 false,
16289 cx,
16290 );
16291 }
16292 self.change_selections(Default::default(), window, cx, |s| {
16293 s.select_ranges(vec![
16294 next_diagnostic.range.start..next_diagnostic.range.start,
16295 ])
16296 });
16297 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16298 self.refresh_edit_prediction(false, true, window, cx);
16299 }
16300
16301 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16303 let snapshot = self.snapshot(window, cx);
16304 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16305 self.go_to_hunk_before_or_after_position(
16306 &snapshot,
16307 selection.head(),
16308 Direction::Next,
16309 window,
16310 cx,
16311 );
16312 }
16313
16314 pub fn go_to_hunk_before_or_after_position(
16315 &mut self,
16316 snapshot: &EditorSnapshot,
16317 position: Point,
16318 direction: Direction,
16319 window: &mut Window,
16320 cx: &mut Context<Editor>,
16321 ) {
16322 let row = if direction == Direction::Next {
16323 self.hunk_after_position(snapshot, position)
16324 .map(|hunk| hunk.row_range.start)
16325 } else {
16326 self.hunk_before_position(snapshot, position)
16327 };
16328
16329 if let Some(row) = row {
16330 let destination = Point::new(row.0, 0);
16331 let autoscroll = Autoscroll::center();
16332
16333 self.unfold_ranges(&[destination..destination], false, false, cx);
16334 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16335 s.select_ranges([destination..destination]);
16336 });
16337 }
16338 }
16339
16340 fn hunk_after_position(
16341 &mut self,
16342 snapshot: &EditorSnapshot,
16343 position: Point,
16344 ) -> Option<MultiBufferDiffHunk> {
16345 snapshot
16346 .buffer_snapshot()
16347 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16348 .find(|hunk| hunk.row_range.start.0 > position.row)
16349 .or_else(|| {
16350 snapshot
16351 .buffer_snapshot()
16352 .diff_hunks_in_range(Point::zero()..position)
16353 .find(|hunk| hunk.row_range.end.0 < position.row)
16354 })
16355 }
16356
16357 fn go_to_prev_hunk(
16358 &mut self,
16359 _: &GoToPreviousHunk,
16360 window: &mut Window,
16361 cx: &mut Context<Self>,
16362 ) {
16363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16364 let snapshot = self.snapshot(window, cx);
16365 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16366 self.go_to_hunk_before_or_after_position(
16367 &snapshot,
16368 selection.head(),
16369 Direction::Prev,
16370 window,
16371 cx,
16372 );
16373 }
16374
16375 fn hunk_before_position(
16376 &mut self,
16377 snapshot: &EditorSnapshot,
16378 position: Point,
16379 ) -> Option<MultiBufferRow> {
16380 snapshot
16381 .buffer_snapshot()
16382 .diff_hunk_before(position)
16383 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16384 }
16385
16386 fn go_to_next_change(
16387 &mut self,
16388 _: &GoToNextChange,
16389 window: &mut Window,
16390 cx: &mut Context<Self>,
16391 ) {
16392 if let Some(selections) = self
16393 .change_list
16394 .next_change(1, Direction::Next)
16395 .map(|s| s.to_vec())
16396 {
16397 self.change_selections(Default::default(), window, cx, |s| {
16398 let map = s.display_snapshot();
16399 s.select_display_ranges(selections.iter().map(|a| {
16400 let point = a.to_display_point(&map);
16401 point..point
16402 }))
16403 })
16404 }
16405 }
16406
16407 fn go_to_previous_change(
16408 &mut self,
16409 _: &GoToPreviousChange,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) {
16413 if let Some(selections) = self
16414 .change_list
16415 .next_change(1, Direction::Prev)
16416 .map(|s| s.to_vec())
16417 {
16418 self.change_selections(Default::default(), window, cx, |s| {
16419 let map = s.display_snapshot();
16420 s.select_display_ranges(selections.iter().map(|a| {
16421 let point = a.to_display_point(&map);
16422 point..point
16423 }))
16424 })
16425 }
16426 }
16427
16428 pub fn go_to_next_document_highlight(
16429 &mut self,
16430 _: &GoToNextDocumentHighlight,
16431 window: &mut Window,
16432 cx: &mut Context<Self>,
16433 ) {
16434 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16435 }
16436
16437 pub fn go_to_prev_document_highlight(
16438 &mut self,
16439 _: &GoToPreviousDocumentHighlight,
16440 window: &mut Window,
16441 cx: &mut Context<Self>,
16442 ) {
16443 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16444 }
16445
16446 pub fn go_to_document_highlight_before_or_after_position(
16447 &mut self,
16448 direction: Direction,
16449 window: &mut Window,
16450 cx: &mut Context<Editor>,
16451 ) {
16452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16453 let snapshot = self.snapshot(window, cx);
16454 let buffer = &snapshot.buffer_snapshot();
16455 let position = self
16456 .selections
16457 .newest::<Point>(&snapshot.display_snapshot)
16458 .head();
16459 let anchor_position = buffer.anchor_after(position);
16460
16461 // Get all document highlights (both read and write)
16462 let mut all_highlights = Vec::new();
16463
16464 if let Some((_, read_highlights)) = self
16465 .background_highlights
16466 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16467 {
16468 all_highlights.extend(read_highlights.iter());
16469 }
16470
16471 if let Some((_, write_highlights)) = self
16472 .background_highlights
16473 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16474 {
16475 all_highlights.extend(write_highlights.iter());
16476 }
16477
16478 if all_highlights.is_empty() {
16479 return;
16480 }
16481
16482 // Sort highlights by position
16483 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16484
16485 let target_highlight = match direction {
16486 Direction::Next => {
16487 // Find the first highlight after the current position
16488 all_highlights
16489 .iter()
16490 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16491 }
16492 Direction::Prev => {
16493 // Find the last highlight before the current position
16494 all_highlights
16495 .iter()
16496 .rev()
16497 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16498 }
16499 };
16500
16501 if let Some(highlight) = target_highlight {
16502 let destination = highlight.start.to_point(buffer);
16503 let autoscroll = Autoscroll::center();
16504
16505 self.unfold_ranges(&[destination..destination], false, false, cx);
16506 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16507 s.select_ranges([destination..destination]);
16508 });
16509 }
16510 }
16511
16512 fn go_to_line<T: 'static>(
16513 &mut self,
16514 position: Anchor,
16515 highlight_color: Option<Hsla>,
16516 window: &mut Window,
16517 cx: &mut Context<Self>,
16518 ) {
16519 let snapshot = self.snapshot(window, cx).display_snapshot;
16520 let position = position.to_point(&snapshot.buffer_snapshot());
16521 let start = snapshot
16522 .buffer_snapshot()
16523 .clip_point(Point::new(position.row, 0), Bias::Left);
16524 let end = start + Point::new(1, 0);
16525 let start = snapshot.buffer_snapshot().anchor_before(start);
16526 let end = snapshot.buffer_snapshot().anchor_before(end);
16527
16528 self.highlight_rows::<T>(
16529 start..end,
16530 highlight_color
16531 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16532 Default::default(),
16533 cx,
16534 );
16535
16536 if self.buffer.read(cx).is_singleton() {
16537 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16538 }
16539 }
16540
16541 pub fn go_to_definition(
16542 &mut self,
16543 _: &GoToDefinition,
16544 window: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) -> Task<Result<Navigated>> {
16547 let definition =
16548 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16549 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16550 cx.spawn_in(window, async move |editor, cx| {
16551 if definition.await? == Navigated::Yes {
16552 return Ok(Navigated::Yes);
16553 }
16554 match fallback_strategy {
16555 GoToDefinitionFallback::None => Ok(Navigated::No),
16556 GoToDefinitionFallback::FindAllReferences => {
16557 match editor.update_in(cx, |editor, window, cx| {
16558 editor.find_all_references(&FindAllReferences, window, cx)
16559 })? {
16560 Some(references) => references.await,
16561 None => Ok(Navigated::No),
16562 }
16563 }
16564 }
16565 })
16566 }
16567
16568 pub fn go_to_declaration(
16569 &mut self,
16570 _: &GoToDeclaration,
16571 window: &mut Window,
16572 cx: &mut Context<Self>,
16573 ) -> Task<Result<Navigated>> {
16574 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16575 }
16576
16577 pub fn go_to_declaration_split(
16578 &mut self,
16579 _: &GoToDeclaration,
16580 window: &mut Window,
16581 cx: &mut Context<Self>,
16582 ) -> Task<Result<Navigated>> {
16583 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16584 }
16585
16586 pub fn go_to_implementation(
16587 &mut self,
16588 _: &GoToImplementation,
16589 window: &mut Window,
16590 cx: &mut Context<Self>,
16591 ) -> Task<Result<Navigated>> {
16592 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16593 }
16594
16595 pub fn go_to_implementation_split(
16596 &mut self,
16597 _: &GoToImplementationSplit,
16598 window: &mut Window,
16599 cx: &mut Context<Self>,
16600 ) -> Task<Result<Navigated>> {
16601 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16602 }
16603
16604 pub fn go_to_type_definition(
16605 &mut self,
16606 _: &GoToTypeDefinition,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) -> Task<Result<Navigated>> {
16610 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16611 }
16612
16613 pub fn go_to_definition_split(
16614 &mut self,
16615 _: &GoToDefinitionSplit,
16616 window: &mut Window,
16617 cx: &mut Context<Self>,
16618 ) -> Task<Result<Navigated>> {
16619 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16620 }
16621
16622 pub fn go_to_type_definition_split(
16623 &mut self,
16624 _: &GoToTypeDefinitionSplit,
16625 window: &mut Window,
16626 cx: &mut Context<Self>,
16627 ) -> Task<Result<Navigated>> {
16628 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16629 }
16630
16631 fn go_to_definition_of_kind(
16632 &mut self,
16633 kind: GotoDefinitionKind,
16634 split: bool,
16635 window: &mut Window,
16636 cx: &mut Context<Self>,
16637 ) -> Task<Result<Navigated>> {
16638 let Some(provider) = self.semantics_provider.clone() else {
16639 return Task::ready(Ok(Navigated::No));
16640 };
16641 let head = self
16642 .selections
16643 .newest::<usize>(&self.display_snapshot(cx))
16644 .head();
16645 let buffer = self.buffer.read(cx);
16646 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16647 return Task::ready(Ok(Navigated::No));
16648 };
16649 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16650 return Task::ready(Ok(Navigated::No));
16651 };
16652
16653 cx.spawn_in(window, async move |editor, cx| {
16654 let Some(definitions) = definitions.await? else {
16655 return Ok(Navigated::No);
16656 };
16657 let navigated = editor
16658 .update_in(cx, |editor, window, cx| {
16659 editor.navigate_to_hover_links(
16660 Some(kind),
16661 definitions
16662 .into_iter()
16663 .filter(|location| {
16664 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16665 })
16666 .map(HoverLink::Text)
16667 .collect::<Vec<_>>(),
16668 split,
16669 window,
16670 cx,
16671 )
16672 })?
16673 .await?;
16674 anyhow::Ok(navigated)
16675 })
16676 }
16677
16678 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16679 let selection = self.selections.newest_anchor();
16680 let head = selection.head();
16681 let tail = selection.tail();
16682
16683 let Some((buffer, start_position)) =
16684 self.buffer.read(cx).text_anchor_for_position(head, cx)
16685 else {
16686 return;
16687 };
16688
16689 let end_position = if head != tail {
16690 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16691 return;
16692 };
16693 Some(pos)
16694 } else {
16695 None
16696 };
16697
16698 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16699 let url = if let Some(end_pos) = end_position {
16700 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16701 } else {
16702 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16703 };
16704
16705 if let Some(url) = url {
16706 cx.update(|window, cx| {
16707 if parse_zed_link(&url, cx).is_some() {
16708 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16709 } else {
16710 cx.open_url(&url);
16711 }
16712 })?;
16713 }
16714
16715 anyhow::Ok(())
16716 });
16717
16718 url_finder.detach();
16719 }
16720
16721 pub fn open_selected_filename(
16722 &mut self,
16723 _: &OpenSelectedFilename,
16724 window: &mut Window,
16725 cx: &mut Context<Self>,
16726 ) {
16727 let Some(workspace) = self.workspace() else {
16728 return;
16729 };
16730
16731 let position = self.selections.newest_anchor().head();
16732
16733 let Some((buffer, buffer_position)) =
16734 self.buffer.read(cx).text_anchor_for_position(position, cx)
16735 else {
16736 return;
16737 };
16738
16739 let project = self.project.clone();
16740
16741 cx.spawn_in(window, async move |_, cx| {
16742 let result = find_file(&buffer, project, buffer_position, cx).await;
16743
16744 if let Some((_, path)) = result {
16745 workspace
16746 .update_in(cx, |workspace, window, cx| {
16747 workspace.open_resolved_path(path, window, cx)
16748 })?
16749 .await?;
16750 }
16751 anyhow::Ok(())
16752 })
16753 .detach();
16754 }
16755
16756 pub(crate) fn navigate_to_hover_links(
16757 &mut self,
16758 kind: Option<GotoDefinitionKind>,
16759 definitions: Vec<HoverLink>,
16760 split: bool,
16761 window: &mut Window,
16762 cx: &mut Context<Editor>,
16763 ) -> Task<Result<Navigated>> {
16764 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16765 let mut first_url_or_file = None;
16766 let definitions: Vec<_> = definitions
16767 .into_iter()
16768 .filter_map(|def| match def {
16769 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16770 HoverLink::InlayHint(lsp_location, server_id) => {
16771 let computation =
16772 self.compute_target_location(lsp_location, server_id, window, cx);
16773 Some(cx.background_spawn(computation))
16774 }
16775 HoverLink::Url(url) => {
16776 first_url_or_file = Some(Either::Left(url));
16777 None
16778 }
16779 HoverLink::File(path) => {
16780 first_url_or_file = Some(Either::Right(path));
16781 None
16782 }
16783 })
16784 .collect();
16785
16786 let workspace = self.workspace();
16787
16788 cx.spawn_in(window, async move |editor, cx| {
16789 let locations: Vec<Location> = future::join_all(definitions)
16790 .await
16791 .into_iter()
16792 .filter_map(|location| location.transpose())
16793 .collect::<Result<_>>()
16794 .context("location tasks")?;
16795 let mut locations = cx.update(|_, cx| {
16796 locations
16797 .into_iter()
16798 .map(|location| {
16799 let buffer = location.buffer.read(cx);
16800 (location.buffer, location.range.to_point(buffer))
16801 })
16802 .into_group_map()
16803 })?;
16804 let mut num_locations = 0;
16805 for ranges in locations.values_mut() {
16806 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16807 ranges.dedup();
16808 num_locations += ranges.len();
16809 }
16810
16811 if num_locations > 1 {
16812 let Some(workspace) = workspace else {
16813 return Ok(Navigated::No);
16814 };
16815
16816 let tab_kind = match kind {
16817 Some(GotoDefinitionKind::Implementation) => "Implementations",
16818 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16819 Some(GotoDefinitionKind::Declaration) => "Declarations",
16820 Some(GotoDefinitionKind::Type) => "Types",
16821 };
16822 let title = editor
16823 .update_in(cx, |_, _, cx| {
16824 let target = locations
16825 .iter()
16826 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16827 .map(|(buffer, location)| {
16828 buffer
16829 .read(cx)
16830 .text_for_range(location.clone())
16831 .collect::<String>()
16832 })
16833 .filter(|text| !text.contains('\n'))
16834 .unique()
16835 .take(3)
16836 .join(", ");
16837 if target.is_empty() {
16838 tab_kind.to_owned()
16839 } else {
16840 format!("{tab_kind} for {target}")
16841 }
16842 })
16843 .context("buffer title")?;
16844
16845 let opened = workspace
16846 .update_in(cx, |workspace, window, cx| {
16847 Self::open_locations_in_multibuffer(
16848 workspace,
16849 locations,
16850 title,
16851 split,
16852 MultibufferSelectionMode::First,
16853 window,
16854 cx,
16855 )
16856 })
16857 .is_ok();
16858
16859 anyhow::Ok(Navigated::from_bool(opened))
16860 } else if num_locations == 0 {
16861 // If there is one url or file, open it directly
16862 match first_url_or_file {
16863 Some(Either::Left(url)) => {
16864 cx.update(|_, cx| cx.open_url(&url))?;
16865 Ok(Navigated::Yes)
16866 }
16867 Some(Either::Right(path)) => {
16868 let Some(workspace) = workspace else {
16869 return Ok(Navigated::No);
16870 };
16871
16872 workspace
16873 .update_in(cx, |workspace, window, cx| {
16874 workspace.open_resolved_path(path, window, cx)
16875 })?
16876 .await?;
16877 Ok(Navigated::Yes)
16878 }
16879 None => Ok(Navigated::No),
16880 }
16881 } else {
16882 let Some(workspace) = workspace else {
16883 return Ok(Navigated::No);
16884 };
16885
16886 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16887 let target_range = target_ranges.first().unwrap().clone();
16888
16889 editor.update_in(cx, |editor, window, cx| {
16890 let range = target_range.to_point(target_buffer.read(cx));
16891 let range = editor.range_for_match(&range, false);
16892 let range = collapse_multiline_range(range);
16893
16894 if !split
16895 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16896 {
16897 editor.go_to_singleton_buffer_range(range, window, cx);
16898 } else {
16899 let pane = workspace.read(cx).active_pane().clone();
16900 window.defer(cx, move |window, cx| {
16901 let target_editor: Entity<Self> =
16902 workspace.update(cx, |workspace, cx| {
16903 let pane = if split {
16904 workspace.adjacent_pane(window, cx)
16905 } else {
16906 workspace.active_pane().clone()
16907 };
16908
16909 workspace.open_project_item(
16910 pane,
16911 target_buffer.clone(),
16912 true,
16913 true,
16914 window,
16915 cx,
16916 )
16917 });
16918 target_editor.update(cx, |target_editor, cx| {
16919 // When selecting a definition in a different buffer, disable the nav history
16920 // to avoid creating a history entry at the previous cursor location.
16921 pane.update(cx, |pane, _| pane.disable_history());
16922 target_editor.go_to_singleton_buffer_range(range, window, cx);
16923 pane.update(cx, |pane, _| pane.enable_history());
16924 });
16925 });
16926 }
16927 Navigated::Yes
16928 })
16929 }
16930 })
16931 }
16932
16933 fn compute_target_location(
16934 &self,
16935 lsp_location: lsp::Location,
16936 server_id: LanguageServerId,
16937 window: &mut Window,
16938 cx: &mut Context<Self>,
16939 ) -> Task<anyhow::Result<Option<Location>>> {
16940 let Some(project) = self.project.clone() else {
16941 return Task::ready(Ok(None));
16942 };
16943
16944 cx.spawn_in(window, async move |editor, cx| {
16945 let location_task = editor.update(cx, |_, cx| {
16946 project.update(cx, |project, cx| {
16947 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16948 })
16949 })?;
16950 let location = Some({
16951 let target_buffer_handle = location_task.await.context("open local buffer")?;
16952 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16953 let target_start = target_buffer
16954 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16955 let target_end = target_buffer
16956 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16957 target_buffer.anchor_after(target_start)
16958 ..target_buffer.anchor_before(target_end)
16959 })?;
16960 Location {
16961 buffer: target_buffer_handle,
16962 range,
16963 }
16964 });
16965 Ok(location)
16966 })
16967 }
16968
16969 fn go_to_next_reference(
16970 &mut self,
16971 _: &GoToNextReference,
16972 window: &mut Window,
16973 cx: &mut Context<Self>,
16974 ) {
16975 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16976 if let Some(task) = task {
16977 task.detach();
16978 };
16979 }
16980
16981 fn go_to_prev_reference(
16982 &mut self,
16983 _: &GoToPreviousReference,
16984 window: &mut Window,
16985 cx: &mut Context<Self>,
16986 ) {
16987 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16988 if let Some(task) = task {
16989 task.detach();
16990 };
16991 }
16992
16993 pub fn go_to_reference_before_or_after_position(
16994 &mut self,
16995 direction: Direction,
16996 count: usize,
16997 window: &mut Window,
16998 cx: &mut Context<Self>,
16999 ) -> Option<Task<Result<()>>> {
17000 let selection = self.selections.newest_anchor();
17001 let head = selection.head();
17002
17003 let multi_buffer = self.buffer.read(cx);
17004
17005 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17006 let workspace = self.workspace()?;
17007 let project = workspace.read(cx).project().clone();
17008 let references =
17009 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17010 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17011 let Some(locations) = references.await? else {
17012 return Ok(());
17013 };
17014
17015 if locations.is_empty() {
17016 // totally normal - the cursor may be on something which is not
17017 // a symbol (e.g. a keyword)
17018 log::info!("no references found under cursor");
17019 return Ok(());
17020 }
17021
17022 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17023
17024 let multi_buffer_snapshot =
17025 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17026
17027 let (locations, current_location_index) =
17028 multi_buffer.update(cx, |multi_buffer, cx| {
17029 let mut locations = locations
17030 .into_iter()
17031 .filter_map(|loc| {
17032 let start = multi_buffer.buffer_anchor_to_anchor(
17033 &loc.buffer,
17034 loc.range.start,
17035 cx,
17036 )?;
17037 let end = multi_buffer.buffer_anchor_to_anchor(
17038 &loc.buffer,
17039 loc.range.end,
17040 cx,
17041 )?;
17042 Some(start..end)
17043 })
17044 .collect::<Vec<_>>();
17045
17046 // There is an O(n) implementation, but given this list will be
17047 // small (usually <100 items), the extra O(log(n)) factor isn't
17048 // worth the (surprisingly large amount of) extra complexity.
17049 locations
17050 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17051
17052 let head_offset = head.to_offset(&multi_buffer_snapshot);
17053
17054 let current_location_index = locations.iter().position(|loc| {
17055 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17056 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17057 });
17058
17059 (locations, current_location_index)
17060 })?;
17061
17062 let Some(current_location_index) = current_location_index else {
17063 // This indicates something has gone wrong, because we already
17064 // handle the "no references" case above
17065 log::error!(
17066 "failed to find current reference under cursor. Total references: {}",
17067 locations.len()
17068 );
17069 return Ok(());
17070 };
17071
17072 let destination_location_index = match direction {
17073 Direction::Next => (current_location_index + count) % locations.len(),
17074 Direction::Prev => {
17075 (current_location_index + locations.len() - count % locations.len())
17076 % locations.len()
17077 }
17078 };
17079
17080 // TODO(cameron): is this needed?
17081 // the thinking is to avoid "jumping to the current location" (avoid
17082 // polluting "jumplist" in vim terms)
17083 if current_location_index == destination_location_index {
17084 return Ok(());
17085 }
17086
17087 let Range { start, end } = locations[destination_location_index];
17088
17089 editor.update_in(cx, |editor, window, cx| {
17090 let effects = SelectionEffects::default();
17091
17092 editor.unfold_ranges(&[start..end], false, false, cx);
17093 editor.change_selections(effects, window, cx, |s| {
17094 s.select_ranges([start..start]);
17095 });
17096 })?;
17097
17098 Ok(())
17099 }))
17100 }
17101
17102 pub fn find_all_references(
17103 &mut self,
17104 _: &FindAllReferences,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) -> Option<Task<Result<Navigated>>> {
17108 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17109 let multi_buffer = self.buffer.read(cx);
17110 let head = selection.head();
17111
17112 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17113 let head_anchor = multi_buffer_snapshot.anchor_at(
17114 head,
17115 if head < selection.tail() {
17116 Bias::Right
17117 } else {
17118 Bias::Left
17119 },
17120 );
17121
17122 match self
17123 .find_all_references_task_sources
17124 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17125 {
17126 Ok(_) => {
17127 log::info!(
17128 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17129 );
17130 return None;
17131 }
17132 Err(i) => {
17133 self.find_all_references_task_sources.insert(i, head_anchor);
17134 }
17135 }
17136
17137 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17138 let workspace = self.workspace()?;
17139 let project = workspace.read(cx).project().clone();
17140 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17141 Some(cx.spawn_in(window, async move |editor, cx| {
17142 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17143 if let Ok(i) = editor
17144 .find_all_references_task_sources
17145 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17146 {
17147 editor.find_all_references_task_sources.remove(i);
17148 }
17149 });
17150
17151 let Some(locations) = references.await? else {
17152 return anyhow::Ok(Navigated::No);
17153 };
17154 let mut locations = cx.update(|_, cx| {
17155 locations
17156 .into_iter()
17157 .map(|location| {
17158 let buffer = location.buffer.read(cx);
17159 (location.buffer, location.range.to_point(buffer))
17160 })
17161 .into_group_map()
17162 })?;
17163 if locations.is_empty() {
17164 return anyhow::Ok(Navigated::No);
17165 }
17166 for ranges in locations.values_mut() {
17167 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17168 ranges.dedup();
17169 }
17170
17171 workspace.update_in(cx, |workspace, window, cx| {
17172 let target = locations
17173 .iter()
17174 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17175 .map(|(buffer, location)| {
17176 buffer
17177 .read(cx)
17178 .text_for_range(location.clone())
17179 .collect::<String>()
17180 })
17181 .filter(|text| !text.contains('\n'))
17182 .unique()
17183 .take(3)
17184 .join(", ");
17185 let title = if target.is_empty() {
17186 "References".to_owned()
17187 } else {
17188 format!("References to {target}")
17189 };
17190 Self::open_locations_in_multibuffer(
17191 workspace,
17192 locations,
17193 title,
17194 false,
17195 MultibufferSelectionMode::First,
17196 window,
17197 cx,
17198 );
17199 Navigated::Yes
17200 })
17201 }))
17202 }
17203
17204 /// Opens a multibuffer with the given project locations in it
17205 pub fn open_locations_in_multibuffer(
17206 workspace: &mut Workspace,
17207 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17208 title: String,
17209 split: bool,
17210 multibuffer_selection_mode: MultibufferSelectionMode,
17211 window: &mut Window,
17212 cx: &mut Context<Workspace>,
17213 ) {
17214 if locations.is_empty() {
17215 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17216 return;
17217 }
17218
17219 let capability = workspace.project().read(cx).capability();
17220 let mut ranges = <Vec<Range<Anchor>>>::new();
17221
17222 // a key to find existing multibuffer editors with the same set of locations
17223 // to prevent us from opening more and more multibuffer tabs for searches and the like
17224 let mut key = (title.clone(), vec![]);
17225 let excerpt_buffer = cx.new(|cx| {
17226 let key = &mut key.1;
17227 let mut multibuffer = MultiBuffer::new(capability);
17228 for (buffer, mut ranges_for_buffer) in locations {
17229 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17230 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17231 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17232 PathKey::for_buffer(&buffer, cx),
17233 buffer.clone(),
17234 ranges_for_buffer,
17235 multibuffer_context_lines(cx),
17236 cx,
17237 );
17238 ranges.extend(new_ranges)
17239 }
17240
17241 multibuffer.with_title(title)
17242 });
17243 let existing = workspace.active_pane().update(cx, |pane, cx| {
17244 pane.items()
17245 .filter_map(|item| item.downcast::<Editor>())
17246 .find(|editor| {
17247 editor
17248 .read(cx)
17249 .lookup_key
17250 .as_ref()
17251 .and_then(|it| {
17252 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17253 })
17254 .is_some_and(|it| *it == key)
17255 })
17256 });
17257 let editor = existing.unwrap_or_else(|| {
17258 cx.new(|cx| {
17259 let mut editor = Editor::for_multibuffer(
17260 excerpt_buffer,
17261 Some(workspace.project().clone()),
17262 window,
17263 cx,
17264 );
17265 editor.lookup_key = Some(Box::new(key));
17266 editor
17267 })
17268 });
17269 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17270 MultibufferSelectionMode::First => {
17271 if let Some(first_range) = ranges.first() {
17272 editor.change_selections(
17273 SelectionEffects::no_scroll(),
17274 window,
17275 cx,
17276 |selections| {
17277 selections.clear_disjoint();
17278 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17279 },
17280 );
17281 }
17282 editor.highlight_background::<Self>(
17283 &ranges,
17284 |theme| theme.colors().editor_highlighted_line_background,
17285 cx,
17286 );
17287 }
17288 MultibufferSelectionMode::All => {
17289 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17290 selections.clear_disjoint();
17291 selections.select_anchor_ranges(ranges);
17292 });
17293 }
17294 });
17295
17296 let item = Box::new(editor);
17297 let item_id = item.item_id();
17298
17299 if split {
17300 let pane = workspace.adjacent_pane(window, cx);
17301 workspace.add_item(pane, item, None, true, true, window, cx);
17302 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17303 let (preview_item_id, preview_item_idx) =
17304 workspace.active_pane().read_with(cx, |pane, _| {
17305 (pane.preview_item_id(), pane.preview_item_idx())
17306 });
17307
17308 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17309
17310 if let Some(preview_item_id) = preview_item_id {
17311 workspace.active_pane().update(cx, |pane, cx| {
17312 pane.remove_item(preview_item_id, false, false, window, cx);
17313 });
17314 }
17315 } else {
17316 workspace.add_item_to_active_pane(item, None, true, window, cx);
17317 }
17318 workspace.active_pane().update(cx, |pane, cx| {
17319 pane.set_preview_item_id(Some(item_id), cx);
17320 });
17321 }
17322
17323 pub fn rename(
17324 &mut self,
17325 _: &Rename,
17326 window: &mut Window,
17327 cx: &mut Context<Self>,
17328 ) -> Option<Task<Result<()>>> {
17329 use language::ToOffset as _;
17330
17331 let provider = self.semantics_provider.clone()?;
17332 let selection = self.selections.newest_anchor().clone();
17333 let (cursor_buffer, cursor_buffer_position) = self
17334 .buffer
17335 .read(cx)
17336 .text_anchor_for_position(selection.head(), cx)?;
17337 let (tail_buffer, cursor_buffer_position_end) = self
17338 .buffer
17339 .read(cx)
17340 .text_anchor_for_position(selection.tail(), cx)?;
17341 if tail_buffer != cursor_buffer {
17342 return None;
17343 }
17344
17345 let snapshot = cursor_buffer.read(cx).snapshot();
17346 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17347 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17348 let prepare_rename = provider
17349 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17350 .unwrap_or_else(|| Task::ready(Ok(None)));
17351 drop(snapshot);
17352
17353 Some(cx.spawn_in(window, async move |this, cx| {
17354 let rename_range = if let Some(range) = prepare_rename.await? {
17355 Some(range)
17356 } else {
17357 this.update(cx, |this, cx| {
17358 let buffer = this.buffer.read(cx).snapshot(cx);
17359 let mut buffer_highlights = this
17360 .document_highlights_for_position(selection.head(), &buffer)
17361 .filter(|highlight| {
17362 highlight.start.excerpt_id == selection.head().excerpt_id
17363 && highlight.end.excerpt_id == selection.head().excerpt_id
17364 });
17365 buffer_highlights
17366 .next()
17367 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17368 })?
17369 };
17370 if let Some(rename_range) = rename_range {
17371 this.update_in(cx, |this, window, cx| {
17372 let snapshot = cursor_buffer.read(cx).snapshot();
17373 let rename_buffer_range = rename_range.to_offset(&snapshot);
17374 let cursor_offset_in_rename_range =
17375 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17376 let cursor_offset_in_rename_range_end =
17377 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17378
17379 this.take_rename(false, window, cx);
17380 let buffer = this.buffer.read(cx).read(cx);
17381 let cursor_offset = selection.head().to_offset(&buffer);
17382 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17383 let rename_end = rename_start + rename_buffer_range.len();
17384 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17385 let mut old_highlight_id = None;
17386 let old_name: Arc<str> = buffer
17387 .chunks(rename_start..rename_end, true)
17388 .map(|chunk| {
17389 if old_highlight_id.is_none() {
17390 old_highlight_id = chunk.syntax_highlight_id;
17391 }
17392 chunk.text
17393 })
17394 .collect::<String>()
17395 .into();
17396
17397 drop(buffer);
17398
17399 // Position the selection in the rename editor so that it matches the current selection.
17400 this.show_local_selections = false;
17401 let rename_editor = cx.new(|cx| {
17402 let mut editor = Editor::single_line(window, cx);
17403 editor.buffer.update(cx, |buffer, cx| {
17404 buffer.edit([(0..0, old_name.clone())], None, cx)
17405 });
17406 let rename_selection_range = match cursor_offset_in_rename_range
17407 .cmp(&cursor_offset_in_rename_range_end)
17408 {
17409 Ordering::Equal => {
17410 editor.select_all(&SelectAll, window, cx);
17411 return editor;
17412 }
17413 Ordering::Less => {
17414 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17415 }
17416 Ordering::Greater => {
17417 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17418 }
17419 };
17420 if rename_selection_range.end > old_name.len() {
17421 editor.select_all(&SelectAll, window, cx);
17422 } else {
17423 editor.change_selections(Default::default(), window, cx, |s| {
17424 s.select_ranges([rename_selection_range]);
17425 });
17426 }
17427 editor
17428 });
17429 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17430 if e == &EditorEvent::Focused {
17431 cx.emit(EditorEvent::FocusedIn)
17432 }
17433 })
17434 .detach();
17435
17436 let write_highlights =
17437 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17438 let read_highlights =
17439 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17440 let ranges = write_highlights
17441 .iter()
17442 .flat_map(|(_, ranges)| ranges.iter())
17443 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17444 .cloned()
17445 .collect();
17446
17447 this.highlight_text::<Rename>(
17448 ranges,
17449 HighlightStyle {
17450 fade_out: Some(0.6),
17451 ..Default::default()
17452 },
17453 cx,
17454 );
17455 let rename_focus_handle = rename_editor.focus_handle(cx);
17456 window.focus(&rename_focus_handle);
17457 let block_id = this.insert_blocks(
17458 [BlockProperties {
17459 style: BlockStyle::Flex,
17460 placement: BlockPlacement::Below(range.start),
17461 height: Some(1),
17462 render: Arc::new({
17463 let rename_editor = rename_editor.clone();
17464 move |cx: &mut BlockContext| {
17465 let mut text_style = cx.editor_style.text.clone();
17466 if let Some(highlight_style) = old_highlight_id
17467 .and_then(|h| h.style(&cx.editor_style.syntax))
17468 {
17469 text_style = text_style.highlight(highlight_style);
17470 }
17471 div()
17472 .block_mouse_except_scroll()
17473 .pl(cx.anchor_x)
17474 .child(EditorElement::new(
17475 &rename_editor,
17476 EditorStyle {
17477 background: cx.theme().system().transparent,
17478 local_player: cx.editor_style.local_player,
17479 text: text_style,
17480 scrollbar_width: cx.editor_style.scrollbar_width,
17481 syntax: cx.editor_style.syntax.clone(),
17482 status: cx.editor_style.status.clone(),
17483 inlay_hints_style: HighlightStyle {
17484 font_weight: Some(FontWeight::BOLD),
17485 ..make_inlay_hints_style(cx.app)
17486 },
17487 edit_prediction_styles: make_suggestion_styles(
17488 cx.app,
17489 ),
17490 ..EditorStyle::default()
17491 },
17492 ))
17493 .into_any_element()
17494 }
17495 }),
17496 priority: 0,
17497 }],
17498 Some(Autoscroll::fit()),
17499 cx,
17500 )[0];
17501 this.pending_rename = Some(RenameState {
17502 range,
17503 old_name,
17504 editor: rename_editor,
17505 block_id,
17506 });
17507 })?;
17508 }
17509
17510 Ok(())
17511 }))
17512 }
17513
17514 pub fn confirm_rename(
17515 &mut self,
17516 _: &ConfirmRename,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) -> Option<Task<Result<()>>> {
17520 let rename = self.take_rename(false, window, cx)?;
17521 let workspace = self.workspace()?.downgrade();
17522 let (buffer, start) = self
17523 .buffer
17524 .read(cx)
17525 .text_anchor_for_position(rename.range.start, cx)?;
17526 let (end_buffer, _) = self
17527 .buffer
17528 .read(cx)
17529 .text_anchor_for_position(rename.range.end, cx)?;
17530 if buffer != end_buffer {
17531 return None;
17532 }
17533
17534 let old_name = rename.old_name;
17535 let new_name = rename.editor.read(cx).text(cx);
17536
17537 let rename = self.semantics_provider.as_ref()?.perform_rename(
17538 &buffer,
17539 start,
17540 new_name.clone(),
17541 cx,
17542 )?;
17543
17544 Some(cx.spawn_in(window, async move |editor, cx| {
17545 let project_transaction = rename.await?;
17546 Self::open_project_transaction(
17547 &editor,
17548 workspace,
17549 project_transaction,
17550 format!("Rename: {} → {}", old_name, new_name),
17551 cx,
17552 )
17553 .await?;
17554
17555 editor.update(cx, |editor, cx| {
17556 editor.refresh_document_highlights(cx);
17557 })?;
17558 Ok(())
17559 }))
17560 }
17561
17562 fn take_rename(
17563 &mut self,
17564 moving_cursor: bool,
17565 window: &mut Window,
17566 cx: &mut Context<Self>,
17567 ) -> Option<RenameState> {
17568 let rename = self.pending_rename.take()?;
17569 if rename.editor.focus_handle(cx).is_focused(window) {
17570 window.focus(&self.focus_handle);
17571 }
17572
17573 self.remove_blocks(
17574 [rename.block_id].into_iter().collect(),
17575 Some(Autoscroll::fit()),
17576 cx,
17577 );
17578 self.clear_highlights::<Rename>(cx);
17579 self.show_local_selections = true;
17580
17581 if moving_cursor {
17582 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17583 editor
17584 .selections
17585 .newest::<usize>(&editor.display_snapshot(cx))
17586 .head()
17587 });
17588
17589 // Update the selection to match the position of the selection inside
17590 // the rename editor.
17591 let snapshot = self.buffer.read(cx).read(cx);
17592 let rename_range = rename.range.to_offset(&snapshot);
17593 let cursor_in_editor = snapshot
17594 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17595 .min(rename_range.end);
17596 drop(snapshot);
17597
17598 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17599 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17600 });
17601 } else {
17602 self.refresh_document_highlights(cx);
17603 }
17604
17605 Some(rename)
17606 }
17607
17608 pub fn pending_rename(&self) -> Option<&RenameState> {
17609 self.pending_rename.as_ref()
17610 }
17611
17612 fn format(
17613 &mut self,
17614 _: &Format,
17615 window: &mut Window,
17616 cx: &mut Context<Self>,
17617 ) -> Option<Task<Result<()>>> {
17618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17619
17620 let project = match &self.project {
17621 Some(project) => project.clone(),
17622 None => return None,
17623 };
17624
17625 Some(self.perform_format(
17626 project,
17627 FormatTrigger::Manual,
17628 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17629 window,
17630 cx,
17631 ))
17632 }
17633
17634 fn format_selections(
17635 &mut self,
17636 _: &FormatSelections,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) -> Option<Task<Result<()>>> {
17640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17641
17642 let project = match &self.project {
17643 Some(project) => project.clone(),
17644 None => return None,
17645 };
17646
17647 let ranges = self
17648 .selections
17649 .all_adjusted(&self.display_snapshot(cx))
17650 .into_iter()
17651 .map(|selection| selection.range())
17652 .collect_vec();
17653
17654 Some(self.perform_format(
17655 project,
17656 FormatTrigger::Manual,
17657 FormatTarget::Ranges(ranges),
17658 window,
17659 cx,
17660 ))
17661 }
17662
17663 fn perform_format(
17664 &mut self,
17665 project: Entity<Project>,
17666 trigger: FormatTrigger,
17667 target: FormatTarget,
17668 window: &mut Window,
17669 cx: &mut Context<Self>,
17670 ) -> Task<Result<()>> {
17671 let buffer = self.buffer.clone();
17672 let (buffers, target) = match target {
17673 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17674 FormatTarget::Ranges(selection_ranges) => {
17675 let multi_buffer = buffer.read(cx);
17676 let snapshot = multi_buffer.read(cx);
17677 let mut buffers = HashSet::default();
17678 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17679 BTreeMap::new();
17680 for selection_range in selection_ranges {
17681 for (buffer, buffer_range, _) in
17682 snapshot.range_to_buffer_ranges(selection_range)
17683 {
17684 let buffer_id = buffer.remote_id();
17685 let start = buffer.anchor_before(buffer_range.start);
17686 let end = buffer.anchor_after(buffer_range.end);
17687 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17688 buffer_id_to_ranges
17689 .entry(buffer_id)
17690 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17691 .or_insert_with(|| vec![start..end]);
17692 }
17693 }
17694 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17695 }
17696 };
17697
17698 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17699 let selections_prev = transaction_id_prev
17700 .and_then(|transaction_id_prev| {
17701 // default to selections as they were after the last edit, if we have them,
17702 // instead of how they are now.
17703 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17704 // will take you back to where you made the last edit, instead of staying where you scrolled
17705 self.selection_history
17706 .transaction(transaction_id_prev)
17707 .map(|t| t.0.clone())
17708 })
17709 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17710
17711 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17712 let format = project.update(cx, |project, cx| {
17713 project.format(buffers, target, true, trigger, cx)
17714 });
17715
17716 cx.spawn_in(window, async move |editor, cx| {
17717 let transaction = futures::select_biased! {
17718 transaction = format.log_err().fuse() => transaction,
17719 () = timeout => {
17720 log::warn!("timed out waiting for formatting");
17721 None
17722 }
17723 };
17724
17725 buffer
17726 .update(cx, |buffer, cx| {
17727 if let Some(transaction) = transaction
17728 && !buffer.is_singleton()
17729 {
17730 buffer.push_transaction(&transaction.0, cx);
17731 }
17732 cx.notify();
17733 })
17734 .ok();
17735
17736 if let Some(transaction_id_now) =
17737 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17738 {
17739 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17740 if has_new_transaction {
17741 _ = editor.update(cx, |editor, _| {
17742 editor
17743 .selection_history
17744 .insert_transaction(transaction_id_now, selections_prev);
17745 });
17746 }
17747 }
17748
17749 Ok(())
17750 })
17751 }
17752
17753 fn organize_imports(
17754 &mut self,
17755 _: &OrganizeImports,
17756 window: &mut Window,
17757 cx: &mut Context<Self>,
17758 ) -> Option<Task<Result<()>>> {
17759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17760 let project = match &self.project {
17761 Some(project) => project.clone(),
17762 None => return None,
17763 };
17764 Some(self.perform_code_action_kind(
17765 project,
17766 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17767 window,
17768 cx,
17769 ))
17770 }
17771
17772 fn perform_code_action_kind(
17773 &mut self,
17774 project: Entity<Project>,
17775 kind: CodeActionKind,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) -> Task<Result<()>> {
17779 let buffer = self.buffer.clone();
17780 let buffers = buffer.read(cx).all_buffers();
17781 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17782 let apply_action = project.update(cx, |project, cx| {
17783 project.apply_code_action_kind(buffers, kind, true, cx)
17784 });
17785 cx.spawn_in(window, async move |_, cx| {
17786 let transaction = futures::select_biased! {
17787 () = timeout => {
17788 log::warn!("timed out waiting for executing code action");
17789 None
17790 }
17791 transaction = apply_action.log_err().fuse() => transaction,
17792 };
17793 buffer
17794 .update(cx, |buffer, cx| {
17795 // check if we need this
17796 if let Some(transaction) = transaction
17797 && !buffer.is_singleton()
17798 {
17799 buffer.push_transaction(&transaction.0, cx);
17800 }
17801 cx.notify();
17802 })
17803 .ok();
17804 Ok(())
17805 })
17806 }
17807
17808 pub fn restart_language_server(
17809 &mut self,
17810 _: &RestartLanguageServer,
17811 _: &mut Window,
17812 cx: &mut Context<Self>,
17813 ) {
17814 if let Some(project) = self.project.clone() {
17815 self.buffer.update(cx, |multi_buffer, cx| {
17816 project.update(cx, |project, cx| {
17817 project.restart_language_servers_for_buffers(
17818 multi_buffer.all_buffers().into_iter().collect(),
17819 HashSet::default(),
17820 cx,
17821 );
17822 });
17823 })
17824 }
17825 }
17826
17827 pub fn stop_language_server(
17828 &mut self,
17829 _: &StopLanguageServer,
17830 _: &mut Window,
17831 cx: &mut Context<Self>,
17832 ) {
17833 if let Some(project) = self.project.clone() {
17834 self.buffer.update(cx, |multi_buffer, cx| {
17835 project.update(cx, |project, cx| {
17836 project.stop_language_servers_for_buffers(
17837 multi_buffer.all_buffers().into_iter().collect(),
17838 HashSet::default(),
17839 cx,
17840 );
17841 });
17842 });
17843 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17844 }
17845 }
17846
17847 fn cancel_language_server_work(
17848 workspace: &mut Workspace,
17849 _: &actions::CancelLanguageServerWork,
17850 _: &mut Window,
17851 cx: &mut Context<Workspace>,
17852 ) {
17853 let project = workspace.project();
17854 let buffers = workspace
17855 .active_item(cx)
17856 .and_then(|item| item.act_as::<Editor>(cx))
17857 .map_or(HashSet::default(), |editor| {
17858 editor.read(cx).buffer.read(cx).all_buffers()
17859 });
17860 project.update(cx, |project, cx| {
17861 project.cancel_language_server_work_for_buffers(buffers, cx);
17862 });
17863 }
17864
17865 fn show_character_palette(
17866 &mut self,
17867 _: &ShowCharacterPalette,
17868 window: &mut Window,
17869 _: &mut Context<Self>,
17870 ) {
17871 window.show_character_palette();
17872 }
17873
17874 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17875 if !self.diagnostics_enabled() {
17876 return;
17877 }
17878
17879 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17880 let buffer = self.buffer.read(cx).snapshot(cx);
17881 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17882 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17883 let is_valid = buffer
17884 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17885 .any(|entry| {
17886 entry.diagnostic.is_primary
17887 && !entry.range.is_empty()
17888 && entry.range.start == primary_range_start
17889 && entry.diagnostic.message == active_diagnostics.active_message
17890 });
17891
17892 if !is_valid {
17893 self.dismiss_diagnostics(cx);
17894 }
17895 }
17896 }
17897
17898 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17899 match &self.active_diagnostics {
17900 ActiveDiagnostic::Group(group) => Some(group),
17901 _ => None,
17902 }
17903 }
17904
17905 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17906 if !self.diagnostics_enabled() {
17907 return;
17908 }
17909 self.dismiss_diagnostics(cx);
17910 self.active_diagnostics = ActiveDiagnostic::All;
17911 }
17912
17913 fn activate_diagnostics(
17914 &mut self,
17915 buffer_id: BufferId,
17916 diagnostic: DiagnosticEntryRef<'_, usize>,
17917 window: &mut Window,
17918 cx: &mut Context<Self>,
17919 ) {
17920 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17921 return;
17922 }
17923 self.dismiss_diagnostics(cx);
17924 let snapshot = self.snapshot(window, cx);
17925 let buffer = self.buffer.read(cx).snapshot(cx);
17926 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17927 return;
17928 };
17929
17930 let diagnostic_group = buffer
17931 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17932 .collect::<Vec<_>>();
17933
17934 let blocks =
17935 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17936
17937 let blocks = self.display_map.update(cx, |display_map, cx| {
17938 display_map.insert_blocks(blocks, cx).into_iter().collect()
17939 });
17940 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17941 active_range: buffer.anchor_before(diagnostic.range.start)
17942 ..buffer.anchor_after(diagnostic.range.end),
17943 active_message: diagnostic.diagnostic.message.clone(),
17944 group_id: diagnostic.diagnostic.group_id,
17945 blocks,
17946 });
17947 cx.notify();
17948 }
17949
17950 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17951 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17952 return;
17953 };
17954
17955 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17956 if let ActiveDiagnostic::Group(group) = prev {
17957 self.display_map.update(cx, |display_map, cx| {
17958 display_map.remove_blocks(group.blocks, cx);
17959 });
17960 cx.notify();
17961 }
17962 }
17963
17964 /// Disable inline diagnostics rendering for this editor.
17965 pub fn disable_inline_diagnostics(&mut self) {
17966 self.inline_diagnostics_enabled = false;
17967 self.inline_diagnostics_update = Task::ready(());
17968 self.inline_diagnostics.clear();
17969 }
17970
17971 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17972 self.diagnostics_enabled = false;
17973 self.dismiss_diagnostics(cx);
17974 self.inline_diagnostics_update = Task::ready(());
17975 self.inline_diagnostics.clear();
17976 }
17977
17978 pub fn disable_word_completions(&mut self) {
17979 self.word_completions_enabled = false;
17980 }
17981
17982 pub fn diagnostics_enabled(&self) -> bool {
17983 self.diagnostics_enabled && self.mode.is_full()
17984 }
17985
17986 pub fn inline_diagnostics_enabled(&self) -> bool {
17987 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17988 }
17989
17990 pub fn show_inline_diagnostics(&self) -> bool {
17991 self.show_inline_diagnostics
17992 }
17993
17994 pub fn toggle_inline_diagnostics(
17995 &mut self,
17996 _: &ToggleInlineDiagnostics,
17997 window: &mut Window,
17998 cx: &mut Context<Editor>,
17999 ) {
18000 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18001 self.refresh_inline_diagnostics(false, window, cx);
18002 }
18003
18004 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18005 self.diagnostics_max_severity = severity;
18006 self.display_map.update(cx, |display_map, _| {
18007 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18008 });
18009 }
18010
18011 pub fn toggle_diagnostics(
18012 &mut self,
18013 _: &ToggleDiagnostics,
18014 window: &mut Window,
18015 cx: &mut Context<Editor>,
18016 ) {
18017 if !self.diagnostics_enabled() {
18018 return;
18019 }
18020
18021 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18022 EditorSettings::get_global(cx)
18023 .diagnostics_max_severity
18024 .filter(|severity| severity != &DiagnosticSeverity::Off)
18025 .unwrap_or(DiagnosticSeverity::Hint)
18026 } else {
18027 DiagnosticSeverity::Off
18028 };
18029 self.set_max_diagnostics_severity(new_severity, cx);
18030 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18031 self.active_diagnostics = ActiveDiagnostic::None;
18032 self.inline_diagnostics_update = Task::ready(());
18033 self.inline_diagnostics.clear();
18034 } else {
18035 self.refresh_inline_diagnostics(false, window, cx);
18036 }
18037
18038 cx.notify();
18039 }
18040
18041 pub fn toggle_minimap(
18042 &mut self,
18043 _: &ToggleMinimap,
18044 window: &mut Window,
18045 cx: &mut Context<Editor>,
18046 ) {
18047 if self.supports_minimap(cx) {
18048 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18049 }
18050 }
18051
18052 fn refresh_inline_diagnostics(
18053 &mut self,
18054 debounce: bool,
18055 window: &mut Window,
18056 cx: &mut Context<Self>,
18057 ) {
18058 let max_severity = ProjectSettings::get_global(cx)
18059 .diagnostics
18060 .inline
18061 .max_severity
18062 .unwrap_or(self.diagnostics_max_severity);
18063
18064 if !self.inline_diagnostics_enabled()
18065 || !self.diagnostics_enabled()
18066 || !self.show_inline_diagnostics
18067 || max_severity == DiagnosticSeverity::Off
18068 {
18069 self.inline_diagnostics_update = Task::ready(());
18070 self.inline_diagnostics.clear();
18071 return;
18072 }
18073
18074 let debounce_ms = ProjectSettings::get_global(cx)
18075 .diagnostics
18076 .inline
18077 .update_debounce_ms;
18078 let debounce = if debounce && debounce_ms > 0 {
18079 Some(Duration::from_millis(debounce_ms))
18080 } else {
18081 None
18082 };
18083 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18084 if let Some(debounce) = debounce {
18085 cx.background_executor().timer(debounce).await;
18086 }
18087 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18088 editor
18089 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18090 .ok()
18091 }) else {
18092 return;
18093 };
18094
18095 let new_inline_diagnostics = cx
18096 .background_spawn(async move {
18097 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18098 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18099 let message = diagnostic_entry
18100 .diagnostic
18101 .message
18102 .split_once('\n')
18103 .map(|(line, _)| line)
18104 .map(SharedString::new)
18105 .unwrap_or_else(|| {
18106 SharedString::new(&*diagnostic_entry.diagnostic.message)
18107 });
18108 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18109 let (Ok(i) | Err(i)) = inline_diagnostics
18110 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18111 inline_diagnostics.insert(
18112 i,
18113 (
18114 start_anchor,
18115 InlineDiagnostic {
18116 message,
18117 group_id: diagnostic_entry.diagnostic.group_id,
18118 start: diagnostic_entry.range.start.to_point(&snapshot),
18119 is_primary: diagnostic_entry.diagnostic.is_primary,
18120 severity: diagnostic_entry.diagnostic.severity,
18121 },
18122 ),
18123 );
18124 }
18125 inline_diagnostics
18126 })
18127 .await;
18128
18129 editor
18130 .update(cx, |editor, cx| {
18131 editor.inline_diagnostics = new_inline_diagnostics;
18132 cx.notify();
18133 })
18134 .ok();
18135 });
18136 }
18137
18138 fn pull_diagnostics(
18139 &mut self,
18140 buffer_id: Option<BufferId>,
18141 window: &Window,
18142 cx: &mut Context<Self>,
18143 ) -> Option<()> {
18144 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18145 return None;
18146 }
18147 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18148 .diagnostics
18149 .lsp_pull_diagnostics;
18150 if !pull_diagnostics_settings.enabled {
18151 return None;
18152 }
18153 let project = self.project()?.downgrade();
18154 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18155 let mut buffers = self.buffer.read(cx).all_buffers();
18156 buffers.retain(|buffer| {
18157 let buffer_id_to_retain = buffer.read(cx).remote_id();
18158 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18159 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18160 });
18161 if buffers.is_empty() {
18162 self.pull_diagnostics_task = Task::ready(());
18163 return None;
18164 }
18165
18166 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18167 cx.background_executor().timer(debounce).await;
18168
18169 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18170 buffers
18171 .into_iter()
18172 .filter_map(|buffer| {
18173 project
18174 .update(cx, |project, cx| {
18175 project.lsp_store().update(cx, |lsp_store, cx| {
18176 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18177 })
18178 })
18179 .ok()
18180 })
18181 .collect::<FuturesUnordered<_>>()
18182 }) else {
18183 return;
18184 };
18185
18186 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18187 match pull_task {
18188 Ok(()) => {
18189 if editor
18190 .update_in(cx, |editor, window, cx| {
18191 editor.update_diagnostics_state(window, cx);
18192 })
18193 .is_err()
18194 {
18195 return;
18196 }
18197 }
18198 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18199 }
18200 }
18201 });
18202
18203 Some(())
18204 }
18205
18206 pub fn set_selections_from_remote(
18207 &mut self,
18208 selections: Vec<Selection<Anchor>>,
18209 pending_selection: Option<Selection<Anchor>>,
18210 window: &mut Window,
18211 cx: &mut Context<Self>,
18212 ) {
18213 let old_cursor_position = self.selections.newest_anchor().head();
18214 self.selections
18215 .change_with(&self.display_snapshot(cx), |s| {
18216 s.select_anchors(selections);
18217 if let Some(pending_selection) = pending_selection {
18218 s.set_pending(pending_selection, SelectMode::Character);
18219 } else {
18220 s.clear_pending();
18221 }
18222 });
18223 self.selections_did_change(
18224 false,
18225 &old_cursor_position,
18226 SelectionEffects::default(),
18227 window,
18228 cx,
18229 );
18230 }
18231
18232 pub fn transact(
18233 &mut self,
18234 window: &mut Window,
18235 cx: &mut Context<Self>,
18236 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18237 ) -> Option<TransactionId> {
18238 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18239 this.start_transaction_at(Instant::now(), window, cx);
18240 update(this, window, cx);
18241 this.end_transaction_at(Instant::now(), cx)
18242 })
18243 }
18244
18245 pub fn start_transaction_at(
18246 &mut self,
18247 now: Instant,
18248 window: &mut Window,
18249 cx: &mut Context<Self>,
18250 ) -> Option<TransactionId> {
18251 self.end_selection(window, cx);
18252 if let Some(tx_id) = self
18253 .buffer
18254 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18255 {
18256 self.selection_history
18257 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18258 cx.emit(EditorEvent::TransactionBegun {
18259 transaction_id: tx_id,
18260 });
18261 Some(tx_id)
18262 } else {
18263 None
18264 }
18265 }
18266
18267 pub fn end_transaction_at(
18268 &mut self,
18269 now: Instant,
18270 cx: &mut Context<Self>,
18271 ) -> Option<TransactionId> {
18272 if let Some(transaction_id) = self
18273 .buffer
18274 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18275 {
18276 if let Some((_, end_selections)) =
18277 self.selection_history.transaction_mut(transaction_id)
18278 {
18279 *end_selections = Some(self.selections.disjoint_anchors_arc());
18280 } else {
18281 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18282 }
18283
18284 cx.emit(EditorEvent::Edited { transaction_id });
18285 Some(transaction_id)
18286 } else {
18287 None
18288 }
18289 }
18290
18291 pub fn modify_transaction_selection_history(
18292 &mut self,
18293 transaction_id: TransactionId,
18294 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18295 ) -> bool {
18296 self.selection_history
18297 .transaction_mut(transaction_id)
18298 .map(modify)
18299 .is_some()
18300 }
18301
18302 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18303 if self.selection_mark_mode {
18304 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18305 s.move_with(|_, sel| {
18306 sel.collapse_to(sel.head(), SelectionGoal::None);
18307 });
18308 })
18309 }
18310 self.selection_mark_mode = true;
18311 cx.notify();
18312 }
18313
18314 pub fn swap_selection_ends(
18315 &mut self,
18316 _: &actions::SwapSelectionEnds,
18317 window: &mut Window,
18318 cx: &mut Context<Self>,
18319 ) {
18320 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18321 s.move_with(|_, sel| {
18322 if sel.start != sel.end {
18323 sel.reversed = !sel.reversed
18324 }
18325 });
18326 });
18327 self.request_autoscroll(Autoscroll::newest(), cx);
18328 cx.notify();
18329 }
18330
18331 pub fn toggle_focus(
18332 workspace: &mut Workspace,
18333 _: &actions::ToggleFocus,
18334 window: &mut Window,
18335 cx: &mut Context<Workspace>,
18336 ) {
18337 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18338 return;
18339 };
18340 workspace.activate_item(&item, true, true, window, cx);
18341 }
18342
18343 pub fn toggle_fold(
18344 &mut self,
18345 _: &actions::ToggleFold,
18346 window: &mut Window,
18347 cx: &mut Context<Self>,
18348 ) {
18349 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18350 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18351 let selection = self.selections.newest::<Point>(&display_map);
18352
18353 let range = if selection.is_empty() {
18354 let point = selection.head().to_display_point(&display_map);
18355 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18356 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18357 .to_point(&display_map);
18358 start..end
18359 } else {
18360 selection.range()
18361 };
18362 if display_map.folds_in_range(range).next().is_some() {
18363 self.unfold_lines(&Default::default(), window, cx)
18364 } else {
18365 self.fold(&Default::default(), window, cx)
18366 }
18367 } else {
18368 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18369 let buffer_ids: HashSet<_> = self
18370 .selections
18371 .disjoint_anchor_ranges()
18372 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18373 .collect();
18374
18375 let should_unfold = buffer_ids
18376 .iter()
18377 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18378
18379 for buffer_id in buffer_ids {
18380 if should_unfold {
18381 self.unfold_buffer(buffer_id, cx);
18382 } else {
18383 self.fold_buffer(buffer_id, cx);
18384 }
18385 }
18386 }
18387 }
18388
18389 pub fn toggle_fold_recursive(
18390 &mut self,
18391 _: &actions::ToggleFoldRecursive,
18392 window: &mut Window,
18393 cx: &mut Context<Self>,
18394 ) {
18395 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18396
18397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18398 let range = if selection.is_empty() {
18399 let point = selection.head().to_display_point(&display_map);
18400 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18401 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18402 .to_point(&display_map);
18403 start..end
18404 } else {
18405 selection.range()
18406 };
18407 if display_map.folds_in_range(range).next().is_some() {
18408 self.unfold_recursive(&Default::default(), window, cx)
18409 } else {
18410 self.fold_recursive(&Default::default(), window, cx)
18411 }
18412 }
18413
18414 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18415 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18416 let mut to_fold = Vec::new();
18417 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18418 let selections = self.selections.all_adjusted(&display_map);
18419
18420 for selection in selections {
18421 let range = selection.range().sorted();
18422 let buffer_start_row = range.start.row;
18423
18424 if range.start.row != range.end.row {
18425 let mut found = false;
18426 let mut row = range.start.row;
18427 while row <= range.end.row {
18428 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18429 {
18430 found = true;
18431 row = crease.range().end.row + 1;
18432 to_fold.push(crease);
18433 } else {
18434 row += 1
18435 }
18436 }
18437 if found {
18438 continue;
18439 }
18440 }
18441
18442 for row in (0..=range.start.row).rev() {
18443 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18444 && crease.range().end.row >= buffer_start_row
18445 {
18446 to_fold.push(crease);
18447 if row <= range.start.row {
18448 break;
18449 }
18450 }
18451 }
18452 }
18453
18454 self.fold_creases(to_fold, true, window, cx);
18455 } else {
18456 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18457 let buffer_ids = self
18458 .selections
18459 .disjoint_anchor_ranges()
18460 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18461 .collect::<HashSet<_>>();
18462 for buffer_id in buffer_ids {
18463 self.fold_buffer(buffer_id, cx);
18464 }
18465 }
18466 }
18467
18468 pub fn toggle_fold_all(
18469 &mut self,
18470 _: &actions::ToggleFoldAll,
18471 window: &mut Window,
18472 cx: &mut Context<Self>,
18473 ) {
18474 if self.buffer.read(cx).is_singleton() {
18475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18476 let has_folds = display_map
18477 .folds_in_range(0..display_map.buffer_snapshot().len())
18478 .next()
18479 .is_some();
18480
18481 if has_folds {
18482 self.unfold_all(&actions::UnfoldAll, window, cx);
18483 } else {
18484 self.fold_all(&actions::FoldAll, window, cx);
18485 }
18486 } else {
18487 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18488 let should_unfold = buffer_ids
18489 .iter()
18490 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18491
18492 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18493 editor
18494 .update_in(cx, |editor, _, cx| {
18495 for buffer_id in buffer_ids {
18496 if should_unfold {
18497 editor.unfold_buffer(buffer_id, cx);
18498 } else {
18499 editor.fold_buffer(buffer_id, cx);
18500 }
18501 }
18502 })
18503 .ok();
18504 });
18505 }
18506 }
18507
18508 fn fold_at_level(
18509 &mut self,
18510 fold_at: &FoldAtLevel,
18511 window: &mut Window,
18512 cx: &mut Context<Self>,
18513 ) {
18514 if !self.buffer.read(cx).is_singleton() {
18515 return;
18516 }
18517
18518 let fold_at_level = fold_at.0;
18519 let snapshot = self.buffer.read(cx).snapshot(cx);
18520 let mut to_fold = Vec::new();
18521 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18522
18523 let row_ranges_to_keep: Vec<Range<u32>> = self
18524 .selections
18525 .all::<Point>(&self.display_snapshot(cx))
18526 .into_iter()
18527 .map(|sel| sel.start.row..sel.end.row)
18528 .collect();
18529
18530 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18531 while start_row < end_row {
18532 match self
18533 .snapshot(window, cx)
18534 .crease_for_buffer_row(MultiBufferRow(start_row))
18535 {
18536 Some(crease) => {
18537 let nested_start_row = crease.range().start.row + 1;
18538 let nested_end_row = crease.range().end.row;
18539
18540 if current_level < fold_at_level {
18541 stack.push((nested_start_row, nested_end_row, current_level + 1));
18542 } else if current_level == fold_at_level {
18543 // Fold iff there is no selection completely contained within the fold region
18544 if !row_ranges_to_keep.iter().any(|selection| {
18545 selection.end >= nested_start_row
18546 && selection.start <= nested_end_row
18547 }) {
18548 to_fold.push(crease);
18549 }
18550 }
18551
18552 start_row = nested_end_row + 1;
18553 }
18554 None => start_row += 1,
18555 }
18556 }
18557 }
18558
18559 self.fold_creases(to_fold, true, window, cx);
18560 }
18561
18562 pub fn fold_at_level_1(
18563 &mut self,
18564 _: &actions::FoldAtLevel1,
18565 window: &mut Window,
18566 cx: &mut Context<Self>,
18567 ) {
18568 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18569 }
18570
18571 pub fn fold_at_level_2(
18572 &mut self,
18573 _: &actions::FoldAtLevel2,
18574 window: &mut Window,
18575 cx: &mut Context<Self>,
18576 ) {
18577 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18578 }
18579
18580 pub fn fold_at_level_3(
18581 &mut self,
18582 _: &actions::FoldAtLevel3,
18583 window: &mut Window,
18584 cx: &mut Context<Self>,
18585 ) {
18586 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18587 }
18588
18589 pub fn fold_at_level_4(
18590 &mut self,
18591 _: &actions::FoldAtLevel4,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18596 }
18597
18598 pub fn fold_at_level_5(
18599 &mut self,
18600 _: &actions::FoldAtLevel5,
18601 window: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18605 }
18606
18607 pub fn fold_at_level_6(
18608 &mut self,
18609 _: &actions::FoldAtLevel6,
18610 window: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18614 }
18615
18616 pub fn fold_at_level_7(
18617 &mut self,
18618 _: &actions::FoldAtLevel7,
18619 window: &mut Window,
18620 cx: &mut Context<Self>,
18621 ) {
18622 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18623 }
18624
18625 pub fn fold_at_level_8(
18626 &mut self,
18627 _: &actions::FoldAtLevel8,
18628 window: &mut Window,
18629 cx: &mut Context<Self>,
18630 ) {
18631 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18632 }
18633
18634 pub fn fold_at_level_9(
18635 &mut self,
18636 _: &actions::FoldAtLevel9,
18637 window: &mut Window,
18638 cx: &mut Context<Self>,
18639 ) {
18640 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18641 }
18642
18643 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18644 if self.buffer.read(cx).is_singleton() {
18645 let mut fold_ranges = Vec::new();
18646 let snapshot = self.buffer.read(cx).snapshot(cx);
18647
18648 for row in 0..snapshot.max_row().0 {
18649 if let Some(foldable_range) = self
18650 .snapshot(window, cx)
18651 .crease_for_buffer_row(MultiBufferRow(row))
18652 {
18653 fold_ranges.push(foldable_range);
18654 }
18655 }
18656
18657 self.fold_creases(fold_ranges, true, window, cx);
18658 } else {
18659 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18660 editor
18661 .update_in(cx, |editor, _, cx| {
18662 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18663 editor.fold_buffer(buffer_id, cx);
18664 }
18665 })
18666 .ok();
18667 });
18668 }
18669 }
18670
18671 pub fn fold_function_bodies(
18672 &mut self,
18673 _: &actions::FoldFunctionBodies,
18674 window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 let snapshot = self.buffer.read(cx).snapshot(cx);
18678
18679 let ranges = snapshot
18680 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18681 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18682 .collect::<Vec<_>>();
18683
18684 let creases = ranges
18685 .into_iter()
18686 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18687 .collect();
18688
18689 self.fold_creases(creases, true, window, cx);
18690 }
18691
18692 pub fn fold_recursive(
18693 &mut self,
18694 _: &actions::FoldRecursive,
18695 window: &mut Window,
18696 cx: &mut Context<Self>,
18697 ) {
18698 let mut to_fold = Vec::new();
18699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18700 let selections = self.selections.all_adjusted(&display_map);
18701
18702 for selection in selections {
18703 let range = selection.range().sorted();
18704 let buffer_start_row = range.start.row;
18705
18706 if range.start.row != range.end.row {
18707 let mut found = false;
18708 for row in range.start.row..=range.end.row {
18709 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18710 found = true;
18711 to_fold.push(crease);
18712 }
18713 }
18714 if found {
18715 continue;
18716 }
18717 }
18718
18719 for row in (0..=range.start.row).rev() {
18720 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18721 if crease.range().end.row >= buffer_start_row {
18722 to_fold.push(crease);
18723 } else {
18724 break;
18725 }
18726 }
18727 }
18728 }
18729
18730 self.fold_creases(to_fold, true, window, cx);
18731 }
18732
18733 pub fn fold_at(
18734 &mut self,
18735 buffer_row: MultiBufferRow,
18736 window: &mut Window,
18737 cx: &mut Context<Self>,
18738 ) {
18739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18740
18741 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18742 let autoscroll = self
18743 .selections
18744 .all::<Point>(&display_map)
18745 .iter()
18746 .any(|selection| crease.range().overlaps(&selection.range()));
18747
18748 self.fold_creases(vec![crease], autoscroll, window, cx);
18749 }
18750 }
18751
18752 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18753 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18754 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18755 let buffer = display_map.buffer_snapshot();
18756 let selections = self.selections.all::<Point>(&display_map);
18757 let ranges = selections
18758 .iter()
18759 .map(|s| {
18760 let range = s.display_range(&display_map).sorted();
18761 let mut start = range.start.to_point(&display_map);
18762 let mut end = range.end.to_point(&display_map);
18763 start.column = 0;
18764 end.column = buffer.line_len(MultiBufferRow(end.row));
18765 start..end
18766 })
18767 .collect::<Vec<_>>();
18768
18769 self.unfold_ranges(&ranges, true, true, cx);
18770 } else {
18771 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18772 let buffer_ids = self
18773 .selections
18774 .disjoint_anchor_ranges()
18775 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18776 .collect::<HashSet<_>>();
18777 for buffer_id in buffer_ids {
18778 self.unfold_buffer(buffer_id, cx);
18779 }
18780 }
18781 }
18782
18783 pub fn unfold_recursive(
18784 &mut self,
18785 _: &UnfoldRecursive,
18786 _window: &mut Window,
18787 cx: &mut Context<Self>,
18788 ) {
18789 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18790 let selections = self.selections.all::<Point>(&display_map);
18791 let ranges = selections
18792 .iter()
18793 .map(|s| {
18794 let mut range = s.display_range(&display_map).sorted();
18795 *range.start.column_mut() = 0;
18796 *range.end.column_mut() = display_map.line_len(range.end.row());
18797 let start = range.start.to_point(&display_map);
18798 let end = range.end.to_point(&display_map);
18799 start..end
18800 })
18801 .collect::<Vec<_>>();
18802
18803 self.unfold_ranges(&ranges, true, true, cx);
18804 }
18805
18806 pub fn unfold_at(
18807 &mut self,
18808 buffer_row: MultiBufferRow,
18809 _window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18813
18814 let intersection_range = Point::new(buffer_row.0, 0)
18815 ..Point::new(
18816 buffer_row.0,
18817 display_map.buffer_snapshot().line_len(buffer_row),
18818 );
18819
18820 let autoscroll = self
18821 .selections
18822 .all::<Point>(&display_map)
18823 .iter()
18824 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18825
18826 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18827 }
18828
18829 pub fn unfold_all(
18830 &mut self,
18831 _: &actions::UnfoldAll,
18832 _window: &mut Window,
18833 cx: &mut Context<Self>,
18834 ) {
18835 if self.buffer.read(cx).is_singleton() {
18836 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18837 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18838 } else {
18839 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18840 editor
18841 .update(cx, |editor, cx| {
18842 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18843 editor.unfold_buffer(buffer_id, cx);
18844 }
18845 })
18846 .ok();
18847 });
18848 }
18849 }
18850
18851 pub fn fold_selected_ranges(
18852 &mut self,
18853 _: &FoldSelectedRanges,
18854 window: &mut Window,
18855 cx: &mut Context<Self>,
18856 ) {
18857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18858 let selections = self.selections.all_adjusted(&display_map);
18859 let ranges = selections
18860 .into_iter()
18861 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18862 .collect::<Vec<_>>();
18863 self.fold_creases(ranges, true, window, cx);
18864 }
18865
18866 pub fn fold_ranges<T: ToOffset + Clone>(
18867 &mut self,
18868 ranges: Vec<Range<T>>,
18869 auto_scroll: bool,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18874 let ranges = ranges
18875 .into_iter()
18876 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18877 .collect::<Vec<_>>();
18878 self.fold_creases(ranges, auto_scroll, window, cx);
18879 }
18880
18881 pub fn fold_creases<T: ToOffset + Clone>(
18882 &mut self,
18883 creases: Vec<Crease<T>>,
18884 auto_scroll: bool,
18885 _window: &mut Window,
18886 cx: &mut Context<Self>,
18887 ) {
18888 if creases.is_empty() {
18889 return;
18890 }
18891
18892 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18893
18894 if auto_scroll {
18895 self.request_autoscroll(Autoscroll::fit(), cx);
18896 }
18897
18898 cx.notify();
18899
18900 self.scrollbar_marker_state.dirty = true;
18901 self.folds_did_change(cx);
18902 }
18903
18904 /// Removes any folds whose ranges intersect any of the given ranges.
18905 pub fn unfold_ranges<T: ToOffset + Clone>(
18906 &mut self,
18907 ranges: &[Range<T>],
18908 inclusive: bool,
18909 auto_scroll: bool,
18910 cx: &mut Context<Self>,
18911 ) {
18912 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18913 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18914 });
18915 self.folds_did_change(cx);
18916 }
18917
18918 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18919 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18920 return;
18921 }
18922
18923 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18924 self.display_map.update(cx, |display_map, cx| {
18925 display_map.fold_buffers([buffer_id], cx)
18926 });
18927
18928 let snapshot = self.display_snapshot(cx);
18929 self.selections.change_with(&snapshot, |selections| {
18930 selections.remove_selections_from_buffer(buffer_id);
18931 });
18932
18933 cx.emit(EditorEvent::BufferFoldToggled {
18934 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18935 folded: true,
18936 });
18937 cx.notify();
18938 }
18939
18940 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18941 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18942 return;
18943 }
18944 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18945 self.display_map.update(cx, |display_map, cx| {
18946 display_map.unfold_buffers([buffer_id], cx);
18947 });
18948 cx.emit(EditorEvent::BufferFoldToggled {
18949 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18950 folded: false,
18951 });
18952 cx.notify();
18953 }
18954
18955 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18956 self.display_map.read(cx).is_buffer_folded(buffer)
18957 }
18958
18959 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18960 self.display_map.read(cx).folded_buffers()
18961 }
18962
18963 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18964 self.display_map.update(cx, |display_map, cx| {
18965 display_map.disable_header_for_buffer(buffer_id, cx);
18966 });
18967 cx.notify();
18968 }
18969
18970 /// Removes any folds with the given ranges.
18971 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18972 &mut self,
18973 ranges: &[Range<T>],
18974 type_id: TypeId,
18975 auto_scroll: bool,
18976 cx: &mut Context<Self>,
18977 ) {
18978 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18979 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18980 });
18981 self.folds_did_change(cx);
18982 }
18983
18984 fn remove_folds_with<T: ToOffset + Clone>(
18985 &mut self,
18986 ranges: &[Range<T>],
18987 auto_scroll: bool,
18988 cx: &mut Context<Self>,
18989 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18990 ) {
18991 if ranges.is_empty() {
18992 return;
18993 }
18994
18995 let mut buffers_affected = HashSet::default();
18996 let multi_buffer = self.buffer().read(cx);
18997 for range in ranges {
18998 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18999 buffers_affected.insert(buffer.read(cx).remote_id());
19000 };
19001 }
19002
19003 self.display_map.update(cx, update);
19004
19005 if auto_scroll {
19006 self.request_autoscroll(Autoscroll::fit(), cx);
19007 }
19008
19009 cx.notify();
19010 self.scrollbar_marker_state.dirty = true;
19011 self.active_indent_guides_state.dirty = true;
19012 }
19013
19014 pub fn update_renderer_widths(
19015 &mut self,
19016 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19017 cx: &mut Context<Self>,
19018 ) -> bool {
19019 self.display_map
19020 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19021 }
19022
19023 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19024 self.display_map.read(cx).fold_placeholder.clone()
19025 }
19026
19027 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19028 self.buffer.update(cx, |buffer, cx| {
19029 buffer.set_all_diff_hunks_expanded(cx);
19030 });
19031 }
19032
19033 pub fn expand_all_diff_hunks(
19034 &mut self,
19035 _: &ExpandAllDiffHunks,
19036 _window: &mut Window,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.buffer.update(cx, |buffer, cx| {
19040 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19041 });
19042 }
19043
19044 pub fn collapse_all_diff_hunks(
19045 &mut self,
19046 _: &CollapseAllDiffHunks,
19047 _window: &mut Window,
19048 cx: &mut Context<Self>,
19049 ) {
19050 self.buffer.update(cx, |buffer, cx| {
19051 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19052 });
19053 }
19054
19055 pub fn toggle_selected_diff_hunks(
19056 &mut self,
19057 _: &ToggleSelectedDiffHunks,
19058 _window: &mut Window,
19059 cx: &mut Context<Self>,
19060 ) {
19061 let ranges: Vec<_> = self
19062 .selections
19063 .disjoint_anchors()
19064 .iter()
19065 .map(|s| s.range())
19066 .collect();
19067 self.toggle_diff_hunks_in_ranges(ranges, cx);
19068 }
19069
19070 pub fn diff_hunks_in_ranges<'a>(
19071 &'a self,
19072 ranges: &'a [Range<Anchor>],
19073 buffer: &'a MultiBufferSnapshot,
19074 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19075 ranges.iter().flat_map(move |range| {
19076 let end_excerpt_id = range.end.excerpt_id;
19077 let range = range.to_point(buffer);
19078 let mut peek_end = range.end;
19079 if range.end.row < buffer.max_row().0 {
19080 peek_end = Point::new(range.end.row + 1, 0);
19081 }
19082 buffer
19083 .diff_hunks_in_range(range.start..peek_end)
19084 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19085 })
19086 }
19087
19088 pub fn has_stageable_diff_hunks_in_ranges(
19089 &self,
19090 ranges: &[Range<Anchor>],
19091 snapshot: &MultiBufferSnapshot,
19092 ) -> bool {
19093 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19094 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19095 }
19096
19097 pub fn toggle_staged_selected_diff_hunks(
19098 &mut self,
19099 _: &::git::ToggleStaged,
19100 _: &mut Window,
19101 cx: &mut Context<Self>,
19102 ) {
19103 let snapshot = self.buffer.read(cx).snapshot(cx);
19104 let ranges: Vec<_> = self
19105 .selections
19106 .disjoint_anchors()
19107 .iter()
19108 .map(|s| s.range())
19109 .collect();
19110 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19111 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19112 }
19113
19114 pub fn set_render_diff_hunk_controls(
19115 &mut self,
19116 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19117 cx: &mut Context<Self>,
19118 ) {
19119 self.render_diff_hunk_controls = render_diff_hunk_controls;
19120 cx.notify();
19121 }
19122
19123 pub fn stage_and_next(
19124 &mut self,
19125 _: &::git::StageAndNext,
19126 window: &mut Window,
19127 cx: &mut Context<Self>,
19128 ) {
19129 self.do_stage_or_unstage_and_next(true, window, cx);
19130 }
19131
19132 pub fn unstage_and_next(
19133 &mut self,
19134 _: &::git::UnstageAndNext,
19135 window: &mut Window,
19136 cx: &mut Context<Self>,
19137 ) {
19138 self.do_stage_or_unstage_and_next(false, window, cx);
19139 }
19140
19141 pub fn stage_or_unstage_diff_hunks(
19142 &mut self,
19143 stage: bool,
19144 ranges: Vec<Range<Anchor>>,
19145 cx: &mut Context<Self>,
19146 ) {
19147 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19148 cx.spawn(async move |this, cx| {
19149 task.await?;
19150 this.update(cx, |this, cx| {
19151 let snapshot = this.buffer.read(cx).snapshot(cx);
19152 let chunk_by = this
19153 .diff_hunks_in_ranges(&ranges, &snapshot)
19154 .chunk_by(|hunk| hunk.buffer_id);
19155 for (buffer_id, hunks) in &chunk_by {
19156 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19157 }
19158 })
19159 })
19160 .detach_and_log_err(cx);
19161 }
19162
19163 fn save_buffers_for_ranges_if_needed(
19164 &mut self,
19165 ranges: &[Range<Anchor>],
19166 cx: &mut Context<Editor>,
19167 ) -> Task<Result<()>> {
19168 let multibuffer = self.buffer.read(cx);
19169 let snapshot = multibuffer.read(cx);
19170 let buffer_ids: HashSet<_> = ranges
19171 .iter()
19172 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19173 .collect();
19174 drop(snapshot);
19175
19176 let mut buffers = HashSet::default();
19177 for buffer_id in buffer_ids {
19178 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19179 let buffer = buffer_entity.read(cx);
19180 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19181 {
19182 buffers.insert(buffer_entity);
19183 }
19184 }
19185 }
19186
19187 if let Some(project) = &self.project {
19188 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19189 } else {
19190 Task::ready(Ok(()))
19191 }
19192 }
19193
19194 fn do_stage_or_unstage_and_next(
19195 &mut self,
19196 stage: bool,
19197 window: &mut Window,
19198 cx: &mut Context<Self>,
19199 ) {
19200 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19201
19202 if ranges.iter().any(|range| range.start != range.end) {
19203 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19204 return;
19205 }
19206
19207 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19208 let snapshot = self.snapshot(window, cx);
19209 let position = self
19210 .selections
19211 .newest::<Point>(&snapshot.display_snapshot)
19212 .head();
19213 let mut row = snapshot
19214 .buffer_snapshot()
19215 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19216 .find(|hunk| hunk.row_range.start.0 > position.row)
19217 .map(|hunk| hunk.row_range.start);
19218
19219 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19220 // Outside of the project diff editor, wrap around to the beginning.
19221 if !all_diff_hunks_expanded {
19222 row = row.or_else(|| {
19223 snapshot
19224 .buffer_snapshot()
19225 .diff_hunks_in_range(Point::zero()..position)
19226 .find(|hunk| hunk.row_range.end.0 < position.row)
19227 .map(|hunk| hunk.row_range.start)
19228 });
19229 }
19230
19231 if let Some(row) = row {
19232 let destination = Point::new(row.0, 0);
19233 let autoscroll = Autoscroll::center();
19234
19235 self.unfold_ranges(&[destination..destination], false, false, cx);
19236 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19237 s.select_ranges([destination..destination]);
19238 });
19239 }
19240 }
19241
19242 fn do_stage_or_unstage(
19243 &self,
19244 stage: bool,
19245 buffer_id: BufferId,
19246 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19247 cx: &mut App,
19248 ) -> Option<()> {
19249 let project = self.project()?;
19250 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19251 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19252 let buffer_snapshot = buffer.read(cx).snapshot();
19253 let file_exists = buffer_snapshot
19254 .file()
19255 .is_some_and(|file| file.disk_state().exists());
19256 diff.update(cx, |diff, cx| {
19257 diff.stage_or_unstage_hunks(
19258 stage,
19259 &hunks
19260 .map(|hunk| buffer_diff::DiffHunk {
19261 buffer_range: hunk.buffer_range,
19262 diff_base_byte_range: hunk.diff_base_byte_range,
19263 secondary_status: hunk.secondary_status,
19264 range: Point::zero()..Point::zero(), // unused
19265 })
19266 .collect::<Vec<_>>(),
19267 &buffer_snapshot,
19268 file_exists,
19269 cx,
19270 )
19271 });
19272 None
19273 }
19274
19275 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19276 let ranges: Vec<_> = self
19277 .selections
19278 .disjoint_anchors()
19279 .iter()
19280 .map(|s| s.range())
19281 .collect();
19282 self.buffer
19283 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19284 }
19285
19286 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19287 self.buffer.update(cx, |buffer, cx| {
19288 let ranges = vec![Anchor::min()..Anchor::max()];
19289 if !buffer.all_diff_hunks_expanded()
19290 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19291 {
19292 buffer.collapse_diff_hunks(ranges, cx);
19293 true
19294 } else {
19295 false
19296 }
19297 })
19298 }
19299
19300 fn toggle_diff_hunks_in_ranges(
19301 &mut self,
19302 ranges: Vec<Range<Anchor>>,
19303 cx: &mut Context<Editor>,
19304 ) {
19305 self.buffer.update(cx, |buffer, cx| {
19306 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19307 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19308 })
19309 }
19310
19311 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19312 self.buffer.update(cx, |buffer, cx| {
19313 let snapshot = buffer.snapshot(cx);
19314 let excerpt_id = range.end.excerpt_id;
19315 let point_range = range.to_point(&snapshot);
19316 let expand = !buffer.single_hunk_is_expanded(range, cx);
19317 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19318 })
19319 }
19320
19321 pub(crate) fn apply_all_diff_hunks(
19322 &mut self,
19323 _: &ApplyAllDiffHunks,
19324 window: &mut Window,
19325 cx: &mut Context<Self>,
19326 ) {
19327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19328
19329 let buffers = self.buffer.read(cx).all_buffers();
19330 for branch_buffer in buffers {
19331 branch_buffer.update(cx, |branch_buffer, cx| {
19332 branch_buffer.merge_into_base(Vec::new(), cx);
19333 });
19334 }
19335
19336 if let Some(project) = self.project.clone() {
19337 self.save(
19338 SaveOptions {
19339 format: true,
19340 autosave: false,
19341 },
19342 project,
19343 window,
19344 cx,
19345 )
19346 .detach_and_log_err(cx);
19347 }
19348 }
19349
19350 pub(crate) fn apply_selected_diff_hunks(
19351 &mut self,
19352 _: &ApplyDiffHunk,
19353 window: &mut Window,
19354 cx: &mut Context<Self>,
19355 ) {
19356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19357 let snapshot = self.snapshot(window, cx);
19358 let hunks = snapshot.hunks_for_ranges(
19359 self.selections
19360 .all(&snapshot.display_snapshot)
19361 .into_iter()
19362 .map(|selection| selection.range()),
19363 );
19364 let mut ranges_by_buffer = HashMap::default();
19365 self.transact(window, cx, |editor, _window, cx| {
19366 for hunk in hunks {
19367 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19368 ranges_by_buffer
19369 .entry(buffer.clone())
19370 .or_insert_with(Vec::new)
19371 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19372 }
19373 }
19374
19375 for (buffer, ranges) in ranges_by_buffer {
19376 buffer.update(cx, |buffer, cx| {
19377 buffer.merge_into_base(ranges, cx);
19378 });
19379 }
19380 });
19381
19382 if let Some(project) = self.project.clone() {
19383 self.save(
19384 SaveOptions {
19385 format: true,
19386 autosave: false,
19387 },
19388 project,
19389 window,
19390 cx,
19391 )
19392 .detach_and_log_err(cx);
19393 }
19394 }
19395
19396 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19397 if hovered != self.gutter_hovered {
19398 self.gutter_hovered = hovered;
19399 cx.notify();
19400 }
19401 }
19402
19403 pub fn insert_blocks(
19404 &mut self,
19405 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19406 autoscroll: Option<Autoscroll>,
19407 cx: &mut Context<Self>,
19408 ) -> Vec<CustomBlockId> {
19409 let blocks = self
19410 .display_map
19411 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19412 if let Some(autoscroll) = autoscroll {
19413 self.request_autoscroll(autoscroll, cx);
19414 }
19415 cx.notify();
19416 blocks
19417 }
19418
19419 pub fn resize_blocks(
19420 &mut self,
19421 heights: HashMap<CustomBlockId, u32>,
19422 autoscroll: Option<Autoscroll>,
19423 cx: &mut Context<Self>,
19424 ) {
19425 self.display_map
19426 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19427 if let Some(autoscroll) = autoscroll {
19428 self.request_autoscroll(autoscroll, cx);
19429 }
19430 cx.notify();
19431 }
19432
19433 pub fn replace_blocks(
19434 &mut self,
19435 renderers: HashMap<CustomBlockId, RenderBlock>,
19436 autoscroll: Option<Autoscroll>,
19437 cx: &mut Context<Self>,
19438 ) {
19439 self.display_map
19440 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19441 if let Some(autoscroll) = autoscroll {
19442 self.request_autoscroll(autoscroll, cx);
19443 }
19444 cx.notify();
19445 }
19446
19447 pub fn remove_blocks(
19448 &mut self,
19449 block_ids: HashSet<CustomBlockId>,
19450 autoscroll: Option<Autoscroll>,
19451 cx: &mut Context<Self>,
19452 ) {
19453 self.display_map.update(cx, |display_map, cx| {
19454 display_map.remove_blocks(block_ids, cx)
19455 });
19456 if let Some(autoscroll) = autoscroll {
19457 self.request_autoscroll(autoscroll, cx);
19458 }
19459 cx.notify();
19460 }
19461
19462 pub fn row_for_block(
19463 &self,
19464 block_id: CustomBlockId,
19465 cx: &mut Context<Self>,
19466 ) -> Option<DisplayRow> {
19467 self.display_map
19468 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19469 }
19470
19471 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19472 self.focused_block = Some(focused_block);
19473 }
19474
19475 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19476 self.focused_block.take()
19477 }
19478
19479 pub fn insert_creases(
19480 &mut self,
19481 creases: impl IntoIterator<Item = Crease<Anchor>>,
19482 cx: &mut Context<Self>,
19483 ) -> Vec<CreaseId> {
19484 self.display_map
19485 .update(cx, |map, cx| map.insert_creases(creases, cx))
19486 }
19487
19488 pub fn remove_creases(
19489 &mut self,
19490 ids: impl IntoIterator<Item = CreaseId>,
19491 cx: &mut Context<Self>,
19492 ) -> Vec<(CreaseId, Range<Anchor>)> {
19493 self.display_map
19494 .update(cx, |map, cx| map.remove_creases(ids, cx))
19495 }
19496
19497 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19498 self.display_map
19499 .update(cx, |map, cx| map.snapshot(cx))
19500 .longest_row()
19501 }
19502
19503 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19504 self.display_map
19505 .update(cx, |map, cx| map.snapshot(cx))
19506 .max_point()
19507 }
19508
19509 pub fn text(&self, cx: &App) -> String {
19510 self.buffer.read(cx).read(cx).text()
19511 }
19512
19513 pub fn is_empty(&self, cx: &App) -> bool {
19514 self.buffer.read(cx).read(cx).is_empty()
19515 }
19516
19517 pub fn text_option(&self, cx: &App) -> Option<String> {
19518 let text = self.text(cx);
19519 let text = text.trim();
19520
19521 if text.is_empty() {
19522 return None;
19523 }
19524
19525 Some(text.to_string())
19526 }
19527
19528 pub fn set_text(
19529 &mut self,
19530 text: impl Into<Arc<str>>,
19531 window: &mut Window,
19532 cx: &mut Context<Self>,
19533 ) {
19534 self.transact(window, cx, |this, _, cx| {
19535 this.buffer
19536 .read(cx)
19537 .as_singleton()
19538 .expect("you can only call set_text on editors for singleton buffers")
19539 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19540 });
19541 }
19542
19543 pub fn display_text(&self, cx: &mut App) -> String {
19544 self.display_map
19545 .update(cx, |map, cx| map.snapshot(cx))
19546 .text()
19547 }
19548
19549 fn create_minimap(
19550 &self,
19551 minimap_settings: MinimapSettings,
19552 window: &mut Window,
19553 cx: &mut Context<Self>,
19554 ) -> Option<Entity<Self>> {
19555 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19556 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19557 }
19558
19559 fn initialize_new_minimap(
19560 &self,
19561 minimap_settings: MinimapSettings,
19562 window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) -> Entity<Self> {
19565 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19566
19567 let mut minimap = Editor::new_internal(
19568 EditorMode::Minimap {
19569 parent: cx.weak_entity(),
19570 },
19571 self.buffer.clone(),
19572 None,
19573 Some(self.display_map.clone()),
19574 window,
19575 cx,
19576 );
19577 minimap.scroll_manager.clone_state(&self.scroll_manager);
19578 minimap.set_text_style_refinement(TextStyleRefinement {
19579 font_size: Some(MINIMAP_FONT_SIZE),
19580 font_weight: Some(MINIMAP_FONT_WEIGHT),
19581 ..Default::default()
19582 });
19583 minimap.update_minimap_configuration(minimap_settings, cx);
19584 cx.new(|_| minimap)
19585 }
19586
19587 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19588 let current_line_highlight = minimap_settings
19589 .current_line_highlight
19590 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19591 self.set_current_line_highlight(Some(current_line_highlight));
19592 }
19593
19594 pub fn minimap(&self) -> Option<&Entity<Self>> {
19595 self.minimap
19596 .as_ref()
19597 .filter(|_| self.minimap_visibility.visible())
19598 }
19599
19600 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19601 let mut wrap_guides = smallvec![];
19602
19603 if self.show_wrap_guides == Some(false) {
19604 return wrap_guides;
19605 }
19606
19607 let settings = self.buffer.read(cx).language_settings(cx);
19608 if settings.show_wrap_guides {
19609 match self.soft_wrap_mode(cx) {
19610 SoftWrap::Column(soft_wrap) => {
19611 wrap_guides.push((soft_wrap as usize, true));
19612 }
19613 SoftWrap::Bounded(soft_wrap) => {
19614 wrap_guides.push((soft_wrap as usize, true));
19615 }
19616 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19617 }
19618 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19619 }
19620
19621 wrap_guides
19622 }
19623
19624 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19625 let settings = self.buffer.read(cx).language_settings(cx);
19626 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19627 match mode {
19628 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19629 SoftWrap::None
19630 }
19631 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19632 language_settings::SoftWrap::PreferredLineLength => {
19633 SoftWrap::Column(settings.preferred_line_length)
19634 }
19635 language_settings::SoftWrap::Bounded => {
19636 SoftWrap::Bounded(settings.preferred_line_length)
19637 }
19638 }
19639 }
19640
19641 pub fn set_soft_wrap_mode(
19642 &mut self,
19643 mode: language_settings::SoftWrap,
19644
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.soft_wrap_mode_override = Some(mode);
19648 cx.notify();
19649 }
19650
19651 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19652 self.hard_wrap = hard_wrap;
19653 cx.notify();
19654 }
19655
19656 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19657 self.text_style_refinement = Some(style);
19658 }
19659
19660 /// called by the Element so we know what style we were most recently rendered with.
19661 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19662 // We intentionally do not inform the display map about the minimap style
19663 // so that wrapping is not recalculated and stays consistent for the editor
19664 // and its linked minimap.
19665 if !self.mode.is_minimap() {
19666 let font = style.text.font();
19667 let font_size = style.text.font_size.to_pixels(window.rem_size());
19668 let display_map = self
19669 .placeholder_display_map
19670 .as_ref()
19671 .filter(|_| self.is_empty(cx))
19672 .unwrap_or(&self.display_map);
19673
19674 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19675 }
19676 self.style = Some(style);
19677 }
19678
19679 pub fn style(&self) -> Option<&EditorStyle> {
19680 self.style.as_ref()
19681 }
19682
19683 // Called by the element. This method is not designed to be called outside of the editor
19684 // element's layout code because it does not notify when rewrapping is computed synchronously.
19685 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19686 if self.is_empty(cx) {
19687 self.placeholder_display_map
19688 .as_ref()
19689 .map_or(false, |display_map| {
19690 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19691 })
19692 } else {
19693 self.display_map
19694 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19695 }
19696 }
19697
19698 pub fn set_soft_wrap(&mut self) {
19699 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19700 }
19701
19702 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19703 if self.soft_wrap_mode_override.is_some() {
19704 self.soft_wrap_mode_override.take();
19705 } else {
19706 let soft_wrap = match self.soft_wrap_mode(cx) {
19707 SoftWrap::GitDiff => return,
19708 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19709 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19710 language_settings::SoftWrap::None
19711 }
19712 };
19713 self.soft_wrap_mode_override = Some(soft_wrap);
19714 }
19715 cx.notify();
19716 }
19717
19718 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19719 let Some(workspace) = self.workspace() else {
19720 return;
19721 };
19722 let fs = workspace.read(cx).app_state().fs.clone();
19723 let current_show = TabBarSettings::get_global(cx).show;
19724 update_settings_file(fs, cx, move |setting, _| {
19725 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19726 });
19727 }
19728
19729 pub fn toggle_indent_guides(
19730 &mut self,
19731 _: &ToggleIndentGuides,
19732 _: &mut Window,
19733 cx: &mut Context<Self>,
19734 ) {
19735 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19736 self.buffer
19737 .read(cx)
19738 .language_settings(cx)
19739 .indent_guides
19740 .enabled
19741 });
19742 self.show_indent_guides = Some(!currently_enabled);
19743 cx.notify();
19744 }
19745
19746 fn should_show_indent_guides(&self) -> Option<bool> {
19747 self.show_indent_guides
19748 }
19749
19750 pub fn toggle_line_numbers(
19751 &mut self,
19752 _: &ToggleLineNumbers,
19753 _: &mut Window,
19754 cx: &mut Context<Self>,
19755 ) {
19756 let mut editor_settings = EditorSettings::get_global(cx).clone();
19757 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19758 EditorSettings::override_global(editor_settings, cx);
19759 }
19760
19761 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19762 if let Some(show_line_numbers) = self.show_line_numbers {
19763 return show_line_numbers;
19764 }
19765 EditorSettings::get_global(cx).gutter.line_numbers
19766 }
19767
19768 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19769 match (
19770 self.use_relative_line_numbers,
19771 EditorSettings::get_global(cx).relative_line_numbers,
19772 ) {
19773 (None, setting) => setting,
19774 (Some(false), _) => RelativeLineNumbers::Disabled,
19775 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19776 (Some(true), _) => RelativeLineNumbers::Enabled,
19777 }
19778 }
19779
19780 pub fn toggle_relative_line_numbers(
19781 &mut self,
19782 _: &ToggleRelativeLineNumbers,
19783 _: &mut Window,
19784 cx: &mut Context<Self>,
19785 ) {
19786 let is_relative = self.relative_line_numbers(cx);
19787 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19788 }
19789
19790 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19791 self.use_relative_line_numbers = is_relative;
19792 cx.notify();
19793 }
19794
19795 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19796 self.show_gutter = show_gutter;
19797 cx.notify();
19798 }
19799
19800 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19801 self.show_scrollbars = ScrollbarAxes {
19802 horizontal: show,
19803 vertical: show,
19804 };
19805 cx.notify();
19806 }
19807
19808 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19809 self.show_scrollbars.vertical = show;
19810 cx.notify();
19811 }
19812
19813 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19814 self.show_scrollbars.horizontal = show;
19815 cx.notify();
19816 }
19817
19818 pub fn set_minimap_visibility(
19819 &mut self,
19820 minimap_visibility: MinimapVisibility,
19821 window: &mut Window,
19822 cx: &mut Context<Self>,
19823 ) {
19824 if self.minimap_visibility != minimap_visibility {
19825 if minimap_visibility.visible() && self.minimap.is_none() {
19826 let minimap_settings = EditorSettings::get_global(cx).minimap;
19827 self.minimap =
19828 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19829 }
19830 self.minimap_visibility = minimap_visibility;
19831 cx.notify();
19832 }
19833 }
19834
19835 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19836 self.set_show_scrollbars(false, cx);
19837 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19838 }
19839
19840 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19841 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19842 }
19843
19844 /// Normally the text in full mode and auto height editors is padded on the
19845 /// left side by roughly half a character width for improved hit testing.
19846 ///
19847 /// Use this method to disable this for cases where this is not wanted (e.g.
19848 /// if you want to align the editor text with some other text above or below)
19849 /// or if you want to add this padding to single-line editors.
19850 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19851 self.offset_content = offset_content;
19852 cx.notify();
19853 }
19854
19855 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19856 self.show_line_numbers = Some(show_line_numbers);
19857 cx.notify();
19858 }
19859
19860 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19861 self.disable_expand_excerpt_buttons = true;
19862 cx.notify();
19863 }
19864
19865 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19866 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19867 cx.notify();
19868 }
19869
19870 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19871 self.show_code_actions = Some(show_code_actions);
19872 cx.notify();
19873 }
19874
19875 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19876 self.show_runnables = Some(show_runnables);
19877 cx.notify();
19878 }
19879
19880 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19881 self.show_breakpoints = Some(show_breakpoints);
19882 cx.notify();
19883 }
19884
19885 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19886 if self.display_map.read(cx).masked != masked {
19887 self.display_map.update(cx, |map, _| map.masked = masked);
19888 }
19889 cx.notify()
19890 }
19891
19892 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19893 self.show_wrap_guides = Some(show_wrap_guides);
19894 cx.notify();
19895 }
19896
19897 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19898 self.show_indent_guides = Some(show_indent_guides);
19899 cx.notify();
19900 }
19901
19902 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19903 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19904 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19905 && let Some(dir) = file.abs_path(cx).parent()
19906 {
19907 return Some(dir.to_owned());
19908 }
19909 }
19910
19911 None
19912 }
19913
19914 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19915 self.active_excerpt(cx)?
19916 .1
19917 .read(cx)
19918 .file()
19919 .and_then(|f| f.as_local())
19920 }
19921
19922 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19923 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19924 let buffer = buffer.read(cx);
19925 if let Some(project_path) = buffer.project_path(cx) {
19926 let project = self.project()?.read(cx);
19927 project.absolute_path(&project_path, cx)
19928 } else {
19929 buffer
19930 .file()
19931 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19932 }
19933 })
19934 }
19935
19936 pub fn reveal_in_finder(
19937 &mut self,
19938 _: &RevealInFileManager,
19939 _window: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) {
19942 if let Some(target) = self.target_file(cx) {
19943 cx.reveal_path(&target.abs_path(cx));
19944 }
19945 }
19946
19947 pub fn copy_path(
19948 &mut self,
19949 _: &zed_actions::workspace::CopyPath,
19950 _window: &mut Window,
19951 cx: &mut Context<Self>,
19952 ) {
19953 if let Some(path) = self.target_file_abs_path(cx)
19954 && let Some(path) = path.to_str()
19955 {
19956 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19957 } else {
19958 cx.propagate();
19959 }
19960 }
19961
19962 pub fn copy_relative_path(
19963 &mut self,
19964 _: &zed_actions::workspace::CopyRelativePath,
19965 _window: &mut Window,
19966 cx: &mut Context<Self>,
19967 ) {
19968 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19969 let project = self.project()?.read(cx);
19970 let path = buffer.read(cx).file()?.path();
19971 let path = path.display(project.path_style(cx));
19972 Some(path)
19973 }) {
19974 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19975 } else {
19976 cx.propagate();
19977 }
19978 }
19979
19980 /// Returns the project path for the editor's buffer, if any buffer is
19981 /// opened in the editor.
19982 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19983 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19984 buffer.read(cx).project_path(cx)
19985 } else {
19986 None
19987 }
19988 }
19989
19990 // Returns true if the editor handled a go-to-line request
19991 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19992 maybe!({
19993 let breakpoint_store = self.breakpoint_store.as_ref()?;
19994
19995 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19996 else {
19997 self.clear_row_highlights::<ActiveDebugLine>();
19998 return None;
19999 };
20000
20001 let position = active_stack_frame.position;
20002 let buffer_id = position.buffer_id?;
20003 let snapshot = self
20004 .project
20005 .as_ref()?
20006 .read(cx)
20007 .buffer_for_id(buffer_id, cx)?
20008 .read(cx)
20009 .snapshot();
20010
20011 let mut handled = false;
20012 for (id, ExcerptRange { context, .. }) in
20013 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20014 {
20015 if context.start.cmp(&position, &snapshot).is_ge()
20016 || context.end.cmp(&position, &snapshot).is_lt()
20017 {
20018 continue;
20019 }
20020 let snapshot = self.buffer.read(cx).snapshot(cx);
20021 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20022
20023 handled = true;
20024 self.clear_row_highlights::<ActiveDebugLine>();
20025
20026 self.go_to_line::<ActiveDebugLine>(
20027 multibuffer_anchor,
20028 Some(cx.theme().colors().editor_debugger_active_line_background),
20029 window,
20030 cx,
20031 );
20032
20033 cx.notify();
20034 }
20035
20036 handled.then_some(())
20037 })
20038 .is_some()
20039 }
20040
20041 pub fn copy_file_name_without_extension(
20042 &mut self,
20043 _: &CopyFileNameWithoutExtension,
20044 _: &mut Window,
20045 cx: &mut Context<Self>,
20046 ) {
20047 if let Some(file) = self.target_file(cx)
20048 && let Some(file_stem) = file.path().file_stem()
20049 {
20050 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20051 }
20052 }
20053
20054 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20055 if let Some(file) = self.target_file(cx)
20056 && let Some(name) = file.path().file_name()
20057 {
20058 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20059 }
20060 }
20061
20062 pub fn toggle_git_blame(
20063 &mut self,
20064 _: &::git::Blame,
20065 window: &mut Window,
20066 cx: &mut Context<Self>,
20067 ) {
20068 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20069
20070 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20071 self.start_git_blame(true, window, cx);
20072 }
20073
20074 cx.notify();
20075 }
20076
20077 pub fn toggle_git_blame_inline(
20078 &mut self,
20079 _: &ToggleGitBlameInline,
20080 window: &mut Window,
20081 cx: &mut Context<Self>,
20082 ) {
20083 self.toggle_git_blame_inline_internal(true, window, cx);
20084 cx.notify();
20085 }
20086
20087 pub fn open_git_blame_commit(
20088 &mut self,
20089 _: &OpenGitBlameCommit,
20090 window: &mut Window,
20091 cx: &mut Context<Self>,
20092 ) {
20093 self.open_git_blame_commit_internal(window, cx);
20094 }
20095
20096 fn open_git_blame_commit_internal(
20097 &mut self,
20098 window: &mut Window,
20099 cx: &mut Context<Self>,
20100 ) -> Option<()> {
20101 let blame = self.blame.as_ref()?;
20102 let snapshot = self.snapshot(window, cx);
20103 let cursor = self
20104 .selections
20105 .newest::<Point>(&snapshot.display_snapshot)
20106 .head();
20107 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20108 let (_, blame_entry) = blame
20109 .update(cx, |blame, cx| {
20110 blame
20111 .blame_for_rows(
20112 &[RowInfo {
20113 buffer_id: Some(buffer.remote_id()),
20114 buffer_row: Some(point.row),
20115 ..Default::default()
20116 }],
20117 cx,
20118 )
20119 .next()
20120 })
20121 .flatten()?;
20122 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20123 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20124 let workspace = self.workspace()?.downgrade();
20125 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20126 None
20127 }
20128
20129 pub fn git_blame_inline_enabled(&self) -> bool {
20130 self.git_blame_inline_enabled
20131 }
20132
20133 pub fn toggle_selection_menu(
20134 &mut self,
20135 _: &ToggleSelectionMenu,
20136 _: &mut Window,
20137 cx: &mut Context<Self>,
20138 ) {
20139 self.show_selection_menu = self
20140 .show_selection_menu
20141 .map(|show_selections_menu| !show_selections_menu)
20142 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20143
20144 cx.notify();
20145 }
20146
20147 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20148 self.show_selection_menu
20149 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20150 }
20151
20152 fn start_git_blame(
20153 &mut self,
20154 user_triggered: bool,
20155 window: &mut Window,
20156 cx: &mut Context<Self>,
20157 ) {
20158 if let Some(project) = self.project() {
20159 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20160 && buffer.read(cx).file().is_none()
20161 {
20162 return;
20163 }
20164
20165 let focused = self.focus_handle(cx).contains_focused(window, cx);
20166
20167 let project = project.clone();
20168 let blame = cx
20169 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20170 self.blame_subscription =
20171 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20172 self.blame = Some(blame);
20173 }
20174 }
20175
20176 fn toggle_git_blame_inline_internal(
20177 &mut self,
20178 user_triggered: bool,
20179 window: &mut Window,
20180 cx: &mut Context<Self>,
20181 ) {
20182 if self.git_blame_inline_enabled {
20183 self.git_blame_inline_enabled = false;
20184 self.show_git_blame_inline = false;
20185 self.show_git_blame_inline_delay_task.take();
20186 } else {
20187 self.git_blame_inline_enabled = true;
20188 self.start_git_blame_inline(user_triggered, window, cx);
20189 }
20190
20191 cx.notify();
20192 }
20193
20194 fn start_git_blame_inline(
20195 &mut self,
20196 user_triggered: bool,
20197 window: &mut Window,
20198 cx: &mut Context<Self>,
20199 ) {
20200 self.start_git_blame(user_triggered, window, cx);
20201
20202 if ProjectSettings::get_global(cx)
20203 .git
20204 .inline_blame_delay()
20205 .is_some()
20206 {
20207 self.start_inline_blame_timer(window, cx);
20208 } else {
20209 self.show_git_blame_inline = true
20210 }
20211 }
20212
20213 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20214 self.blame.as_ref()
20215 }
20216
20217 pub fn show_git_blame_gutter(&self) -> bool {
20218 self.show_git_blame_gutter
20219 }
20220
20221 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20222 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20223 }
20224
20225 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20226 self.show_git_blame_inline
20227 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20228 && !self.newest_selection_head_on_empty_line(cx)
20229 && self.has_blame_entries(cx)
20230 }
20231
20232 fn has_blame_entries(&self, cx: &App) -> bool {
20233 self.blame()
20234 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20235 }
20236
20237 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20238 let cursor_anchor = self.selections.newest_anchor().head();
20239
20240 let snapshot = self.buffer.read(cx).snapshot(cx);
20241 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20242
20243 snapshot.line_len(buffer_row) == 0
20244 }
20245
20246 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20247 let buffer_and_selection = maybe!({
20248 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20249 let selection_range = selection.range();
20250
20251 let multi_buffer = self.buffer().read(cx);
20252 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20253 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20254
20255 let (buffer, range, _) = if selection.reversed {
20256 buffer_ranges.first()
20257 } else {
20258 buffer_ranges.last()
20259 }?;
20260
20261 let selection = text::ToPoint::to_point(&range.start, buffer).row
20262 ..text::ToPoint::to_point(&range.end, buffer).row;
20263 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20264 });
20265
20266 let Some((buffer, selection)) = buffer_and_selection else {
20267 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20268 };
20269
20270 let Some(project) = self.project() else {
20271 return Task::ready(Err(anyhow!("editor does not have project")));
20272 };
20273
20274 project.update(cx, |project, cx| {
20275 project.get_permalink_to_line(&buffer, selection, cx)
20276 })
20277 }
20278
20279 pub fn copy_permalink_to_line(
20280 &mut self,
20281 _: &CopyPermalinkToLine,
20282 window: &mut Window,
20283 cx: &mut Context<Self>,
20284 ) {
20285 let permalink_task = self.get_permalink_to_line(cx);
20286 let workspace = self.workspace();
20287
20288 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20289 Ok(permalink) => {
20290 cx.update(|_, cx| {
20291 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20292 })
20293 .ok();
20294 }
20295 Err(err) => {
20296 let message = format!("Failed to copy permalink: {err}");
20297
20298 anyhow::Result::<()>::Err(err).log_err();
20299
20300 if let Some(workspace) = workspace {
20301 workspace
20302 .update_in(cx, |workspace, _, cx| {
20303 struct CopyPermalinkToLine;
20304
20305 workspace.show_toast(
20306 Toast::new(
20307 NotificationId::unique::<CopyPermalinkToLine>(),
20308 message,
20309 ),
20310 cx,
20311 )
20312 })
20313 .ok();
20314 }
20315 }
20316 })
20317 .detach();
20318 }
20319
20320 pub fn copy_file_location(
20321 &mut self,
20322 _: &CopyFileLocation,
20323 _: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) {
20326 let selection = self
20327 .selections
20328 .newest::<Point>(&self.display_snapshot(cx))
20329 .start
20330 .row
20331 + 1;
20332 if let Some(file) = self.target_file(cx) {
20333 let path = file.path().display(file.path_style(cx));
20334 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20335 }
20336 }
20337
20338 pub fn open_permalink_to_line(
20339 &mut self,
20340 _: &OpenPermalinkToLine,
20341 window: &mut Window,
20342 cx: &mut Context<Self>,
20343 ) {
20344 let permalink_task = self.get_permalink_to_line(cx);
20345 let workspace = self.workspace();
20346
20347 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20348 Ok(permalink) => {
20349 cx.update(|_, cx| {
20350 cx.open_url(permalink.as_ref());
20351 })
20352 .ok();
20353 }
20354 Err(err) => {
20355 let message = format!("Failed to open permalink: {err}");
20356
20357 anyhow::Result::<()>::Err(err).log_err();
20358
20359 if let Some(workspace) = workspace {
20360 workspace
20361 .update(cx, |workspace, cx| {
20362 struct OpenPermalinkToLine;
20363
20364 workspace.show_toast(
20365 Toast::new(
20366 NotificationId::unique::<OpenPermalinkToLine>(),
20367 message,
20368 ),
20369 cx,
20370 )
20371 })
20372 .ok();
20373 }
20374 }
20375 })
20376 .detach();
20377 }
20378
20379 pub fn insert_uuid_v4(
20380 &mut self,
20381 _: &InsertUuidV4,
20382 window: &mut Window,
20383 cx: &mut Context<Self>,
20384 ) {
20385 self.insert_uuid(UuidVersion::V4, window, cx);
20386 }
20387
20388 pub fn insert_uuid_v7(
20389 &mut self,
20390 _: &InsertUuidV7,
20391 window: &mut Window,
20392 cx: &mut Context<Self>,
20393 ) {
20394 self.insert_uuid(UuidVersion::V7, window, cx);
20395 }
20396
20397 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20399 self.transact(window, cx, |this, window, cx| {
20400 let edits = this
20401 .selections
20402 .all::<Point>(&this.display_snapshot(cx))
20403 .into_iter()
20404 .map(|selection| {
20405 let uuid = match version {
20406 UuidVersion::V4 => uuid::Uuid::new_v4(),
20407 UuidVersion::V7 => uuid::Uuid::now_v7(),
20408 };
20409
20410 (selection.range(), uuid.to_string())
20411 });
20412 this.edit(edits, cx);
20413 this.refresh_edit_prediction(true, false, window, cx);
20414 });
20415 }
20416
20417 pub fn open_selections_in_multibuffer(
20418 &mut self,
20419 _: &OpenSelectionsInMultibuffer,
20420 window: &mut Window,
20421 cx: &mut Context<Self>,
20422 ) {
20423 let multibuffer = self.buffer.read(cx);
20424
20425 let Some(buffer) = multibuffer.as_singleton() else {
20426 return;
20427 };
20428
20429 let Some(workspace) = self.workspace() else {
20430 return;
20431 };
20432
20433 let title = multibuffer.title(cx).to_string();
20434
20435 let locations = self
20436 .selections
20437 .all_anchors(&self.display_snapshot(cx))
20438 .iter()
20439 .map(|selection| {
20440 (
20441 buffer.clone(),
20442 (selection.start.text_anchor..selection.end.text_anchor)
20443 .to_point(buffer.read(cx)),
20444 )
20445 })
20446 .into_group_map();
20447
20448 cx.spawn_in(window, async move |_, cx| {
20449 workspace.update_in(cx, |workspace, window, cx| {
20450 Self::open_locations_in_multibuffer(
20451 workspace,
20452 locations,
20453 format!("Selections for '{title}'"),
20454 false,
20455 MultibufferSelectionMode::All,
20456 window,
20457 cx,
20458 );
20459 })
20460 })
20461 .detach();
20462 }
20463
20464 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20465 /// last highlight added will be used.
20466 ///
20467 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20468 pub fn highlight_rows<T: 'static>(
20469 &mut self,
20470 range: Range<Anchor>,
20471 color: Hsla,
20472 options: RowHighlightOptions,
20473 cx: &mut Context<Self>,
20474 ) {
20475 let snapshot = self.buffer().read(cx).snapshot(cx);
20476 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20477 let ix = row_highlights.binary_search_by(|highlight| {
20478 Ordering::Equal
20479 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20480 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20481 });
20482
20483 if let Err(mut ix) = ix {
20484 let index = post_inc(&mut self.highlight_order);
20485
20486 // If this range intersects with the preceding highlight, then merge it with
20487 // the preceding highlight. Otherwise insert a new highlight.
20488 let mut merged = false;
20489 if ix > 0 {
20490 let prev_highlight = &mut row_highlights[ix - 1];
20491 if prev_highlight
20492 .range
20493 .end
20494 .cmp(&range.start, &snapshot)
20495 .is_ge()
20496 {
20497 ix -= 1;
20498 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20499 prev_highlight.range.end = range.end;
20500 }
20501 merged = true;
20502 prev_highlight.index = index;
20503 prev_highlight.color = color;
20504 prev_highlight.options = options;
20505 }
20506 }
20507
20508 if !merged {
20509 row_highlights.insert(
20510 ix,
20511 RowHighlight {
20512 range,
20513 index,
20514 color,
20515 options,
20516 type_id: TypeId::of::<T>(),
20517 },
20518 );
20519 }
20520
20521 // If any of the following highlights intersect with this one, merge them.
20522 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20523 let highlight = &row_highlights[ix];
20524 if next_highlight
20525 .range
20526 .start
20527 .cmp(&highlight.range.end, &snapshot)
20528 .is_le()
20529 {
20530 if next_highlight
20531 .range
20532 .end
20533 .cmp(&highlight.range.end, &snapshot)
20534 .is_gt()
20535 {
20536 row_highlights[ix].range.end = next_highlight.range.end;
20537 }
20538 row_highlights.remove(ix + 1);
20539 } else {
20540 break;
20541 }
20542 }
20543 }
20544 }
20545
20546 /// Remove any highlighted row ranges of the given type that intersect the
20547 /// given ranges.
20548 pub fn remove_highlighted_rows<T: 'static>(
20549 &mut self,
20550 ranges_to_remove: Vec<Range<Anchor>>,
20551 cx: &mut Context<Self>,
20552 ) {
20553 let snapshot = self.buffer().read(cx).snapshot(cx);
20554 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20555 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20556 row_highlights.retain(|highlight| {
20557 while let Some(range_to_remove) = ranges_to_remove.peek() {
20558 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20559 Ordering::Less | Ordering::Equal => {
20560 ranges_to_remove.next();
20561 }
20562 Ordering::Greater => {
20563 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20564 Ordering::Less | Ordering::Equal => {
20565 return false;
20566 }
20567 Ordering::Greater => break,
20568 }
20569 }
20570 }
20571 }
20572
20573 true
20574 })
20575 }
20576
20577 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20578 pub fn clear_row_highlights<T: 'static>(&mut self) {
20579 self.highlighted_rows.remove(&TypeId::of::<T>());
20580 }
20581
20582 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20583 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20584 self.highlighted_rows
20585 .get(&TypeId::of::<T>())
20586 .map_or(&[] as &[_], |vec| vec.as_slice())
20587 .iter()
20588 .map(|highlight| (highlight.range.clone(), highlight.color))
20589 }
20590
20591 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20592 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20593 /// Allows to ignore certain kinds of highlights.
20594 pub fn highlighted_display_rows(
20595 &self,
20596 window: &mut Window,
20597 cx: &mut App,
20598 ) -> BTreeMap<DisplayRow, LineHighlight> {
20599 let snapshot = self.snapshot(window, cx);
20600 let mut used_highlight_orders = HashMap::default();
20601 self.highlighted_rows
20602 .iter()
20603 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20604 .fold(
20605 BTreeMap::<DisplayRow, LineHighlight>::new(),
20606 |mut unique_rows, highlight| {
20607 let start = highlight.range.start.to_display_point(&snapshot);
20608 let end = highlight.range.end.to_display_point(&snapshot);
20609 let start_row = start.row().0;
20610 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20611 && end.column() == 0
20612 {
20613 end.row().0.saturating_sub(1)
20614 } else {
20615 end.row().0
20616 };
20617 for row in start_row..=end_row {
20618 let used_index =
20619 used_highlight_orders.entry(row).or_insert(highlight.index);
20620 if highlight.index >= *used_index {
20621 *used_index = highlight.index;
20622 unique_rows.insert(
20623 DisplayRow(row),
20624 LineHighlight {
20625 include_gutter: highlight.options.include_gutter,
20626 border: None,
20627 background: highlight.color.into(),
20628 type_id: Some(highlight.type_id),
20629 },
20630 );
20631 }
20632 }
20633 unique_rows
20634 },
20635 )
20636 }
20637
20638 pub fn highlighted_display_row_for_autoscroll(
20639 &self,
20640 snapshot: &DisplaySnapshot,
20641 ) -> Option<DisplayRow> {
20642 self.highlighted_rows
20643 .values()
20644 .flat_map(|highlighted_rows| highlighted_rows.iter())
20645 .filter_map(|highlight| {
20646 if highlight.options.autoscroll {
20647 Some(highlight.range.start.to_display_point(snapshot).row())
20648 } else {
20649 None
20650 }
20651 })
20652 .min()
20653 }
20654
20655 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20656 self.highlight_background::<SearchWithinRange>(
20657 ranges,
20658 |colors| colors.colors().editor_document_highlight_read_background,
20659 cx,
20660 )
20661 }
20662
20663 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20664 self.breadcrumb_header = Some(new_header);
20665 }
20666
20667 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20668 self.clear_background_highlights::<SearchWithinRange>(cx);
20669 }
20670
20671 pub fn highlight_background<T: 'static>(
20672 &mut self,
20673 ranges: &[Range<Anchor>],
20674 color_fetcher: fn(&Theme) -> Hsla,
20675 cx: &mut Context<Self>,
20676 ) {
20677 self.background_highlights.insert(
20678 HighlightKey::Type(TypeId::of::<T>()),
20679 (color_fetcher, Arc::from(ranges)),
20680 );
20681 self.scrollbar_marker_state.dirty = true;
20682 cx.notify();
20683 }
20684
20685 pub fn highlight_background_key<T: 'static>(
20686 &mut self,
20687 key: usize,
20688 ranges: &[Range<Anchor>],
20689 color_fetcher: fn(&Theme) -> Hsla,
20690 cx: &mut Context<Self>,
20691 ) {
20692 self.background_highlights.insert(
20693 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20694 (color_fetcher, Arc::from(ranges)),
20695 );
20696 self.scrollbar_marker_state.dirty = true;
20697 cx.notify();
20698 }
20699
20700 pub fn clear_background_highlights<T: 'static>(
20701 &mut self,
20702 cx: &mut Context<Self>,
20703 ) -> Option<BackgroundHighlight> {
20704 let text_highlights = self
20705 .background_highlights
20706 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20707 if !text_highlights.1.is_empty() {
20708 self.scrollbar_marker_state.dirty = true;
20709 cx.notify();
20710 }
20711 Some(text_highlights)
20712 }
20713
20714 pub fn highlight_gutter<T: 'static>(
20715 &mut self,
20716 ranges: impl Into<Vec<Range<Anchor>>>,
20717 color_fetcher: fn(&App) -> Hsla,
20718 cx: &mut Context<Self>,
20719 ) {
20720 self.gutter_highlights
20721 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20722 cx.notify();
20723 }
20724
20725 pub fn clear_gutter_highlights<T: 'static>(
20726 &mut self,
20727 cx: &mut Context<Self>,
20728 ) -> Option<GutterHighlight> {
20729 cx.notify();
20730 self.gutter_highlights.remove(&TypeId::of::<T>())
20731 }
20732
20733 pub fn insert_gutter_highlight<T: 'static>(
20734 &mut self,
20735 range: Range<Anchor>,
20736 color_fetcher: fn(&App) -> Hsla,
20737 cx: &mut Context<Self>,
20738 ) {
20739 let snapshot = self.buffer().read(cx).snapshot(cx);
20740 let mut highlights = self
20741 .gutter_highlights
20742 .remove(&TypeId::of::<T>())
20743 .map(|(_, highlights)| highlights)
20744 .unwrap_or_default();
20745 let ix = highlights.binary_search_by(|highlight| {
20746 Ordering::Equal
20747 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20748 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20749 });
20750 if let Err(ix) = ix {
20751 highlights.insert(ix, range);
20752 }
20753 self.gutter_highlights
20754 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20755 }
20756
20757 pub fn remove_gutter_highlights<T: 'static>(
20758 &mut self,
20759 ranges_to_remove: Vec<Range<Anchor>>,
20760 cx: &mut Context<Self>,
20761 ) {
20762 let snapshot = self.buffer().read(cx).snapshot(cx);
20763 let Some((color_fetcher, mut gutter_highlights)) =
20764 self.gutter_highlights.remove(&TypeId::of::<T>())
20765 else {
20766 return;
20767 };
20768 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20769 gutter_highlights.retain(|highlight| {
20770 while let Some(range_to_remove) = ranges_to_remove.peek() {
20771 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20772 Ordering::Less | Ordering::Equal => {
20773 ranges_to_remove.next();
20774 }
20775 Ordering::Greater => {
20776 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20777 Ordering::Less | Ordering::Equal => {
20778 return false;
20779 }
20780 Ordering::Greater => break,
20781 }
20782 }
20783 }
20784 }
20785
20786 true
20787 });
20788 self.gutter_highlights
20789 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20790 }
20791
20792 #[cfg(feature = "test-support")]
20793 pub fn all_text_highlights(
20794 &self,
20795 window: &mut Window,
20796 cx: &mut Context<Self>,
20797 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20798 let snapshot = self.snapshot(window, cx);
20799 self.display_map.update(cx, |display_map, _| {
20800 display_map
20801 .all_text_highlights()
20802 .map(|highlight| {
20803 let (style, ranges) = highlight.as_ref();
20804 (
20805 *style,
20806 ranges
20807 .iter()
20808 .map(|range| range.clone().to_display_points(&snapshot))
20809 .collect(),
20810 )
20811 })
20812 .collect()
20813 })
20814 }
20815
20816 #[cfg(feature = "test-support")]
20817 pub fn all_text_background_highlights(
20818 &self,
20819 window: &mut Window,
20820 cx: &mut Context<Self>,
20821 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20822 let snapshot = self.snapshot(window, cx);
20823 let buffer = &snapshot.buffer_snapshot();
20824 let start = buffer.anchor_before(0);
20825 let end = buffer.anchor_after(buffer.len());
20826 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20827 }
20828
20829 #[cfg(any(test, feature = "test-support"))]
20830 pub fn sorted_background_highlights_in_range(
20831 &self,
20832 search_range: Range<Anchor>,
20833 display_snapshot: &DisplaySnapshot,
20834 theme: &Theme,
20835 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20836 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20837 res.sort_by(|a, b| {
20838 a.0.start
20839 .cmp(&b.0.start)
20840 .then_with(|| a.0.end.cmp(&b.0.end))
20841 .then_with(|| a.1.cmp(&b.1))
20842 });
20843 res
20844 }
20845
20846 #[cfg(feature = "test-support")]
20847 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20848 let snapshot = self.buffer().read(cx).snapshot(cx);
20849
20850 let highlights = self
20851 .background_highlights
20852 .get(&HighlightKey::Type(TypeId::of::<
20853 items::BufferSearchHighlights,
20854 >()));
20855
20856 if let Some((_color, ranges)) = highlights {
20857 ranges
20858 .iter()
20859 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20860 .collect_vec()
20861 } else {
20862 vec![]
20863 }
20864 }
20865
20866 fn document_highlights_for_position<'a>(
20867 &'a self,
20868 position: Anchor,
20869 buffer: &'a MultiBufferSnapshot,
20870 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20871 let read_highlights = self
20872 .background_highlights
20873 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20874 .map(|h| &h.1);
20875 let write_highlights = self
20876 .background_highlights
20877 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20878 .map(|h| &h.1);
20879 let left_position = position.bias_left(buffer);
20880 let right_position = position.bias_right(buffer);
20881 read_highlights
20882 .into_iter()
20883 .chain(write_highlights)
20884 .flat_map(move |ranges| {
20885 let start_ix = match ranges.binary_search_by(|probe| {
20886 let cmp = probe.end.cmp(&left_position, buffer);
20887 if cmp.is_ge() {
20888 Ordering::Greater
20889 } else {
20890 Ordering::Less
20891 }
20892 }) {
20893 Ok(i) | Err(i) => i,
20894 };
20895
20896 ranges[start_ix..]
20897 .iter()
20898 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20899 })
20900 }
20901
20902 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20903 self.background_highlights
20904 .get(&HighlightKey::Type(TypeId::of::<T>()))
20905 .is_some_and(|(_, highlights)| !highlights.is_empty())
20906 }
20907
20908 /// Returns all background highlights for a given range.
20909 ///
20910 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20911 pub fn background_highlights_in_range(
20912 &self,
20913 search_range: Range<Anchor>,
20914 display_snapshot: &DisplaySnapshot,
20915 theme: &Theme,
20916 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20917 let mut results = Vec::new();
20918 for (color_fetcher, ranges) in self.background_highlights.values() {
20919 let color = color_fetcher(theme);
20920 let start_ix = match ranges.binary_search_by(|probe| {
20921 let cmp = probe
20922 .end
20923 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20924 if cmp.is_gt() {
20925 Ordering::Greater
20926 } else {
20927 Ordering::Less
20928 }
20929 }) {
20930 Ok(i) | Err(i) => i,
20931 };
20932 for range in &ranges[start_ix..] {
20933 if range
20934 .start
20935 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20936 .is_ge()
20937 {
20938 break;
20939 }
20940
20941 let start = range.start.to_display_point(display_snapshot);
20942 let end = range.end.to_display_point(display_snapshot);
20943 results.push((start..end, color))
20944 }
20945 }
20946 results
20947 }
20948
20949 pub fn gutter_highlights_in_range(
20950 &self,
20951 search_range: Range<Anchor>,
20952 display_snapshot: &DisplaySnapshot,
20953 cx: &App,
20954 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20955 let mut results = Vec::new();
20956 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20957 let color = color_fetcher(cx);
20958 let start_ix = match ranges.binary_search_by(|probe| {
20959 let cmp = probe
20960 .end
20961 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20962 if cmp.is_gt() {
20963 Ordering::Greater
20964 } else {
20965 Ordering::Less
20966 }
20967 }) {
20968 Ok(i) | Err(i) => i,
20969 };
20970 for range in &ranges[start_ix..] {
20971 if range
20972 .start
20973 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20974 .is_ge()
20975 {
20976 break;
20977 }
20978
20979 let start = range.start.to_display_point(display_snapshot);
20980 let end = range.end.to_display_point(display_snapshot);
20981 results.push((start..end, color))
20982 }
20983 }
20984 results
20985 }
20986
20987 /// Get the text ranges corresponding to the redaction query
20988 pub fn redacted_ranges(
20989 &self,
20990 search_range: Range<Anchor>,
20991 display_snapshot: &DisplaySnapshot,
20992 cx: &App,
20993 ) -> Vec<Range<DisplayPoint>> {
20994 display_snapshot
20995 .buffer_snapshot()
20996 .redacted_ranges(search_range, |file| {
20997 if let Some(file) = file {
20998 file.is_private()
20999 && EditorSettings::get(
21000 Some(SettingsLocation {
21001 worktree_id: file.worktree_id(cx),
21002 path: file.path().as_ref(),
21003 }),
21004 cx,
21005 )
21006 .redact_private_values
21007 } else {
21008 false
21009 }
21010 })
21011 .map(|range| {
21012 range.start.to_display_point(display_snapshot)
21013 ..range.end.to_display_point(display_snapshot)
21014 })
21015 .collect()
21016 }
21017
21018 pub fn highlight_text_key<T: 'static>(
21019 &mut self,
21020 key: usize,
21021 ranges: Vec<Range<Anchor>>,
21022 style: HighlightStyle,
21023 cx: &mut Context<Self>,
21024 ) {
21025 self.display_map.update(cx, |map, _| {
21026 map.highlight_text(
21027 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21028 ranges,
21029 style,
21030 );
21031 });
21032 cx.notify();
21033 }
21034
21035 pub fn highlight_text<T: 'static>(
21036 &mut self,
21037 ranges: Vec<Range<Anchor>>,
21038 style: HighlightStyle,
21039 cx: &mut Context<Self>,
21040 ) {
21041 self.display_map.update(cx, |map, _| {
21042 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21043 });
21044 cx.notify();
21045 }
21046
21047 pub fn text_highlights<'a, T: 'static>(
21048 &'a self,
21049 cx: &'a App,
21050 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21051 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21052 }
21053
21054 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21055 let cleared = self
21056 .display_map
21057 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21058 if cleared {
21059 cx.notify();
21060 }
21061 }
21062
21063 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21064 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21065 && self.focus_handle.is_focused(window)
21066 }
21067
21068 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21069 self.show_cursor_when_unfocused = is_enabled;
21070 cx.notify();
21071 }
21072
21073 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21074 cx.notify();
21075 }
21076
21077 fn on_debug_session_event(
21078 &mut self,
21079 _session: Entity<Session>,
21080 event: &SessionEvent,
21081 cx: &mut Context<Self>,
21082 ) {
21083 if let SessionEvent::InvalidateInlineValue = event {
21084 self.refresh_inline_values(cx);
21085 }
21086 }
21087
21088 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21089 let Some(project) = self.project.clone() else {
21090 return;
21091 };
21092
21093 if !self.inline_value_cache.enabled {
21094 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21095 self.splice_inlays(&inlays, Vec::new(), cx);
21096 return;
21097 }
21098
21099 let current_execution_position = self
21100 .highlighted_rows
21101 .get(&TypeId::of::<ActiveDebugLine>())
21102 .and_then(|lines| lines.last().map(|line| line.range.end));
21103
21104 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21105 let inline_values = editor
21106 .update(cx, |editor, cx| {
21107 let Some(current_execution_position) = current_execution_position else {
21108 return Some(Task::ready(Ok(Vec::new())));
21109 };
21110
21111 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21112 let snapshot = buffer.snapshot(cx);
21113
21114 let excerpt = snapshot.excerpt_containing(
21115 current_execution_position..current_execution_position,
21116 )?;
21117
21118 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21119 })?;
21120
21121 let range =
21122 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21123
21124 project.inline_values(buffer, range, cx)
21125 })
21126 .ok()
21127 .flatten()?
21128 .await
21129 .context("refreshing debugger inlays")
21130 .log_err()?;
21131
21132 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21133
21134 for (buffer_id, inline_value) in inline_values
21135 .into_iter()
21136 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21137 {
21138 buffer_inline_values
21139 .entry(buffer_id)
21140 .or_default()
21141 .push(inline_value);
21142 }
21143
21144 editor
21145 .update(cx, |editor, cx| {
21146 let snapshot = editor.buffer.read(cx).snapshot(cx);
21147 let mut new_inlays = Vec::default();
21148
21149 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21150 let buffer_id = buffer_snapshot.remote_id();
21151 buffer_inline_values
21152 .get(&buffer_id)
21153 .into_iter()
21154 .flatten()
21155 .for_each(|hint| {
21156 let inlay = Inlay::debugger(
21157 post_inc(&mut editor.next_inlay_id),
21158 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21159 hint.text(),
21160 );
21161 if !inlay.text().chars().contains(&'\n') {
21162 new_inlays.push(inlay);
21163 }
21164 });
21165 }
21166
21167 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21168 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21169
21170 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21171 })
21172 .ok()?;
21173 Some(())
21174 });
21175 }
21176
21177 fn on_buffer_event(
21178 &mut self,
21179 multibuffer: &Entity<MultiBuffer>,
21180 event: &multi_buffer::Event,
21181 window: &mut Window,
21182 cx: &mut Context<Self>,
21183 ) {
21184 match event {
21185 multi_buffer::Event::Edited { edited_buffer } => {
21186 self.scrollbar_marker_state.dirty = true;
21187 self.active_indent_guides_state.dirty = true;
21188 self.refresh_active_diagnostics(cx);
21189 self.refresh_code_actions(window, cx);
21190 self.refresh_selected_text_highlights(true, window, cx);
21191 self.refresh_single_line_folds(window, cx);
21192 self.refresh_matching_bracket_highlights(window, cx);
21193 if self.has_active_edit_prediction() {
21194 self.update_visible_edit_prediction(window, cx);
21195 }
21196
21197 if let Some(buffer) = edited_buffer {
21198 if buffer.read(cx).file().is_none() {
21199 cx.emit(EditorEvent::TitleChanged);
21200 }
21201
21202 if self.project.is_some() {
21203 let buffer_id = buffer.read(cx).remote_id();
21204 self.register_buffer(buffer_id, cx);
21205 self.update_lsp_data(Some(buffer_id), window, cx);
21206 self.refresh_inlay_hints(
21207 InlayHintRefreshReason::BufferEdited(buffer_id),
21208 cx,
21209 );
21210 }
21211 }
21212
21213 cx.emit(EditorEvent::BufferEdited);
21214 cx.emit(SearchEvent::MatchesInvalidated);
21215
21216 let Some(project) = &self.project else { return };
21217 let (telemetry, is_via_ssh) = {
21218 let project = project.read(cx);
21219 let telemetry = project.client().telemetry().clone();
21220 let is_via_ssh = project.is_via_remote_server();
21221 (telemetry, is_via_ssh)
21222 };
21223 telemetry.log_edit_event("editor", is_via_ssh);
21224 }
21225 multi_buffer::Event::ExcerptsAdded {
21226 buffer,
21227 predecessor,
21228 excerpts,
21229 } => {
21230 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21231 let buffer_id = buffer.read(cx).remote_id();
21232 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21233 && let Some(project) = &self.project
21234 {
21235 update_uncommitted_diff_for_buffer(
21236 cx.entity(),
21237 project,
21238 [buffer.clone()],
21239 self.buffer.clone(),
21240 cx,
21241 )
21242 .detach();
21243 }
21244 self.update_lsp_data(Some(buffer_id), window, cx);
21245 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21246 cx.emit(EditorEvent::ExcerptsAdded {
21247 buffer: buffer.clone(),
21248 predecessor: *predecessor,
21249 excerpts: excerpts.clone(),
21250 });
21251 }
21252 multi_buffer::Event::ExcerptsRemoved {
21253 ids,
21254 removed_buffer_ids,
21255 } => {
21256 if let Some(inlay_hints) = &mut self.inlay_hints {
21257 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21258 }
21259 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21260 for buffer_id in removed_buffer_ids {
21261 self.registered_buffers.remove(buffer_id);
21262 }
21263 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21264 cx.emit(EditorEvent::ExcerptsRemoved {
21265 ids: ids.clone(),
21266 removed_buffer_ids: removed_buffer_ids.clone(),
21267 });
21268 }
21269 multi_buffer::Event::ExcerptsEdited {
21270 excerpt_ids,
21271 buffer_ids,
21272 } => {
21273 self.display_map.update(cx, |map, cx| {
21274 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21275 });
21276 cx.emit(EditorEvent::ExcerptsEdited {
21277 ids: excerpt_ids.clone(),
21278 });
21279 }
21280 multi_buffer::Event::ExcerptsExpanded { ids } => {
21281 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21282 self.refresh_document_highlights(cx);
21283 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21284 }
21285 multi_buffer::Event::Reparsed(buffer_id) => {
21286 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21287 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21288
21289 cx.emit(EditorEvent::Reparsed(*buffer_id));
21290 }
21291 multi_buffer::Event::DiffHunksToggled => {
21292 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21293 }
21294 multi_buffer::Event::LanguageChanged(buffer_id) => {
21295 self.registered_buffers.remove(&buffer_id);
21296 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21297 cx.emit(EditorEvent::Reparsed(*buffer_id));
21298 cx.notify();
21299 }
21300 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21301 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21302 multi_buffer::Event::FileHandleChanged
21303 | multi_buffer::Event::Reloaded
21304 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21305 multi_buffer::Event::DiagnosticsUpdated => {
21306 self.update_diagnostics_state(window, cx);
21307 }
21308 _ => {}
21309 };
21310 }
21311
21312 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21313 if !self.diagnostics_enabled() {
21314 return;
21315 }
21316 self.refresh_active_diagnostics(cx);
21317 self.refresh_inline_diagnostics(true, window, cx);
21318 self.scrollbar_marker_state.dirty = true;
21319 cx.notify();
21320 }
21321
21322 pub fn start_temporary_diff_override(&mut self) {
21323 self.load_diff_task.take();
21324 self.temporary_diff_override = true;
21325 }
21326
21327 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21328 self.temporary_diff_override = false;
21329 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21330 self.buffer.update(cx, |buffer, cx| {
21331 buffer.set_all_diff_hunks_collapsed(cx);
21332 });
21333
21334 if let Some(project) = self.project.clone() {
21335 self.load_diff_task = Some(
21336 update_uncommitted_diff_for_buffer(
21337 cx.entity(),
21338 &project,
21339 self.buffer.read(cx).all_buffers(),
21340 self.buffer.clone(),
21341 cx,
21342 )
21343 .shared(),
21344 );
21345 }
21346 }
21347
21348 fn on_display_map_changed(
21349 &mut self,
21350 _: Entity<DisplayMap>,
21351 _: &mut Window,
21352 cx: &mut Context<Self>,
21353 ) {
21354 cx.notify();
21355 }
21356
21357 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21358 if self.diagnostics_enabled() {
21359 let new_severity = EditorSettings::get_global(cx)
21360 .diagnostics_max_severity
21361 .unwrap_or(DiagnosticSeverity::Hint);
21362 self.set_max_diagnostics_severity(new_severity, cx);
21363 }
21364 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21365 self.update_edit_prediction_settings(cx);
21366 self.refresh_edit_prediction(true, false, window, cx);
21367 self.refresh_inline_values(cx);
21368 self.refresh_inlay_hints(
21369 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21370 self.selections.newest_anchor().head(),
21371 &self.buffer.read(cx).snapshot(cx),
21372 cx,
21373 )),
21374 cx,
21375 );
21376
21377 let old_cursor_shape = self.cursor_shape;
21378 let old_show_breadcrumbs = self.show_breadcrumbs;
21379
21380 {
21381 let editor_settings = EditorSettings::get_global(cx);
21382 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21383 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21384 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21385 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21386 }
21387
21388 if old_cursor_shape != self.cursor_shape {
21389 cx.emit(EditorEvent::CursorShapeChanged);
21390 }
21391
21392 if old_show_breadcrumbs != self.show_breadcrumbs {
21393 cx.emit(EditorEvent::BreadcrumbsChanged);
21394 }
21395
21396 let project_settings = ProjectSettings::get_global(cx);
21397 self.buffer_serialization = self
21398 .should_serialize_buffer()
21399 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21400
21401 if self.mode.is_full() {
21402 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21403 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21404 if self.show_inline_diagnostics != show_inline_diagnostics {
21405 self.show_inline_diagnostics = show_inline_diagnostics;
21406 self.refresh_inline_diagnostics(false, window, cx);
21407 }
21408
21409 if self.git_blame_inline_enabled != inline_blame_enabled {
21410 self.toggle_git_blame_inline_internal(false, window, cx);
21411 }
21412
21413 let minimap_settings = EditorSettings::get_global(cx).minimap;
21414 if self.minimap_visibility != MinimapVisibility::Disabled {
21415 if self.minimap_visibility.settings_visibility()
21416 != minimap_settings.minimap_enabled()
21417 {
21418 self.set_minimap_visibility(
21419 MinimapVisibility::for_mode(self.mode(), cx),
21420 window,
21421 cx,
21422 );
21423 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21424 minimap_entity.update(cx, |minimap_editor, cx| {
21425 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21426 })
21427 }
21428 }
21429 }
21430
21431 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21432 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21433 }) {
21434 if !inlay_splice.is_empty() {
21435 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21436 }
21437 self.refresh_colors_for_visible_range(None, window, cx);
21438 }
21439
21440 cx.notify();
21441 }
21442
21443 pub fn set_searchable(&mut self, searchable: bool) {
21444 self.searchable = searchable;
21445 }
21446
21447 pub fn searchable(&self) -> bool {
21448 self.searchable
21449 }
21450
21451 pub fn open_excerpts_in_split(
21452 &mut self,
21453 _: &OpenExcerptsSplit,
21454 window: &mut Window,
21455 cx: &mut Context<Self>,
21456 ) {
21457 self.open_excerpts_common(None, true, window, cx)
21458 }
21459
21460 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21461 self.open_excerpts_common(None, false, window, cx)
21462 }
21463
21464 fn open_excerpts_common(
21465 &mut self,
21466 jump_data: Option<JumpData>,
21467 split: bool,
21468 window: &mut Window,
21469 cx: &mut Context<Self>,
21470 ) {
21471 let Some(workspace) = self.workspace() else {
21472 cx.propagate();
21473 return;
21474 };
21475
21476 if self.buffer.read(cx).is_singleton() {
21477 cx.propagate();
21478 return;
21479 }
21480
21481 let mut new_selections_by_buffer = HashMap::default();
21482 match &jump_data {
21483 Some(JumpData::MultiBufferPoint {
21484 excerpt_id,
21485 position,
21486 anchor,
21487 line_offset_from_top,
21488 }) => {
21489 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21490 if let Some(buffer) = multi_buffer_snapshot
21491 .buffer_id_for_excerpt(*excerpt_id)
21492 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21493 {
21494 let buffer_snapshot = buffer.read(cx).snapshot();
21495 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21496 language::ToPoint::to_point(anchor, &buffer_snapshot)
21497 } else {
21498 buffer_snapshot.clip_point(*position, Bias::Left)
21499 };
21500 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21501 new_selections_by_buffer.insert(
21502 buffer,
21503 (
21504 vec![jump_to_offset..jump_to_offset],
21505 Some(*line_offset_from_top),
21506 ),
21507 );
21508 }
21509 }
21510 Some(JumpData::MultiBufferRow {
21511 row,
21512 line_offset_from_top,
21513 }) => {
21514 let point = MultiBufferPoint::new(row.0, 0);
21515 if let Some((buffer, buffer_point, _)) =
21516 self.buffer.read(cx).point_to_buffer_point(point, cx)
21517 {
21518 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21519 new_selections_by_buffer
21520 .entry(buffer)
21521 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21522 .0
21523 .push(buffer_offset..buffer_offset)
21524 }
21525 }
21526 None => {
21527 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21528 let multi_buffer = self.buffer.read(cx);
21529 for selection in selections {
21530 for (snapshot, range, _, anchor) in multi_buffer
21531 .snapshot(cx)
21532 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21533 {
21534 if let Some(anchor) = anchor {
21535 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21536 else {
21537 continue;
21538 };
21539 let offset = text::ToOffset::to_offset(
21540 &anchor.text_anchor,
21541 &buffer_handle.read(cx).snapshot(),
21542 );
21543 let range = offset..offset;
21544 new_selections_by_buffer
21545 .entry(buffer_handle)
21546 .or_insert((Vec::new(), None))
21547 .0
21548 .push(range)
21549 } else {
21550 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21551 else {
21552 continue;
21553 };
21554 new_selections_by_buffer
21555 .entry(buffer_handle)
21556 .or_insert((Vec::new(), None))
21557 .0
21558 .push(range)
21559 }
21560 }
21561 }
21562 }
21563 }
21564
21565 new_selections_by_buffer
21566 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21567
21568 if new_selections_by_buffer.is_empty() {
21569 return;
21570 }
21571
21572 // We defer the pane interaction because we ourselves are a workspace item
21573 // and activating a new item causes the pane to call a method on us reentrantly,
21574 // which panics if we're on the stack.
21575 window.defer(cx, move |window, cx| {
21576 workspace.update(cx, |workspace, cx| {
21577 let pane = if split {
21578 workspace.adjacent_pane(window, cx)
21579 } else {
21580 workspace.active_pane().clone()
21581 };
21582
21583 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21584 let editor = buffer
21585 .read(cx)
21586 .file()
21587 .is_none()
21588 .then(|| {
21589 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21590 // so `workspace.open_project_item` will never find them, always opening a new editor.
21591 // Instead, we try to activate the existing editor in the pane first.
21592 let (editor, pane_item_index) =
21593 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21594 let editor = item.downcast::<Editor>()?;
21595 let singleton_buffer =
21596 editor.read(cx).buffer().read(cx).as_singleton()?;
21597 if singleton_buffer == buffer {
21598 Some((editor, i))
21599 } else {
21600 None
21601 }
21602 })?;
21603 pane.update(cx, |pane, cx| {
21604 pane.activate_item(pane_item_index, true, true, window, cx)
21605 });
21606 Some(editor)
21607 })
21608 .flatten()
21609 .unwrap_or_else(|| {
21610 workspace.open_project_item::<Self>(
21611 pane.clone(),
21612 buffer,
21613 true,
21614 true,
21615 window,
21616 cx,
21617 )
21618 });
21619
21620 editor.update(cx, |editor, cx| {
21621 let autoscroll = match scroll_offset {
21622 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21623 None => Autoscroll::newest(),
21624 };
21625 let nav_history = editor.nav_history.take();
21626 editor.change_selections(
21627 SelectionEffects::scroll(autoscroll),
21628 window,
21629 cx,
21630 |s| {
21631 s.select_ranges(ranges);
21632 },
21633 );
21634 editor.nav_history = nav_history;
21635 });
21636 }
21637 })
21638 });
21639 }
21640
21641 // For now, don't allow opening excerpts in buffers that aren't backed by
21642 // regular project files.
21643 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21644 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21645 }
21646
21647 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21648 let snapshot = self.buffer.read(cx).read(cx);
21649 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21650 Some(
21651 ranges
21652 .iter()
21653 .map(move |range| {
21654 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21655 })
21656 .collect(),
21657 )
21658 }
21659
21660 fn selection_replacement_ranges(
21661 &self,
21662 range: Range<OffsetUtf16>,
21663 cx: &mut App,
21664 ) -> Vec<Range<OffsetUtf16>> {
21665 let selections = self
21666 .selections
21667 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21668 let newest_selection = selections
21669 .iter()
21670 .max_by_key(|selection| selection.id)
21671 .unwrap();
21672 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21673 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21674 let snapshot = self.buffer.read(cx).read(cx);
21675 selections
21676 .into_iter()
21677 .map(|mut selection| {
21678 selection.start.0 =
21679 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21680 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21681 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21682 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21683 })
21684 .collect()
21685 }
21686
21687 fn report_editor_event(
21688 &self,
21689 reported_event: ReportEditorEvent,
21690 file_extension: Option<String>,
21691 cx: &App,
21692 ) {
21693 if cfg!(any(test, feature = "test-support")) {
21694 return;
21695 }
21696
21697 let Some(project) = &self.project else { return };
21698
21699 // If None, we are in a file without an extension
21700 let file = self
21701 .buffer
21702 .read(cx)
21703 .as_singleton()
21704 .and_then(|b| b.read(cx).file());
21705 let file_extension = file_extension.or(file
21706 .as_ref()
21707 .and_then(|file| Path::new(file.file_name(cx)).extension())
21708 .and_then(|e| e.to_str())
21709 .map(|a| a.to_string()));
21710
21711 let vim_mode = vim_flavor(cx).is_some();
21712
21713 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21714 let copilot_enabled = edit_predictions_provider
21715 == language::language_settings::EditPredictionProvider::Copilot;
21716 let copilot_enabled_for_language = self
21717 .buffer
21718 .read(cx)
21719 .language_settings(cx)
21720 .show_edit_predictions;
21721
21722 let project = project.read(cx);
21723 let event_type = reported_event.event_type();
21724
21725 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21726 telemetry::event!(
21727 event_type,
21728 type = if auto_saved {"autosave"} else {"manual"},
21729 file_extension,
21730 vim_mode,
21731 copilot_enabled,
21732 copilot_enabled_for_language,
21733 edit_predictions_provider,
21734 is_via_ssh = project.is_via_remote_server(),
21735 );
21736 } else {
21737 telemetry::event!(
21738 event_type,
21739 file_extension,
21740 vim_mode,
21741 copilot_enabled,
21742 copilot_enabled_for_language,
21743 edit_predictions_provider,
21744 is_via_ssh = project.is_via_remote_server(),
21745 );
21746 };
21747 }
21748
21749 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21750 /// with each line being an array of {text, highlight} objects.
21751 fn copy_highlight_json(
21752 &mut self,
21753 _: &CopyHighlightJson,
21754 window: &mut Window,
21755 cx: &mut Context<Self>,
21756 ) {
21757 #[derive(Serialize)]
21758 struct Chunk<'a> {
21759 text: String,
21760 highlight: Option<&'a str>,
21761 }
21762
21763 let snapshot = self.buffer.read(cx).snapshot(cx);
21764 let range = self
21765 .selected_text_range(false, window, cx)
21766 .and_then(|selection| {
21767 if selection.range.is_empty() {
21768 None
21769 } else {
21770 Some(
21771 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21772 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21773 )
21774 }
21775 })
21776 .unwrap_or_else(|| 0..snapshot.len());
21777
21778 let chunks = snapshot.chunks(range, true);
21779 let mut lines = Vec::new();
21780 let mut line: VecDeque<Chunk> = VecDeque::new();
21781
21782 let Some(style) = self.style.as_ref() else {
21783 return;
21784 };
21785
21786 for chunk in chunks {
21787 let highlight = chunk
21788 .syntax_highlight_id
21789 .and_then(|id| id.name(&style.syntax));
21790 let mut chunk_lines = chunk.text.split('\n').peekable();
21791 while let Some(text) = chunk_lines.next() {
21792 let mut merged_with_last_token = false;
21793 if let Some(last_token) = line.back_mut()
21794 && last_token.highlight == highlight
21795 {
21796 last_token.text.push_str(text);
21797 merged_with_last_token = true;
21798 }
21799
21800 if !merged_with_last_token {
21801 line.push_back(Chunk {
21802 text: text.into(),
21803 highlight,
21804 });
21805 }
21806
21807 if chunk_lines.peek().is_some() {
21808 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21809 line.pop_front();
21810 }
21811 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21812 line.pop_back();
21813 }
21814
21815 lines.push(mem::take(&mut line));
21816 }
21817 }
21818 }
21819
21820 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21821 return;
21822 };
21823 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21824 }
21825
21826 pub fn open_context_menu(
21827 &mut self,
21828 _: &OpenContextMenu,
21829 window: &mut Window,
21830 cx: &mut Context<Self>,
21831 ) {
21832 self.request_autoscroll(Autoscroll::newest(), cx);
21833 let position = self
21834 .selections
21835 .newest_display(&self.display_snapshot(cx))
21836 .start;
21837 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21838 }
21839
21840 pub fn replay_insert_event(
21841 &mut self,
21842 text: &str,
21843 relative_utf16_range: Option<Range<isize>>,
21844 window: &mut Window,
21845 cx: &mut Context<Self>,
21846 ) {
21847 if !self.input_enabled {
21848 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21849 return;
21850 }
21851 if let Some(relative_utf16_range) = relative_utf16_range {
21852 let selections = self
21853 .selections
21854 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21855 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21856 let new_ranges = selections.into_iter().map(|range| {
21857 let start = OffsetUtf16(
21858 range
21859 .head()
21860 .0
21861 .saturating_add_signed(relative_utf16_range.start),
21862 );
21863 let end = OffsetUtf16(
21864 range
21865 .head()
21866 .0
21867 .saturating_add_signed(relative_utf16_range.end),
21868 );
21869 start..end
21870 });
21871 s.select_ranges(new_ranges);
21872 });
21873 }
21874
21875 self.handle_input(text, window, cx);
21876 }
21877
21878 pub fn is_focused(&self, window: &Window) -> bool {
21879 self.focus_handle.is_focused(window)
21880 }
21881
21882 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21883 cx.emit(EditorEvent::Focused);
21884
21885 if let Some(descendant) = self
21886 .last_focused_descendant
21887 .take()
21888 .and_then(|descendant| descendant.upgrade())
21889 {
21890 window.focus(&descendant);
21891 } else {
21892 if let Some(blame) = self.blame.as_ref() {
21893 blame.update(cx, GitBlame::focus)
21894 }
21895
21896 self.blink_manager.update(cx, BlinkManager::enable);
21897 self.show_cursor_names(window, cx);
21898 self.buffer.update(cx, |buffer, cx| {
21899 buffer.finalize_last_transaction(cx);
21900 if self.leader_id.is_none() {
21901 buffer.set_active_selections(
21902 &self.selections.disjoint_anchors_arc(),
21903 self.selections.line_mode(),
21904 self.cursor_shape,
21905 cx,
21906 );
21907 }
21908 });
21909 }
21910 }
21911
21912 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21913 cx.emit(EditorEvent::FocusedIn)
21914 }
21915
21916 fn handle_focus_out(
21917 &mut self,
21918 event: FocusOutEvent,
21919 _window: &mut Window,
21920 cx: &mut Context<Self>,
21921 ) {
21922 if event.blurred != self.focus_handle {
21923 self.last_focused_descendant = Some(event.blurred);
21924 }
21925 self.selection_drag_state = SelectionDragState::None;
21926 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21927 }
21928
21929 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21930 self.blink_manager.update(cx, BlinkManager::disable);
21931 self.buffer
21932 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21933
21934 if let Some(blame) = self.blame.as_ref() {
21935 blame.update(cx, GitBlame::blur)
21936 }
21937 if !self.hover_state.focused(window, cx) {
21938 hide_hover(self, cx);
21939 }
21940 if !self
21941 .context_menu
21942 .borrow()
21943 .as_ref()
21944 .is_some_and(|context_menu| context_menu.focused(window, cx))
21945 {
21946 self.hide_context_menu(window, cx);
21947 }
21948 self.take_active_edit_prediction(cx);
21949 cx.emit(EditorEvent::Blurred);
21950 cx.notify();
21951 }
21952
21953 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21954 let mut pending: String = window
21955 .pending_input_keystrokes()
21956 .into_iter()
21957 .flatten()
21958 .filter_map(|keystroke| {
21959 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21960 keystroke.key_char.clone()
21961 } else {
21962 None
21963 }
21964 })
21965 .collect();
21966
21967 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21968 pending = "".to_string();
21969 }
21970
21971 let existing_pending = self
21972 .text_highlights::<PendingInput>(cx)
21973 .map(|(_, ranges)| ranges.to_vec());
21974 if existing_pending.is_none() && pending.is_empty() {
21975 return;
21976 }
21977 let transaction =
21978 self.transact(window, cx, |this, window, cx| {
21979 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21980 let edits = selections
21981 .iter()
21982 .map(|selection| (selection.end..selection.end, pending.clone()));
21983 this.edit(edits, cx);
21984 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21985 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21986 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21987 }));
21988 });
21989 if let Some(existing_ranges) = existing_pending {
21990 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21991 this.edit(edits, cx);
21992 }
21993 });
21994
21995 let snapshot = self.snapshot(window, cx);
21996 let ranges = self
21997 .selections
21998 .all::<usize>(&snapshot.display_snapshot)
21999 .into_iter()
22000 .map(|selection| {
22001 snapshot.buffer_snapshot().anchor_after(selection.end)
22002 ..snapshot
22003 .buffer_snapshot()
22004 .anchor_before(selection.end + pending.len())
22005 })
22006 .collect();
22007
22008 if pending.is_empty() {
22009 self.clear_highlights::<PendingInput>(cx);
22010 } else {
22011 self.highlight_text::<PendingInput>(
22012 ranges,
22013 HighlightStyle {
22014 underline: Some(UnderlineStyle {
22015 thickness: px(1.),
22016 color: None,
22017 wavy: false,
22018 }),
22019 ..Default::default()
22020 },
22021 cx,
22022 );
22023 }
22024
22025 self.ime_transaction = self.ime_transaction.or(transaction);
22026 if let Some(transaction) = self.ime_transaction {
22027 self.buffer.update(cx, |buffer, cx| {
22028 buffer.group_until_transaction(transaction, cx);
22029 });
22030 }
22031
22032 if self.text_highlights::<PendingInput>(cx).is_none() {
22033 self.ime_transaction.take();
22034 }
22035 }
22036
22037 pub fn register_action_renderer(
22038 &mut self,
22039 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22040 ) -> Subscription {
22041 let id = self.next_editor_action_id.post_inc();
22042 self.editor_actions
22043 .borrow_mut()
22044 .insert(id, Box::new(listener));
22045
22046 let editor_actions = self.editor_actions.clone();
22047 Subscription::new(move || {
22048 editor_actions.borrow_mut().remove(&id);
22049 })
22050 }
22051
22052 pub fn register_action<A: Action>(
22053 &mut self,
22054 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22055 ) -> Subscription {
22056 let id = self.next_editor_action_id.post_inc();
22057 let listener = Arc::new(listener);
22058 self.editor_actions.borrow_mut().insert(
22059 id,
22060 Box::new(move |_, window, _| {
22061 let listener = listener.clone();
22062 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22063 let action = action.downcast_ref().unwrap();
22064 if phase == DispatchPhase::Bubble {
22065 listener(action, window, cx)
22066 }
22067 })
22068 }),
22069 );
22070
22071 let editor_actions = self.editor_actions.clone();
22072 Subscription::new(move || {
22073 editor_actions.borrow_mut().remove(&id);
22074 })
22075 }
22076
22077 pub fn file_header_size(&self) -> u32 {
22078 FILE_HEADER_HEIGHT
22079 }
22080
22081 pub fn restore(
22082 &mut self,
22083 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22084 window: &mut Window,
22085 cx: &mut Context<Self>,
22086 ) {
22087 let workspace = self.workspace();
22088 let project = self.project();
22089 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22090 let mut tasks = Vec::new();
22091 for (buffer_id, changes) in revert_changes {
22092 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22093 buffer.update(cx, |buffer, cx| {
22094 buffer.edit(
22095 changes
22096 .into_iter()
22097 .map(|(range, text)| (range, text.to_string())),
22098 None,
22099 cx,
22100 );
22101 });
22102
22103 if let Some(project) =
22104 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22105 {
22106 project.update(cx, |project, cx| {
22107 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22108 })
22109 }
22110 }
22111 }
22112 tasks
22113 });
22114 cx.spawn_in(window, async move |_, cx| {
22115 for (buffer, task) in save_tasks {
22116 let result = task.await;
22117 if result.is_err() {
22118 let Some(path) = buffer
22119 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22120 .ok()
22121 else {
22122 continue;
22123 };
22124 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22125 let Some(task) = cx
22126 .update_window_entity(workspace, |workspace, window, cx| {
22127 workspace
22128 .open_path_preview(path, None, false, false, false, window, cx)
22129 })
22130 .ok()
22131 else {
22132 continue;
22133 };
22134 task.await.log_err();
22135 }
22136 }
22137 }
22138 })
22139 .detach();
22140 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22141 selections.refresh()
22142 });
22143 }
22144
22145 pub fn to_pixel_point(
22146 &self,
22147 source: multi_buffer::Anchor,
22148 editor_snapshot: &EditorSnapshot,
22149 window: &mut Window,
22150 ) -> Option<gpui::Point<Pixels>> {
22151 let source_point = source.to_display_point(editor_snapshot);
22152 self.display_to_pixel_point(source_point, editor_snapshot, window)
22153 }
22154
22155 pub fn display_to_pixel_point(
22156 &self,
22157 source: DisplayPoint,
22158 editor_snapshot: &EditorSnapshot,
22159 window: &mut Window,
22160 ) -> Option<gpui::Point<Pixels>> {
22161 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22162 let text_layout_details = self.text_layout_details(window);
22163 let scroll_top = text_layout_details
22164 .scroll_anchor
22165 .scroll_position(editor_snapshot)
22166 .y;
22167
22168 if source.row().as_f64() < scroll_top.floor() {
22169 return None;
22170 }
22171 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22172 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22173 Some(gpui::Point::new(source_x, source_y))
22174 }
22175
22176 pub fn has_visible_completions_menu(&self) -> bool {
22177 !self.edit_prediction_preview_is_active()
22178 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22179 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22180 })
22181 }
22182
22183 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22184 if self.mode.is_minimap() {
22185 return;
22186 }
22187 self.addons
22188 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22189 }
22190
22191 pub fn unregister_addon<T: Addon>(&mut self) {
22192 self.addons.remove(&std::any::TypeId::of::<T>());
22193 }
22194
22195 pub fn addon<T: Addon>(&self) -> Option<&T> {
22196 let type_id = std::any::TypeId::of::<T>();
22197 self.addons
22198 .get(&type_id)
22199 .and_then(|item| item.to_any().downcast_ref::<T>())
22200 }
22201
22202 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22203 let type_id = std::any::TypeId::of::<T>();
22204 self.addons
22205 .get_mut(&type_id)
22206 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22207 }
22208
22209 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22210 let text_layout_details = self.text_layout_details(window);
22211 let style = &text_layout_details.editor_style;
22212 let font_id = window.text_system().resolve_font(&style.text.font());
22213 let font_size = style.text.font_size.to_pixels(window.rem_size());
22214 let line_height = style.text.line_height_in_pixels(window.rem_size());
22215 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22216 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22217
22218 CharacterDimensions {
22219 em_width,
22220 em_advance,
22221 line_height,
22222 }
22223 }
22224
22225 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22226 self.load_diff_task.clone()
22227 }
22228
22229 fn read_metadata_from_db(
22230 &mut self,
22231 item_id: u64,
22232 workspace_id: WorkspaceId,
22233 window: &mut Window,
22234 cx: &mut Context<Editor>,
22235 ) {
22236 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22237 && !self.mode.is_minimap()
22238 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22239 {
22240 let buffer_snapshot = OnceCell::new();
22241
22242 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22243 && !folds.is_empty()
22244 {
22245 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22246 self.fold_ranges(
22247 folds
22248 .into_iter()
22249 .map(|(start, end)| {
22250 snapshot.clip_offset(start, Bias::Left)
22251 ..snapshot.clip_offset(end, Bias::Right)
22252 })
22253 .collect(),
22254 false,
22255 window,
22256 cx,
22257 );
22258 }
22259
22260 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22261 && !selections.is_empty()
22262 {
22263 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22264 // skip adding the initial selection to selection history
22265 self.selection_history.mode = SelectionHistoryMode::Skipping;
22266 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22267 s.select_ranges(selections.into_iter().map(|(start, end)| {
22268 snapshot.clip_offset(start, Bias::Left)
22269 ..snapshot.clip_offset(end, Bias::Right)
22270 }));
22271 });
22272 self.selection_history.mode = SelectionHistoryMode::Normal;
22273 };
22274 }
22275
22276 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22277 }
22278
22279 fn update_lsp_data(
22280 &mut self,
22281 for_buffer: Option<BufferId>,
22282 window: &mut Window,
22283 cx: &mut Context<'_, Self>,
22284 ) {
22285 self.pull_diagnostics(for_buffer, window, cx);
22286 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22287 }
22288
22289 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22290 if self.ignore_lsp_data() {
22291 return;
22292 }
22293 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22294 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22295 }
22296 }
22297
22298 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22299 if self.ignore_lsp_data() {
22300 return;
22301 }
22302
22303 if !self.registered_buffers.contains_key(&buffer_id)
22304 && let Some(project) = self.project.as_ref()
22305 {
22306 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22307 project.update(cx, |project, cx| {
22308 self.registered_buffers.insert(
22309 buffer_id,
22310 project.register_buffer_with_language_servers(&buffer, cx),
22311 );
22312 });
22313 } else {
22314 self.registered_buffers.remove(&buffer_id);
22315 }
22316 }
22317 }
22318
22319 fn ignore_lsp_data(&self) -> bool {
22320 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22321 // skip any LSP updates for it.
22322 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22323 }
22324}
22325
22326fn edit_for_markdown_paste<'a>(
22327 buffer: &MultiBufferSnapshot,
22328 range: Range<usize>,
22329 to_insert: &'a str,
22330 url: Option<url::Url>,
22331) -> (Range<usize>, Cow<'a, str>) {
22332 if url.is_none() {
22333 return (range, Cow::Borrowed(to_insert));
22334 };
22335
22336 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22337
22338 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22339 Cow::Borrowed(to_insert)
22340 } else {
22341 Cow::Owned(format!("[{old_text}]({to_insert})"))
22342 };
22343 (range, new_text)
22344}
22345
22346#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22347pub enum VimFlavor {
22348 Vim,
22349 Helix,
22350}
22351
22352pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22353 if vim_mode_setting::HelixModeSetting::try_get(cx)
22354 .map(|helix_mode| helix_mode.0)
22355 .unwrap_or(false)
22356 {
22357 Some(VimFlavor::Helix)
22358 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22359 .map(|vim_mode| vim_mode.0)
22360 .unwrap_or(false)
22361 {
22362 Some(VimFlavor::Vim)
22363 } else {
22364 None // neither vim nor helix mode
22365 }
22366}
22367
22368fn process_completion_for_edit(
22369 completion: &Completion,
22370 intent: CompletionIntent,
22371 buffer: &Entity<Buffer>,
22372 cursor_position: &text::Anchor,
22373 cx: &mut Context<Editor>,
22374) -> CompletionEdit {
22375 let buffer = buffer.read(cx);
22376 let buffer_snapshot = buffer.snapshot();
22377 let (snippet, new_text) = if completion.is_snippet() {
22378 let mut snippet_source = completion.new_text.clone();
22379 // Workaround for typescript language server issues so that methods don't expand within
22380 // strings and functions with type expressions. The previous point is used because the query
22381 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22382 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22383 let previous_point = if previous_point.column > 0 {
22384 cursor_position.to_previous_offset(&buffer_snapshot)
22385 } else {
22386 cursor_position.to_offset(&buffer_snapshot)
22387 };
22388 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22389 && scope.prefers_label_for_snippet_in_completion()
22390 && let Some(label) = completion.label()
22391 && matches!(
22392 completion.kind(),
22393 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22394 )
22395 {
22396 snippet_source = label;
22397 }
22398 match Snippet::parse(&snippet_source).log_err() {
22399 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22400 None => (None, completion.new_text.clone()),
22401 }
22402 } else {
22403 (None, completion.new_text.clone())
22404 };
22405
22406 let mut range_to_replace = {
22407 let replace_range = &completion.replace_range;
22408 if let CompletionSource::Lsp {
22409 insert_range: Some(insert_range),
22410 ..
22411 } = &completion.source
22412 {
22413 debug_assert_eq!(
22414 insert_range.start, replace_range.start,
22415 "insert_range and replace_range should start at the same position"
22416 );
22417 debug_assert!(
22418 insert_range
22419 .start
22420 .cmp(cursor_position, &buffer_snapshot)
22421 .is_le(),
22422 "insert_range should start before or at cursor position"
22423 );
22424 debug_assert!(
22425 replace_range
22426 .start
22427 .cmp(cursor_position, &buffer_snapshot)
22428 .is_le(),
22429 "replace_range should start before or at cursor position"
22430 );
22431
22432 let should_replace = match intent {
22433 CompletionIntent::CompleteWithInsert => false,
22434 CompletionIntent::CompleteWithReplace => true,
22435 CompletionIntent::Complete | CompletionIntent::Compose => {
22436 let insert_mode =
22437 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22438 .completions
22439 .lsp_insert_mode;
22440 match insert_mode {
22441 LspInsertMode::Insert => false,
22442 LspInsertMode::Replace => true,
22443 LspInsertMode::ReplaceSubsequence => {
22444 let mut text_to_replace = buffer.chars_for_range(
22445 buffer.anchor_before(replace_range.start)
22446 ..buffer.anchor_after(replace_range.end),
22447 );
22448 let mut current_needle = text_to_replace.next();
22449 for haystack_ch in completion.label.text.chars() {
22450 if let Some(needle_ch) = current_needle
22451 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22452 {
22453 current_needle = text_to_replace.next();
22454 }
22455 }
22456 current_needle.is_none()
22457 }
22458 LspInsertMode::ReplaceSuffix => {
22459 if replace_range
22460 .end
22461 .cmp(cursor_position, &buffer_snapshot)
22462 .is_gt()
22463 {
22464 let range_after_cursor = *cursor_position..replace_range.end;
22465 let text_after_cursor = buffer
22466 .text_for_range(
22467 buffer.anchor_before(range_after_cursor.start)
22468 ..buffer.anchor_after(range_after_cursor.end),
22469 )
22470 .collect::<String>()
22471 .to_ascii_lowercase();
22472 completion
22473 .label
22474 .text
22475 .to_ascii_lowercase()
22476 .ends_with(&text_after_cursor)
22477 } else {
22478 true
22479 }
22480 }
22481 }
22482 }
22483 };
22484
22485 if should_replace {
22486 replace_range.clone()
22487 } else {
22488 insert_range.clone()
22489 }
22490 } else {
22491 replace_range.clone()
22492 }
22493 };
22494
22495 if range_to_replace
22496 .end
22497 .cmp(cursor_position, &buffer_snapshot)
22498 .is_lt()
22499 {
22500 range_to_replace.end = *cursor_position;
22501 }
22502
22503 CompletionEdit {
22504 new_text,
22505 replace_range: range_to_replace.to_offset(buffer),
22506 snippet,
22507 }
22508}
22509
22510struct CompletionEdit {
22511 new_text: String,
22512 replace_range: Range<usize>,
22513 snippet: Option<Snippet>,
22514}
22515
22516fn insert_extra_newline_brackets(
22517 buffer: &MultiBufferSnapshot,
22518 range: Range<usize>,
22519 language: &language::LanguageScope,
22520) -> bool {
22521 let leading_whitespace_len = buffer
22522 .reversed_chars_at(range.start)
22523 .take_while(|c| c.is_whitespace() && *c != '\n')
22524 .map(|c| c.len_utf8())
22525 .sum::<usize>();
22526 let trailing_whitespace_len = buffer
22527 .chars_at(range.end)
22528 .take_while(|c| c.is_whitespace() && *c != '\n')
22529 .map(|c| c.len_utf8())
22530 .sum::<usize>();
22531 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22532
22533 language.brackets().any(|(pair, enabled)| {
22534 let pair_start = pair.start.trim_end();
22535 let pair_end = pair.end.trim_start();
22536
22537 enabled
22538 && pair.newline
22539 && buffer.contains_str_at(range.end, pair_end)
22540 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22541 })
22542}
22543
22544fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22545 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22546 [(buffer, range, _)] => (*buffer, range.clone()),
22547 _ => return false,
22548 };
22549 let pair = {
22550 let mut result: Option<BracketMatch> = None;
22551
22552 for pair in buffer
22553 .all_bracket_ranges(range.clone())
22554 .filter(move |pair| {
22555 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22556 })
22557 {
22558 let len = pair.close_range.end - pair.open_range.start;
22559
22560 if let Some(existing) = &result {
22561 let existing_len = existing.close_range.end - existing.open_range.start;
22562 if len > existing_len {
22563 continue;
22564 }
22565 }
22566
22567 result = Some(pair);
22568 }
22569
22570 result
22571 };
22572 let Some(pair) = pair else {
22573 return false;
22574 };
22575 pair.newline_only
22576 && buffer
22577 .chars_for_range(pair.open_range.end..range.start)
22578 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22579 .all(|c| c.is_whitespace() && c != '\n')
22580}
22581
22582fn update_uncommitted_diff_for_buffer(
22583 editor: Entity<Editor>,
22584 project: &Entity<Project>,
22585 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22586 buffer: Entity<MultiBuffer>,
22587 cx: &mut App,
22588) -> Task<()> {
22589 let mut tasks = Vec::new();
22590 project.update(cx, |project, cx| {
22591 for buffer in buffers {
22592 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22593 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22594 }
22595 }
22596 });
22597 cx.spawn(async move |cx| {
22598 let diffs = future::join_all(tasks).await;
22599 if editor
22600 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22601 .unwrap_or(false)
22602 {
22603 return;
22604 }
22605
22606 buffer
22607 .update(cx, |buffer, cx| {
22608 for diff in diffs.into_iter().flatten() {
22609 buffer.add_diff(diff, cx);
22610 }
22611 })
22612 .ok();
22613 })
22614}
22615
22616fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22617 let tab_size = tab_size.get() as usize;
22618 let mut width = offset;
22619
22620 for ch in text.chars() {
22621 width += if ch == '\t' {
22622 tab_size - (width % tab_size)
22623 } else {
22624 1
22625 };
22626 }
22627
22628 width - offset
22629}
22630
22631#[cfg(test)]
22632mod tests {
22633 use super::*;
22634
22635 #[test]
22636 fn test_string_size_with_expanded_tabs() {
22637 let nz = |val| NonZeroU32::new(val).unwrap();
22638 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22639 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22640 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22641 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22642 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22643 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22644 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22645 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22646 }
22647}
22648
22649/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22650struct WordBreakingTokenizer<'a> {
22651 input: &'a str,
22652}
22653
22654impl<'a> WordBreakingTokenizer<'a> {
22655 fn new(input: &'a str) -> Self {
22656 Self { input }
22657 }
22658}
22659
22660fn is_char_ideographic(ch: char) -> bool {
22661 use unicode_script::Script::*;
22662 use unicode_script::UnicodeScript;
22663 matches!(ch.script(), Han | Tangut | Yi)
22664}
22665
22666fn is_grapheme_ideographic(text: &str) -> bool {
22667 text.chars().any(is_char_ideographic)
22668}
22669
22670fn is_grapheme_whitespace(text: &str) -> bool {
22671 text.chars().any(|x| x.is_whitespace())
22672}
22673
22674fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22675 text.chars()
22676 .next()
22677 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22678}
22679
22680#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22681enum WordBreakToken<'a> {
22682 Word { token: &'a str, grapheme_len: usize },
22683 InlineWhitespace { token: &'a str, grapheme_len: usize },
22684 Newline,
22685}
22686
22687impl<'a> Iterator for WordBreakingTokenizer<'a> {
22688 /// Yields a span, the count of graphemes in the token, and whether it was
22689 /// whitespace. Note that it also breaks at word boundaries.
22690 type Item = WordBreakToken<'a>;
22691
22692 fn next(&mut self) -> Option<Self::Item> {
22693 use unicode_segmentation::UnicodeSegmentation;
22694 if self.input.is_empty() {
22695 return None;
22696 }
22697
22698 let mut iter = self.input.graphemes(true).peekable();
22699 let mut offset = 0;
22700 let mut grapheme_len = 0;
22701 if let Some(first_grapheme) = iter.next() {
22702 let is_newline = first_grapheme == "\n";
22703 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22704 offset += first_grapheme.len();
22705 grapheme_len += 1;
22706 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22707 if let Some(grapheme) = iter.peek().copied()
22708 && should_stay_with_preceding_ideograph(grapheme)
22709 {
22710 offset += grapheme.len();
22711 grapheme_len += 1;
22712 }
22713 } else {
22714 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22715 let mut next_word_bound = words.peek().copied();
22716 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22717 next_word_bound = words.next();
22718 }
22719 while let Some(grapheme) = iter.peek().copied() {
22720 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22721 break;
22722 };
22723 if is_grapheme_whitespace(grapheme) != is_whitespace
22724 || (grapheme == "\n") != is_newline
22725 {
22726 break;
22727 };
22728 offset += grapheme.len();
22729 grapheme_len += 1;
22730 iter.next();
22731 }
22732 }
22733 let token = &self.input[..offset];
22734 self.input = &self.input[offset..];
22735 if token == "\n" {
22736 Some(WordBreakToken::Newline)
22737 } else if is_whitespace {
22738 Some(WordBreakToken::InlineWhitespace {
22739 token,
22740 grapheme_len,
22741 })
22742 } else {
22743 Some(WordBreakToken::Word {
22744 token,
22745 grapheme_len,
22746 })
22747 }
22748 } else {
22749 None
22750 }
22751 }
22752}
22753
22754#[test]
22755fn test_word_breaking_tokenizer() {
22756 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22757 ("", &[]),
22758 (" ", &[whitespace(" ", 2)]),
22759 ("Ʒ", &[word("Ʒ", 1)]),
22760 ("Ǽ", &[word("Ǽ", 1)]),
22761 ("⋑", &[word("⋑", 1)]),
22762 ("⋑⋑", &[word("⋑⋑", 2)]),
22763 (
22764 "原理,进而",
22765 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22766 ),
22767 (
22768 "hello world",
22769 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22770 ),
22771 (
22772 "hello, world",
22773 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22774 ),
22775 (
22776 " hello world",
22777 &[
22778 whitespace(" ", 2),
22779 word("hello", 5),
22780 whitespace(" ", 1),
22781 word("world", 5),
22782 ],
22783 ),
22784 (
22785 "这是什么 \n 钢笔",
22786 &[
22787 word("这", 1),
22788 word("是", 1),
22789 word("什", 1),
22790 word("么", 1),
22791 whitespace(" ", 1),
22792 newline(),
22793 whitespace(" ", 1),
22794 word("钢", 1),
22795 word("笔", 1),
22796 ],
22797 ),
22798 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22799 ];
22800
22801 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22802 WordBreakToken::Word {
22803 token,
22804 grapheme_len,
22805 }
22806 }
22807
22808 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22809 WordBreakToken::InlineWhitespace {
22810 token,
22811 grapheme_len,
22812 }
22813 }
22814
22815 fn newline() -> WordBreakToken<'static> {
22816 WordBreakToken::Newline
22817 }
22818
22819 for (input, result) in tests {
22820 assert_eq!(
22821 WordBreakingTokenizer::new(input)
22822 .collect::<Vec<_>>()
22823 .as_slice(),
22824 *result,
22825 );
22826 }
22827}
22828
22829fn wrap_with_prefix(
22830 first_line_prefix: String,
22831 subsequent_lines_prefix: String,
22832 unwrapped_text: String,
22833 wrap_column: usize,
22834 tab_size: NonZeroU32,
22835 preserve_existing_whitespace: bool,
22836) -> String {
22837 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22838 let subsequent_lines_prefix_len =
22839 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22840 let mut wrapped_text = String::new();
22841 let mut current_line = first_line_prefix;
22842 let mut is_first_line = true;
22843
22844 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22845 let mut current_line_len = first_line_prefix_len;
22846 let mut in_whitespace = false;
22847 for token in tokenizer {
22848 let have_preceding_whitespace = in_whitespace;
22849 match token {
22850 WordBreakToken::Word {
22851 token,
22852 grapheme_len,
22853 } => {
22854 in_whitespace = false;
22855 let current_prefix_len = if is_first_line {
22856 first_line_prefix_len
22857 } else {
22858 subsequent_lines_prefix_len
22859 };
22860 if current_line_len + grapheme_len > wrap_column
22861 && current_line_len != current_prefix_len
22862 {
22863 wrapped_text.push_str(current_line.trim_end());
22864 wrapped_text.push('\n');
22865 is_first_line = false;
22866 current_line = subsequent_lines_prefix.clone();
22867 current_line_len = subsequent_lines_prefix_len;
22868 }
22869 current_line.push_str(token);
22870 current_line_len += grapheme_len;
22871 }
22872 WordBreakToken::InlineWhitespace {
22873 mut token,
22874 mut grapheme_len,
22875 } => {
22876 in_whitespace = true;
22877 if have_preceding_whitespace && !preserve_existing_whitespace {
22878 continue;
22879 }
22880 if !preserve_existing_whitespace {
22881 // Keep a single whitespace grapheme as-is
22882 if let Some(first) =
22883 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22884 {
22885 token = first;
22886 } else {
22887 token = " ";
22888 }
22889 grapheme_len = 1;
22890 }
22891 let current_prefix_len = if is_first_line {
22892 first_line_prefix_len
22893 } else {
22894 subsequent_lines_prefix_len
22895 };
22896 if current_line_len + grapheme_len > wrap_column {
22897 wrapped_text.push_str(current_line.trim_end());
22898 wrapped_text.push('\n');
22899 is_first_line = false;
22900 current_line = subsequent_lines_prefix.clone();
22901 current_line_len = subsequent_lines_prefix_len;
22902 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22903 current_line.push_str(token);
22904 current_line_len += grapheme_len;
22905 }
22906 }
22907 WordBreakToken::Newline => {
22908 in_whitespace = true;
22909 let current_prefix_len = if is_first_line {
22910 first_line_prefix_len
22911 } else {
22912 subsequent_lines_prefix_len
22913 };
22914 if preserve_existing_whitespace {
22915 wrapped_text.push_str(current_line.trim_end());
22916 wrapped_text.push('\n');
22917 is_first_line = false;
22918 current_line = subsequent_lines_prefix.clone();
22919 current_line_len = subsequent_lines_prefix_len;
22920 } else if have_preceding_whitespace {
22921 continue;
22922 } else if current_line_len + 1 > wrap_column
22923 && current_line_len != current_prefix_len
22924 {
22925 wrapped_text.push_str(current_line.trim_end());
22926 wrapped_text.push('\n');
22927 is_first_line = false;
22928 current_line = subsequent_lines_prefix.clone();
22929 current_line_len = subsequent_lines_prefix_len;
22930 } else if current_line_len != current_prefix_len {
22931 current_line.push(' ');
22932 current_line_len += 1;
22933 }
22934 }
22935 }
22936 }
22937
22938 if !current_line.is_empty() {
22939 wrapped_text.push_str(¤t_line);
22940 }
22941 wrapped_text
22942}
22943
22944#[test]
22945fn test_wrap_with_prefix() {
22946 assert_eq!(
22947 wrap_with_prefix(
22948 "# ".to_string(),
22949 "# ".to_string(),
22950 "abcdefg".to_string(),
22951 4,
22952 NonZeroU32::new(4).unwrap(),
22953 false,
22954 ),
22955 "# abcdefg"
22956 );
22957 assert_eq!(
22958 wrap_with_prefix(
22959 "".to_string(),
22960 "".to_string(),
22961 "\thello world".to_string(),
22962 8,
22963 NonZeroU32::new(4).unwrap(),
22964 false,
22965 ),
22966 "hello\nworld"
22967 );
22968 assert_eq!(
22969 wrap_with_prefix(
22970 "// ".to_string(),
22971 "// ".to_string(),
22972 "xx \nyy zz aa bb cc".to_string(),
22973 12,
22974 NonZeroU32::new(4).unwrap(),
22975 false,
22976 ),
22977 "// xx yy zz\n// aa bb cc"
22978 );
22979 assert_eq!(
22980 wrap_with_prefix(
22981 String::new(),
22982 String::new(),
22983 "这是什么 \n 钢笔".to_string(),
22984 3,
22985 NonZeroU32::new(4).unwrap(),
22986 false,
22987 ),
22988 "这是什\n么 钢\n笔"
22989 );
22990 assert_eq!(
22991 wrap_with_prefix(
22992 String::new(),
22993 String::new(),
22994 format!("foo{}bar", '\u{2009}'), // thin space
22995 80,
22996 NonZeroU32::new(4).unwrap(),
22997 false,
22998 ),
22999 format!("foo{}bar", '\u{2009}')
23000 );
23001}
23002
23003pub trait CollaborationHub {
23004 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23005 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23006 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23007}
23008
23009impl CollaborationHub for Entity<Project> {
23010 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23011 self.read(cx).collaborators()
23012 }
23013
23014 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23015 self.read(cx).user_store().read(cx).participant_indices()
23016 }
23017
23018 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23019 let this = self.read(cx);
23020 let user_ids = this.collaborators().values().map(|c| c.user_id);
23021 this.user_store().read(cx).participant_names(user_ids, cx)
23022 }
23023}
23024
23025pub trait SemanticsProvider {
23026 fn hover(
23027 &self,
23028 buffer: &Entity<Buffer>,
23029 position: text::Anchor,
23030 cx: &mut App,
23031 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23032
23033 fn inline_values(
23034 &self,
23035 buffer_handle: Entity<Buffer>,
23036 range: Range<text::Anchor>,
23037 cx: &mut App,
23038 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23039
23040 fn applicable_inlay_chunks(
23041 &self,
23042 buffer: &Entity<Buffer>,
23043 ranges: &[Range<text::Anchor>],
23044 cx: &mut App,
23045 ) -> Vec<Range<BufferRow>>;
23046
23047 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23048
23049 fn inlay_hints(
23050 &self,
23051 invalidate: InvalidationStrategy,
23052 buffer: Entity<Buffer>,
23053 ranges: Vec<Range<text::Anchor>>,
23054 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23055 cx: &mut App,
23056 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23057
23058 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23059
23060 fn document_highlights(
23061 &self,
23062 buffer: &Entity<Buffer>,
23063 position: text::Anchor,
23064 cx: &mut App,
23065 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23066
23067 fn definitions(
23068 &self,
23069 buffer: &Entity<Buffer>,
23070 position: text::Anchor,
23071 kind: GotoDefinitionKind,
23072 cx: &mut App,
23073 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23074
23075 fn range_for_rename(
23076 &self,
23077 buffer: &Entity<Buffer>,
23078 position: text::Anchor,
23079 cx: &mut App,
23080 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23081
23082 fn perform_rename(
23083 &self,
23084 buffer: &Entity<Buffer>,
23085 position: text::Anchor,
23086 new_name: String,
23087 cx: &mut App,
23088 ) -> Option<Task<Result<ProjectTransaction>>>;
23089}
23090
23091pub trait CompletionProvider {
23092 fn completions(
23093 &self,
23094 excerpt_id: ExcerptId,
23095 buffer: &Entity<Buffer>,
23096 buffer_position: text::Anchor,
23097 trigger: CompletionContext,
23098 window: &mut Window,
23099 cx: &mut Context<Editor>,
23100 ) -> Task<Result<Vec<CompletionResponse>>>;
23101
23102 fn resolve_completions(
23103 &self,
23104 _buffer: Entity<Buffer>,
23105 _completion_indices: Vec<usize>,
23106 _completions: Rc<RefCell<Box<[Completion]>>>,
23107 _cx: &mut Context<Editor>,
23108 ) -> Task<Result<bool>> {
23109 Task::ready(Ok(false))
23110 }
23111
23112 fn apply_additional_edits_for_completion(
23113 &self,
23114 _buffer: Entity<Buffer>,
23115 _completions: Rc<RefCell<Box<[Completion]>>>,
23116 _completion_index: usize,
23117 _push_to_history: bool,
23118 _cx: &mut Context<Editor>,
23119 ) -> Task<Result<Option<language::Transaction>>> {
23120 Task::ready(Ok(None))
23121 }
23122
23123 fn is_completion_trigger(
23124 &self,
23125 buffer: &Entity<Buffer>,
23126 position: language::Anchor,
23127 text: &str,
23128 trigger_in_words: bool,
23129 menu_is_open: bool,
23130 cx: &mut Context<Editor>,
23131 ) -> bool;
23132
23133 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23134
23135 fn sort_completions(&self) -> bool {
23136 true
23137 }
23138
23139 fn filter_completions(&self) -> bool {
23140 true
23141 }
23142
23143 fn show_snippets(&self) -> bool {
23144 false
23145 }
23146}
23147
23148pub trait CodeActionProvider {
23149 fn id(&self) -> Arc<str>;
23150
23151 fn code_actions(
23152 &self,
23153 buffer: &Entity<Buffer>,
23154 range: Range<text::Anchor>,
23155 window: &mut Window,
23156 cx: &mut App,
23157 ) -> Task<Result<Vec<CodeAction>>>;
23158
23159 fn apply_code_action(
23160 &self,
23161 buffer_handle: Entity<Buffer>,
23162 action: CodeAction,
23163 excerpt_id: ExcerptId,
23164 push_to_history: bool,
23165 window: &mut Window,
23166 cx: &mut App,
23167 ) -> Task<Result<ProjectTransaction>>;
23168}
23169
23170impl CodeActionProvider for Entity<Project> {
23171 fn id(&self) -> Arc<str> {
23172 "project".into()
23173 }
23174
23175 fn code_actions(
23176 &self,
23177 buffer: &Entity<Buffer>,
23178 range: Range<text::Anchor>,
23179 _window: &mut Window,
23180 cx: &mut App,
23181 ) -> Task<Result<Vec<CodeAction>>> {
23182 self.update(cx, |project, cx| {
23183 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23184 let code_actions = project.code_actions(buffer, range, None, cx);
23185 cx.background_spawn(async move {
23186 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23187 Ok(code_lens_actions
23188 .context("code lens fetch")?
23189 .into_iter()
23190 .flatten()
23191 .chain(
23192 code_actions
23193 .context("code action fetch")?
23194 .into_iter()
23195 .flatten(),
23196 )
23197 .collect())
23198 })
23199 })
23200 }
23201
23202 fn apply_code_action(
23203 &self,
23204 buffer_handle: Entity<Buffer>,
23205 action: CodeAction,
23206 _excerpt_id: ExcerptId,
23207 push_to_history: bool,
23208 _window: &mut Window,
23209 cx: &mut App,
23210 ) -> Task<Result<ProjectTransaction>> {
23211 self.update(cx, |project, cx| {
23212 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23213 })
23214 }
23215}
23216
23217fn snippet_completions(
23218 project: &Project,
23219 buffer: &Entity<Buffer>,
23220 buffer_anchor: text::Anchor,
23221 classifier: CharClassifier,
23222 cx: &mut App,
23223) -> Task<Result<CompletionResponse>> {
23224 let languages = buffer.read(cx).languages_at(buffer_anchor);
23225 let snippet_store = project.snippets().read(cx);
23226
23227 let scopes: Vec<_> = languages
23228 .iter()
23229 .filter_map(|language| {
23230 let language_name = language.lsp_id();
23231 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23232
23233 if snippets.is_empty() {
23234 None
23235 } else {
23236 Some((language.default_scope(), snippets))
23237 }
23238 })
23239 .collect();
23240
23241 if scopes.is_empty() {
23242 return Task::ready(Ok(CompletionResponse {
23243 completions: vec![],
23244 display_options: CompletionDisplayOptions::default(),
23245 is_incomplete: false,
23246 }));
23247 }
23248
23249 let snapshot = buffer.read(cx).text_snapshot();
23250 let executor = cx.background_executor().clone();
23251
23252 cx.background_spawn(async move {
23253 let is_word_char = |c| classifier.is_word(c);
23254
23255 let mut is_incomplete = false;
23256 let mut completions: Vec<Completion> = Vec::new();
23257
23258 const MAX_PREFIX_LEN: usize = 128;
23259 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23260 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23261 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23262
23263 let max_buffer_window: String = snapshot
23264 .text_for_range(window_start..buffer_offset)
23265 .collect();
23266
23267 if max_buffer_window.is_empty() {
23268 return Ok(CompletionResponse {
23269 completions: vec![],
23270 display_options: CompletionDisplayOptions::default(),
23271 is_incomplete: true,
23272 });
23273 }
23274
23275 for (_scope, snippets) in scopes.into_iter() {
23276 // Sort snippets by word count to match longer snippet prefixes first.
23277 let mut sorted_snippet_candidates = snippets
23278 .iter()
23279 .enumerate()
23280 .flat_map(|(snippet_ix, snippet)| {
23281 snippet
23282 .prefix
23283 .iter()
23284 .enumerate()
23285 .map(move |(prefix_ix, prefix)| {
23286 let word_count =
23287 snippet_candidate_suffixes(prefix, is_word_char).count();
23288 ((snippet_ix, prefix_ix), prefix, word_count)
23289 })
23290 })
23291 .collect_vec();
23292 sorted_snippet_candidates
23293 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23294
23295 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23296
23297 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23298 .take(
23299 sorted_snippet_candidates
23300 .first()
23301 .map(|(_, _, word_count)| *word_count)
23302 .unwrap_or_default(),
23303 )
23304 .collect_vec();
23305
23306 const MAX_RESULTS: usize = 100;
23307 // Each match also remembers how many characters from the buffer it consumed
23308 let mut matches: Vec<(StringMatch, usize)> = vec![];
23309
23310 let mut snippet_list_cutoff_index = 0;
23311 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23312 let word_count = buffer_index + 1;
23313 // Increase `snippet_list_cutoff_index` until we have all of the
23314 // snippets with sufficiently many words.
23315 while sorted_snippet_candidates
23316 .get(snippet_list_cutoff_index)
23317 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23318 *snippet_word_count >= word_count
23319 })
23320 {
23321 snippet_list_cutoff_index += 1;
23322 }
23323
23324 // Take only the candidates with at least `word_count` many words
23325 let snippet_candidates_at_word_len =
23326 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23327
23328 let candidates = snippet_candidates_at_word_len
23329 .iter()
23330 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23331 .enumerate() // index in `sorted_snippet_candidates`
23332 // First char must match
23333 .filter(|(_ix, prefix)| {
23334 itertools::equal(
23335 prefix
23336 .chars()
23337 .next()
23338 .into_iter()
23339 .flat_map(|c| c.to_lowercase()),
23340 buffer_window
23341 .chars()
23342 .next()
23343 .into_iter()
23344 .flat_map(|c| c.to_lowercase()),
23345 )
23346 })
23347 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23348 .collect::<Vec<StringMatchCandidate>>();
23349
23350 matches.extend(
23351 fuzzy::match_strings(
23352 &candidates,
23353 &buffer_window,
23354 buffer_window.chars().any(|c| c.is_uppercase()),
23355 true,
23356 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23357 &Default::default(),
23358 executor.clone(),
23359 )
23360 .await
23361 .into_iter()
23362 .map(|string_match| (string_match, buffer_window.len())),
23363 );
23364
23365 if matches.len() >= MAX_RESULTS {
23366 break;
23367 }
23368 }
23369
23370 let to_lsp = |point: &text::Anchor| {
23371 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23372 point_to_lsp(end)
23373 };
23374 let lsp_end = to_lsp(&buffer_anchor);
23375
23376 if matches.len() >= MAX_RESULTS {
23377 is_incomplete = true;
23378 }
23379
23380 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23381 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23382 sorted_snippet_candidates[string_match.candidate_id];
23383 let snippet = &snippets[snippet_index];
23384 let start = buffer_offset - buffer_window_len;
23385 let start = snapshot.anchor_before(start);
23386 let range = start..buffer_anchor;
23387 let lsp_start = to_lsp(&start);
23388 let lsp_range = lsp::Range {
23389 start: lsp_start,
23390 end: lsp_end,
23391 };
23392 Completion {
23393 replace_range: range,
23394 new_text: snippet.body.clone(),
23395 source: CompletionSource::Lsp {
23396 insert_range: None,
23397 server_id: LanguageServerId(usize::MAX),
23398 resolved: true,
23399 lsp_completion: Box::new(lsp::CompletionItem {
23400 label: snippet.prefix.first().unwrap().clone(),
23401 kind: Some(CompletionItemKind::SNIPPET),
23402 label_details: snippet.description.as_ref().map(|description| {
23403 lsp::CompletionItemLabelDetails {
23404 detail: Some(description.clone()),
23405 description: None,
23406 }
23407 }),
23408 insert_text_format: Some(InsertTextFormat::SNIPPET),
23409 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23410 lsp::InsertReplaceEdit {
23411 new_text: snippet.body.clone(),
23412 insert: lsp_range,
23413 replace: lsp_range,
23414 },
23415 )),
23416 filter_text: Some(snippet.body.clone()),
23417 sort_text: Some(char::MAX.to_string()),
23418 ..lsp::CompletionItem::default()
23419 }),
23420 lsp_defaults: None,
23421 },
23422 label: CodeLabel {
23423 text: matching_prefix.clone(),
23424 runs: Vec::new(),
23425 filter_range: 0..matching_prefix.len(),
23426 },
23427 icon_path: None,
23428 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23429 single_line: snippet.name.clone().into(),
23430 plain_text: snippet
23431 .description
23432 .clone()
23433 .map(|description| description.into()),
23434 }),
23435 insert_text_mode: None,
23436 confirm: None,
23437 match_start: Some(start),
23438 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23439 }
23440 }));
23441 }
23442
23443 Ok(CompletionResponse {
23444 completions,
23445 display_options: CompletionDisplayOptions::default(),
23446 is_incomplete,
23447 })
23448 })
23449}
23450
23451impl CompletionProvider for Entity<Project> {
23452 fn completions(
23453 &self,
23454 _excerpt_id: ExcerptId,
23455 buffer: &Entity<Buffer>,
23456 buffer_position: text::Anchor,
23457 options: CompletionContext,
23458 _window: &mut Window,
23459 cx: &mut Context<Editor>,
23460 ) -> Task<Result<Vec<CompletionResponse>>> {
23461 self.update(cx, |project, cx| {
23462 let task = project.completions(buffer, buffer_position, options, cx);
23463 cx.background_spawn(task)
23464 })
23465 }
23466
23467 fn resolve_completions(
23468 &self,
23469 buffer: Entity<Buffer>,
23470 completion_indices: Vec<usize>,
23471 completions: Rc<RefCell<Box<[Completion]>>>,
23472 cx: &mut Context<Editor>,
23473 ) -> Task<Result<bool>> {
23474 self.update(cx, |project, cx| {
23475 project.lsp_store().update(cx, |lsp_store, cx| {
23476 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23477 })
23478 })
23479 }
23480
23481 fn apply_additional_edits_for_completion(
23482 &self,
23483 buffer: Entity<Buffer>,
23484 completions: Rc<RefCell<Box<[Completion]>>>,
23485 completion_index: usize,
23486 push_to_history: bool,
23487 cx: &mut Context<Editor>,
23488 ) -> Task<Result<Option<language::Transaction>>> {
23489 self.update(cx, |project, cx| {
23490 project.lsp_store().update(cx, |lsp_store, cx| {
23491 lsp_store.apply_additional_edits_for_completion(
23492 buffer,
23493 completions,
23494 completion_index,
23495 push_to_history,
23496 cx,
23497 )
23498 })
23499 })
23500 }
23501
23502 fn is_completion_trigger(
23503 &self,
23504 buffer: &Entity<Buffer>,
23505 position: language::Anchor,
23506 text: &str,
23507 trigger_in_words: bool,
23508 menu_is_open: bool,
23509 cx: &mut Context<Editor>,
23510 ) -> bool {
23511 let mut chars = text.chars();
23512 let char = if let Some(char) = chars.next() {
23513 char
23514 } else {
23515 return false;
23516 };
23517 if chars.next().is_some() {
23518 return false;
23519 }
23520
23521 let buffer = buffer.read(cx);
23522 let snapshot = buffer.snapshot();
23523 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23524 return false;
23525 }
23526 let classifier = snapshot
23527 .char_classifier_at(position)
23528 .scope_context(Some(CharScopeContext::Completion));
23529 if trigger_in_words && classifier.is_word(char) {
23530 return true;
23531 }
23532
23533 buffer.completion_triggers().contains(text)
23534 }
23535
23536 fn show_snippets(&self) -> bool {
23537 true
23538 }
23539}
23540
23541impl SemanticsProvider for Entity<Project> {
23542 fn hover(
23543 &self,
23544 buffer: &Entity<Buffer>,
23545 position: text::Anchor,
23546 cx: &mut App,
23547 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23548 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23549 }
23550
23551 fn document_highlights(
23552 &self,
23553 buffer: &Entity<Buffer>,
23554 position: text::Anchor,
23555 cx: &mut App,
23556 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23557 Some(self.update(cx, |project, cx| {
23558 project.document_highlights(buffer, position, cx)
23559 }))
23560 }
23561
23562 fn definitions(
23563 &self,
23564 buffer: &Entity<Buffer>,
23565 position: text::Anchor,
23566 kind: GotoDefinitionKind,
23567 cx: &mut App,
23568 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23569 Some(self.update(cx, |project, cx| match kind {
23570 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23571 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23572 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23573 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23574 }))
23575 }
23576
23577 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23578 self.update(cx, |project, cx| {
23579 if project
23580 .active_debug_session(cx)
23581 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23582 {
23583 return true;
23584 }
23585
23586 buffer.update(cx, |buffer, cx| {
23587 project.any_language_server_supports_inlay_hints(buffer, cx)
23588 })
23589 })
23590 }
23591
23592 fn inline_values(
23593 &self,
23594 buffer_handle: Entity<Buffer>,
23595 range: Range<text::Anchor>,
23596 cx: &mut App,
23597 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23598 self.update(cx, |project, cx| {
23599 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23600
23601 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23602 })
23603 }
23604
23605 fn applicable_inlay_chunks(
23606 &self,
23607 buffer: &Entity<Buffer>,
23608 ranges: &[Range<text::Anchor>],
23609 cx: &mut App,
23610 ) -> Vec<Range<BufferRow>> {
23611 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23612 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23613 })
23614 }
23615
23616 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23617 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23618 lsp_store.invalidate_inlay_hints(for_buffers)
23619 });
23620 }
23621
23622 fn inlay_hints(
23623 &self,
23624 invalidate: InvalidationStrategy,
23625 buffer: Entity<Buffer>,
23626 ranges: Vec<Range<text::Anchor>>,
23627 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23628 cx: &mut App,
23629 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23630 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23631 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23632 }))
23633 }
23634
23635 fn range_for_rename(
23636 &self,
23637 buffer: &Entity<Buffer>,
23638 position: text::Anchor,
23639 cx: &mut App,
23640 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23641 Some(self.update(cx, |project, cx| {
23642 let buffer = buffer.clone();
23643 let task = project.prepare_rename(buffer.clone(), position, cx);
23644 cx.spawn(async move |_, cx| {
23645 Ok(match task.await? {
23646 PrepareRenameResponse::Success(range) => Some(range),
23647 PrepareRenameResponse::InvalidPosition => None,
23648 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23649 // Fallback on using TreeSitter info to determine identifier range
23650 buffer.read_with(cx, |buffer, _| {
23651 let snapshot = buffer.snapshot();
23652 let (range, kind) = snapshot.surrounding_word(position, None);
23653 if kind != Some(CharKind::Word) {
23654 return None;
23655 }
23656 Some(
23657 snapshot.anchor_before(range.start)
23658 ..snapshot.anchor_after(range.end),
23659 )
23660 })?
23661 }
23662 })
23663 })
23664 }))
23665 }
23666
23667 fn perform_rename(
23668 &self,
23669 buffer: &Entity<Buffer>,
23670 position: text::Anchor,
23671 new_name: String,
23672 cx: &mut App,
23673 ) -> Option<Task<Result<ProjectTransaction>>> {
23674 Some(self.update(cx, |project, cx| {
23675 project.perform_rename(buffer.clone(), position, new_name, cx)
23676 }))
23677 }
23678}
23679
23680fn consume_contiguous_rows(
23681 contiguous_row_selections: &mut Vec<Selection<Point>>,
23682 selection: &Selection<Point>,
23683 display_map: &DisplaySnapshot,
23684 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23685) -> (MultiBufferRow, MultiBufferRow) {
23686 contiguous_row_selections.push(selection.clone());
23687 let start_row = starting_row(selection, display_map);
23688 let mut end_row = ending_row(selection, display_map);
23689
23690 while let Some(next_selection) = selections.peek() {
23691 if next_selection.start.row <= end_row.0 {
23692 end_row = ending_row(next_selection, display_map);
23693 contiguous_row_selections.push(selections.next().unwrap().clone());
23694 } else {
23695 break;
23696 }
23697 }
23698 (start_row, end_row)
23699}
23700
23701fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23702 if selection.start.column > 0 {
23703 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23704 } else {
23705 MultiBufferRow(selection.start.row)
23706 }
23707}
23708
23709fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23710 if next_selection.end.column > 0 || next_selection.is_empty() {
23711 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23712 } else {
23713 MultiBufferRow(next_selection.end.row)
23714 }
23715}
23716
23717impl EditorSnapshot {
23718 pub fn remote_selections_in_range<'a>(
23719 &'a self,
23720 range: &'a Range<Anchor>,
23721 collaboration_hub: &dyn CollaborationHub,
23722 cx: &'a App,
23723 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23724 let participant_names = collaboration_hub.user_names(cx);
23725 let participant_indices = collaboration_hub.user_participant_indices(cx);
23726 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23727 let collaborators_by_replica_id = collaborators_by_peer_id
23728 .values()
23729 .map(|collaborator| (collaborator.replica_id, collaborator))
23730 .collect::<HashMap<_, _>>();
23731 self.buffer_snapshot()
23732 .selections_in_range(range, false)
23733 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23734 if replica_id == ReplicaId::AGENT {
23735 Some(RemoteSelection {
23736 replica_id,
23737 selection,
23738 cursor_shape,
23739 line_mode,
23740 collaborator_id: CollaboratorId::Agent,
23741 user_name: Some("Agent".into()),
23742 color: cx.theme().players().agent(),
23743 })
23744 } else {
23745 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23746 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23747 let user_name = participant_names.get(&collaborator.user_id).cloned();
23748 Some(RemoteSelection {
23749 replica_id,
23750 selection,
23751 cursor_shape,
23752 line_mode,
23753 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23754 user_name,
23755 color: if let Some(index) = participant_index {
23756 cx.theme().players().color_for_participant(index.0)
23757 } else {
23758 cx.theme().players().absent()
23759 },
23760 })
23761 }
23762 })
23763 }
23764
23765 pub fn hunks_for_ranges(
23766 &self,
23767 ranges: impl IntoIterator<Item = Range<Point>>,
23768 ) -> Vec<MultiBufferDiffHunk> {
23769 let mut hunks = Vec::new();
23770 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23771 HashMap::default();
23772 for query_range in ranges {
23773 let query_rows =
23774 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23775 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23776 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23777 ) {
23778 // Include deleted hunks that are adjacent to the query range, because
23779 // otherwise they would be missed.
23780 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23781 if hunk.status().is_deleted() {
23782 intersects_range |= hunk.row_range.start == query_rows.end;
23783 intersects_range |= hunk.row_range.end == query_rows.start;
23784 }
23785 if intersects_range {
23786 if !processed_buffer_rows
23787 .entry(hunk.buffer_id)
23788 .or_default()
23789 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23790 {
23791 continue;
23792 }
23793 hunks.push(hunk);
23794 }
23795 }
23796 }
23797
23798 hunks
23799 }
23800
23801 fn display_diff_hunks_for_rows<'a>(
23802 &'a self,
23803 display_rows: Range<DisplayRow>,
23804 folded_buffers: &'a HashSet<BufferId>,
23805 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23806 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23807 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23808
23809 self.buffer_snapshot()
23810 .diff_hunks_in_range(buffer_start..buffer_end)
23811 .filter_map(|hunk| {
23812 if folded_buffers.contains(&hunk.buffer_id) {
23813 return None;
23814 }
23815
23816 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23817 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23818
23819 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23820 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23821
23822 let display_hunk = if hunk_display_start.column() != 0 {
23823 DisplayDiffHunk::Folded {
23824 display_row: hunk_display_start.row(),
23825 }
23826 } else {
23827 let mut end_row = hunk_display_end.row();
23828 if hunk_display_end.column() > 0 {
23829 end_row.0 += 1;
23830 }
23831 let is_created_file = hunk.is_created_file();
23832 DisplayDiffHunk::Unfolded {
23833 status: hunk.status(),
23834 diff_base_byte_range: hunk.diff_base_byte_range,
23835 display_row_range: hunk_display_start.row()..end_row,
23836 multi_buffer_range: Anchor::range_in_buffer(
23837 hunk.excerpt_id,
23838 hunk.buffer_id,
23839 hunk.buffer_range,
23840 ),
23841 is_created_file,
23842 }
23843 };
23844
23845 Some(display_hunk)
23846 })
23847 }
23848
23849 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23850 self.display_snapshot
23851 .buffer_snapshot()
23852 .language_at(position)
23853 }
23854
23855 pub fn is_focused(&self) -> bool {
23856 self.is_focused
23857 }
23858
23859 pub fn placeholder_text(&self) -> Option<String> {
23860 self.placeholder_display_snapshot
23861 .as_ref()
23862 .map(|display_map| display_map.text())
23863 }
23864
23865 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23866 self.scroll_anchor.scroll_position(&self.display_snapshot)
23867 }
23868
23869 fn gutter_dimensions(
23870 &self,
23871 font_id: FontId,
23872 font_size: Pixels,
23873 max_line_number_width: Pixels,
23874 cx: &App,
23875 ) -> Option<GutterDimensions> {
23876 if !self.show_gutter {
23877 return None;
23878 }
23879
23880 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23881 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23882
23883 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23884 matches!(
23885 ProjectSettings::get_global(cx).git.git_gutter,
23886 GitGutterSetting::TrackedFiles
23887 )
23888 });
23889 let gutter_settings = EditorSettings::get_global(cx).gutter;
23890 let show_line_numbers = self
23891 .show_line_numbers
23892 .unwrap_or(gutter_settings.line_numbers);
23893 let line_gutter_width = if show_line_numbers {
23894 // Avoid flicker-like gutter resizes when the line number gains another digit by
23895 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23896 let min_width_for_number_on_gutter =
23897 ch_advance * gutter_settings.min_line_number_digits as f32;
23898 max_line_number_width.max(min_width_for_number_on_gutter)
23899 } else {
23900 0.0.into()
23901 };
23902
23903 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23904 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23905
23906 let git_blame_entries_width =
23907 self.git_blame_gutter_max_author_length
23908 .map(|max_author_length| {
23909 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23910 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23911
23912 /// The number of characters to dedicate to gaps and margins.
23913 const SPACING_WIDTH: usize = 4;
23914
23915 let max_char_count = max_author_length.min(renderer.max_author_length())
23916 + ::git::SHORT_SHA_LENGTH
23917 + MAX_RELATIVE_TIMESTAMP.len()
23918 + SPACING_WIDTH;
23919
23920 ch_advance * max_char_count
23921 });
23922
23923 let is_singleton = self.buffer_snapshot().is_singleton();
23924
23925 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23926 left_padding += if !is_singleton {
23927 ch_width * 4.0
23928 } else if show_runnables || show_breakpoints {
23929 ch_width * 3.0
23930 } else if show_git_gutter && show_line_numbers {
23931 ch_width * 2.0
23932 } else if show_git_gutter || show_line_numbers {
23933 ch_width
23934 } else {
23935 px(0.)
23936 };
23937
23938 let shows_folds = is_singleton && gutter_settings.folds;
23939
23940 let right_padding = if shows_folds && show_line_numbers {
23941 ch_width * 4.0
23942 } else if shows_folds || (!is_singleton && show_line_numbers) {
23943 ch_width * 3.0
23944 } else if show_line_numbers {
23945 ch_width
23946 } else {
23947 px(0.)
23948 };
23949
23950 Some(GutterDimensions {
23951 left_padding,
23952 right_padding,
23953 width: line_gutter_width + left_padding + right_padding,
23954 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23955 git_blame_entries_width,
23956 })
23957 }
23958
23959 pub fn render_crease_toggle(
23960 &self,
23961 buffer_row: MultiBufferRow,
23962 row_contains_cursor: bool,
23963 editor: Entity<Editor>,
23964 window: &mut Window,
23965 cx: &mut App,
23966 ) -> Option<AnyElement> {
23967 let folded = self.is_line_folded(buffer_row);
23968 let mut is_foldable = false;
23969
23970 if let Some(crease) = self
23971 .crease_snapshot
23972 .query_row(buffer_row, self.buffer_snapshot())
23973 {
23974 is_foldable = true;
23975 match crease {
23976 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23977 if let Some(render_toggle) = render_toggle {
23978 let toggle_callback =
23979 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23980 if folded {
23981 editor.update(cx, |editor, cx| {
23982 editor.fold_at(buffer_row, window, cx)
23983 });
23984 } else {
23985 editor.update(cx, |editor, cx| {
23986 editor.unfold_at(buffer_row, window, cx)
23987 });
23988 }
23989 });
23990 return Some((render_toggle)(
23991 buffer_row,
23992 folded,
23993 toggle_callback,
23994 window,
23995 cx,
23996 ));
23997 }
23998 }
23999 }
24000 }
24001
24002 is_foldable |= self.starts_indent(buffer_row);
24003
24004 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24005 Some(
24006 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24007 .toggle_state(folded)
24008 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24009 if folded {
24010 this.unfold_at(buffer_row, window, cx);
24011 } else {
24012 this.fold_at(buffer_row, window, cx);
24013 }
24014 }))
24015 .into_any_element(),
24016 )
24017 } else {
24018 None
24019 }
24020 }
24021
24022 pub fn render_crease_trailer(
24023 &self,
24024 buffer_row: MultiBufferRow,
24025 window: &mut Window,
24026 cx: &mut App,
24027 ) -> Option<AnyElement> {
24028 let folded = self.is_line_folded(buffer_row);
24029 if let Crease::Inline { render_trailer, .. } = self
24030 .crease_snapshot
24031 .query_row(buffer_row, self.buffer_snapshot())?
24032 {
24033 let render_trailer = render_trailer.as_ref()?;
24034 Some(render_trailer(buffer_row, folded, window, cx))
24035 } else {
24036 None
24037 }
24038 }
24039}
24040
24041impl Deref for EditorSnapshot {
24042 type Target = DisplaySnapshot;
24043
24044 fn deref(&self) -> &Self::Target {
24045 &self.display_snapshot
24046 }
24047}
24048
24049#[derive(Clone, Debug, PartialEq, Eq)]
24050pub enum EditorEvent {
24051 InputIgnored {
24052 text: Arc<str>,
24053 },
24054 InputHandled {
24055 utf16_range_to_replace: Option<Range<isize>>,
24056 text: Arc<str>,
24057 },
24058 ExcerptsAdded {
24059 buffer: Entity<Buffer>,
24060 predecessor: ExcerptId,
24061 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24062 },
24063 ExcerptsRemoved {
24064 ids: Vec<ExcerptId>,
24065 removed_buffer_ids: Vec<BufferId>,
24066 },
24067 BufferFoldToggled {
24068 ids: Vec<ExcerptId>,
24069 folded: bool,
24070 },
24071 ExcerptsEdited {
24072 ids: Vec<ExcerptId>,
24073 },
24074 ExcerptsExpanded {
24075 ids: Vec<ExcerptId>,
24076 },
24077 BufferEdited,
24078 Edited {
24079 transaction_id: clock::Lamport,
24080 },
24081 Reparsed(BufferId),
24082 Focused,
24083 FocusedIn,
24084 Blurred,
24085 DirtyChanged,
24086 Saved,
24087 TitleChanged,
24088 SelectionsChanged {
24089 local: bool,
24090 },
24091 ScrollPositionChanged {
24092 local: bool,
24093 autoscroll: bool,
24094 },
24095 TransactionUndone {
24096 transaction_id: clock::Lamport,
24097 },
24098 TransactionBegun {
24099 transaction_id: clock::Lamport,
24100 },
24101 CursorShapeChanged,
24102 BreadcrumbsChanged,
24103 PushedToNavHistory {
24104 anchor: Anchor,
24105 is_deactivate: bool,
24106 },
24107}
24108
24109impl EventEmitter<EditorEvent> for Editor {}
24110
24111impl Focusable for Editor {
24112 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24113 self.focus_handle.clone()
24114 }
24115}
24116
24117impl Render for Editor {
24118 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24119 let settings = ThemeSettings::get_global(cx);
24120
24121 let mut text_style = match self.mode {
24122 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24123 color: cx.theme().colors().editor_foreground,
24124 font_family: settings.ui_font.family.clone(),
24125 font_features: settings.ui_font.features.clone(),
24126 font_fallbacks: settings.ui_font.fallbacks.clone(),
24127 font_size: rems(0.875).into(),
24128 font_weight: settings.ui_font.weight,
24129 line_height: relative(settings.buffer_line_height.value()),
24130 ..Default::default()
24131 },
24132 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24133 color: cx.theme().colors().editor_foreground,
24134 font_family: settings.buffer_font.family.clone(),
24135 font_features: settings.buffer_font.features.clone(),
24136 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24137 font_size: settings.buffer_font_size(cx).into(),
24138 font_weight: settings.buffer_font.weight,
24139 line_height: relative(settings.buffer_line_height.value()),
24140 ..Default::default()
24141 },
24142 };
24143 if let Some(text_style_refinement) = &self.text_style_refinement {
24144 text_style.refine(text_style_refinement)
24145 }
24146
24147 let background = match self.mode {
24148 EditorMode::SingleLine => cx.theme().system().transparent,
24149 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24150 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24151 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24152 };
24153
24154 EditorElement::new(
24155 &cx.entity(),
24156 EditorStyle {
24157 background,
24158 border: cx.theme().colors().border,
24159 local_player: cx.theme().players().local(),
24160 text: text_style,
24161 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24162 syntax: cx.theme().syntax().clone(),
24163 status: cx.theme().status().clone(),
24164 inlay_hints_style: make_inlay_hints_style(cx),
24165 edit_prediction_styles: make_suggestion_styles(cx),
24166 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24167 show_underlines: self.diagnostics_enabled(),
24168 },
24169 )
24170 }
24171}
24172
24173impl EntityInputHandler for Editor {
24174 fn text_for_range(
24175 &mut self,
24176 range_utf16: Range<usize>,
24177 adjusted_range: &mut Option<Range<usize>>,
24178 _: &mut Window,
24179 cx: &mut Context<Self>,
24180 ) -> Option<String> {
24181 let snapshot = self.buffer.read(cx).read(cx);
24182 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24183 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24184 if (start.0..end.0) != range_utf16 {
24185 adjusted_range.replace(start.0..end.0);
24186 }
24187 Some(snapshot.text_for_range(start..end).collect())
24188 }
24189
24190 fn selected_text_range(
24191 &mut self,
24192 ignore_disabled_input: bool,
24193 _: &mut Window,
24194 cx: &mut Context<Self>,
24195 ) -> Option<UTF16Selection> {
24196 // Prevent the IME menu from appearing when holding down an alphabetic key
24197 // while input is disabled.
24198 if !ignore_disabled_input && !self.input_enabled {
24199 return None;
24200 }
24201
24202 let selection = self
24203 .selections
24204 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24205 let range = selection.range();
24206
24207 Some(UTF16Selection {
24208 range: range.start.0..range.end.0,
24209 reversed: selection.reversed,
24210 })
24211 }
24212
24213 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24214 let snapshot = self.buffer.read(cx).read(cx);
24215 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24216 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24217 }
24218
24219 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24220 self.clear_highlights::<InputComposition>(cx);
24221 self.ime_transaction.take();
24222 }
24223
24224 fn replace_text_in_range(
24225 &mut self,
24226 range_utf16: Option<Range<usize>>,
24227 text: &str,
24228 window: &mut Window,
24229 cx: &mut Context<Self>,
24230 ) {
24231 if !self.input_enabled {
24232 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24233 return;
24234 }
24235
24236 self.transact(window, cx, |this, window, cx| {
24237 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24238 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24239 Some(this.selection_replacement_ranges(range_utf16, cx))
24240 } else {
24241 this.marked_text_ranges(cx)
24242 };
24243
24244 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24245 let newest_selection_id = this.selections.newest_anchor().id;
24246 this.selections
24247 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24248 .iter()
24249 .zip(ranges_to_replace.iter())
24250 .find_map(|(selection, range)| {
24251 if selection.id == newest_selection_id {
24252 Some(
24253 (range.start.0 as isize - selection.head().0 as isize)
24254 ..(range.end.0 as isize - selection.head().0 as isize),
24255 )
24256 } else {
24257 None
24258 }
24259 })
24260 });
24261
24262 cx.emit(EditorEvent::InputHandled {
24263 utf16_range_to_replace: range_to_replace,
24264 text: text.into(),
24265 });
24266
24267 if let Some(new_selected_ranges) = new_selected_ranges {
24268 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24269 selections.select_ranges(new_selected_ranges)
24270 });
24271 this.backspace(&Default::default(), window, cx);
24272 }
24273
24274 this.handle_input(text, window, cx);
24275 });
24276
24277 if let Some(transaction) = self.ime_transaction {
24278 self.buffer.update(cx, |buffer, cx| {
24279 buffer.group_until_transaction(transaction, cx);
24280 });
24281 }
24282
24283 self.unmark_text(window, cx);
24284 }
24285
24286 fn replace_and_mark_text_in_range(
24287 &mut self,
24288 range_utf16: Option<Range<usize>>,
24289 text: &str,
24290 new_selected_range_utf16: Option<Range<usize>>,
24291 window: &mut Window,
24292 cx: &mut Context<Self>,
24293 ) {
24294 if !self.input_enabled {
24295 return;
24296 }
24297
24298 let transaction = self.transact(window, cx, |this, window, cx| {
24299 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24300 let snapshot = this.buffer.read(cx).read(cx);
24301 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24302 for marked_range in &mut marked_ranges {
24303 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24304 marked_range.start.0 += relative_range_utf16.start;
24305 marked_range.start =
24306 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24307 marked_range.end =
24308 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24309 }
24310 }
24311 Some(marked_ranges)
24312 } else if let Some(range_utf16) = range_utf16 {
24313 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24314 Some(this.selection_replacement_ranges(range_utf16, cx))
24315 } else {
24316 None
24317 };
24318
24319 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24320 let newest_selection_id = this.selections.newest_anchor().id;
24321 this.selections
24322 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24323 .iter()
24324 .zip(ranges_to_replace.iter())
24325 .find_map(|(selection, range)| {
24326 if selection.id == newest_selection_id {
24327 Some(
24328 (range.start.0 as isize - selection.head().0 as isize)
24329 ..(range.end.0 as isize - selection.head().0 as isize),
24330 )
24331 } else {
24332 None
24333 }
24334 })
24335 });
24336
24337 cx.emit(EditorEvent::InputHandled {
24338 utf16_range_to_replace: range_to_replace,
24339 text: text.into(),
24340 });
24341
24342 if let Some(ranges) = ranges_to_replace {
24343 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24344 s.select_ranges(ranges)
24345 });
24346 }
24347
24348 let marked_ranges = {
24349 let snapshot = this.buffer.read(cx).read(cx);
24350 this.selections
24351 .disjoint_anchors_arc()
24352 .iter()
24353 .map(|selection| {
24354 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24355 })
24356 .collect::<Vec<_>>()
24357 };
24358
24359 if text.is_empty() {
24360 this.unmark_text(window, cx);
24361 } else {
24362 this.highlight_text::<InputComposition>(
24363 marked_ranges.clone(),
24364 HighlightStyle {
24365 underline: Some(UnderlineStyle {
24366 thickness: px(1.),
24367 color: None,
24368 wavy: false,
24369 }),
24370 ..Default::default()
24371 },
24372 cx,
24373 );
24374 }
24375
24376 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24377 let use_autoclose = this.use_autoclose;
24378 let use_auto_surround = this.use_auto_surround;
24379 this.set_use_autoclose(false);
24380 this.set_use_auto_surround(false);
24381 this.handle_input(text, window, cx);
24382 this.set_use_autoclose(use_autoclose);
24383 this.set_use_auto_surround(use_auto_surround);
24384
24385 if let Some(new_selected_range) = new_selected_range_utf16 {
24386 let snapshot = this.buffer.read(cx).read(cx);
24387 let new_selected_ranges = marked_ranges
24388 .into_iter()
24389 .map(|marked_range| {
24390 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24391 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24392 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24393 snapshot.clip_offset_utf16(new_start, Bias::Left)
24394 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24395 })
24396 .collect::<Vec<_>>();
24397
24398 drop(snapshot);
24399 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24400 selections.select_ranges(new_selected_ranges)
24401 });
24402 }
24403 });
24404
24405 self.ime_transaction = self.ime_transaction.or(transaction);
24406 if let Some(transaction) = self.ime_transaction {
24407 self.buffer.update(cx, |buffer, cx| {
24408 buffer.group_until_transaction(transaction, cx);
24409 });
24410 }
24411
24412 if self.text_highlights::<InputComposition>(cx).is_none() {
24413 self.ime_transaction.take();
24414 }
24415 }
24416
24417 fn bounds_for_range(
24418 &mut self,
24419 range_utf16: Range<usize>,
24420 element_bounds: gpui::Bounds<Pixels>,
24421 window: &mut Window,
24422 cx: &mut Context<Self>,
24423 ) -> Option<gpui::Bounds<Pixels>> {
24424 let text_layout_details = self.text_layout_details(window);
24425 let CharacterDimensions {
24426 em_width,
24427 em_advance,
24428 line_height,
24429 } = self.character_dimensions(window);
24430
24431 let snapshot = self.snapshot(window, cx);
24432 let scroll_position = snapshot.scroll_position();
24433 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24434
24435 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24436 let x = Pixels::from(
24437 ScrollOffset::from(
24438 snapshot.x_for_display_point(start, &text_layout_details)
24439 + self.gutter_dimensions.full_width(),
24440 ) - scroll_left,
24441 );
24442 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24443
24444 Some(Bounds {
24445 origin: element_bounds.origin + point(x, y),
24446 size: size(em_width, line_height),
24447 })
24448 }
24449
24450 fn character_index_for_point(
24451 &mut self,
24452 point: gpui::Point<Pixels>,
24453 _window: &mut Window,
24454 _cx: &mut Context<Self>,
24455 ) -> Option<usize> {
24456 let position_map = self.last_position_map.as_ref()?;
24457 if !position_map.text_hitbox.contains(&point) {
24458 return None;
24459 }
24460 let display_point = position_map.point_for_position(point).previous_valid;
24461 let anchor = position_map
24462 .snapshot
24463 .display_point_to_anchor(display_point, Bias::Left);
24464 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24465 Some(utf16_offset.0)
24466 }
24467
24468 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24469 self.input_enabled
24470 }
24471}
24472
24473trait SelectionExt {
24474 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24475 fn spanned_rows(
24476 &self,
24477 include_end_if_at_line_start: bool,
24478 map: &DisplaySnapshot,
24479 ) -> Range<MultiBufferRow>;
24480}
24481
24482impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24483 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24484 let start = self
24485 .start
24486 .to_point(map.buffer_snapshot())
24487 .to_display_point(map);
24488 let end = self
24489 .end
24490 .to_point(map.buffer_snapshot())
24491 .to_display_point(map);
24492 if self.reversed {
24493 end..start
24494 } else {
24495 start..end
24496 }
24497 }
24498
24499 fn spanned_rows(
24500 &self,
24501 include_end_if_at_line_start: bool,
24502 map: &DisplaySnapshot,
24503 ) -> Range<MultiBufferRow> {
24504 let start = self.start.to_point(map.buffer_snapshot());
24505 let mut end = self.end.to_point(map.buffer_snapshot());
24506 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24507 end.row -= 1;
24508 }
24509
24510 let buffer_start = map.prev_line_boundary(start).0;
24511 let buffer_end = map.next_line_boundary(end).0;
24512 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24513 }
24514}
24515
24516impl<T: InvalidationRegion> InvalidationStack<T> {
24517 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24518 where
24519 S: Clone + ToOffset,
24520 {
24521 while let Some(region) = self.last() {
24522 let all_selections_inside_invalidation_ranges =
24523 if selections.len() == region.ranges().len() {
24524 selections
24525 .iter()
24526 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24527 .all(|(selection, invalidation_range)| {
24528 let head = selection.head().to_offset(buffer);
24529 invalidation_range.start <= head && invalidation_range.end >= head
24530 })
24531 } else {
24532 false
24533 };
24534
24535 if all_selections_inside_invalidation_ranges {
24536 break;
24537 } else {
24538 self.pop();
24539 }
24540 }
24541 }
24542}
24543
24544impl<T> Default for InvalidationStack<T> {
24545 fn default() -> Self {
24546 Self(Default::default())
24547 }
24548}
24549
24550impl<T> Deref for InvalidationStack<T> {
24551 type Target = Vec<T>;
24552
24553 fn deref(&self) -> &Self::Target {
24554 &self.0
24555 }
24556}
24557
24558impl<T> DerefMut for InvalidationStack<T> {
24559 fn deref_mut(&mut self) -> &mut Self::Target {
24560 &mut self.0
24561 }
24562}
24563
24564impl InvalidationRegion for SnippetState {
24565 fn ranges(&self) -> &[Range<Anchor>] {
24566 &self.ranges[self.active_index]
24567 }
24568}
24569
24570fn edit_prediction_edit_text(
24571 current_snapshot: &BufferSnapshot,
24572 edits: &[(Range<Anchor>, impl AsRef<str>)],
24573 edit_preview: &EditPreview,
24574 include_deletions: bool,
24575 cx: &App,
24576) -> HighlightedText {
24577 let edits = edits
24578 .iter()
24579 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24580 .collect::<Vec<_>>();
24581
24582 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24583}
24584
24585fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24586 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24587 // Just show the raw edit text with basic styling
24588 let mut text = String::new();
24589 let mut highlights = Vec::new();
24590
24591 let insertion_highlight_style = HighlightStyle {
24592 color: Some(cx.theme().colors().text),
24593 ..Default::default()
24594 };
24595
24596 for (_, edit_text) in edits {
24597 let start_offset = text.len();
24598 text.push_str(edit_text);
24599 let end_offset = text.len();
24600
24601 if start_offset < end_offset {
24602 highlights.push((start_offset..end_offset, insertion_highlight_style));
24603 }
24604 }
24605
24606 HighlightedText {
24607 text: text.into(),
24608 highlights,
24609 }
24610}
24611
24612pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24613 match severity {
24614 lsp::DiagnosticSeverity::ERROR => colors.error,
24615 lsp::DiagnosticSeverity::WARNING => colors.warning,
24616 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24617 lsp::DiagnosticSeverity::HINT => colors.info,
24618 _ => colors.ignored,
24619 }
24620}
24621
24622pub fn styled_runs_for_code_label<'a>(
24623 label: &'a CodeLabel,
24624 syntax_theme: &'a theme::SyntaxTheme,
24625) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24626 let fade_out = HighlightStyle {
24627 fade_out: Some(0.35),
24628 ..Default::default()
24629 };
24630
24631 let mut prev_end = label.filter_range.end;
24632 label
24633 .runs
24634 .iter()
24635 .enumerate()
24636 .flat_map(move |(ix, (range, highlight_id))| {
24637 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24638 style
24639 } else {
24640 return Default::default();
24641 };
24642 let muted_style = style.highlight(fade_out);
24643
24644 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24645 if range.start >= label.filter_range.end {
24646 if range.start > prev_end {
24647 runs.push((prev_end..range.start, fade_out));
24648 }
24649 runs.push((range.clone(), muted_style));
24650 } else if range.end <= label.filter_range.end {
24651 runs.push((range.clone(), style));
24652 } else {
24653 runs.push((range.start..label.filter_range.end, style));
24654 runs.push((label.filter_range.end..range.end, muted_style));
24655 }
24656 prev_end = cmp::max(prev_end, range.end);
24657
24658 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24659 runs.push((prev_end..label.text.len(), fade_out));
24660 }
24661
24662 runs
24663 })
24664}
24665
24666pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24667 let mut prev_index = 0;
24668 let mut prev_codepoint: Option<char> = None;
24669 text.char_indices()
24670 .chain([(text.len(), '\0')])
24671 .filter_map(move |(index, codepoint)| {
24672 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24673 let is_boundary = index == text.len()
24674 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24675 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24676 if is_boundary {
24677 let chunk = &text[prev_index..index];
24678 prev_index = index;
24679 Some(chunk)
24680 } else {
24681 None
24682 }
24683 })
24684}
24685
24686/// Given a string of text immediately before the cursor, iterates over possible
24687/// strings a snippet could match to. More precisely: returns an iterator over
24688/// suffixes of `text` created by splitting at word boundaries (before & after
24689/// every non-word character).
24690///
24691/// Shorter suffixes are returned first.
24692pub(crate) fn snippet_candidate_suffixes(
24693 text: &str,
24694 is_word_char: impl Fn(char) -> bool,
24695) -> impl std::iter::Iterator<Item = &str> {
24696 let mut prev_index = text.len();
24697 let mut prev_codepoint = None;
24698 text.char_indices()
24699 .rev()
24700 .chain([(0, '\0')])
24701 .filter_map(move |(index, codepoint)| {
24702 let prev_index = std::mem::replace(&mut prev_index, index);
24703 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24704 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24705 None
24706 } else {
24707 let chunk = &text[prev_index..]; // go to end of string
24708 Some(chunk)
24709 }
24710 })
24711}
24712
24713pub trait RangeToAnchorExt: Sized {
24714 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24715
24716 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24717 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24718 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24719 }
24720}
24721
24722impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24723 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24724 let start_offset = self.start.to_offset(snapshot);
24725 let end_offset = self.end.to_offset(snapshot);
24726 if start_offset == end_offset {
24727 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24728 } else {
24729 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24730 }
24731 }
24732}
24733
24734pub trait RowExt {
24735 fn as_f64(&self) -> f64;
24736
24737 fn next_row(&self) -> Self;
24738
24739 fn previous_row(&self) -> Self;
24740
24741 fn minus(&self, other: Self) -> u32;
24742}
24743
24744impl RowExt for DisplayRow {
24745 fn as_f64(&self) -> f64 {
24746 self.0 as _
24747 }
24748
24749 fn next_row(&self) -> Self {
24750 Self(self.0 + 1)
24751 }
24752
24753 fn previous_row(&self) -> Self {
24754 Self(self.0.saturating_sub(1))
24755 }
24756
24757 fn minus(&self, other: Self) -> u32 {
24758 self.0 - other.0
24759 }
24760}
24761
24762impl RowExt for MultiBufferRow {
24763 fn as_f64(&self) -> f64 {
24764 self.0 as _
24765 }
24766
24767 fn next_row(&self) -> Self {
24768 Self(self.0 + 1)
24769 }
24770
24771 fn previous_row(&self) -> Self {
24772 Self(self.0.saturating_sub(1))
24773 }
24774
24775 fn minus(&self, other: Self) -> u32 {
24776 self.0 - other.0
24777 }
24778}
24779
24780trait RowRangeExt {
24781 type Row;
24782
24783 fn len(&self) -> usize;
24784
24785 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24786}
24787
24788impl RowRangeExt for Range<MultiBufferRow> {
24789 type Row = MultiBufferRow;
24790
24791 fn len(&self) -> usize {
24792 (self.end.0 - self.start.0) as usize
24793 }
24794
24795 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24796 (self.start.0..self.end.0).map(MultiBufferRow)
24797 }
24798}
24799
24800impl RowRangeExt for Range<DisplayRow> {
24801 type Row = DisplayRow;
24802
24803 fn len(&self) -> usize {
24804 (self.end.0 - self.start.0) as usize
24805 }
24806
24807 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24808 (self.start.0..self.end.0).map(DisplayRow)
24809 }
24810}
24811
24812/// If select range has more than one line, we
24813/// just point the cursor to range.start.
24814fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24815 if range.start.row == range.end.row {
24816 range
24817 } else {
24818 range.start..range.start
24819 }
24820}
24821pub struct KillRing(ClipboardItem);
24822impl Global for KillRing {}
24823
24824const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24825
24826enum BreakpointPromptEditAction {
24827 Log,
24828 Condition,
24829 HitCondition,
24830}
24831
24832struct BreakpointPromptEditor {
24833 pub(crate) prompt: Entity<Editor>,
24834 editor: WeakEntity<Editor>,
24835 breakpoint_anchor: Anchor,
24836 breakpoint: Breakpoint,
24837 edit_action: BreakpointPromptEditAction,
24838 block_ids: HashSet<CustomBlockId>,
24839 editor_margins: Arc<Mutex<EditorMargins>>,
24840 _subscriptions: Vec<Subscription>,
24841}
24842
24843impl BreakpointPromptEditor {
24844 const MAX_LINES: u8 = 4;
24845
24846 fn new(
24847 editor: WeakEntity<Editor>,
24848 breakpoint_anchor: Anchor,
24849 breakpoint: Breakpoint,
24850 edit_action: BreakpointPromptEditAction,
24851 window: &mut Window,
24852 cx: &mut Context<Self>,
24853 ) -> Self {
24854 let base_text = match edit_action {
24855 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24856 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24857 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24858 }
24859 .map(|msg| msg.to_string())
24860 .unwrap_or_default();
24861
24862 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24863 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24864
24865 let prompt = cx.new(|cx| {
24866 let mut prompt = Editor::new(
24867 EditorMode::AutoHeight {
24868 min_lines: 1,
24869 max_lines: Some(Self::MAX_LINES as usize),
24870 },
24871 buffer,
24872 None,
24873 window,
24874 cx,
24875 );
24876 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24877 prompt.set_show_cursor_when_unfocused(false, cx);
24878 prompt.set_placeholder_text(
24879 match edit_action {
24880 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24881 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24882 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24883 },
24884 window,
24885 cx,
24886 );
24887
24888 prompt
24889 });
24890
24891 Self {
24892 prompt,
24893 editor,
24894 breakpoint_anchor,
24895 breakpoint,
24896 edit_action,
24897 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24898 block_ids: Default::default(),
24899 _subscriptions: vec![],
24900 }
24901 }
24902
24903 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24904 self.block_ids.extend(block_ids)
24905 }
24906
24907 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24908 if let Some(editor) = self.editor.upgrade() {
24909 let message = self
24910 .prompt
24911 .read(cx)
24912 .buffer
24913 .read(cx)
24914 .as_singleton()
24915 .expect("A multi buffer in breakpoint prompt isn't possible")
24916 .read(cx)
24917 .as_rope()
24918 .to_string();
24919
24920 editor.update(cx, |editor, cx| {
24921 editor.edit_breakpoint_at_anchor(
24922 self.breakpoint_anchor,
24923 self.breakpoint.clone(),
24924 match self.edit_action {
24925 BreakpointPromptEditAction::Log => {
24926 BreakpointEditAction::EditLogMessage(message.into())
24927 }
24928 BreakpointPromptEditAction::Condition => {
24929 BreakpointEditAction::EditCondition(message.into())
24930 }
24931 BreakpointPromptEditAction::HitCondition => {
24932 BreakpointEditAction::EditHitCondition(message.into())
24933 }
24934 },
24935 cx,
24936 );
24937
24938 editor.remove_blocks(self.block_ids.clone(), None, cx);
24939 cx.focus_self(window);
24940 });
24941 }
24942 }
24943
24944 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24945 self.editor
24946 .update(cx, |editor, cx| {
24947 editor.remove_blocks(self.block_ids.clone(), None, cx);
24948 window.focus(&editor.focus_handle);
24949 })
24950 .log_err();
24951 }
24952
24953 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24954 let settings = ThemeSettings::get_global(cx);
24955 let text_style = TextStyle {
24956 color: if self.prompt.read(cx).read_only(cx) {
24957 cx.theme().colors().text_disabled
24958 } else {
24959 cx.theme().colors().text
24960 },
24961 font_family: settings.buffer_font.family.clone(),
24962 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24963 font_size: settings.buffer_font_size(cx).into(),
24964 font_weight: settings.buffer_font.weight,
24965 line_height: relative(settings.buffer_line_height.value()),
24966 ..Default::default()
24967 };
24968 EditorElement::new(
24969 &self.prompt,
24970 EditorStyle {
24971 background: cx.theme().colors().editor_background,
24972 local_player: cx.theme().players().local(),
24973 text: text_style,
24974 ..Default::default()
24975 },
24976 )
24977 }
24978}
24979
24980impl Render for BreakpointPromptEditor {
24981 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24982 let editor_margins = *self.editor_margins.lock();
24983 let gutter_dimensions = editor_margins.gutter;
24984 h_flex()
24985 .key_context("Editor")
24986 .bg(cx.theme().colors().editor_background)
24987 .border_y_1()
24988 .border_color(cx.theme().status().info_border)
24989 .size_full()
24990 .py(window.line_height() / 2.5)
24991 .on_action(cx.listener(Self::confirm))
24992 .on_action(cx.listener(Self::cancel))
24993 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24994 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24995 }
24996}
24997
24998impl Focusable for BreakpointPromptEditor {
24999 fn focus_handle(&self, cx: &App) -> FocusHandle {
25000 self.prompt.focus_handle(cx)
25001 }
25002}
25003
25004fn all_edits_insertions_or_deletions(
25005 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25006 snapshot: &MultiBufferSnapshot,
25007) -> bool {
25008 let mut all_insertions = true;
25009 let mut all_deletions = true;
25010
25011 for (range, new_text) in edits.iter() {
25012 let range_is_empty = range.to_offset(snapshot).is_empty();
25013 let text_is_empty = new_text.is_empty();
25014
25015 if range_is_empty != text_is_empty {
25016 if range_is_empty {
25017 all_deletions = false;
25018 } else {
25019 all_insertions = false;
25020 }
25021 } else {
25022 return false;
25023 }
25024
25025 if !all_insertions && !all_deletions {
25026 return false;
25027 }
25028 }
25029 all_insertions || all_deletions
25030}
25031
25032struct MissingEditPredictionKeybindingTooltip;
25033
25034impl Render for MissingEditPredictionKeybindingTooltip {
25035 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25036 ui::tooltip_container(cx, |container, cx| {
25037 container
25038 .flex_shrink_0()
25039 .max_w_80()
25040 .min_h(rems_from_px(124.))
25041 .justify_between()
25042 .child(
25043 v_flex()
25044 .flex_1()
25045 .text_ui_sm(cx)
25046 .child(Label::new("Conflict with Accept Keybinding"))
25047 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25048 )
25049 .child(
25050 h_flex()
25051 .pb_1()
25052 .gap_1()
25053 .items_end()
25054 .w_full()
25055 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25056 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25057 }))
25058 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25059 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25060 })),
25061 )
25062 })
25063 }
25064}
25065
25066#[derive(Debug, Clone, Copy, PartialEq)]
25067pub struct LineHighlight {
25068 pub background: Background,
25069 pub border: Option<gpui::Hsla>,
25070 pub include_gutter: bool,
25071 pub type_id: Option<TypeId>,
25072}
25073
25074struct LineManipulationResult {
25075 pub new_text: String,
25076 pub line_count_before: usize,
25077 pub line_count_after: usize,
25078}
25079
25080fn render_diff_hunk_controls(
25081 row: u32,
25082 status: &DiffHunkStatus,
25083 hunk_range: Range<Anchor>,
25084 is_created_file: bool,
25085 line_height: Pixels,
25086 editor: &Entity<Editor>,
25087 _window: &mut Window,
25088 cx: &mut App,
25089) -> AnyElement {
25090 h_flex()
25091 .h(line_height)
25092 .mr_1()
25093 .gap_1()
25094 .px_0p5()
25095 .pb_1()
25096 .border_x_1()
25097 .border_b_1()
25098 .border_color(cx.theme().colors().border_variant)
25099 .rounded_b_lg()
25100 .bg(cx.theme().colors().editor_background)
25101 .gap_1()
25102 .block_mouse_except_scroll()
25103 .shadow_md()
25104 .child(if status.has_secondary_hunk() {
25105 Button::new(("stage", row as u64), "Stage")
25106 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25107 .tooltip({
25108 let focus_handle = editor.focus_handle(cx);
25109 move |_window, cx| {
25110 Tooltip::for_action_in(
25111 "Stage Hunk",
25112 &::git::ToggleStaged,
25113 &focus_handle,
25114 cx,
25115 )
25116 }
25117 })
25118 .on_click({
25119 let editor = editor.clone();
25120 move |_event, _window, cx| {
25121 editor.update(cx, |editor, cx| {
25122 editor.stage_or_unstage_diff_hunks(
25123 true,
25124 vec![hunk_range.start..hunk_range.start],
25125 cx,
25126 );
25127 });
25128 }
25129 })
25130 } else {
25131 Button::new(("unstage", row as u64), "Unstage")
25132 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25133 .tooltip({
25134 let focus_handle = editor.focus_handle(cx);
25135 move |_window, cx| {
25136 Tooltip::for_action_in(
25137 "Unstage Hunk",
25138 &::git::ToggleStaged,
25139 &focus_handle,
25140 cx,
25141 )
25142 }
25143 })
25144 .on_click({
25145 let editor = editor.clone();
25146 move |_event, _window, cx| {
25147 editor.update(cx, |editor, cx| {
25148 editor.stage_or_unstage_diff_hunks(
25149 false,
25150 vec![hunk_range.start..hunk_range.start],
25151 cx,
25152 );
25153 });
25154 }
25155 })
25156 })
25157 .child(
25158 Button::new(("restore", row as u64), "Restore")
25159 .tooltip({
25160 let focus_handle = editor.focus_handle(cx);
25161 move |_window, cx| {
25162 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25163 }
25164 })
25165 .on_click({
25166 let editor = editor.clone();
25167 move |_event, window, cx| {
25168 editor.update(cx, |editor, cx| {
25169 let snapshot = editor.snapshot(window, cx);
25170 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25171 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25172 });
25173 }
25174 })
25175 .disabled(is_created_file),
25176 )
25177 .when(
25178 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25179 |el| {
25180 el.child(
25181 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25182 .shape(IconButtonShape::Square)
25183 .icon_size(IconSize::Small)
25184 // .disabled(!has_multiple_hunks)
25185 .tooltip({
25186 let focus_handle = editor.focus_handle(cx);
25187 move |_window, cx| {
25188 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25189 }
25190 })
25191 .on_click({
25192 let editor = editor.clone();
25193 move |_event, window, cx| {
25194 editor.update(cx, |editor, cx| {
25195 let snapshot = editor.snapshot(window, cx);
25196 let position =
25197 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25198 editor.go_to_hunk_before_or_after_position(
25199 &snapshot,
25200 position,
25201 Direction::Next,
25202 window,
25203 cx,
25204 );
25205 editor.expand_selected_diff_hunks(cx);
25206 });
25207 }
25208 }),
25209 )
25210 .child(
25211 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25212 .shape(IconButtonShape::Square)
25213 .icon_size(IconSize::Small)
25214 // .disabled(!has_multiple_hunks)
25215 .tooltip({
25216 let focus_handle = editor.focus_handle(cx);
25217 move |_window, cx| {
25218 Tooltip::for_action_in(
25219 "Previous Hunk",
25220 &GoToPreviousHunk,
25221 &focus_handle,
25222 cx,
25223 )
25224 }
25225 })
25226 .on_click({
25227 let editor = editor.clone();
25228 move |_event, window, cx| {
25229 editor.update(cx, |editor, cx| {
25230 let snapshot = editor.snapshot(window, cx);
25231 let point =
25232 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25233 editor.go_to_hunk_before_or_after_position(
25234 &snapshot,
25235 point,
25236 Direction::Prev,
25237 window,
25238 cx,
25239 );
25240 editor.expand_selected_diff_hunks(cx);
25241 });
25242 }
25243 }),
25244 )
25245 },
25246 )
25247 .into_any_element()
25248}
25249
25250pub fn multibuffer_context_lines(cx: &App) -> u32 {
25251 EditorSettings::try_get(cx)
25252 .map(|settings| settings.excerpt_context_lines)
25253 .unwrap_or(2)
25254 .min(32)
25255}