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 collapse_matches: bool,
1100 autoindent_mode: Option<AutoindentMode>,
1101 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1102 input_enabled: bool,
1103 use_modal_editing: bool,
1104 read_only: bool,
1105 leader_id: Option<CollaboratorId>,
1106 remote_id: Option<ViewId>,
1107 pub hover_state: HoverState,
1108 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1109 gutter_hovered: bool,
1110 hovered_link_state: Option<HoveredLinkState>,
1111 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1112 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1113 active_edit_prediction: Option<EditPredictionState>,
1114 /// Used to prevent flickering as the user types while the menu is open
1115 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1116 edit_prediction_settings: EditPredictionSettings,
1117 edit_predictions_hidden_for_vim_mode: bool,
1118 show_edit_predictions_override: Option<bool>,
1119 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1120 edit_prediction_preview: EditPredictionPreview,
1121 edit_prediction_indent_conflict: bool,
1122 edit_prediction_requires_modifier_in_indent_conflict: bool,
1123 next_inlay_id: usize,
1124 next_color_inlay_id: usize,
1125 _subscriptions: Vec<Subscription>,
1126 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1127 gutter_dimensions: GutterDimensions,
1128 style: Option<EditorStyle>,
1129 text_style_refinement: Option<TextStyleRefinement>,
1130 next_editor_action_id: EditorActionId,
1131 editor_actions: Rc<
1132 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1133 >,
1134 use_autoclose: bool,
1135 use_auto_surround: bool,
1136 auto_replace_emoji_shortcode: bool,
1137 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1138 show_git_blame_gutter: bool,
1139 show_git_blame_inline: bool,
1140 show_git_blame_inline_delay_task: Option<Task<()>>,
1141 git_blame_inline_enabled: bool,
1142 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1143 buffer_serialization: Option<BufferSerialization>,
1144 show_selection_menu: Option<bool>,
1145 blame: Option<Entity<GitBlame>>,
1146 blame_subscription: Option<Subscription>,
1147 custom_context_menu: Option<
1148 Box<
1149 dyn 'static
1150 + Fn(
1151 &mut Self,
1152 DisplayPoint,
1153 &mut Window,
1154 &mut Context<Self>,
1155 ) -> Option<Entity<ui::ContextMenu>>,
1156 >,
1157 >,
1158 last_bounds: Option<Bounds<Pixels>>,
1159 last_position_map: Option<Rc<PositionMap>>,
1160 expect_bounds_change: Option<Bounds<Pixels>>,
1161 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1162 tasks_update_task: Option<Task<()>>,
1163 breakpoint_store: Option<Entity<BreakpointStore>>,
1164 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1165 hovered_diff_hunk_row: Option<DisplayRow>,
1166 pull_diagnostics_task: Task<()>,
1167 in_project_search: bool,
1168 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1169 breadcrumb_header: Option<String>,
1170 focused_block: Option<FocusedBlock>,
1171 next_scroll_position: NextScrollCursorCenterTopBottom,
1172 addons: HashMap<TypeId, Box<dyn Addon>>,
1173 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1174 load_diff_task: Option<Shared<Task<()>>>,
1175 /// Whether we are temporarily displaying a diff other than git's
1176 temporary_diff_override: bool,
1177 selection_mark_mode: bool,
1178 toggle_fold_multiple_buffers: Task<()>,
1179 _scroll_cursor_center_top_bottom_task: Task<()>,
1180 serialize_selections: Task<()>,
1181 serialize_folds: Task<()>,
1182 mouse_cursor_hidden: bool,
1183 minimap: Option<Entity<Self>>,
1184 hide_mouse_mode: HideMouseMode,
1185 pub change_list: ChangeList,
1186 inline_value_cache: InlineValueCache,
1187
1188 selection_drag_state: SelectionDragState,
1189 colors: Option<LspColorData>,
1190 post_scroll_update: Task<()>,
1191 refresh_colors_task: Task<()>,
1192 inlay_hints: Option<LspInlayHintData>,
1193 folding_newlines: Task<()>,
1194 select_next_is_case_sensitive: Option<bool>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1199 if debounce_ms > 0 {
1200 Some(Duration::from_millis(debounce_ms))
1201 } else {
1202 None
1203 }
1204}
1205
1206#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1207enum NextScrollCursorCenterTopBottom {
1208 #[default]
1209 Center,
1210 Top,
1211 Bottom,
1212}
1213
1214impl NextScrollCursorCenterTopBottom {
1215 fn next(&self) -> Self {
1216 match self {
1217 Self::Center => Self::Top,
1218 Self::Top => Self::Bottom,
1219 Self::Bottom => Self::Center,
1220 }
1221 }
1222}
1223
1224#[derive(Clone)]
1225pub struct EditorSnapshot {
1226 pub mode: EditorMode,
1227 show_gutter: bool,
1228 show_line_numbers: Option<bool>,
1229 show_git_diff_gutter: Option<bool>,
1230 show_code_actions: Option<bool>,
1231 show_runnables: Option<bool>,
1232 show_breakpoints: Option<bool>,
1233 git_blame_gutter_max_author_length: Option<usize>,
1234 pub display_snapshot: DisplaySnapshot,
1235 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1236 is_focused: bool,
1237 scroll_anchor: ScrollAnchor,
1238 ongoing_scroll: OngoingScroll,
1239 current_line_highlight: CurrentLineHighlight,
1240 gutter_hovered: bool,
1241}
1242
1243#[derive(Default, Debug, Clone, Copy)]
1244pub struct GutterDimensions {
1245 pub left_padding: Pixels,
1246 pub right_padding: Pixels,
1247 pub width: Pixels,
1248 pub margin: Pixels,
1249 pub git_blame_entries_width: Option<Pixels>,
1250}
1251
1252impl GutterDimensions {
1253 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1254 Self {
1255 margin: Self::default_gutter_margin(font_id, font_size, cx),
1256 ..Default::default()
1257 }
1258 }
1259
1260 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1261 -cx.text_system().descent(font_id, font_size)
1262 }
1263 /// The full width of the space taken up by the gutter.
1264 pub fn full_width(&self) -> Pixels {
1265 self.margin + self.width
1266 }
1267
1268 /// The width of the space reserved for the fold indicators,
1269 /// use alongside 'justify_end' and `gutter_width` to
1270 /// right align content with the line numbers
1271 pub fn fold_area_width(&self) -> Pixels {
1272 self.margin + self.right_padding
1273 }
1274}
1275
1276struct CharacterDimensions {
1277 em_width: Pixels,
1278 em_advance: Pixels,
1279 line_height: Pixels,
1280}
1281
1282#[derive(Debug)]
1283pub struct RemoteSelection {
1284 pub replica_id: ReplicaId,
1285 pub selection: Selection<Anchor>,
1286 pub cursor_shape: CursorShape,
1287 pub collaborator_id: CollaboratorId,
1288 pub line_mode: bool,
1289 pub user_name: Option<SharedString>,
1290 pub color: PlayerColor,
1291}
1292
1293#[derive(Clone, Debug)]
1294struct SelectionHistoryEntry {
1295 selections: Arc<[Selection<Anchor>]>,
1296 select_next_state: Option<SelectNextState>,
1297 select_prev_state: Option<SelectNextState>,
1298 add_selections_state: Option<AddSelectionsState>,
1299}
1300
1301#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1302enum SelectionHistoryMode {
1303 Normal,
1304 Undoing,
1305 Redoing,
1306 Skipping,
1307}
1308
1309#[derive(Clone, PartialEq, Eq, Hash)]
1310struct HoveredCursor {
1311 replica_id: ReplicaId,
1312 selection_id: usize,
1313}
1314
1315impl Default for SelectionHistoryMode {
1316 fn default() -> Self {
1317 Self::Normal
1318 }
1319}
1320
1321#[derive(Debug)]
1322/// SelectionEffects controls the side-effects of updating the selection.
1323///
1324/// The default behaviour does "what you mostly want":
1325/// - it pushes to the nav history if the cursor moved by >10 lines
1326/// - it re-triggers completion requests
1327/// - it scrolls to fit
1328///
1329/// You might want to modify these behaviours. For example when doing a "jump"
1330/// like go to definition, we always want to add to nav history; but when scrolling
1331/// in vim mode we never do.
1332///
1333/// Similarly, you might want to disable scrolling if you don't want the viewport to
1334/// move.
1335#[derive(Clone)]
1336pub struct SelectionEffects {
1337 nav_history: Option<bool>,
1338 completions: bool,
1339 scroll: Option<Autoscroll>,
1340}
1341
1342impl Default for SelectionEffects {
1343 fn default() -> Self {
1344 Self {
1345 nav_history: None,
1346 completions: true,
1347 scroll: Some(Autoscroll::fit()),
1348 }
1349 }
1350}
1351impl SelectionEffects {
1352 pub fn scroll(scroll: Autoscroll) -> Self {
1353 Self {
1354 scroll: Some(scroll),
1355 ..Default::default()
1356 }
1357 }
1358
1359 pub fn no_scroll() -> Self {
1360 Self {
1361 scroll: None,
1362 ..Default::default()
1363 }
1364 }
1365
1366 pub fn completions(self, completions: bool) -> Self {
1367 Self {
1368 completions,
1369 ..self
1370 }
1371 }
1372
1373 pub fn nav_history(self, nav_history: bool) -> Self {
1374 Self {
1375 nav_history: Some(nav_history),
1376 ..self
1377 }
1378 }
1379}
1380
1381struct DeferredSelectionEffectsState {
1382 changed: bool,
1383 effects: SelectionEffects,
1384 old_cursor_position: Anchor,
1385 history_entry: SelectionHistoryEntry,
1386}
1387
1388#[derive(Default)]
1389struct SelectionHistory {
1390 #[allow(clippy::type_complexity)]
1391 selections_by_transaction:
1392 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1393 mode: SelectionHistoryMode,
1394 undo_stack: VecDeque<SelectionHistoryEntry>,
1395 redo_stack: VecDeque<SelectionHistoryEntry>,
1396}
1397
1398impl SelectionHistory {
1399 #[track_caller]
1400 fn insert_transaction(
1401 &mut self,
1402 transaction_id: TransactionId,
1403 selections: Arc<[Selection<Anchor>]>,
1404 ) {
1405 if selections.is_empty() {
1406 log::error!(
1407 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1408 std::panic::Location::caller()
1409 );
1410 return;
1411 }
1412 self.selections_by_transaction
1413 .insert(transaction_id, (selections, None));
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction(
1418 &self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get(&transaction_id)
1422 }
1423
1424 #[allow(clippy::type_complexity)]
1425 fn transaction_mut(
1426 &mut self,
1427 transaction_id: TransactionId,
1428 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1429 self.selections_by_transaction.get_mut(&transaction_id)
1430 }
1431
1432 fn push(&mut self, entry: SelectionHistoryEntry) {
1433 if !entry.selections.is_empty() {
1434 match self.mode {
1435 SelectionHistoryMode::Normal => {
1436 self.push_undo(entry);
1437 self.redo_stack.clear();
1438 }
1439 SelectionHistoryMode::Undoing => self.push_redo(entry),
1440 SelectionHistoryMode::Redoing => self.push_undo(entry),
1441 SelectionHistoryMode::Skipping => {}
1442 }
1443 }
1444 }
1445
1446 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1447 if self
1448 .undo_stack
1449 .back()
1450 .is_none_or(|e| e.selections != entry.selections)
1451 {
1452 self.undo_stack.push_back(entry);
1453 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1454 self.undo_stack.pop_front();
1455 }
1456 }
1457 }
1458
1459 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1460 if self
1461 .redo_stack
1462 .back()
1463 .is_none_or(|e| e.selections != entry.selections)
1464 {
1465 self.redo_stack.push_back(entry);
1466 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1467 self.redo_stack.pop_front();
1468 }
1469 }
1470 }
1471}
1472
1473#[derive(Clone, Copy)]
1474pub struct RowHighlightOptions {
1475 pub autoscroll: bool,
1476 pub include_gutter: bool,
1477}
1478
1479impl Default for RowHighlightOptions {
1480 fn default() -> Self {
1481 Self {
1482 autoscroll: Default::default(),
1483 include_gutter: true,
1484 }
1485 }
1486}
1487
1488struct RowHighlight {
1489 index: usize,
1490 range: Range<Anchor>,
1491 color: Hsla,
1492 options: RowHighlightOptions,
1493 type_id: TypeId,
1494}
1495
1496#[derive(Clone, Debug)]
1497struct AddSelectionsState {
1498 groups: Vec<AddSelectionsGroup>,
1499}
1500
1501#[derive(Clone, Debug)]
1502struct AddSelectionsGroup {
1503 above: bool,
1504 stack: Vec<usize>,
1505}
1506
1507#[derive(Clone)]
1508struct SelectNextState {
1509 query: AhoCorasick,
1510 wordwise: bool,
1511 done: bool,
1512}
1513
1514impl std::fmt::Debug for SelectNextState {
1515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1516 f.debug_struct(std::any::type_name::<Self>())
1517 .field("wordwise", &self.wordwise)
1518 .field("done", &self.done)
1519 .finish()
1520 }
1521}
1522
1523#[derive(Debug)]
1524struct AutocloseRegion {
1525 selection_id: usize,
1526 range: Range<Anchor>,
1527 pair: BracketPair,
1528}
1529
1530#[derive(Debug)]
1531struct SnippetState {
1532 ranges: Vec<Vec<Range<Anchor>>>,
1533 active_index: usize,
1534 choices: Vec<Option<Vec<String>>>,
1535}
1536
1537#[doc(hidden)]
1538pub struct RenameState {
1539 pub range: Range<Anchor>,
1540 pub old_name: Arc<str>,
1541 pub editor: Entity<Editor>,
1542 block_id: CustomBlockId,
1543}
1544
1545struct InvalidationStack<T>(Vec<T>);
1546
1547struct RegisteredEditPredictionProvider {
1548 provider: Arc<dyn EditPredictionProviderHandle>,
1549 _subscription: Subscription,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553pub struct ActiveDiagnosticGroup {
1554 pub active_range: Range<Anchor>,
1555 pub active_message: String,
1556 pub group_id: usize,
1557 pub blocks: HashSet<CustomBlockId>,
1558}
1559
1560#[derive(Debug, PartialEq, Eq)]
1561
1562pub(crate) enum ActiveDiagnostic {
1563 None,
1564 All,
1565 Group(ActiveDiagnosticGroup),
1566}
1567
1568#[derive(Serialize, Deserialize, Clone, Debug)]
1569pub struct ClipboardSelection {
1570 /// The number of bytes in this selection.
1571 pub len: usize,
1572 /// Whether this was a full-line selection.
1573 pub is_entire_line: bool,
1574 /// The indentation of the first line when this content was originally copied.
1575 pub first_line_indent: u32,
1576}
1577
1578// selections, scroll behavior, was newest selection reversed
1579type SelectSyntaxNodeHistoryState = (
1580 Box<[Selection<usize>]>,
1581 SelectSyntaxNodeScrollBehavior,
1582 bool,
1583);
1584
1585#[derive(Default)]
1586struct SelectSyntaxNodeHistory {
1587 stack: Vec<SelectSyntaxNodeHistoryState>,
1588 // disable temporarily to allow changing selections without losing the stack
1589 pub disable_clearing: bool,
1590}
1591
1592impl SelectSyntaxNodeHistory {
1593 pub fn try_clear(&mut self) {
1594 if !self.disable_clearing {
1595 self.stack.clear();
1596 }
1597 }
1598
1599 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1600 self.stack.push(selection);
1601 }
1602
1603 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1604 self.stack.pop()
1605 }
1606}
1607
1608enum SelectSyntaxNodeScrollBehavior {
1609 CursorTop,
1610 FitSelection,
1611 CursorBottom,
1612}
1613
1614#[derive(Debug)]
1615pub(crate) struct NavigationData {
1616 cursor_anchor: Anchor,
1617 cursor_position: Point,
1618 scroll_anchor: ScrollAnchor,
1619 scroll_top_row: u32,
1620}
1621
1622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1623pub enum GotoDefinitionKind {
1624 Symbol,
1625 Declaration,
1626 Type,
1627 Implementation,
1628}
1629
1630pub enum FormatTarget {
1631 Buffers(HashSet<Entity<Buffer>>),
1632 Ranges(Vec<Range<MultiBufferPoint>>),
1633}
1634
1635pub(crate) struct FocusedBlock {
1636 id: BlockId,
1637 focus_handle: WeakFocusHandle,
1638}
1639
1640#[derive(Clone)]
1641enum JumpData {
1642 MultiBufferRow {
1643 row: MultiBufferRow,
1644 line_offset_from_top: u32,
1645 },
1646 MultiBufferPoint {
1647 excerpt_id: ExcerptId,
1648 position: Point,
1649 anchor: text::Anchor,
1650 line_offset_from_top: u32,
1651 },
1652}
1653
1654pub enum MultibufferSelectionMode {
1655 First,
1656 All,
1657}
1658
1659#[derive(Clone, Copy, Debug, Default)]
1660pub struct RewrapOptions {
1661 pub override_language_settings: bool,
1662 pub preserve_existing_whitespace: bool,
1663}
1664
1665impl Editor {
1666 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1667 let buffer = cx.new(|cx| Buffer::local("", cx));
1668 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1669 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1670 }
1671
1672 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1673 let buffer = cx.new(|cx| Buffer::local("", cx));
1674 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1675 Self::new(EditorMode::full(), buffer, None, window, cx)
1676 }
1677
1678 pub fn auto_height(
1679 min_lines: usize,
1680 max_lines: usize,
1681 window: &mut Window,
1682 cx: &mut Context<Self>,
1683 ) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(
1687 EditorMode::AutoHeight {
1688 min_lines,
1689 max_lines: Some(max_lines),
1690 },
1691 buffer,
1692 None,
1693 window,
1694 cx,
1695 )
1696 }
1697
1698 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1699 /// The editor grows as tall as needed to fit its content.
1700 pub fn auto_height_unbounded(
1701 min_lines: usize,
1702 window: &mut Window,
1703 cx: &mut Context<Self>,
1704 ) -> Self {
1705 let buffer = cx.new(|cx| Buffer::local("", cx));
1706 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1707 Self::new(
1708 EditorMode::AutoHeight {
1709 min_lines,
1710 max_lines: None,
1711 },
1712 buffer,
1713 None,
1714 window,
1715 cx,
1716 )
1717 }
1718
1719 pub fn for_buffer(
1720 buffer: Entity<Buffer>,
1721 project: Option<Entity<Project>>,
1722 window: &mut Window,
1723 cx: &mut Context<Self>,
1724 ) -> Self {
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn for_multibuffer(
1730 buffer: Entity<MultiBuffer>,
1731 project: Option<Entity<Project>>,
1732 window: &mut Window,
1733 cx: &mut Context<Self>,
1734 ) -> Self {
1735 Self::new(EditorMode::full(), buffer, project, window, cx)
1736 }
1737
1738 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1739 let mut clone = Self::new(
1740 self.mode.clone(),
1741 self.buffer.clone(),
1742 self.project.clone(),
1743 window,
1744 cx,
1745 );
1746 self.display_map.update(cx, |display_map, cx| {
1747 let snapshot = display_map.snapshot(cx);
1748 clone.display_map.update(cx, |display_map, cx| {
1749 display_map.set_state(&snapshot, cx);
1750 });
1751 });
1752 clone.folds_did_change(cx);
1753 clone.selections.clone_state(&self.selections);
1754 clone.scroll_manager.clone_state(&self.scroll_manager);
1755 clone.searchable = self.searchable;
1756 clone.read_only = self.read_only;
1757 clone
1758 }
1759
1760 pub fn new(
1761 mode: EditorMode,
1762 buffer: Entity<MultiBuffer>,
1763 project: Option<Entity<Project>>,
1764 window: &mut Window,
1765 cx: &mut Context<Self>,
1766 ) -> Self {
1767 Editor::new_internal(mode, buffer, project, None, window, cx)
1768 }
1769
1770 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1771 let multi_buffer = self.buffer().read(cx);
1772 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1773 let multi_buffer_visible_start = self
1774 .scroll_manager
1775 .anchor()
1776 .anchor
1777 .to_point(&multi_buffer_snapshot);
1778 let max_row = multi_buffer_snapshot.max_point().row;
1779
1780 let start_row = (multi_buffer_visible_start.row).min(max_row);
1781 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1782
1783 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1784 let outline_items = buffer
1785 .outline_items_containing(
1786 Point::new(start_row, 0)..Point::new(end_row, 0),
1787 true,
1788 self.style().map(|style| style.syntax.as_ref()),
1789 )
1790 .into_iter()
1791 .map(|outline_item| OutlineItem {
1792 depth: outline_item.depth,
1793 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1794 source_range_for_text: Anchor::range_in_buffer(
1795 *excerpt_id,
1796 buffer_id,
1797 outline_item.source_range_for_text,
1798 ),
1799 text: outline_item.text,
1800 highlight_ranges: outline_item.highlight_ranges,
1801 name_ranges: outline_item.name_ranges,
1802 body_range: outline_item
1803 .body_range
1804 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1805 annotation_range: outline_item
1806 .annotation_range
1807 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1808 });
1809 return Some(outline_items.collect());
1810 }
1811
1812 None
1813 }
1814
1815 fn new_internal(
1816 mode: EditorMode,
1817 multi_buffer: Entity<MultiBuffer>,
1818 project: Option<Entity<Project>>,
1819 display_map: Option<Entity<DisplayMap>>,
1820 window: &mut Window,
1821 cx: &mut Context<Self>,
1822 ) -> Self {
1823 debug_assert!(
1824 display_map.is_none() || mode.is_minimap(),
1825 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1826 );
1827
1828 let full_mode = mode.is_full();
1829 let is_minimap = mode.is_minimap();
1830 let diagnostics_max_severity = if full_mode {
1831 EditorSettings::get_global(cx)
1832 .diagnostics_max_severity
1833 .unwrap_or(DiagnosticSeverity::Hint)
1834 } else {
1835 DiagnosticSeverity::Off
1836 };
1837 let style = window.text_style();
1838 let font_size = style.font_size.to_pixels(window.rem_size());
1839 let editor = cx.entity().downgrade();
1840 let fold_placeholder = FoldPlaceholder {
1841 constrain_width: false,
1842 render: Arc::new(move |fold_id, fold_range, cx| {
1843 let editor = editor.clone();
1844 div()
1845 .id(fold_id)
1846 .bg(cx.theme().colors().ghost_element_background)
1847 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1848 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1849 .rounded_xs()
1850 .size_full()
1851 .cursor_pointer()
1852 .child("⋯")
1853 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1854 .on_click(move |_, _window, cx| {
1855 editor
1856 .update(cx, |editor, cx| {
1857 editor.unfold_ranges(
1858 &[fold_range.start..fold_range.end],
1859 true,
1860 false,
1861 cx,
1862 );
1863 cx.stop_propagation();
1864 })
1865 .ok();
1866 })
1867 .into_any()
1868 }),
1869 merge_adjacent: true,
1870 ..FoldPlaceholder::default()
1871 };
1872 let display_map = display_map.unwrap_or_else(|| {
1873 cx.new(|cx| {
1874 DisplayMap::new(
1875 multi_buffer.clone(),
1876 style.font(),
1877 font_size,
1878 None,
1879 FILE_HEADER_HEIGHT,
1880 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1881 fold_placeholder,
1882 diagnostics_max_severity,
1883 cx,
1884 )
1885 })
1886 });
1887
1888 let selections = SelectionsCollection::new();
1889
1890 let blink_manager = cx.new(|cx| {
1891 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1892 if is_minimap {
1893 blink_manager.disable(cx);
1894 }
1895 blink_manager
1896 });
1897
1898 let soft_wrap_mode_override =
1899 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1900
1901 let mut project_subscriptions = Vec::new();
1902 if full_mode && let Some(project) = project.as_ref() {
1903 project_subscriptions.push(cx.subscribe_in(
1904 project,
1905 window,
1906 |editor, _, event, window, cx| match event {
1907 project::Event::RefreshCodeLens => {
1908 // we always query lens with actions, without storing them, always refreshing them
1909 }
1910 project::Event::RefreshInlayHints {
1911 server_id,
1912 request_id,
1913 } => {
1914 editor.refresh_inlay_hints(
1915 InlayHintRefreshReason::RefreshRequested {
1916 server_id: *server_id,
1917 request_id: *request_id,
1918 },
1919 cx,
1920 );
1921 }
1922 project::Event::LanguageServerRemoved(..) => {
1923 if editor.tasks_update_task.is_none() {
1924 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1925 }
1926 editor.registered_buffers.clear();
1927 editor.register_visible_buffers(cx);
1928 }
1929 project::Event::LanguageServerAdded(..) => {
1930 if editor.tasks_update_task.is_none() {
1931 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1932 }
1933 }
1934 project::Event::SnippetEdit(id, snippet_edits) => {
1935 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1936 let focus_handle = editor.focus_handle(cx);
1937 if focus_handle.is_focused(window) {
1938 let snapshot = buffer.read(cx).snapshot();
1939 for (range, snippet) in snippet_edits {
1940 let editor_range =
1941 language::range_from_lsp(*range).to_offset(&snapshot);
1942 editor
1943 .insert_snippet(
1944 &[editor_range],
1945 snippet.clone(),
1946 window,
1947 cx,
1948 )
1949 .ok();
1950 }
1951 }
1952 }
1953 }
1954 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1955 let buffer_id = *buffer_id;
1956 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1957 editor.register_buffer(buffer_id, cx);
1958 editor.update_lsp_data(Some(buffer_id), window, cx);
1959 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1960 refresh_linked_ranges(editor, window, cx);
1961 editor.refresh_code_actions(window, cx);
1962 editor.refresh_document_highlights(cx);
1963 }
1964 }
1965
1966 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1967 let Some(workspace) = editor.workspace() else {
1968 return;
1969 };
1970 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1971 else {
1972 return;
1973 };
1974
1975 if active_editor.entity_id() == cx.entity_id() {
1976 let entity_id = cx.entity_id();
1977 workspace.update(cx, |this, cx| {
1978 this.panes_mut()
1979 .iter_mut()
1980 .filter(|pane| pane.entity_id() != entity_id)
1981 .for_each(|p| {
1982 p.update(cx, |pane, _| {
1983 pane.nav_history_mut().rename_item(
1984 entity_id,
1985 project_path.clone(),
1986 abs_path.clone().into(),
1987 );
1988 })
1989 });
1990 });
1991 let edited_buffers_already_open = {
1992 let other_editors: Vec<Entity<Editor>> = workspace
1993 .read(cx)
1994 .panes()
1995 .iter()
1996 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1997 .filter(|editor| editor.entity_id() != cx.entity_id())
1998 .collect();
1999
2000 transaction.0.keys().all(|buffer| {
2001 other_editors.iter().any(|editor| {
2002 let multi_buffer = editor.read(cx).buffer();
2003 multi_buffer.read(cx).is_singleton()
2004 && multi_buffer.read(cx).as_singleton().map_or(
2005 false,
2006 |singleton| {
2007 singleton.entity_id() == buffer.entity_id()
2008 },
2009 )
2010 })
2011 })
2012 };
2013 if !edited_buffers_already_open {
2014 let workspace = workspace.downgrade();
2015 let transaction = transaction.clone();
2016 cx.defer_in(window, move |_, window, cx| {
2017 cx.spawn_in(window, async move |editor, cx| {
2018 Self::open_project_transaction(
2019 &editor,
2020 workspace,
2021 transaction,
2022 "Rename".to_string(),
2023 cx,
2024 )
2025 .await
2026 .ok()
2027 })
2028 .detach();
2029 });
2030 }
2031 }
2032 }
2033
2034 _ => {}
2035 },
2036 ));
2037 if let Some(task_inventory) = project
2038 .read(cx)
2039 .task_store()
2040 .read(cx)
2041 .task_inventory()
2042 .cloned()
2043 {
2044 project_subscriptions.push(cx.observe_in(
2045 &task_inventory,
2046 window,
2047 |editor, _, window, cx| {
2048 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2049 },
2050 ));
2051 };
2052
2053 project_subscriptions.push(cx.subscribe_in(
2054 &project.read(cx).breakpoint_store(),
2055 window,
2056 |editor, _, event, window, cx| match event {
2057 BreakpointStoreEvent::ClearDebugLines => {
2058 editor.clear_row_highlights::<ActiveDebugLine>();
2059 editor.refresh_inline_values(cx);
2060 }
2061 BreakpointStoreEvent::SetDebugLine => {
2062 if editor.go_to_active_debug_line(window, cx) {
2063 cx.stop_propagation();
2064 }
2065
2066 editor.refresh_inline_values(cx);
2067 }
2068 _ => {}
2069 },
2070 ));
2071 let git_store = project.read(cx).git_store().clone();
2072 let project = project.clone();
2073 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2074 if let GitStoreEvent::RepositoryAdded = event {
2075 this.load_diff_task = Some(
2076 update_uncommitted_diff_for_buffer(
2077 cx.entity(),
2078 &project,
2079 this.buffer.read(cx).all_buffers(),
2080 this.buffer.clone(),
2081 cx,
2082 )
2083 .shared(),
2084 );
2085 }
2086 }));
2087 }
2088
2089 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2090
2091 let inlay_hint_settings =
2092 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2093 let focus_handle = cx.focus_handle();
2094 if !is_minimap {
2095 cx.on_focus(&focus_handle, window, Self::handle_focus)
2096 .detach();
2097 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2098 .detach();
2099 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2100 .detach();
2101 cx.on_blur(&focus_handle, window, Self::handle_blur)
2102 .detach();
2103 cx.observe_pending_input(window, Self::observe_pending_input)
2104 .detach();
2105 }
2106
2107 let show_indent_guides =
2108 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2109 Some(false)
2110 } else {
2111 None
2112 };
2113
2114 let breakpoint_store = match (&mode, project.as_ref()) {
2115 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2116 _ => None,
2117 };
2118
2119 let mut code_action_providers = Vec::new();
2120 let mut load_uncommitted_diff = None;
2121 if let Some(project) = project.clone() {
2122 load_uncommitted_diff = Some(
2123 update_uncommitted_diff_for_buffer(
2124 cx.entity(),
2125 &project,
2126 multi_buffer.read(cx).all_buffers(),
2127 multi_buffer.clone(),
2128 cx,
2129 )
2130 .shared(),
2131 );
2132 code_action_providers.push(Rc::new(project) as Rc<_>);
2133 }
2134
2135 let mut editor = Self {
2136 focus_handle,
2137 show_cursor_when_unfocused: false,
2138 last_focused_descendant: None,
2139 buffer: multi_buffer.clone(),
2140 display_map: display_map.clone(),
2141 placeholder_display_map: None,
2142 selections,
2143 scroll_manager: ScrollManager::new(cx),
2144 columnar_selection_state: None,
2145 add_selections_state: None,
2146 select_next_state: None,
2147 select_prev_state: None,
2148 selection_history: SelectionHistory::default(),
2149 defer_selection_effects: false,
2150 deferred_selection_effects_state: None,
2151 autoclose_regions: Vec::new(),
2152 snippet_stack: InvalidationStack::default(),
2153 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2154 ime_transaction: None,
2155 active_diagnostics: ActiveDiagnostic::None,
2156 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2157 inline_diagnostics_update: Task::ready(()),
2158 inline_diagnostics: Vec::new(),
2159 soft_wrap_mode_override,
2160 diagnostics_max_severity,
2161 hard_wrap: None,
2162 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2163 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2164 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2165 project,
2166 blink_manager: blink_manager.clone(),
2167 show_local_selections: true,
2168 show_scrollbars: ScrollbarAxes {
2169 horizontal: full_mode,
2170 vertical: full_mode,
2171 },
2172 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2173 offset_content: !matches!(mode, EditorMode::SingleLine),
2174 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2175 show_gutter: full_mode,
2176 show_line_numbers: (!full_mode).then_some(false),
2177 use_relative_line_numbers: None,
2178 disable_expand_excerpt_buttons: !full_mode,
2179 show_git_diff_gutter: None,
2180 show_code_actions: None,
2181 show_runnables: None,
2182 show_breakpoints: None,
2183 show_wrap_guides: None,
2184 show_indent_guides,
2185 highlight_order: 0,
2186 highlighted_rows: HashMap::default(),
2187 background_highlights: HashMap::default(),
2188 gutter_highlights: HashMap::default(),
2189 scrollbar_marker_state: ScrollbarMarkerState::default(),
2190 active_indent_guides_state: ActiveIndentGuidesState::default(),
2191 nav_history: None,
2192 context_menu: RefCell::new(None),
2193 context_menu_options: None,
2194 mouse_context_menu: None,
2195 completion_tasks: Vec::new(),
2196 inline_blame_popover: None,
2197 inline_blame_popover_show_task: None,
2198 signature_help_state: SignatureHelpState::default(),
2199 auto_signature_help: None,
2200 find_all_references_task_sources: Vec::new(),
2201 next_completion_id: 0,
2202 next_inlay_id: 0,
2203 code_action_providers,
2204 available_code_actions: None,
2205 code_actions_task: None,
2206 quick_selection_highlight_task: None,
2207 debounced_selection_highlight_task: None,
2208 document_highlights_task: None,
2209 linked_editing_range_task: None,
2210 pending_rename: None,
2211 searchable: !is_minimap,
2212 cursor_shape: EditorSettings::get_global(cx)
2213 .cursor_shape
2214 .unwrap_or_default(),
2215 current_line_highlight: None,
2216 autoindent_mode: Some(AutoindentMode::EachLine),
2217 collapse_matches: false,
2218 workspace: None,
2219 input_enabled: !is_minimap,
2220 use_modal_editing: full_mode,
2221 read_only: is_minimap,
2222 use_autoclose: true,
2223 use_auto_surround: true,
2224 auto_replace_emoji_shortcode: false,
2225 jsx_tag_auto_close_enabled_in_any_buffer: false,
2226 leader_id: None,
2227 remote_id: None,
2228 hover_state: HoverState::default(),
2229 pending_mouse_down: None,
2230 hovered_link_state: None,
2231 edit_prediction_provider: None,
2232 active_edit_prediction: None,
2233 stale_edit_prediction_in_menu: None,
2234 edit_prediction_preview: EditPredictionPreview::Inactive {
2235 released_too_fast: false,
2236 },
2237 inline_diagnostics_enabled: full_mode,
2238 diagnostics_enabled: full_mode,
2239 word_completions_enabled: full_mode,
2240 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2241 gutter_hovered: false,
2242 pixel_position_of_newest_cursor: None,
2243 last_bounds: None,
2244 last_position_map: None,
2245 expect_bounds_change: None,
2246 gutter_dimensions: GutterDimensions::default(),
2247 style: None,
2248 show_cursor_names: false,
2249 hovered_cursors: HashMap::default(),
2250 next_editor_action_id: EditorActionId::default(),
2251 editor_actions: Rc::default(),
2252 edit_predictions_hidden_for_vim_mode: false,
2253 show_edit_predictions_override: None,
2254 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2255 edit_prediction_settings: EditPredictionSettings::Disabled,
2256 edit_prediction_indent_conflict: false,
2257 edit_prediction_requires_modifier_in_indent_conflict: true,
2258 custom_context_menu: None,
2259 show_git_blame_gutter: false,
2260 show_git_blame_inline: false,
2261 show_selection_menu: None,
2262 show_git_blame_inline_delay_task: None,
2263 git_blame_inline_enabled: full_mode
2264 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2265 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2266 buffer_serialization: is_minimap.not().then(|| {
2267 BufferSerialization::new(
2268 ProjectSettings::get_global(cx)
2269 .session
2270 .restore_unsaved_buffers,
2271 )
2272 }),
2273 blame: None,
2274 blame_subscription: None,
2275 tasks: BTreeMap::default(),
2276
2277 breakpoint_store,
2278 gutter_breakpoint_indicator: (None, None),
2279 hovered_diff_hunk_row: None,
2280 _subscriptions: (!is_minimap)
2281 .then(|| {
2282 vec![
2283 cx.observe(&multi_buffer, Self::on_buffer_changed),
2284 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2285 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2286 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2287 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2288 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2289 cx.observe_window_activation(window, |editor, window, cx| {
2290 let active = window.is_window_active();
2291 editor.blink_manager.update(cx, |blink_manager, cx| {
2292 if active {
2293 blink_manager.enable(cx);
2294 } else {
2295 blink_manager.disable(cx);
2296 }
2297 });
2298 if active {
2299 editor.show_mouse_cursor(cx);
2300 }
2301 }),
2302 ]
2303 })
2304 .unwrap_or_default(),
2305 tasks_update_task: None,
2306 pull_diagnostics_task: Task::ready(()),
2307 colors: None,
2308 refresh_colors_task: Task::ready(()),
2309 inlay_hints: None,
2310 next_color_inlay_id: 0,
2311 post_scroll_update: Task::ready(()),
2312 linked_edit_ranges: Default::default(),
2313 in_project_search: false,
2314 previous_search_ranges: None,
2315 breadcrumb_header: None,
2316 focused_block: None,
2317 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2318 addons: HashMap::default(),
2319 registered_buffers: HashMap::default(),
2320 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2321 selection_mark_mode: false,
2322 toggle_fold_multiple_buffers: Task::ready(()),
2323 serialize_selections: Task::ready(()),
2324 serialize_folds: Task::ready(()),
2325 text_style_refinement: None,
2326 load_diff_task: load_uncommitted_diff,
2327 temporary_diff_override: false,
2328 mouse_cursor_hidden: false,
2329 minimap: None,
2330 hide_mouse_mode: EditorSettings::get_global(cx)
2331 .hide_mouse
2332 .unwrap_or_default(),
2333 change_list: ChangeList::new(),
2334 mode,
2335 selection_drag_state: SelectionDragState::None,
2336 folding_newlines: Task::ready(()),
2337 lookup_key: None,
2338 select_next_is_case_sensitive: None,
2339 };
2340
2341 if is_minimap {
2342 return editor;
2343 }
2344
2345 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2346 editor
2347 ._subscriptions
2348 .push(cx.observe(breakpoints, |_, _, cx| {
2349 cx.notify();
2350 }));
2351 }
2352 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2353 editor._subscriptions.extend(project_subscriptions);
2354
2355 editor._subscriptions.push(cx.subscribe_in(
2356 &cx.entity(),
2357 window,
2358 |editor, _, e: &EditorEvent, window, cx| match e {
2359 EditorEvent::ScrollPositionChanged { local, .. } => {
2360 if *local {
2361 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2362 editor.inline_blame_popover.take();
2363 let new_anchor = editor.scroll_manager.anchor();
2364 let snapshot = editor.snapshot(window, cx);
2365 editor.update_restoration_data(cx, move |data| {
2366 data.scroll_position = (
2367 new_anchor.top_row(snapshot.buffer_snapshot()),
2368 new_anchor.offset,
2369 );
2370 });
2371
2372 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2373 cx.background_executor()
2374 .timer(Duration::from_millis(50))
2375 .await;
2376 editor
2377 .update_in(cx, |editor, window, cx| {
2378 editor.register_visible_buffers(cx);
2379 editor.refresh_colors_for_visible_range(None, window, cx);
2380 editor.refresh_inlay_hints(
2381 InlayHintRefreshReason::NewLinesShown,
2382 cx,
2383 );
2384 })
2385 .ok();
2386 });
2387 }
2388 }
2389 EditorEvent::Edited { .. } => {
2390 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2391 .map(|vim_mode| vim_mode.0)
2392 .unwrap_or(false);
2393 if !vim_mode {
2394 let display_map = editor.display_snapshot(cx);
2395 let selections = editor.selections.all_adjusted_display(&display_map);
2396 let pop_state = editor
2397 .change_list
2398 .last()
2399 .map(|previous| {
2400 previous.len() == selections.len()
2401 && previous.iter().enumerate().all(|(ix, p)| {
2402 p.to_display_point(&display_map).row()
2403 == selections[ix].head().row()
2404 })
2405 })
2406 .unwrap_or(false);
2407 let new_positions = selections
2408 .into_iter()
2409 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2410 .collect();
2411 editor
2412 .change_list
2413 .push_to_change_list(pop_state, new_positions);
2414 }
2415 }
2416 _ => (),
2417 },
2418 ));
2419
2420 if let Some(dap_store) = editor
2421 .project
2422 .as_ref()
2423 .map(|project| project.read(cx).dap_store())
2424 {
2425 let weak_editor = cx.weak_entity();
2426
2427 editor
2428 ._subscriptions
2429 .push(
2430 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2431 let session_entity = cx.entity();
2432 weak_editor
2433 .update(cx, |editor, cx| {
2434 editor._subscriptions.push(
2435 cx.subscribe(&session_entity, Self::on_debug_session_event),
2436 );
2437 })
2438 .ok();
2439 }),
2440 );
2441
2442 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2443 editor
2444 ._subscriptions
2445 .push(cx.subscribe(&session, Self::on_debug_session_event));
2446 }
2447 }
2448
2449 // skip adding the initial selection to selection history
2450 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2451 editor.end_selection(window, cx);
2452 editor.selection_history.mode = SelectionHistoryMode::Normal;
2453
2454 editor.scroll_manager.show_scrollbars(window, cx);
2455 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2456
2457 if full_mode {
2458 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2459 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2460
2461 if editor.git_blame_inline_enabled {
2462 editor.start_git_blame_inline(false, window, cx);
2463 }
2464
2465 editor.go_to_active_debug_line(window, cx);
2466
2467 editor.minimap =
2468 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2469 editor.colors = Some(LspColorData::new(cx));
2470 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2471
2472 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2473 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2474 }
2475 editor.update_lsp_data(None, window, cx);
2476 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2477 }
2478
2479 editor
2480 }
2481
2482 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2483 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2484 }
2485
2486 pub fn deploy_mouse_context_menu(
2487 &mut self,
2488 position: gpui::Point<Pixels>,
2489 context_menu: Entity<ContextMenu>,
2490 window: &mut Window,
2491 cx: &mut Context<Self>,
2492 ) {
2493 self.mouse_context_menu = Some(MouseContextMenu::new(
2494 self,
2495 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2496 context_menu,
2497 window,
2498 cx,
2499 ));
2500 }
2501
2502 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2503 self.mouse_context_menu
2504 .as_ref()
2505 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2506 }
2507
2508 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2509 if self
2510 .selections
2511 .pending_anchor()
2512 .is_some_and(|pending_selection| {
2513 let snapshot = self.buffer().read(cx).snapshot(cx);
2514 pending_selection.range().includes(range, &snapshot)
2515 })
2516 {
2517 return true;
2518 }
2519
2520 self.selections
2521 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2522 .into_iter()
2523 .any(|selection| {
2524 // This is needed to cover a corner case, if we just check for an existing
2525 // selection in the fold range, having a cursor at the start of the fold
2526 // marks it as selected. Non-empty selections don't cause this.
2527 let length = selection.end - selection.start;
2528 length > 0
2529 })
2530 }
2531
2532 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2533 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2534 }
2535
2536 fn key_context_internal(
2537 &self,
2538 has_active_edit_prediction: bool,
2539 window: &mut Window,
2540 cx: &mut App,
2541 ) -> KeyContext {
2542 let mut key_context = KeyContext::new_with_defaults();
2543 key_context.add("Editor");
2544 let mode = match self.mode {
2545 EditorMode::SingleLine => "single_line",
2546 EditorMode::AutoHeight { .. } => "auto_height",
2547 EditorMode::Minimap { .. } => "minimap",
2548 EditorMode::Full { .. } => "full",
2549 };
2550
2551 if EditorSettings::jupyter_enabled(cx) {
2552 key_context.add("jupyter");
2553 }
2554
2555 key_context.set("mode", mode);
2556 if self.pending_rename.is_some() {
2557 key_context.add("renaming");
2558 }
2559
2560 if let Some(snippet_stack) = self.snippet_stack.last() {
2561 key_context.add("in_snippet");
2562
2563 if snippet_stack.active_index > 0 {
2564 key_context.add("has_previous_tabstop");
2565 }
2566
2567 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2568 key_context.add("has_next_tabstop");
2569 }
2570 }
2571
2572 match self.context_menu.borrow().as_ref() {
2573 Some(CodeContextMenu::Completions(menu)) => {
2574 if menu.visible() {
2575 key_context.add("menu");
2576 key_context.add("showing_completions");
2577 }
2578 }
2579 Some(CodeContextMenu::CodeActions(menu)) => {
2580 if menu.visible() {
2581 key_context.add("menu");
2582 key_context.add("showing_code_actions")
2583 }
2584 }
2585 None => {}
2586 }
2587
2588 if self.signature_help_state.has_multiple_signatures() {
2589 key_context.add("showing_signature_help");
2590 }
2591
2592 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2593 if !self.focus_handle(cx).contains_focused(window, cx)
2594 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2595 {
2596 for addon in self.addons.values() {
2597 addon.extend_key_context(&mut key_context, cx)
2598 }
2599 }
2600
2601 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2602 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2603 Some(
2604 file.full_path(cx)
2605 .extension()?
2606 .to_string_lossy()
2607 .into_owned(),
2608 )
2609 }) {
2610 key_context.set("extension", extension);
2611 }
2612 } else {
2613 key_context.add("multibuffer");
2614 }
2615
2616 if has_active_edit_prediction {
2617 if self.edit_prediction_in_conflict() {
2618 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2619 } else {
2620 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2621 key_context.add("copilot_suggestion");
2622 }
2623 }
2624
2625 if self.selection_mark_mode {
2626 key_context.add("selection_mode");
2627 }
2628
2629 let disjoint = self.selections.disjoint_anchors();
2630 let snapshot = self.snapshot(window, cx);
2631 let snapshot = snapshot.buffer_snapshot();
2632 if self.mode == EditorMode::SingleLine
2633 && let [selection] = disjoint
2634 && selection.start == selection.end
2635 && selection.end.to_offset(snapshot) == snapshot.len()
2636 {
2637 key_context.add("end_of_input");
2638 }
2639
2640 key_context
2641 }
2642
2643 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2644 self.last_bounds.as_ref()
2645 }
2646
2647 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2648 if self.mouse_cursor_hidden {
2649 self.mouse_cursor_hidden = false;
2650 cx.notify();
2651 }
2652 }
2653
2654 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2655 let hide_mouse_cursor = match origin {
2656 HideMouseCursorOrigin::TypingAction => {
2657 matches!(
2658 self.hide_mouse_mode,
2659 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2660 )
2661 }
2662 HideMouseCursorOrigin::MovementAction => {
2663 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2664 }
2665 };
2666 if self.mouse_cursor_hidden != hide_mouse_cursor {
2667 self.mouse_cursor_hidden = hide_mouse_cursor;
2668 cx.notify();
2669 }
2670 }
2671
2672 pub fn edit_prediction_in_conflict(&self) -> bool {
2673 if !self.show_edit_predictions_in_menu() {
2674 return false;
2675 }
2676
2677 let showing_completions = self
2678 .context_menu
2679 .borrow()
2680 .as_ref()
2681 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2682
2683 showing_completions
2684 || self.edit_prediction_requires_modifier()
2685 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2686 // bindings to insert tab characters.
2687 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2688 }
2689
2690 pub fn accept_edit_prediction_keybind(
2691 &self,
2692 accept_partial: bool,
2693 window: &mut Window,
2694 cx: &mut App,
2695 ) -> AcceptEditPredictionBinding {
2696 let key_context = self.key_context_internal(true, window, cx);
2697 let in_conflict = self.edit_prediction_in_conflict();
2698
2699 let bindings = if accept_partial {
2700 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2701 } else {
2702 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2703 };
2704
2705 // TODO: if the binding contains multiple keystrokes, display all of them, not
2706 // just the first one.
2707 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2708 !in_conflict
2709 || binding
2710 .keystrokes()
2711 .first()
2712 .is_some_and(|keystroke| keystroke.modifiers().modified())
2713 }))
2714 }
2715
2716 pub fn new_file(
2717 workspace: &mut Workspace,
2718 _: &workspace::NewFile,
2719 window: &mut Window,
2720 cx: &mut Context<Workspace>,
2721 ) {
2722 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2723 "Failed to create buffer",
2724 window,
2725 cx,
2726 |e, _, _| match e.error_code() {
2727 ErrorCode::RemoteUpgradeRequired => Some(format!(
2728 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2729 e.error_tag("required").unwrap_or("the latest version")
2730 )),
2731 _ => None,
2732 },
2733 );
2734 }
2735
2736 pub fn new_in_workspace(
2737 workspace: &mut Workspace,
2738 window: &mut Window,
2739 cx: &mut Context<Workspace>,
2740 ) -> Task<Result<Entity<Editor>>> {
2741 let project = workspace.project().clone();
2742 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2743
2744 cx.spawn_in(window, async move |workspace, cx| {
2745 let buffer = create.await?;
2746 workspace.update_in(cx, |workspace, window, cx| {
2747 let editor =
2748 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2749 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2750 editor
2751 })
2752 })
2753 }
2754
2755 fn new_file_vertical(
2756 workspace: &mut Workspace,
2757 _: &workspace::NewFileSplitVertical,
2758 window: &mut Window,
2759 cx: &mut Context<Workspace>,
2760 ) {
2761 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2762 }
2763
2764 fn new_file_horizontal(
2765 workspace: &mut Workspace,
2766 _: &workspace::NewFileSplitHorizontal,
2767 window: &mut Window,
2768 cx: &mut Context<Workspace>,
2769 ) {
2770 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2771 }
2772
2773 fn new_file_split(
2774 workspace: &mut Workspace,
2775 action: &workspace::NewFileSplit,
2776 window: &mut Window,
2777 cx: &mut Context<Workspace>,
2778 ) {
2779 Self::new_file_in_direction(workspace, action.0, window, cx)
2780 }
2781
2782 fn new_file_in_direction(
2783 workspace: &mut Workspace,
2784 direction: SplitDirection,
2785 window: &mut Window,
2786 cx: &mut Context<Workspace>,
2787 ) {
2788 let project = workspace.project().clone();
2789 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2790
2791 cx.spawn_in(window, async move |workspace, cx| {
2792 let buffer = create.await?;
2793 workspace.update_in(cx, move |workspace, window, cx| {
2794 workspace.split_item(
2795 direction,
2796 Box::new(
2797 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2798 ),
2799 window,
2800 cx,
2801 )
2802 })?;
2803 anyhow::Ok(())
2804 })
2805 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2806 match e.error_code() {
2807 ErrorCode::RemoteUpgradeRequired => Some(format!(
2808 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2809 e.error_tag("required").unwrap_or("the latest version")
2810 )),
2811 _ => None,
2812 }
2813 });
2814 }
2815
2816 pub fn leader_id(&self) -> Option<CollaboratorId> {
2817 self.leader_id
2818 }
2819
2820 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2821 &self.buffer
2822 }
2823
2824 pub fn project(&self) -> Option<&Entity<Project>> {
2825 self.project.as_ref()
2826 }
2827
2828 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2829 self.workspace.as_ref()?.0.upgrade()
2830 }
2831
2832 /// Returns the workspace serialization ID if this editor should be serialized.
2833 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2834 self.workspace
2835 .as_ref()
2836 .filter(|_| self.should_serialize_buffer())
2837 .and_then(|workspace| workspace.1)
2838 }
2839
2840 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2841 self.buffer().read(cx).title(cx)
2842 }
2843
2844 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2845 let git_blame_gutter_max_author_length = self
2846 .render_git_blame_gutter(cx)
2847 .then(|| {
2848 if let Some(blame) = self.blame.as_ref() {
2849 let max_author_length =
2850 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2851 Some(max_author_length)
2852 } else {
2853 None
2854 }
2855 })
2856 .flatten();
2857
2858 EditorSnapshot {
2859 mode: self.mode.clone(),
2860 show_gutter: self.show_gutter,
2861 show_line_numbers: self.show_line_numbers,
2862 show_git_diff_gutter: self.show_git_diff_gutter,
2863 show_code_actions: self.show_code_actions,
2864 show_runnables: self.show_runnables,
2865 show_breakpoints: self.show_breakpoints,
2866 git_blame_gutter_max_author_length,
2867 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2868 placeholder_display_snapshot: self
2869 .placeholder_display_map
2870 .as_ref()
2871 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2872 scroll_anchor: self.scroll_manager.anchor(),
2873 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2874 is_focused: self.focus_handle.is_focused(window),
2875 current_line_highlight: self
2876 .current_line_highlight
2877 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2878 gutter_hovered: self.gutter_hovered,
2879 }
2880 }
2881
2882 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2883 self.buffer.read(cx).language_at(point, cx)
2884 }
2885
2886 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2887 self.buffer.read(cx).read(cx).file_at(point).cloned()
2888 }
2889
2890 pub fn active_excerpt(
2891 &self,
2892 cx: &App,
2893 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2894 self.buffer
2895 .read(cx)
2896 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2897 }
2898
2899 pub fn mode(&self) -> &EditorMode {
2900 &self.mode
2901 }
2902
2903 pub fn set_mode(&mut self, mode: EditorMode) {
2904 self.mode = mode;
2905 }
2906
2907 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2908 self.collaboration_hub.as_deref()
2909 }
2910
2911 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2912 self.collaboration_hub = Some(hub);
2913 }
2914
2915 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2916 self.in_project_search = in_project_search;
2917 }
2918
2919 pub fn set_custom_context_menu(
2920 &mut self,
2921 f: impl 'static
2922 + Fn(
2923 &mut Self,
2924 DisplayPoint,
2925 &mut Window,
2926 &mut Context<Self>,
2927 ) -> Option<Entity<ui::ContextMenu>>,
2928 ) {
2929 self.custom_context_menu = Some(Box::new(f))
2930 }
2931
2932 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2933 self.completion_provider = provider;
2934 }
2935
2936 #[cfg(any(test, feature = "test-support"))]
2937 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2938 self.completion_provider.clone()
2939 }
2940
2941 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2942 self.semantics_provider.clone()
2943 }
2944
2945 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2946 self.semantics_provider = provider;
2947 }
2948
2949 pub fn set_edit_prediction_provider<T>(
2950 &mut self,
2951 provider: Option<Entity<T>>,
2952 window: &mut Window,
2953 cx: &mut Context<Self>,
2954 ) where
2955 T: EditPredictionProvider,
2956 {
2957 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2958 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2959 if this.focus_handle.is_focused(window) {
2960 this.update_visible_edit_prediction(window, cx);
2961 }
2962 }),
2963 provider: Arc::new(provider),
2964 });
2965 self.update_edit_prediction_settings(cx);
2966 self.refresh_edit_prediction(false, false, window, cx);
2967 }
2968
2969 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2970 self.placeholder_display_map
2971 .as_ref()
2972 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2973 }
2974
2975 pub fn set_placeholder_text(
2976 &mut self,
2977 placeholder_text: &str,
2978 window: &mut Window,
2979 cx: &mut Context<Self>,
2980 ) {
2981 let multibuffer = cx
2982 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2983
2984 let style = window.text_style();
2985
2986 self.placeholder_display_map = Some(cx.new(|cx| {
2987 DisplayMap::new(
2988 multibuffer,
2989 style.font(),
2990 style.font_size.to_pixels(window.rem_size()),
2991 None,
2992 FILE_HEADER_HEIGHT,
2993 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2994 Default::default(),
2995 DiagnosticSeverity::Off,
2996 cx,
2997 )
2998 }));
2999 cx.notify();
3000 }
3001
3002 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3003 self.cursor_shape = cursor_shape;
3004
3005 // Disrupt blink for immediate user feedback that the cursor shape has changed
3006 self.blink_manager.update(cx, BlinkManager::show_cursor);
3007
3008 cx.notify();
3009 }
3010
3011 pub fn set_current_line_highlight(
3012 &mut self,
3013 current_line_highlight: Option<CurrentLineHighlight>,
3014 ) {
3015 self.current_line_highlight = current_line_highlight;
3016 }
3017
3018 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3019 self.collapse_matches = collapse_matches;
3020 }
3021
3022 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3023 if self.collapse_matches {
3024 return range.start..range.start;
3025 }
3026 range.clone()
3027 }
3028
3029 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3030 if self.display_map.read(cx).clip_at_line_ends != clip {
3031 self.display_map
3032 .update(cx, |map, _| map.clip_at_line_ends = clip);
3033 }
3034 }
3035
3036 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3037 self.input_enabled = input_enabled;
3038 }
3039
3040 pub fn set_edit_predictions_hidden_for_vim_mode(
3041 &mut self,
3042 hidden: bool,
3043 window: &mut Window,
3044 cx: &mut Context<Self>,
3045 ) {
3046 if hidden != self.edit_predictions_hidden_for_vim_mode {
3047 self.edit_predictions_hidden_for_vim_mode = hidden;
3048 if hidden {
3049 self.update_visible_edit_prediction(window, cx);
3050 } else {
3051 self.refresh_edit_prediction(true, false, window, cx);
3052 }
3053 }
3054 }
3055
3056 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3057 self.menu_edit_predictions_policy = value;
3058 }
3059
3060 pub fn set_autoindent(&mut self, autoindent: bool) {
3061 if autoindent {
3062 self.autoindent_mode = Some(AutoindentMode::EachLine);
3063 } else {
3064 self.autoindent_mode = None;
3065 }
3066 }
3067
3068 pub fn read_only(&self, cx: &App) -> bool {
3069 self.read_only || self.buffer.read(cx).read_only()
3070 }
3071
3072 pub fn set_read_only(&mut self, read_only: bool) {
3073 self.read_only = read_only;
3074 }
3075
3076 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3077 self.use_autoclose = autoclose;
3078 }
3079
3080 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3081 self.use_auto_surround = auto_surround;
3082 }
3083
3084 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3085 self.auto_replace_emoji_shortcode = auto_replace;
3086 }
3087
3088 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3089 self.buffer_serialization = should_serialize.then(|| {
3090 BufferSerialization::new(
3091 ProjectSettings::get_global(cx)
3092 .session
3093 .restore_unsaved_buffers,
3094 )
3095 })
3096 }
3097
3098 fn should_serialize_buffer(&self) -> bool {
3099 self.buffer_serialization.is_some()
3100 }
3101
3102 pub fn toggle_edit_predictions(
3103 &mut self,
3104 _: &ToggleEditPrediction,
3105 window: &mut Window,
3106 cx: &mut Context<Self>,
3107 ) {
3108 if self.show_edit_predictions_override.is_some() {
3109 self.set_show_edit_predictions(None, window, cx);
3110 } else {
3111 let show_edit_predictions = !self.edit_predictions_enabled();
3112 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3113 }
3114 }
3115
3116 pub fn set_show_edit_predictions(
3117 &mut self,
3118 show_edit_predictions: Option<bool>,
3119 window: &mut Window,
3120 cx: &mut Context<Self>,
3121 ) {
3122 self.show_edit_predictions_override = show_edit_predictions;
3123 self.update_edit_prediction_settings(cx);
3124
3125 if let Some(false) = show_edit_predictions {
3126 self.discard_edit_prediction(false, cx);
3127 } else {
3128 self.refresh_edit_prediction(false, true, window, cx);
3129 }
3130 }
3131
3132 fn edit_predictions_disabled_in_scope(
3133 &self,
3134 buffer: &Entity<Buffer>,
3135 buffer_position: language::Anchor,
3136 cx: &App,
3137 ) -> bool {
3138 let snapshot = buffer.read(cx).snapshot();
3139 let settings = snapshot.settings_at(buffer_position, cx);
3140
3141 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3142 return false;
3143 };
3144
3145 scope.override_name().is_some_and(|scope_name| {
3146 settings
3147 .edit_predictions_disabled_in
3148 .iter()
3149 .any(|s| s == scope_name)
3150 })
3151 }
3152
3153 pub fn set_use_modal_editing(&mut self, to: bool) {
3154 self.use_modal_editing = to;
3155 }
3156
3157 pub fn use_modal_editing(&self) -> bool {
3158 self.use_modal_editing
3159 }
3160
3161 fn selections_did_change(
3162 &mut self,
3163 local: bool,
3164 old_cursor_position: &Anchor,
3165 effects: SelectionEffects,
3166 window: &mut Window,
3167 cx: &mut Context<Self>,
3168 ) {
3169 window.invalidate_character_coordinates();
3170
3171 // Copy selections to primary selection buffer
3172 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3173 if local {
3174 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3175 let buffer_handle = self.buffer.read(cx).read(cx);
3176
3177 let mut text = String::new();
3178 for (index, selection) in selections.iter().enumerate() {
3179 let text_for_selection = buffer_handle
3180 .text_for_range(selection.start..selection.end)
3181 .collect::<String>();
3182
3183 text.push_str(&text_for_selection);
3184 if index != selections.len() - 1 {
3185 text.push('\n');
3186 }
3187 }
3188
3189 if !text.is_empty() {
3190 cx.write_to_primary(ClipboardItem::new_string(text));
3191 }
3192 }
3193
3194 let selection_anchors = self.selections.disjoint_anchors_arc();
3195
3196 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3197 self.buffer.update(cx, |buffer, cx| {
3198 buffer.set_active_selections(
3199 &selection_anchors,
3200 self.selections.line_mode(),
3201 self.cursor_shape,
3202 cx,
3203 )
3204 });
3205 }
3206 let display_map = self
3207 .display_map
3208 .update(cx, |display_map, cx| display_map.snapshot(cx));
3209 let buffer = display_map.buffer_snapshot();
3210 if self.selections.count() == 1 {
3211 self.add_selections_state = None;
3212 }
3213 self.select_next_state = None;
3214 self.select_prev_state = None;
3215 self.select_syntax_node_history.try_clear();
3216 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3217 self.snippet_stack.invalidate(&selection_anchors, buffer);
3218 self.take_rename(false, window, cx);
3219
3220 let newest_selection = self.selections.newest_anchor();
3221 let new_cursor_position = newest_selection.head();
3222 let selection_start = newest_selection.start;
3223
3224 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3225 self.push_to_nav_history(
3226 *old_cursor_position,
3227 Some(new_cursor_position.to_point(buffer)),
3228 false,
3229 effects.nav_history == Some(true),
3230 cx,
3231 );
3232 }
3233
3234 if local {
3235 if let Some(buffer_id) = new_cursor_position.buffer_id {
3236 self.register_buffer(buffer_id, cx);
3237 }
3238
3239 let mut context_menu = self.context_menu.borrow_mut();
3240 let completion_menu = match context_menu.as_ref() {
3241 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3242 Some(CodeContextMenu::CodeActions(_)) => {
3243 *context_menu = None;
3244 None
3245 }
3246 None => None,
3247 };
3248 let completion_position = completion_menu.map(|menu| menu.initial_position);
3249 drop(context_menu);
3250
3251 if effects.completions
3252 && let Some(completion_position) = completion_position
3253 {
3254 let start_offset = selection_start.to_offset(buffer);
3255 let position_matches = start_offset == completion_position.to_offset(buffer);
3256 let continue_showing = if position_matches {
3257 if self.snippet_stack.is_empty() {
3258 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3259 == Some(CharKind::Word)
3260 } else {
3261 // Snippet choices can be shown even when the cursor is in whitespace.
3262 // Dismissing the menu with actions like backspace is handled by
3263 // invalidation regions.
3264 true
3265 }
3266 } else {
3267 false
3268 };
3269
3270 if continue_showing {
3271 self.open_or_update_completions_menu(None, None, false, window, cx);
3272 } else {
3273 self.hide_context_menu(window, cx);
3274 }
3275 }
3276
3277 hide_hover(self, cx);
3278
3279 if old_cursor_position.to_display_point(&display_map).row()
3280 != new_cursor_position.to_display_point(&display_map).row()
3281 {
3282 self.available_code_actions.take();
3283 }
3284 self.refresh_code_actions(window, cx);
3285 self.refresh_document_highlights(cx);
3286 refresh_linked_ranges(self, window, cx);
3287
3288 self.refresh_selected_text_highlights(false, window, cx);
3289 self.refresh_matching_bracket_highlights(window, cx);
3290 self.update_visible_edit_prediction(window, cx);
3291 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3292 self.inline_blame_popover.take();
3293 if self.git_blame_inline_enabled {
3294 self.start_inline_blame_timer(window, cx);
3295 }
3296 }
3297
3298 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3299 cx.emit(EditorEvent::SelectionsChanged { local });
3300
3301 let selections = &self.selections.disjoint_anchors_arc();
3302 if selections.len() == 1 {
3303 cx.emit(SearchEvent::ActiveMatchChanged)
3304 }
3305 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3306 let inmemory_selections = selections
3307 .iter()
3308 .map(|s| {
3309 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3310 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3311 })
3312 .collect();
3313 self.update_restoration_data(cx, |data| {
3314 data.selections = inmemory_selections;
3315 });
3316
3317 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3318 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3319 {
3320 let snapshot = self.buffer().read(cx).snapshot(cx);
3321 let selections = selections.clone();
3322 let background_executor = cx.background_executor().clone();
3323 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3324 self.serialize_selections = cx.background_spawn(async move {
3325 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3326 let db_selections = selections
3327 .iter()
3328 .map(|selection| {
3329 (
3330 selection.start.to_offset(&snapshot),
3331 selection.end.to_offset(&snapshot),
3332 )
3333 })
3334 .collect();
3335
3336 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3337 .await
3338 .with_context(|| {
3339 format!(
3340 "persisting editor selections for editor {editor_id}, \
3341 workspace {workspace_id:?}"
3342 )
3343 })
3344 .log_err();
3345 });
3346 }
3347 }
3348
3349 cx.notify();
3350 }
3351
3352 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3353 use text::ToOffset as _;
3354 use text::ToPoint as _;
3355
3356 if self.mode.is_minimap()
3357 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3358 {
3359 return;
3360 }
3361
3362 if !self.buffer().read(cx).is_singleton() {
3363 return;
3364 }
3365
3366 let display_snapshot = self
3367 .display_map
3368 .update(cx, |display_map, cx| display_map.snapshot(cx));
3369 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3370 return;
3371 };
3372 let inmemory_folds = display_snapshot
3373 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3374 .map(|fold| {
3375 fold.range.start.text_anchor.to_point(&snapshot)
3376 ..fold.range.end.text_anchor.to_point(&snapshot)
3377 })
3378 .collect();
3379 self.update_restoration_data(cx, |data| {
3380 data.folds = inmemory_folds;
3381 });
3382
3383 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3384 return;
3385 };
3386 let background_executor = cx.background_executor().clone();
3387 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3388 let db_folds = display_snapshot
3389 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3390 .map(|fold| {
3391 (
3392 fold.range.start.text_anchor.to_offset(&snapshot),
3393 fold.range.end.text_anchor.to_offset(&snapshot),
3394 )
3395 })
3396 .collect();
3397 self.serialize_folds = cx.background_spawn(async move {
3398 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3399 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3400 .await
3401 .with_context(|| {
3402 format!(
3403 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3404 )
3405 })
3406 .log_err();
3407 });
3408 }
3409
3410 pub fn sync_selections(
3411 &mut self,
3412 other: Entity<Editor>,
3413 cx: &mut Context<Self>,
3414 ) -> gpui::Subscription {
3415 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3416 if !other_selections.is_empty() {
3417 self.selections
3418 .change_with(&self.display_snapshot(cx), |selections| {
3419 selections.select_anchors(other_selections);
3420 });
3421 }
3422
3423 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3424 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3425 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3426 if other_selections.is_empty() {
3427 return;
3428 }
3429 let snapshot = this.display_snapshot(cx);
3430 this.selections.change_with(&snapshot, |selections| {
3431 selections.select_anchors(other_selections);
3432 });
3433 }
3434 });
3435
3436 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3437 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3438 let these_selections = this.selections.disjoint_anchors().to_vec();
3439 if these_selections.is_empty() {
3440 return;
3441 }
3442 other.update(cx, |other_editor, cx| {
3443 let snapshot = other_editor.display_snapshot(cx);
3444 other_editor
3445 .selections
3446 .change_with(&snapshot, |selections| {
3447 selections.select_anchors(these_selections);
3448 })
3449 });
3450 }
3451 });
3452
3453 Subscription::join(other_subscription, this_subscription)
3454 }
3455
3456 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3457 if self.buffer().read(cx).is_singleton() {
3458 return;
3459 }
3460 let snapshot = self.buffer.read(cx).snapshot(cx);
3461 let buffer_ids: HashSet<BufferId> = self
3462 .selections
3463 .disjoint_anchor_ranges()
3464 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3465 .collect();
3466 for buffer_id in buffer_ids {
3467 self.unfold_buffer(buffer_id, cx);
3468 }
3469 }
3470
3471 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3472 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3473 /// effects of selection change occur at the end of the transaction.
3474 pub fn change_selections<R>(
3475 &mut self,
3476 effects: SelectionEffects,
3477 window: &mut Window,
3478 cx: &mut Context<Self>,
3479 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3480 ) -> R {
3481 let snapshot = self.display_snapshot(cx);
3482 if let Some(state) = &mut self.deferred_selection_effects_state {
3483 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3484 state.effects.completions = effects.completions;
3485 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3486 let (changed, result) = self.selections.change_with(&snapshot, change);
3487 state.changed |= changed;
3488 return result;
3489 }
3490 let mut state = DeferredSelectionEffectsState {
3491 changed: false,
3492 effects,
3493 old_cursor_position: self.selections.newest_anchor().head(),
3494 history_entry: SelectionHistoryEntry {
3495 selections: self.selections.disjoint_anchors_arc(),
3496 select_next_state: self.select_next_state.clone(),
3497 select_prev_state: self.select_prev_state.clone(),
3498 add_selections_state: self.add_selections_state.clone(),
3499 },
3500 };
3501 let (changed, result) = self.selections.change_with(&snapshot, change);
3502 state.changed = state.changed || changed;
3503 if self.defer_selection_effects {
3504 self.deferred_selection_effects_state = Some(state);
3505 } else {
3506 self.apply_selection_effects(state, window, cx);
3507 }
3508 result
3509 }
3510
3511 /// Defers the effects of selection change, so that the effects of multiple calls to
3512 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3513 /// to selection history and the state of popovers based on selection position aren't
3514 /// erroneously updated.
3515 pub fn with_selection_effects_deferred<R>(
3516 &mut self,
3517 window: &mut Window,
3518 cx: &mut Context<Self>,
3519 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3520 ) -> R {
3521 let already_deferred = self.defer_selection_effects;
3522 self.defer_selection_effects = true;
3523 let result = update(self, window, cx);
3524 if !already_deferred {
3525 self.defer_selection_effects = false;
3526 if let Some(state) = self.deferred_selection_effects_state.take() {
3527 self.apply_selection_effects(state, window, cx);
3528 }
3529 }
3530 result
3531 }
3532
3533 fn apply_selection_effects(
3534 &mut self,
3535 state: DeferredSelectionEffectsState,
3536 window: &mut Window,
3537 cx: &mut Context<Self>,
3538 ) {
3539 if state.changed {
3540 self.selection_history.push(state.history_entry);
3541
3542 if let Some(autoscroll) = state.effects.scroll {
3543 self.request_autoscroll(autoscroll, cx);
3544 }
3545
3546 let old_cursor_position = &state.old_cursor_position;
3547
3548 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3549
3550 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3551 self.show_signature_help(&ShowSignatureHelp, window, cx);
3552 }
3553 }
3554 }
3555
3556 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3557 where
3558 I: IntoIterator<Item = (Range<S>, T)>,
3559 S: ToOffset,
3560 T: Into<Arc<str>>,
3561 {
3562 if self.read_only(cx) {
3563 return;
3564 }
3565
3566 self.buffer
3567 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3568 }
3569
3570 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3571 where
3572 I: IntoIterator<Item = (Range<S>, T)>,
3573 S: ToOffset,
3574 T: Into<Arc<str>>,
3575 {
3576 if self.read_only(cx) {
3577 return;
3578 }
3579
3580 self.buffer.update(cx, |buffer, cx| {
3581 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3582 });
3583 }
3584
3585 pub fn edit_with_block_indent<I, S, T>(
3586 &mut self,
3587 edits: I,
3588 original_indent_columns: Vec<Option<u32>>,
3589 cx: &mut Context<Self>,
3590 ) where
3591 I: IntoIterator<Item = (Range<S>, T)>,
3592 S: ToOffset,
3593 T: Into<Arc<str>>,
3594 {
3595 if self.read_only(cx) {
3596 return;
3597 }
3598
3599 self.buffer.update(cx, |buffer, cx| {
3600 buffer.edit(
3601 edits,
3602 Some(AutoindentMode::Block {
3603 original_indent_columns,
3604 }),
3605 cx,
3606 )
3607 });
3608 }
3609
3610 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3611 self.hide_context_menu(window, cx);
3612
3613 match phase {
3614 SelectPhase::Begin {
3615 position,
3616 add,
3617 click_count,
3618 } => self.begin_selection(position, add, click_count, window, cx),
3619 SelectPhase::BeginColumnar {
3620 position,
3621 goal_column,
3622 reset,
3623 mode,
3624 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3625 SelectPhase::Extend {
3626 position,
3627 click_count,
3628 } => self.extend_selection(position, click_count, window, cx),
3629 SelectPhase::Update {
3630 position,
3631 goal_column,
3632 scroll_delta,
3633 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3634 SelectPhase::End => self.end_selection(window, cx),
3635 }
3636 }
3637
3638 fn extend_selection(
3639 &mut self,
3640 position: DisplayPoint,
3641 click_count: usize,
3642 window: &mut Window,
3643 cx: &mut Context<Self>,
3644 ) {
3645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3646 let tail = self.selections.newest::<usize>(&display_map).tail();
3647 let click_count = click_count.max(match self.selections.select_mode() {
3648 SelectMode::Character => 1,
3649 SelectMode::Word(_) => 2,
3650 SelectMode::Line(_) => 3,
3651 SelectMode::All => 4,
3652 });
3653 self.begin_selection(position, false, click_count, window, cx);
3654
3655 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3656
3657 let current_selection = match self.selections.select_mode() {
3658 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3659 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3660 };
3661
3662 let mut pending_selection = self
3663 .selections
3664 .pending_anchor()
3665 .cloned()
3666 .expect("extend_selection not called with pending selection");
3667
3668 if pending_selection
3669 .start
3670 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3671 == Ordering::Greater
3672 {
3673 pending_selection.start = current_selection.start;
3674 }
3675 if pending_selection
3676 .end
3677 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3678 == Ordering::Less
3679 {
3680 pending_selection.end = current_selection.end;
3681 pending_selection.reversed = true;
3682 }
3683
3684 let mut pending_mode = self.selections.pending_mode().unwrap();
3685 match &mut pending_mode {
3686 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3687 _ => {}
3688 }
3689
3690 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3691 SelectionEffects::scroll(Autoscroll::fit())
3692 } else {
3693 SelectionEffects::no_scroll()
3694 };
3695
3696 self.change_selections(effects, window, cx, |s| {
3697 s.set_pending(pending_selection.clone(), pending_mode);
3698 s.set_is_extending(true);
3699 });
3700 }
3701
3702 fn begin_selection(
3703 &mut self,
3704 position: DisplayPoint,
3705 add: bool,
3706 click_count: usize,
3707 window: &mut Window,
3708 cx: &mut Context<Self>,
3709 ) {
3710 if !self.focus_handle.is_focused(window) {
3711 self.last_focused_descendant = None;
3712 window.focus(&self.focus_handle);
3713 }
3714
3715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3716 let buffer = display_map.buffer_snapshot();
3717 let position = display_map.clip_point(position, Bias::Left);
3718
3719 let start;
3720 let end;
3721 let mode;
3722 let mut auto_scroll;
3723 match click_count {
3724 1 => {
3725 start = buffer.anchor_before(position.to_point(&display_map));
3726 end = start;
3727 mode = SelectMode::Character;
3728 auto_scroll = true;
3729 }
3730 2 => {
3731 let position = display_map
3732 .clip_point(position, Bias::Left)
3733 .to_offset(&display_map, Bias::Left);
3734 let (range, _) = buffer.surrounding_word(position, None);
3735 start = buffer.anchor_before(range.start);
3736 end = buffer.anchor_before(range.end);
3737 mode = SelectMode::Word(start..end);
3738 auto_scroll = true;
3739 }
3740 3 => {
3741 let position = display_map
3742 .clip_point(position, Bias::Left)
3743 .to_point(&display_map);
3744 let line_start = display_map.prev_line_boundary(position).0;
3745 let next_line_start = buffer.clip_point(
3746 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3747 Bias::Left,
3748 );
3749 start = buffer.anchor_before(line_start);
3750 end = buffer.anchor_before(next_line_start);
3751 mode = SelectMode::Line(start..end);
3752 auto_scroll = true;
3753 }
3754 _ => {
3755 start = buffer.anchor_before(0);
3756 end = buffer.anchor_before(buffer.len());
3757 mode = SelectMode::All;
3758 auto_scroll = false;
3759 }
3760 }
3761 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3762
3763 let point_to_delete: Option<usize> = {
3764 let selected_points: Vec<Selection<Point>> =
3765 self.selections.disjoint_in_range(start..end, &display_map);
3766
3767 if !add || click_count > 1 {
3768 None
3769 } else if !selected_points.is_empty() {
3770 Some(selected_points[0].id)
3771 } else {
3772 let clicked_point_already_selected =
3773 self.selections.disjoint_anchors().iter().find(|selection| {
3774 selection.start.to_point(buffer) == start.to_point(buffer)
3775 || selection.end.to_point(buffer) == end.to_point(buffer)
3776 });
3777
3778 clicked_point_already_selected.map(|selection| selection.id)
3779 }
3780 };
3781
3782 let selections_count = self.selections.count();
3783 let effects = if auto_scroll {
3784 SelectionEffects::default()
3785 } else {
3786 SelectionEffects::no_scroll()
3787 };
3788
3789 self.change_selections(effects, window, cx, |s| {
3790 if let Some(point_to_delete) = point_to_delete {
3791 s.delete(point_to_delete);
3792
3793 if selections_count == 1 {
3794 s.set_pending_anchor_range(start..end, mode);
3795 }
3796 } else {
3797 if !add {
3798 s.clear_disjoint();
3799 }
3800
3801 s.set_pending_anchor_range(start..end, mode);
3802 }
3803 });
3804 }
3805
3806 fn begin_columnar_selection(
3807 &mut self,
3808 position: DisplayPoint,
3809 goal_column: u32,
3810 reset: bool,
3811 mode: ColumnarMode,
3812 window: &mut Window,
3813 cx: &mut Context<Self>,
3814 ) {
3815 if !self.focus_handle.is_focused(window) {
3816 self.last_focused_descendant = None;
3817 window.focus(&self.focus_handle);
3818 }
3819
3820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3821
3822 if reset {
3823 let pointer_position = display_map
3824 .buffer_snapshot()
3825 .anchor_before(position.to_point(&display_map));
3826
3827 self.change_selections(
3828 SelectionEffects::scroll(Autoscroll::newest()),
3829 window,
3830 cx,
3831 |s| {
3832 s.clear_disjoint();
3833 s.set_pending_anchor_range(
3834 pointer_position..pointer_position,
3835 SelectMode::Character,
3836 );
3837 },
3838 );
3839 };
3840
3841 let tail = self.selections.newest::<Point>(&display_map).tail();
3842 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3843 self.columnar_selection_state = match mode {
3844 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3845 selection_tail: selection_anchor,
3846 display_point: if reset {
3847 if position.column() != goal_column {
3848 Some(DisplayPoint::new(position.row(), goal_column))
3849 } else {
3850 None
3851 }
3852 } else {
3853 None
3854 },
3855 }),
3856 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3857 selection_tail: selection_anchor,
3858 }),
3859 };
3860
3861 if !reset {
3862 self.select_columns(position, goal_column, &display_map, window, cx);
3863 }
3864 }
3865
3866 fn update_selection(
3867 &mut self,
3868 position: DisplayPoint,
3869 goal_column: u32,
3870 scroll_delta: gpui::Point<f32>,
3871 window: &mut Window,
3872 cx: &mut Context<Self>,
3873 ) {
3874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3875
3876 if self.columnar_selection_state.is_some() {
3877 self.select_columns(position, goal_column, &display_map, window, cx);
3878 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3879 let buffer = display_map.buffer_snapshot();
3880 let head;
3881 let tail;
3882 let mode = self.selections.pending_mode().unwrap();
3883 match &mode {
3884 SelectMode::Character => {
3885 head = position.to_point(&display_map);
3886 tail = pending.tail().to_point(buffer);
3887 }
3888 SelectMode::Word(original_range) => {
3889 let offset = display_map
3890 .clip_point(position, Bias::Left)
3891 .to_offset(&display_map, Bias::Left);
3892 let original_range = original_range.to_offset(buffer);
3893
3894 let head_offset = if buffer.is_inside_word(offset, None)
3895 || original_range.contains(&offset)
3896 {
3897 let (word_range, _) = buffer.surrounding_word(offset, None);
3898 if word_range.start < original_range.start {
3899 word_range.start
3900 } else {
3901 word_range.end
3902 }
3903 } else {
3904 offset
3905 };
3906
3907 head = head_offset.to_point(buffer);
3908 if head_offset <= original_range.start {
3909 tail = original_range.end.to_point(buffer);
3910 } else {
3911 tail = original_range.start.to_point(buffer);
3912 }
3913 }
3914 SelectMode::Line(original_range) => {
3915 let original_range = original_range.to_point(display_map.buffer_snapshot());
3916
3917 let position = display_map
3918 .clip_point(position, Bias::Left)
3919 .to_point(&display_map);
3920 let line_start = display_map.prev_line_boundary(position).0;
3921 let next_line_start = buffer.clip_point(
3922 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3923 Bias::Left,
3924 );
3925
3926 if line_start < original_range.start {
3927 head = line_start
3928 } else {
3929 head = next_line_start
3930 }
3931
3932 if head <= original_range.start {
3933 tail = original_range.end;
3934 } else {
3935 tail = original_range.start;
3936 }
3937 }
3938 SelectMode::All => {
3939 return;
3940 }
3941 };
3942
3943 if head < tail {
3944 pending.start = buffer.anchor_before(head);
3945 pending.end = buffer.anchor_before(tail);
3946 pending.reversed = true;
3947 } else {
3948 pending.start = buffer.anchor_before(tail);
3949 pending.end = buffer.anchor_before(head);
3950 pending.reversed = false;
3951 }
3952
3953 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3954 s.set_pending(pending.clone(), mode);
3955 });
3956 } else {
3957 log::error!("update_selection dispatched with no pending selection");
3958 return;
3959 }
3960
3961 self.apply_scroll_delta(scroll_delta, window, cx);
3962 cx.notify();
3963 }
3964
3965 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3966 self.columnar_selection_state.take();
3967 if let Some(pending_mode) = self.selections.pending_mode() {
3968 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3970 s.select(selections);
3971 s.clear_pending();
3972 if s.is_extending() {
3973 s.set_is_extending(false);
3974 } else {
3975 s.set_select_mode(pending_mode);
3976 }
3977 });
3978 }
3979 }
3980
3981 fn select_columns(
3982 &mut self,
3983 head: DisplayPoint,
3984 goal_column: u32,
3985 display_map: &DisplaySnapshot,
3986 window: &mut Window,
3987 cx: &mut Context<Self>,
3988 ) {
3989 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3990 return;
3991 };
3992
3993 let tail = match columnar_state {
3994 ColumnarSelectionState::FromMouse {
3995 selection_tail,
3996 display_point,
3997 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3998 ColumnarSelectionState::FromSelection { selection_tail } => {
3999 selection_tail.to_display_point(display_map)
4000 }
4001 };
4002
4003 let start_row = cmp::min(tail.row(), head.row());
4004 let end_row = cmp::max(tail.row(), head.row());
4005 let start_column = cmp::min(tail.column(), goal_column);
4006 let end_column = cmp::max(tail.column(), goal_column);
4007 let reversed = start_column < tail.column();
4008
4009 let selection_ranges = (start_row.0..=end_row.0)
4010 .map(DisplayRow)
4011 .filter_map(|row| {
4012 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4013 || start_column <= display_map.line_len(row))
4014 && !display_map.is_block_line(row)
4015 {
4016 let start = display_map
4017 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4018 .to_point(display_map);
4019 let end = display_map
4020 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4021 .to_point(display_map);
4022 if reversed {
4023 Some(end..start)
4024 } else {
4025 Some(start..end)
4026 }
4027 } else {
4028 None
4029 }
4030 })
4031 .collect::<Vec<_>>();
4032 if selection_ranges.is_empty() {
4033 return;
4034 }
4035
4036 let ranges = match columnar_state {
4037 ColumnarSelectionState::FromMouse { .. } => {
4038 let mut non_empty_ranges = selection_ranges
4039 .iter()
4040 .filter(|selection_range| selection_range.start != selection_range.end)
4041 .peekable();
4042 if non_empty_ranges.peek().is_some() {
4043 non_empty_ranges.cloned().collect()
4044 } else {
4045 selection_ranges
4046 }
4047 }
4048 _ => selection_ranges,
4049 };
4050
4051 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4052 s.select_ranges(ranges);
4053 });
4054 cx.notify();
4055 }
4056
4057 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4058 self.selections
4059 .all_adjusted(snapshot)
4060 .iter()
4061 .any(|selection| !selection.is_empty())
4062 }
4063
4064 pub fn has_pending_nonempty_selection(&self) -> bool {
4065 let pending_nonempty_selection = match self.selections.pending_anchor() {
4066 Some(Selection { start, end, .. }) => start != end,
4067 None => false,
4068 };
4069
4070 pending_nonempty_selection
4071 || (self.columnar_selection_state.is_some()
4072 && self.selections.disjoint_anchors().len() > 1)
4073 }
4074
4075 pub fn has_pending_selection(&self) -> bool {
4076 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4077 }
4078
4079 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4080 self.selection_mark_mode = false;
4081 self.selection_drag_state = SelectionDragState::None;
4082
4083 if self.dismiss_menus_and_popups(true, window, cx) {
4084 cx.notify();
4085 return;
4086 }
4087 if self.clear_expanded_diff_hunks(cx) {
4088 cx.notify();
4089 return;
4090 }
4091 if self.show_git_blame_gutter {
4092 self.show_git_blame_gutter = false;
4093 cx.notify();
4094 return;
4095 }
4096
4097 if self.mode.is_full()
4098 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4099 {
4100 cx.notify();
4101 return;
4102 }
4103
4104 cx.propagate();
4105 }
4106
4107 pub fn dismiss_menus_and_popups(
4108 &mut self,
4109 is_user_requested: bool,
4110 window: &mut Window,
4111 cx: &mut Context<Self>,
4112 ) -> bool {
4113 if self.take_rename(false, window, cx).is_some() {
4114 return true;
4115 }
4116
4117 if self.hide_blame_popover(true, cx) {
4118 return true;
4119 }
4120
4121 if hide_hover(self, cx) {
4122 return true;
4123 }
4124
4125 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4126 return true;
4127 }
4128
4129 if self.hide_context_menu(window, cx).is_some() {
4130 return true;
4131 }
4132
4133 if self.mouse_context_menu.take().is_some() {
4134 return true;
4135 }
4136
4137 if is_user_requested && self.discard_edit_prediction(true, cx) {
4138 return true;
4139 }
4140
4141 if self.snippet_stack.pop().is_some() {
4142 return true;
4143 }
4144
4145 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4146 self.dismiss_diagnostics(cx);
4147 return true;
4148 }
4149
4150 false
4151 }
4152
4153 fn linked_editing_ranges_for(
4154 &self,
4155 selection: Range<text::Anchor>,
4156 cx: &App,
4157 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4158 if self.linked_edit_ranges.is_empty() {
4159 return None;
4160 }
4161 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4162 selection.end.buffer_id.and_then(|end_buffer_id| {
4163 if selection.start.buffer_id != Some(end_buffer_id) {
4164 return None;
4165 }
4166 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4167 let snapshot = buffer.read(cx).snapshot();
4168 self.linked_edit_ranges
4169 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4170 .map(|ranges| (ranges, snapshot, buffer))
4171 })?;
4172 use text::ToOffset as TO;
4173 // find offset from the start of current range to current cursor position
4174 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4175
4176 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4177 let start_difference = start_offset - start_byte_offset;
4178 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4179 let end_difference = end_offset - start_byte_offset;
4180 // Current range has associated linked ranges.
4181 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4182 for range in linked_ranges.iter() {
4183 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4184 let end_offset = start_offset + end_difference;
4185 let start_offset = start_offset + start_difference;
4186 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4187 continue;
4188 }
4189 if self.selections.disjoint_anchor_ranges().any(|s| {
4190 if s.start.buffer_id != selection.start.buffer_id
4191 || s.end.buffer_id != selection.end.buffer_id
4192 {
4193 return false;
4194 }
4195 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4196 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4197 }) {
4198 continue;
4199 }
4200 let start = buffer_snapshot.anchor_after(start_offset);
4201 let end = buffer_snapshot.anchor_after(end_offset);
4202 linked_edits
4203 .entry(buffer.clone())
4204 .or_default()
4205 .push(start..end);
4206 }
4207 Some(linked_edits)
4208 }
4209
4210 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4211 let text: Arc<str> = text.into();
4212
4213 if self.read_only(cx) {
4214 return;
4215 }
4216
4217 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4218
4219 self.unfold_buffers_with_selections(cx);
4220
4221 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4222 let mut bracket_inserted = false;
4223 let mut edits = Vec::new();
4224 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4225 let mut new_selections = Vec::with_capacity(selections.len());
4226 let mut new_autoclose_regions = Vec::new();
4227 let snapshot = self.buffer.read(cx).read(cx);
4228 let mut clear_linked_edit_ranges = false;
4229
4230 for (selection, autoclose_region) in
4231 self.selections_with_autoclose_regions(selections, &snapshot)
4232 {
4233 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4234 // Determine if the inserted text matches the opening or closing
4235 // bracket of any of this language's bracket pairs.
4236 let mut bracket_pair = None;
4237 let mut is_bracket_pair_start = false;
4238 let mut is_bracket_pair_end = false;
4239 if !text.is_empty() {
4240 let mut bracket_pair_matching_end = None;
4241 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4242 // and they are removing the character that triggered IME popup.
4243 for (pair, enabled) in scope.brackets() {
4244 if !pair.close && !pair.surround {
4245 continue;
4246 }
4247
4248 if enabled && pair.start.ends_with(text.as_ref()) {
4249 let prefix_len = pair.start.len() - text.len();
4250 let preceding_text_matches_prefix = prefix_len == 0
4251 || (selection.start.column >= (prefix_len as u32)
4252 && snapshot.contains_str_at(
4253 Point::new(
4254 selection.start.row,
4255 selection.start.column - (prefix_len as u32),
4256 ),
4257 &pair.start[..prefix_len],
4258 ));
4259 if preceding_text_matches_prefix {
4260 bracket_pair = Some(pair.clone());
4261 is_bracket_pair_start = true;
4262 break;
4263 }
4264 }
4265 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4266 {
4267 // take first bracket pair matching end, but don't break in case a later bracket
4268 // pair matches start
4269 bracket_pair_matching_end = Some(pair.clone());
4270 }
4271 }
4272 if let Some(end) = bracket_pair_matching_end
4273 && bracket_pair.is_none()
4274 {
4275 bracket_pair = Some(end);
4276 is_bracket_pair_end = true;
4277 }
4278 }
4279
4280 if let Some(bracket_pair) = bracket_pair {
4281 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4282 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4283 let auto_surround =
4284 self.use_auto_surround && snapshot_settings.use_auto_surround;
4285 if selection.is_empty() {
4286 if is_bracket_pair_start {
4287 // If the inserted text is a suffix of an opening bracket and the
4288 // selection is preceded by the rest of the opening bracket, then
4289 // insert the closing bracket.
4290 let following_text_allows_autoclose = snapshot
4291 .chars_at(selection.start)
4292 .next()
4293 .is_none_or(|c| scope.should_autoclose_before(c));
4294
4295 let preceding_text_allows_autoclose = selection.start.column == 0
4296 || snapshot
4297 .reversed_chars_at(selection.start)
4298 .next()
4299 .is_none_or(|c| {
4300 bracket_pair.start != bracket_pair.end
4301 || !snapshot
4302 .char_classifier_at(selection.start)
4303 .is_word(c)
4304 });
4305
4306 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4307 && bracket_pair.start.len() == 1
4308 {
4309 let target = bracket_pair.start.chars().next().unwrap();
4310 let current_line_count = snapshot
4311 .reversed_chars_at(selection.start)
4312 .take_while(|&c| c != '\n')
4313 .filter(|&c| c == target)
4314 .count();
4315 current_line_count % 2 == 1
4316 } else {
4317 false
4318 };
4319
4320 if autoclose
4321 && bracket_pair.close
4322 && following_text_allows_autoclose
4323 && preceding_text_allows_autoclose
4324 && !is_closing_quote
4325 {
4326 let anchor = snapshot.anchor_before(selection.end);
4327 new_selections.push((selection.map(|_| anchor), text.len()));
4328 new_autoclose_regions.push((
4329 anchor,
4330 text.len(),
4331 selection.id,
4332 bracket_pair.clone(),
4333 ));
4334 edits.push((
4335 selection.range(),
4336 format!("{}{}", text, bracket_pair.end).into(),
4337 ));
4338 bracket_inserted = true;
4339 continue;
4340 }
4341 }
4342
4343 if let Some(region) = autoclose_region {
4344 // If the selection is followed by an auto-inserted closing bracket,
4345 // then don't insert that closing bracket again; just move the selection
4346 // past the closing bracket.
4347 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4348 && text.as_ref() == region.pair.end.as_str()
4349 && snapshot.contains_str_at(region.range.end, text.as_ref());
4350 if should_skip {
4351 let anchor = snapshot.anchor_after(selection.end);
4352 new_selections
4353 .push((selection.map(|_| anchor), region.pair.end.len()));
4354 continue;
4355 }
4356 }
4357
4358 let always_treat_brackets_as_autoclosed = snapshot
4359 .language_settings_at(selection.start, cx)
4360 .always_treat_brackets_as_autoclosed;
4361 if always_treat_brackets_as_autoclosed
4362 && is_bracket_pair_end
4363 && snapshot.contains_str_at(selection.end, text.as_ref())
4364 {
4365 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4366 // and the inserted text is a closing bracket and the selection is followed
4367 // by the closing bracket then move the selection past the closing bracket.
4368 let anchor = snapshot.anchor_after(selection.end);
4369 new_selections.push((selection.map(|_| anchor), text.len()));
4370 continue;
4371 }
4372 }
4373 // If an opening bracket is 1 character long and is typed while
4374 // text is selected, then surround that text with the bracket pair.
4375 else if auto_surround
4376 && bracket_pair.surround
4377 && is_bracket_pair_start
4378 && bracket_pair.start.chars().count() == 1
4379 {
4380 edits.push((selection.start..selection.start, text.clone()));
4381 edits.push((
4382 selection.end..selection.end,
4383 bracket_pair.end.as_str().into(),
4384 ));
4385 bracket_inserted = true;
4386 new_selections.push((
4387 Selection {
4388 id: selection.id,
4389 start: snapshot.anchor_after(selection.start),
4390 end: snapshot.anchor_before(selection.end),
4391 reversed: selection.reversed,
4392 goal: selection.goal,
4393 },
4394 0,
4395 ));
4396 continue;
4397 }
4398 }
4399 }
4400
4401 if self.auto_replace_emoji_shortcode
4402 && selection.is_empty()
4403 && text.as_ref().ends_with(':')
4404 && let Some(possible_emoji_short_code) =
4405 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4406 && !possible_emoji_short_code.is_empty()
4407 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4408 {
4409 let emoji_shortcode_start = Point::new(
4410 selection.start.row,
4411 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4412 );
4413
4414 // Remove shortcode from buffer
4415 edits.push((
4416 emoji_shortcode_start..selection.start,
4417 "".to_string().into(),
4418 ));
4419 new_selections.push((
4420 Selection {
4421 id: selection.id,
4422 start: snapshot.anchor_after(emoji_shortcode_start),
4423 end: snapshot.anchor_before(selection.start),
4424 reversed: selection.reversed,
4425 goal: selection.goal,
4426 },
4427 0,
4428 ));
4429
4430 // Insert emoji
4431 let selection_start_anchor = snapshot.anchor_after(selection.start);
4432 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4433 edits.push((selection.start..selection.end, emoji.to_string().into()));
4434
4435 continue;
4436 }
4437
4438 // If not handling any auto-close operation, then just replace the selected
4439 // text with the given input and move the selection to the end of the
4440 // newly inserted text.
4441 let anchor = snapshot.anchor_after(selection.end);
4442 if !self.linked_edit_ranges.is_empty() {
4443 let start_anchor = snapshot.anchor_before(selection.start);
4444
4445 let is_word_char = text.chars().next().is_none_or(|char| {
4446 let classifier = snapshot
4447 .char_classifier_at(start_anchor.to_offset(&snapshot))
4448 .scope_context(Some(CharScopeContext::LinkedEdit));
4449 classifier.is_word(char)
4450 });
4451
4452 if is_word_char {
4453 if let Some(ranges) = self
4454 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4455 {
4456 for (buffer, edits) in ranges {
4457 linked_edits
4458 .entry(buffer.clone())
4459 .or_default()
4460 .extend(edits.into_iter().map(|range| (range, text.clone())));
4461 }
4462 }
4463 } else {
4464 clear_linked_edit_ranges = true;
4465 }
4466 }
4467
4468 new_selections.push((selection.map(|_| anchor), 0));
4469 edits.push((selection.start..selection.end, text.clone()));
4470 }
4471
4472 drop(snapshot);
4473
4474 self.transact(window, cx, |this, window, cx| {
4475 if clear_linked_edit_ranges {
4476 this.linked_edit_ranges.clear();
4477 }
4478 let initial_buffer_versions =
4479 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4480
4481 this.buffer.update(cx, |buffer, cx| {
4482 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4483 });
4484 for (buffer, edits) in linked_edits {
4485 buffer.update(cx, |buffer, cx| {
4486 let snapshot = buffer.snapshot();
4487 let edits = edits
4488 .into_iter()
4489 .map(|(range, text)| {
4490 use text::ToPoint as TP;
4491 let end_point = TP::to_point(&range.end, &snapshot);
4492 let start_point = TP::to_point(&range.start, &snapshot);
4493 (start_point..end_point, text)
4494 })
4495 .sorted_by_key(|(range, _)| range.start);
4496 buffer.edit(edits, None, cx);
4497 })
4498 }
4499 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4500 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4501 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4502 let new_selections =
4503 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4504 .zip(new_selection_deltas)
4505 .map(|(selection, delta)| Selection {
4506 id: selection.id,
4507 start: selection.start + delta,
4508 end: selection.end + delta,
4509 reversed: selection.reversed,
4510 goal: SelectionGoal::None,
4511 })
4512 .collect::<Vec<_>>();
4513
4514 let mut i = 0;
4515 for (position, delta, selection_id, pair) in new_autoclose_regions {
4516 let position = position.to_offset(map.buffer_snapshot()) + delta;
4517 let start = map.buffer_snapshot().anchor_before(position);
4518 let end = map.buffer_snapshot().anchor_after(position);
4519 while let Some(existing_state) = this.autoclose_regions.get(i) {
4520 match existing_state
4521 .range
4522 .start
4523 .cmp(&start, map.buffer_snapshot())
4524 {
4525 Ordering::Less => i += 1,
4526 Ordering::Greater => break,
4527 Ordering::Equal => {
4528 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4529 Ordering::Less => i += 1,
4530 Ordering::Equal => break,
4531 Ordering::Greater => break,
4532 }
4533 }
4534 }
4535 }
4536 this.autoclose_regions.insert(
4537 i,
4538 AutocloseRegion {
4539 selection_id,
4540 range: start..end,
4541 pair,
4542 },
4543 );
4544 }
4545
4546 let had_active_edit_prediction = this.has_active_edit_prediction();
4547 this.change_selections(
4548 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4549 window,
4550 cx,
4551 |s| s.select(new_selections),
4552 );
4553
4554 if !bracket_inserted
4555 && let Some(on_type_format_task) =
4556 this.trigger_on_type_formatting(text.to_string(), window, cx)
4557 {
4558 on_type_format_task.detach_and_log_err(cx);
4559 }
4560
4561 let editor_settings = EditorSettings::get_global(cx);
4562 if bracket_inserted
4563 && (editor_settings.auto_signature_help
4564 || editor_settings.show_signature_help_after_edits)
4565 {
4566 this.show_signature_help(&ShowSignatureHelp, window, cx);
4567 }
4568
4569 let trigger_in_words =
4570 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4571 if this.hard_wrap.is_some() {
4572 let latest: Range<Point> = this.selections.newest(&map).range();
4573 if latest.is_empty()
4574 && this
4575 .buffer()
4576 .read(cx)
4577 .snapshot(cx)
4578 .line_len(MultiBufferRow(latest.start.row))
4579 == latest.start.column
4580 {
4581 this.rewrap_impl(
4582 RewrapOptions {
4583 override_language_settings: true,
4584 preserve_existing_whitespace: true,
4585 },
4586 cx,
4587 )
4588 }
4589 }
4590 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4591 refresh_linked_ranges(this, window, cx);
4592 this.refresh_edit_prediction(true, false, window, cx);
4593 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4594 });
4595 }
4596
4597 fn find_possible_emoji_shortcode_at_position(
4598 snapshot: &MultiBufferSnapshot,
4599 position: Point,
4600 ) -> Option<String> {
4601 let mut chars = Vec::new();
4602 let mut found_colon = false;
4603 for char in snapshot.reversed_chars_at(position).take(100) {
4604 // Found a possible emoji shortcode in the middle of the buffer
4605 if found_colon {
4606 if char.is_whitespace() {
4607 chars.reverse();
4608 return Some(chars.iter().collect());
4609 }
4610 // If the previous character is not a whitespace, we are in the middle of a word
4611 // and we only want to complete the shortcode if the word is made up of other emojis
4612 let mut containing_word = String::new();
4613 for ch in snapshot
4614 .reversed_chars_at(position)
4615 .skip(chars.len() + 1)
4616 .take(100)
4617 {
4618 if ch.is_whitespace() {
4619 break;
4620 }
4621 containing_word.push(ch);
4622 }
4623 let containing_word = containing_word.chars().rev().collect::<String>();
4624 if util::word_consists_of_emojis(containing_word.as_str()) {
4625 chars.reverse();
4626 return Some(chars.iter().collect());
4627 }
4628 }
4629
4630 if char.is_whitespace() || !char.is_ascii() {
4631 return None;
4632 }
4633 if char == ':' {
4634 found_colon = true;
4635 } else {
4636 chars.push(char);
4637 }
4638 }
4639 // Found a possible emoji shortcode at the beginning of the buffer
4640 chars.reverse();
4641 Some(chars.iter().collect())
4642 }
4643
4644 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4645 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4646 self.transact(window, cx, |this, window, cx| {
4647 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4648 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4649 let multi_buffer = this.buffer.read(cx);
4650 let buffer = multi_buffer.snapshot(cx);
4651 selections
4652 .iter()
4653 .map(|selection| {
4654 let start_point = selection.start.to_point(&buffer);
4655 let mut existing_indent =
4656 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4657 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4658 let start = selection.start;
4659 let end = selection.end;
4660 let selection_is_empty = start == end;
4661 let language_scope = buffer.language_scope_at(start);
4662 let (
4663 comment_delimiter,
4664 doc_delimiter,
4665 insert_extra_newline,
4666 indent_on_newline,
4667 indent_on_extra_newline,
4668 ) = if let Some(language) = &language_scope {
4669 let mut insert_extra_newline =
4670 insert_extra_newline_brackets(&buffer, start..end, language)
4671 || insert_extra_newline_tree_sitter(&buffer, start..end);
4672
4673 // Comment extension on newline is allowed only for cursor selections
4674 let comment_delimiter = maybe!({
4675 if !selection_is_empty {
4676 return None;
4677 }
4678
4679 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4680 return None;
4681 }
4682
4683 let delimiters = language.line_comment_prefixes();
4684 let max_len_of_delimiter =
4685 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4686 let (snapshot, range) =
4687 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4688
4689 let num_of_whitespaces = snapshot
4690 .chars_for_range(range.clone())
4691 .take_while(|c| c.is_whitespace())
4692 .count();
4693 let comment_candidate = snapshot
4694 .chars_for_range(range.clone())
4695 .skip(num_of_whitespaces)
4696 .take(max_len_of_delimiter)
4697 .collect::<String>();
4698 let (delimiter, trimmed_len) = delimiters
4699 .iter()
4700 .filter_map(|delimiter| {
4701 let prefix = delimiter.trim_end();
4702 if comment_candidate.starts_with(prefix) {
4703 Some((delimiter, prefix.len()))
4704 } else {
4705 None
4706 }
4707 })
4708 .max_by_key(|(_, len)| *len)?;
4709
4710 if let Some(BlockCommentConfig {
4711 start: block_start, ..
4712 }) = language.block_comment()
4713 {
4714 let block_start_trimmed = block_start.trim_end();
4715 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4716 let line_content = snapshot
4717 .chars_for_range(range)
4718 .skip(num_of_whitespaces)
4719 .take(block_start_trimmed.len())
4720 .collect::<String>();
4721
4722 if line_content.starts_with(block_start_trimmed) {
4723 return None;
4724 }
4725 }
4726 }
4727
4728 let cursor_is_placed_after_comment_marker =
4729 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4730 if cursor_is_placed_after_comment_marker {
4731 Some(delimiter.clone())
4732 } else {
4733 None
4734 }
4735 });
4736
4737 let mut indent_on_newline = IndentSize::spaces(0);
4738 let mut indent_on_extra_newline = IndentSize::spaces(0);
4739
4740 let doc_delimiter = maybe!({
4741 if !selection_is_empty {
4742 return None;
4743 }
4744
4745 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4746 return None;
4747 }
4748
4749 let BlockCommentConfig {
4750 start: start_tag,
4751 end: end_tag,
4752 prefix: delimiter,
4753 tab_size: len,
4754 } = language.documentation_comment()?;
4755 let is_within_block_comment = buffer
4756 .language_scope_at(start_point)
4757 .is_some_and(|scope| scope.override_name() == Some("comment"));
4758 if !is_within_block_comment {
4759 return None;
4760 }
4761
4762 let (snapshot, range) =
4763 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4764
4765 let num_of_whitespaces = snapshot
4766 .chars_for_range(range.clone())
4767 .take_while(|c| c.is_whitespace())
4768 .count();
4769
4770 // 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.
4771 let column = start_point.column;
4772 let cursor_is_after_start_tag = {
4773 let start_tag_len = start_tag.len();
4774 let start_tag_line = snapshot
4775 .chars_for_range(range.clone())
4776 .skip(num_of_whitespaces)
4777 .take(start_tag_len)
4778 .collect::<String>();
4779 if start_tag_line.starts_with(start_tag.as_ref()) {
4780 num_of_whitespaces + start_tag_len <= column as usize
4781 } else {
4782 false
4783 }
4784 };
4785
4786 let cursor_is_after_delimiter = {
4787 let delimiter_trim = delimiter.trim_end();
4788 let delimiter_line = snapshot
4789 .chars_for_range(range.clone())
4790 .skip(num_of_whitespaces)
4791 .take(delimiter_trim.len())
4792 .collect::<String>();
4793 if delimiter_line.starts_with(delimiter_trim) {
4794 num_of_whitespaces + delimiter_trim.len() <= column as usize
4795 } else {
4796 false
4797 }
4798 };
4799
4800 let cursor_is_before_end_tag_if_exists = {
4801 let mut char_position = 0u32;
4802 let mut end_tag_offset = None;
4803
4804 'outer: for chunk in snapshot.text_for_range(range) {
4805 if let Some(byte_pos) = chunk.find(&**end_tag) {
4806 let chars_before_match =
4807 chunk[..byte_pos].chars().count() as u32;
4808 end_tag_offset =
4809 Some(char_position + chars_before_match);
4810 break 'outer;
4811 }
4812 char_position += chunk.chars().count() as u32;
4813 }
4814
4815 if let Some(end_tag_offset) = end_tag_offset {
4816 let cursor_is_before_end_tag = column <= end_tag_offset;
4817 if cursor_is_after_start_tag {
4818 if cursor_is_before_end_tag {
4819 insert_extra_newline = true;
4820 }
4821 let cursor_is_at_start_of_end_tag =
4822 column == end_tag_offset;
4823 if cursor_is_at_start_of_end_tag {
4824 indent_on_extra_newline.len = *len;
4825 }
4826 }
4827 cursor_is_before_end_tag
4828 } else {
4829 true
4830 }
4831 };
4832
4833 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4834 && cursor_is_before_end_tag_if_exists
4835 {
4836 if cursor_is_after_start_tag {
4837 indent_on_newline.len = *len;
4838 }
4839 Some(delimiter.clone())
4840 } else {
4841 None
4842 }
4843 });
4844
4845 (
4846 comment_delimiter,
4847 doc_delimiter,
4848 insert_extra_newline,
4849 indent_on_newline,
4850 indent_on_extra_newline,
4851 )
4852 } else {
4853 (
4854 None,
4855 None,
4856 false,
4857 IndentSize::default(),
4858 IndentSize::default(),
4859 )
4860 };
4861
4862 let prevent_auto_indent = doc_delimiter.is_some();
4863 let delimiter = comment_delimiter.or(doc_delimiter);
4864
4865 let capacity_for_delimiter =
4866 delimiter.as_deref().map(str::len).unwrap_or_default();
4867 let mut new_text = String::with_capacity(
4868 1 + capacity_for_delimiter
4869 + existing_indent.len as usize
4870 + indent_on_newline.len as usize
4871 + indent_on_extra_newline.len as usize,
4872 );
4873 new_text.push('\n');
4874 new_text.extend(existing_indent.chars());
4875 new_text.extend(indent_on_newline.chars());
4876
4877 if let Some(delimiter) = &delimiter {
4878 new_text.push_str(delimiter);
4879 }
4880
4881 if insert_extra_newline {
4882 new_text.push('\n');
4883 new_text.extend(existing_indent.chars());
4884 new_text.extend(indent_on_extra_newline.chars());
4885 }
4886
4887 let anchor = buffer.anchor_after(end);
4888 let new_selection = selection.map(|_| anchor);
4889 (
4890 ((start..end, new_text), prevent_auto_indent),
4891 (insert_extra_newline, new_selection),
4892 )
4893 })
4894 .unzip()
4895 };
4896
4897 let mut auto_indent_edits = Vec::new();
4898 let mut edits = Vec::new();
4899 for (edit, prevent_auto_indent) in edits_with_flags {
4900 if prevent_auto_indent {
4901 edits.push(edit);
4902 } else {
4903 auto_indent_edits.push(edit);
4904 }
4905 }
4906 if !edits.is_empty() {
4907 this.edit(edits, cx);
4908 }
4909 if !auto_indent_edits.is_empty() {
4910 this.edit_with_autoindent(auto_indent_edits, cx);
4911 }
4912
4913 let buffer = this.buffer.read(cx).snapshot(cx);
4914 let new_selections = selection_info
4915 .into_iter()
4916 .map(|(extra_newline_inserted, new_selection)| {
4917 let mut cursor = new_selection.end.to_point(&buffer);
4918 if extra_newline_inserted {
4919 cursor.row -= 1;
4920 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4921 }
4922 new_selection.map(|_| cursor)
4923 })
4924 .collect();
4925
4926 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4927 this.refresh_edit_prediction(true, false, window, cx);
4928 });
4929 }
4930
4931 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4933
4934 let buffer = self.buffer.read(cx);
4935 let snapshot = buffer.snapshot(cx);
4936
4937 let mut edits = Vec::new();
4938 let mut rows = Vec::new();
4939
4940 for (rows_inserted, selection) in self
4941 .selections
4942 .all_adjusted(&self.display_snapshot(cx))
4943 .into_iter()
4944 .enumerate()
4945 {
4946 let cursor = selection.head();
4947 let row = cursor.row;
4948
4949 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4950
4951 let newline = "\n".to_string();
4952 edits.push((start_of_line..start_of_line, newline));
4953
4954 rows.push(row + rows_inserted as u32);
4955 }
4956
4957 self.transact(window, cx, |editor, window, cx| {
4958 editor.edit(edits, cx);
4959
4960 editor.change_selections(Default::default(), window, cx, |s| {
4961 let mut index = 0;
4962 s.move_cursors_with(|map, _, _| {
4963 let row = rows[index];
4964 index += 1;
4965
4966 let point = Point::new(row, 0);
4967 let boundary = map.next_line_boundary(point).1;
4968 let clipped = map.clip_point(boundary, Bias::Left);
4969
4970 (clipped, SelectionGoal::None)
4971 });
4972 });
4973
4974 let mut indent_edits = Vec::new();
4975 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4976 for row in rows {
4977 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4978 for (row, indent) in indents {
4979 if indent.len == 0 {
4980 continue;
4981 }
4982
4983 let text = match indent.kind {
4984 IndentKind::Space => " ".repeat(indent.len as usize),
4985 IndentKind::Tab => "\t".repeat(indent.len as usize),
4986 };
4987 let point = Point::new(row.0, 0);
4988 indent_edits.push((point..point, text));
4989 }
4990 }
4991 editor.edit(indent_edits, cx);
4992 });
4993 }
4994
4995 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4996 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4997
4998 let buffer = self.buffer.read(cx);
4999 let snapshot = buffer.snapshot(cx);
5000
5001 let mut edits = Vec::new();
5002 let mut rows = Vec::new();
5003 let mut rows_inserted = 0;
5004
5005 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5006 let cursor = selection.head();
5007 let row = cursor.row;
5008
5009 let point = Point::new(row + 1, 0);
5010 let start_of_line = snapshot.clip_point(point, Bias::Left);
5011
5012 let newline = "\n".to_string();
5013 edits.push((start_of_line..start_of_line, newline));
5014
5015 rows_inserted += 1;
5016 rows.push(row + rows_inserted);
5017 }
5018
5019 self.transact(window, cx, |editor, window, cx| {
5020 editor.edit(edits, cx);
5021
5022 editor.change_selections(Default::default(), window, cx, |s| {
5023 let mut index = 0;
5024 s.move_cursors_with(|map, _, _| {
5025 let row = rows[index];
5026 index += 1;
5027
5028 let point = Point::new(row, 0);
5029 let boundary = map.next_line_boundary(point).1;
5030 let clipped = map.clip_point(boundary, Bias::Left);
5031
5032 (clipped, SelectionGoal::None)
5033 });
5034 });
5035
5036 let mut indent_edits = Vec::new();
5037 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5038 for row in rows {
5039 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5040 for (row, indent) in indents {
5041 if indent.len == 0 {
5042 continue;
5043 }
5044
5045 let text = match indent.kind {
5046 IndentKind::Space => " ".repeat(indent.len as usize),
5047 IndentKind::Tab => "\t".repeat(indent.len as usize),
5048 };
5049 let point = Point::new(row.0, 0);
5050 indent_edits.push((point..point, text));
5051 }
5052 }
5053 editor.edit(indent_edits, cx);
5054 });
5055 }
5056
5057 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5058 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5059 original_indent_columns: Vec::new(),
5060 });
5061 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5062 }
5063
5064 fn insert_with_autoindent_mode(
5065 &mut self,
5066 text: &str,
5067 autoindent_mode: Option<AutoindentMode>,
5068 window: &mut Window,
5069 cx: &mut Context<Self>,
5070 ) {
5071 if self.read_only(cx) {
5072 return;
5073 }
5074
5075 let text: Arc<str> = text.into();
5076 self.transact(window, cx, |this, window, cx| {
5077 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5078 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5079 let anchors = {
5080 let snapshot = buffer.read(cx);
5081 old_selections
5082 .iter()
5083 .map(|s| {
5084 let anchor = snapshot.anchor_after(s.head());
5085 s.map(|_| anchor)
5086 })
5087 .collect::<Vec<_>>()
5088 };
5089 buffer.edit(
5090 old_selections
5091 .iter()
5092 .map(|s| (s.start..s.end, text.clone())),
5093 autoindent_mode,
5094 cx,
5095 );
5096 anchors
5097 });
5098
5099 this.change_selections(Default::default(), window, cx, |s| {
5100 s.select_anchors(selection_anchors);
5101 });
5102
5103 cx.notify();
5104 });
5105 }
5106
5107 fn trigger_completion_on_input(
5108 &mut self,
5109 text: &str,
5110 trigger_in_words: bool,
5111 window: &mut Window,
5112 cx: &mut Context<Self>,
5113 ) {
5114 let completions_source = self
5115 .context_menu
5116 .borrow()
5117 .as_ref()
5118 .and_then(|menu| match menu {
5119 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5120 CodeContextMenu::CodeActions(_) => None,
5121 });
5122
5123 match completions_source {
5124 Some(CompletionsMenuSource::Words { .. }) => {
5125 self.open_or_update_completions_menu(
5126 Some(CompletionsMenuSource::Words {
5127 ignore_threshold: false,
5128 }),
5129 None,
5130 trigger_in_words,
5131 window,
5132 cx,
5133 );
5134 }
5135 _ => self.open_or_update_completions_menu(
5136 None,
5137 Some(text.to_owned()).filter(|x| !x.is_empty()),
5138 true,
5139 window,
5140 cx,
5141 ),
5142 }
5143 }
5144
5145 /// If any empty selections is touching the start of its innermost containing autoclose
5146 /// region, expand it to select the brackets.
5147 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5148 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5149 let buffer = self.buffer.read(cx).read(cx);
5150 let new_selections = self
5151 .selections_with_autoclose_regions(selections, &buffer)
5152 .map(|(mut selection, region)| {
5153 if !selection.is_empty() {
5154 return selection;
5155 }
5156
5157 if let Some(region) = region {
5158 let mut range = region.range.to_offset(&buffer);
5159 if selection.start == range.start && range.start >= region.pair.start.len() {
5160 range.start -= region.pair.start.len();
5161 if buffer.contains_str_at(range.start, ®ion.pair.start)
5162 && buffer.contains_str_at(range.end, ®ion.pair.end)
5163 {
5164 range.end += region.pair.end.len();
5165 selection.start = range.start;
5166 selection.end = range.end;
5167
5168 return selection;
5169 }
5170 }
5171 }
5172
5173 let always_treat_brackets_as_autoclosed = buffer
5174 .language_settings_at(selection.start, cx)
5175 .always_treat_brackets_as_autoclosed;
5176
5177 if !always_treat_brackets_as_autoclosed {
5178 return selection;
5179 }
5180
5181 if let Some(scope) = buffer.language_scope_at(selection.start) {
5182 for (pair, enabled) in scope.brackets() {
5183 if !enabled || !pair.close {
5184 continue;
5185 }
5186
5187 if buffer.contains_str_at(selection.start, &pair.end) {
5188 let pair_start_len = pair.start.len();
5189 if buffer.contains_str_at(
5190 selection.start.saturating_sub(pair_start_len),
5191 &pair.start,
5192 ) {
5193 selection.start -= pair_start_len;
5194 selection.end += pair.end.len();
5195
5196 return selection;
5197 }
5198 }
5199 }
5200 }
5201
5202 selection
5203 })
5204 .collect();
5205
5206 drop(buffer);
5207 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5208 selections.select(new_selections)
5209 });
5210 }
5211
5212 /// Iterate the given selections, and for each one, find the smallest surrounding
5213 /// autoclose region. This uses the ordering of the selections and the autoclose
5214 /// regions to avoid repeated comparisons.
5215 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5216 &'a self,
5217 selections: impl IntoIterator<Item = Selection<D>>,
5218 buffer: &'a MultiBufferSnapshot,
5219 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5220 let mut i = 0;
5221 let mut regions = self.autoclose_regions.as_slice();
5222 selections.into_iter().map(move |selection| {
5223 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5224
5225 let mut enclosing = None;
5226 while let Some(pair_state) = regions.get(i) {
5227 if pair_state.range.end.to_offset(buffer) < range.start {
5228 regions = ®ions[i + 1..];
5229 i = 0;
5230 } else if pair_state.range.start.to_offset(buffer) > range.end {
5231 break;
5232 } else {
5233 if pair_state.selection_id == selection.id {
5234 enclosing = Some(pair_state);
5235 }
5236 i += 1;
5237 }
5238 }
5239
5240 (selection, enclosing)
5241 })
5242 }
5243
5244 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5245 fn invalidate_autoclose_regions(
5246 &mut self,
5247 mut selections: &[Selection<Anchor>],
5248 buffer: &MultiBufferSnapshot,
5249 ) {
5250 self.autoclose_regions.retain(|state| {
5251 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5252 return false;
5253 }
5254
5255 let mut i = 0;
5256 while let Some(selection) = selections.get(i) {
5257 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5258 selections = &selections[1..];
5259 continue;
5260 }
5261 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5262 break;
5263 }
5264 if selection.id == state.selection_id {
5265 return true;
5266 } else {
5267 i += 1;
5268 }
5269 }
5270 false
5271 });
5272 }
5273
5274 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5275 let offset = position.to_offset(buffer);
5276 let (word_range, kind) =
5277 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5278 if offset > word_range.start && kind == Some(CharKind::Word) {
5279 Some(
5280 buffer
5281 .text_for_range(word_range.start..offset)
5282 .collect::<String>(),
5283 )
5284 } else {
5285 None
5286 }
5287 }
5288
5289 pub fn visible_excerpts(
5290 &self,
5291 cx: &mut Context<Editor>,
5292 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5293 let Some(project) = self.project() else {
5294 return HashMap::default();
5295 };
5296 let project = project.read(cx);
5297 let multi_buffer = self.buffer().read(cx);
5298 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5299 let multi_buffer_visible_start = self
5300 .scroll_manager
5301 .anchor()
5302 .anchor
5303 .to_point(&multi_buffer_snapshot);
5304 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5305 multi_buffer_visible_start
5306 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5307 Bias::Left,
5308 );
5309 multi_buffer_snapshot
5310 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5311 .into_iter()
5312 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5313 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5314 let buffer_file = project::File::from_dyn(buffer.file())?;
5315 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5316 let worktree_entry = buffer_worktree
5317 .read(cx)
5318 .entry_for_id(buffer_file.project_entry_id()?)?;
5319 if worktree_entry.is_ignored {
5320 None
5321 } else {
5322 Some((
5323 excerpt_id,
5324 (
5325 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5326 buffer.version().clone(),
5327 excerpt_visible_range,
5328 ),
5329 ))
5330 }
5331 })
5332 .collect()
5333 }
5334
5335 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5336 TextLayoutDetails {
5337 text_system: window.text_system().clone(),
5338 editor_style: self.style.clone().unwrap(),
5339 rem_size: window.rem_size(),
5340 scroll_anchor: self.scroll_manager.anchor(),
5341 visible_rows: self.visible_line_count(),
5342 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5343 }
5344 }
5345
5346 fn trigger_on_type_formatting(
5347 &self,
5348 input: String,
5349 window: &mut Window,
5350 cx: &mut Context<Self>,
5351 ) -> Option<Task<Result<()>>> {
5352 if input.len() != 1 {
5353 return None;
5354 }
5355
5356 let project = self.project()?;
5357 let position = self.selections.newest_anchor().head();
5358 let (buffer, buffer_position) = self
5359 .buffer
5360 .read(cx)
5361 .text_anchor_for_position(position, cx)?;
5362
5363 let settings = language_settings::language_settings(
5364 buffer
5365 .read(cx)
5366 .language_at(buffer_position)
5367 .map(|l| l.name()),
5368 buffer.read(cx).file(),
5369 cx,
5370 );
5371 if !settings.use_on_type_format {
5372 return None;
5373 }
5374
5375 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5376 // hence we do LSP request & edit on host side only — add formats to host's history.
5377 let push_to_lsp_host_history = true;
5378 // If this is not the host, append its history with new edits.
5379 let push_to_client_history = project.read(cx).is_via_collab();
5380
5381 let on_type_formatting = project.update(cx, |project, cx| {
5382 project.on_type_format(
5383 buffer.clone(),
5384 buffer_position,
5385 input,
5386 push_to_lsp_host_history,
5387 cx,
5388 )
5389 });
5390 Some(cx.spawn_in(window, async move |editor, cx| {
5391 if let Some(transaction) = on_type_formatting.await? {
5392 if push_to_client_history {
5393 buffer
5394 .update(cx, |buffer, _| {
5395 buffer.push_transaction(transaction, Instant::now());
5396 buffer.finalize_last_transaction();
5397 })
5398 .ok();
5399 }
5400 editor.update(cx, |editor, cx| {
5401 editor.refresh_document_highlights(cx);
5402 })?;
5403 }
5404 Ok(())
5405 }))
5406 }
5407
5408 pub fn show_word_completions(
5409 &mut self,
5410 _: &ShowWordCompletions,
5411 window: &mut Window,
5412 cx: &mut Context<Self>,
5413 ) {
5414 self.open_or_update_completions_menu(
5415 Some(CompletionsMenuSource::Words {
5416 ignore_threshold: true,
5417 }),
5418 None,
5419 false,
5420 window,
5421 cx,
5422 );
5423 }
5424
5425 pub fn show_completions(
5426 &mut self,
5427 _: &ShowCompletions,
5428 window: &mut Window,
5429 cx: &mut Context<Self>,
5430 ) {
5431 self.open_or_update_completions_menu(None, None, false, window, cx);
5432 }
5433
5434 fn open_or_update_completions_menu(
5435 &mut self,
5436 requested_source: Option<CompletionsMenuSource>,
5437 trigger: Option<String>,
5438 trigger_in_words: bool,
5439 window: &mut Window,
5440 cx: &mut Context<Self>,
5441 ) {
5442 if self.pending_rename.is_some() {
5443 return;
5444 }
5445
5446 let completions_source = self
5447 .context_menu
5448 .borrow()
5449 .as_ref()
5450 .and_then(|menu| match menu {
5451 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5452 CodeContextMenu::CodeActions(_) => None,
5453 });
5454
5455 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5456
5457 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5458 // inserted and selected. To handle that case, the start of the selection is used so that
5459 // the menu starts with all choices.
5460 let position = self
5461 .selections
5462 .newest_anchor()
5463 .start
5464 .bias_right(&multibuffer_snapshot);
5465 if position.diff_base_anchor.is_some() {
5466 return;
5467 }
5468 let buffer_position = multibuffer_snapshot.anchor_before(position);
5469 let Some(buffer) = buffer_position
5470 .buffer_id
5471 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5472 else {
5473 return;
5474 };
5475 let buffer_snapshot = buffer.read(cx).snapshot();
5476
5477 let query: Option<Arc<String>> =
5478 Self::completion_query(&multibuffer_snapshot, buffer_position)
5479 .map(|query| query.into());
5480
5481 drop(multibuffer_snapshot);
5482
5483 // Hide the current completions menu when query is empty. Without this, cached
5484 // completions from before the trigger char may be reused (#32774).
5485 if query.is_none() {
5486 let menu_is_open = matches!(
5487 self.context_menu.borrow().as_ref(),
5488 Some(CodeContextMenu::Completions(_))
5489 );
5490 if menu_is_open {
5491 self.hide_context_menu(window, cx);
5492 }
5493 }
5494
5495 let mut ignore_word_threshold = false;
5496 let provider = match requested_source {
5497 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5498 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5499 ignore_word_threshold = ignore_threshold;
5500 None
5501 }
5502 Some(CompletionsMenuSource::SnippetChoices)
5503 | Some(CompletionsMenuSource::SnippetsOnly) => {
5504 log::error!("bug: SnippetChoices requested_source is not handled");
5505 None
5506 }
5507 };
5508
5509 let sort_completions = provider
5510 .as_ref()
5511 .is_some_and(|provider| provider.sort_completions());
5512
5513 let filter_completions = provider
5514 .as_ref()
5515 .is_none_or(|provider| provider.filter_completions());
5516
5517 let was_snippets_only = matches!(
5518 completions_source,
5519 Some(CompletionsMenuSource::SnippetsOnly)
5520 );
5521
5522 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5523 if filter_completions {
5524 menu.filter(
5525 query.clone().unwrap_or_default(),
5526 buffer_position.text_anchor,
5527 &buffer,
5528 provider.clone(),
5529 window,
5530 cx,
5531 );
5532 }
5533 // When `is_incomplete` is false, no need to re-query completions when the current query
5534 // is a suffix of the initial query.
5535 let was_complete = !menu.is_incomplete;
5536 if was_complete && !was_snippets_only {
5537 // If the new query is a suffix of the old query (typing more characters) and
5538 // the previous result was complete, the existing completions can be filtered.
5539 //
5540 // Note that snippet completions are always complete.
5541 let query_matches = match (&menu.initial_query, &query) {
5542 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5543 (None, _) => true,
5544 _ => false,
5545 };
5546 if query_matches {
5547 let position_matches = if menu.initial_position == position {
5548 true
5549 } else {
5550 let snapshot = self.buffer.read(cx).read(cx);
5551 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5552 };
5553 if position_matches {
5554 return;
5555 }
5556 }
5557 }
5558 };
5559
5560 let Anchor {
5561 excerpt_id: buffer_excerpt_id,
5562 text_anchor: buffer_position,
5563 ..
5564 } = buffer_position;
5565
5566 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5567 buffer_snapshot.surrounding_word(buffer_position, None)
5568 {
5569 let word_to_exclude = buffer_snapshot
5570 .text_for_range(word_range.clone())
5571 .collect::<String>();
5572 (
5573 buffer_snapshot.anchor_before(word_range.start)
5574 ..buffer_snapshot.anchor_after(buffer_position),
5575 Some(word_to_exclude),
5576 )
5577 } else {
5578 (buffer_position..buffer_position, None)
5579 };
5580
5581 let language = buffer_snapshot
5582 .language_at(buffer_position)
5583 .map(|language| language.name());
5584
5585 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5586 .completions
5587 .clone();
5588
5589 let show_completion_documentation = buffer_snapshot
5590 .settings_at(buffer_position, cx)
5591 .show_completion_documentation;
5592
5593 // The document can be large, so stay in reasonable bounds when searching for words,
5594 // otherwise completion pop-up might be slow to appear.
5595 const WORD_LOOKUP_ROWS: u32 = 5_000;
5596 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5597 let min_word_search = buffer_snapshot.clip_point(
5598 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5599 Bias::Left,
5600 );
5601 let max_word_search = buffer_snapshot.clip_point(
5602 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5603 Bias::Right,
5604 );
5605 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5606 ..buffer_snapshot.point_to_offset(max_word_search);
5607
5608 let skip_digits = query
5609 .as_ref()
5610 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5611
5612 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5613 trigger.as_ref().is_none_or(|trigger| {
5614 provider.is_completion_trigger(
5615 &buffer,
5616 position.text_anchor,
5617 trigger,
5618 trigger_in_words,
5619 completions_source.is_some(),
5620 cx,
5621 )
5622 })
5623 });
5624
5625 let provider_responses = if let Some(provider) = &provider
5626 && load_provider_completions
5627 {
5628 let trigger_character =
5629 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5630 let completion_context = CompletionContext {
5631 trigger_kind: match &trigger_character {
5632 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5633 None => CompletionTriggerKind::INVOKED,
5634 },
5635 trigger_character,
5636 };
5637
5638 provider.completions(
5639 buffer_excerpt_id,
5640 &buffer,
5641 buffer_position,
5642 completion_context,
5643 window,
5644 cx,
5645 )
5646 } else {
5647 Task::ready(Ok(Vec::new()))
5648 };
5649
5650 let load_word_completions = if !self.word_completions_enabled {
5651 false
5652 } else if requested_source
5653 == Some(CompletionsMenuSource::Words {
5654 ignore_threshold: true,
5655 })
5656 {
5657 true
5658 } else {
5659 load_provider_completions
5660 && completion_settings.words != WordsCompletionMode::Disabled
5661 && (ignore_word_threshold || {
5662 let words_min_length = completion_settings.words_min_length;
5663 // check whether word has at least `words_min_length` characters
5664 let query_chars = query.iter().flat_map(|q| q.chars());
5665 query_chars.take(words_min_length).count() == words_min_length
5666 })
5667 };
5668
5669 let mut words = if load_word_completions {
5670 cx.background_spawn({
5671 let buffer_snapshot = buffer_snapshot.clone();
5672 async move {
5673 buffer_snapshot.words_in_range(WordsQuery {
5674 fuzzy_contents: None,
5675 range: word_search_range,
5676 skip_digits,
5677 })
5678 }
5679 })
5680 } else {
5681 Task::ready(BTreeMap::default())
5682 };
5683
5684 let snippets = if let Some(provider) = &provider
5685 && provider.show_snippets()
5686 && let Some(project) = self.project()
5687 {
5688 let char_classifier = buffer_snapshot
5689 .char_classifier_at(buffer_position)
5690 .scope_context(Some(CharScopeContext::Completion));
5691 project.update(cx, |project, cx| {
5692 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5693 })
5694 } else {
5695 Task::ready(Ok(CompletionResponse {
5696 completions: Vec::new(),
5697 display_options: Default::default(),
5698 is_incomplete: false,
5699 }))
5700 };
5701
5702 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5703
5704 let id = post_inc(&mut self.next_completion_id);
5705 let task = cx.spawn_in(window, async move |editor, cx| {
5706 let Ok(()) = editor.update(cx, |this, _| {
5707 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5708 }) else {
5709 return;
5710 };
5711
5712 // TODO: Ideally completions from different sources would be selectively re-queried, so
5713 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5714 let mut completions = Vec::new();
5715 let mut is_incomplete = false;
5716 let mut display_options: Option<CompletionDisplayOptions> = None;
5717 if let Some(provider_responses) = provider_responses.await.log_err()
5718 && !provider_responses.is_empty()
5719 {
5720 for response in provider_responses {
5721 completions.extend(response.completions);
5722 is_incomplete = is_incomplete || response.is_incomplete;
5723 match display_options.as_mut() {
5724 None => {
5725 display_options = Some(response.display_options);
5726 }
5727 Some(options) => options.merge(&response.display_options),
5728 }
5729 }
5730 if completion_settings.words == WordsCompletionMode::Fallback {
5731 words = Task::ready(BTreeMap::default());
5732 }
5733 }
5734 let display_options = display_options.unwrap_or_default();
5735
5736 let mut words = words.await;
5737 if let Some(word_to_exclude) = &word_to_exclude {
5738 words.remove(word_to_exclude);
5739 }
5740 for lsp_completion in &completions {
5741 words.remove(&lsp_completion.new_text);
5742 }
5743 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5744 replace_range: word_replace_range.clone(),
5745 new_text: word.clone(),
5746 label: CodeLabel::plain(word, None),
5747 match_start: None,
5748 snippet_deduplication_key: None,
5749 icon_path: None,
5750 documentation: None,
5751 source: CompletionSource::BufferWord {
5752 word_range,
5753 resolved: false,
5754 },
5755 insert_text_mode: Some(InsertTextMode::AS_IS),
5756 confirm: None,
5757 }));
5758
5759 completions.extend(
5760 snippets
5761 .await
5762 .into_iter()
5763 .flat_map(|response| response.completions),
5764 );
5765
5766 let menu = if completions.is_empty() {
5767 None
5768 } else {
5769 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5770 let languages = editor
5771 .workspace
5772 .as_ref()
5773 .and_then(|(workspace, _)| workspace.upgrade())
5774 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5775 let menu = CompletionsMenu::new(
5776 id,
5777 requested_source.unwrap_or(if load_provider_completions {
5778 CompletionsMenuSource::Normal
5779 } else {
5780 CompletionsMenuSource::SnippetsOnly
5781 }),
5782 sort_completions,
5783 show_completion_documentation,
5784 position,
5785 query.clone(),
5786 is_incomplete,
5787 buffer.clone(),
5788 completions.into(),
5789 display_options,
5790 snippet_sort_order,
5791 languages,
5792 language,
5793 cx,
5794 );
5795
5796 let query = if filter_completions { query } else { None };
5797 let matches_task = menu.do_async_filtering(
5798 query.unwrap_or_default(),
5799 buffer_position,
5800 &buffer,
5801 cx,
5802 );
5803 (menu, matches_task)
5804 }) else {
5805 return;
5806 };
5807
5808 let matches = matches_task.await;
5809
5810 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5811 // Newer menu already set, so exit.
5812 if let Some(CodeContextMenu::Completions(prev_menu)) =
5813 editor.context_menu.borrow().as_ref()
5814 && prev_menu.id > id
5815 {
5816 return;
5817 };
5818
5819 // Only valid to take prev_menu because either the new menu is immediately set
5820 // below, or the menu is hidden.
5821 if let Some(CodeContextMenu::Completions(prev_menu)) =
5822 editor.context_menu.borrow_mut().take()
5823 {
5824 let position_matches =
5825 if prev_menu.initial_position == menu.initial_position {
5826 true
5827 } else {
5828 let snapshot = editor.buffer.read(cx).read(cx);
5829 prev_menu.initial_position.to_offset(&snapshot)
5830 == menu.initial_position.to_offset(&snapshot)
5831 };
5832 if position_matches {
5833 // Preserve markdown cache before `set_filter_results` because it will
5834 // try to populate the documentation cache.
5835 menu.preserve_markdown_cache(prev_menu);
5836 }
5837 };
5838
5839 menu.set_filter_results(matches, provider, window, cx);
5840 }) else {
5841 return;
5842 };
5843
5844 menu.visible().then_some(menu)
5845 };
5846
5847 editor
5848 .update_in(cx, |editor, window, cx| {
5849 if editor.focus_handle.is_focused(window)
5850 && let Some(menu) = menu
5851 {
5852 *editor.context_menu.borrow_mut() =
5853 Some(CodeContextMenu::Completions(menu));
5854
5855 crate::hover_popover::hide_hover(editor, cx);
5856 if editor.show_edit_predictions_in_menu() {
5857 editor.update_visible_edit_prediction(window, cx);
5858 } else {
5859 editor.discard_edit_prediction(false, cx);
5860 }
5861
5862 cx.notify();
5863 return;
5864 }
5865
5866 if editor.completion_tasks.len() <= 1 {
5867 // If there are no more completion tasks and the last menu was empty, we should hide it.
5868 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5869 // If it was already hidden and we don't show edit predictions in the menu,
5870 // we should also show the edit prediction when available.
5871 if was_hidden && editor.show_edit_predictions_in_menu() {
5872 editor.update_visible_edit_prediction(window, cx);
5873 }
5874 }
5875 })
5876 .ok();
5877 });
5878
5879 self.completion_tasks.push((id, task));
5880 }
5881
5882 #[cfg(feature = "test-support")]
5883 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5884 let menu = self.context_menu.borrow();
5885 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5886 let completions = menu.completions.borrow();
5887 Some(completions.to_vec())
5888 } else {
5889 None
5890 }
5891 }
5892
5893 pub fn with_completions_menu_matching_id<R>(
5894 &self,
5895 id: CompletionId,
5896 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5897 ) -> R {
5898 let mut context_menu = self.context_menu.borrow_mut();
5899 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5900 return f(None);
5901 };
5902 if completions_menu.id != id {
5903 return f(None);
5904 }
5905 f(Some(completions_menu))
5906 }
5907
5908 pub fn confirm_completion(
5909 &mut self,
5910 action: &ConfirmCompletion,
5911 window: &mut Window,
5912 cx: &mut Context<Self>,
5913 ) -> Option<Task<Result<()>>> {
5914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5915 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5916 }
5917
5918 pub fn confirm_completion_insert(
5919 &mut self,
5920 _: &ConfirmCompletionInsert,
5921 window: &mut Window,
5922 cx: &mut Context<Self>,
5923 ) -> Option<Task<Result<()>>> {
5924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5925 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5926 }
5927
5928 pub fn confirm_completion_replace(
5929 &mut self,
5930 _: &ConfirmCompletionReplace,
5931 window: &mut Window,
5932 cx: &mut Context<Self>,
5933 ) -> Option<Task<Result<()>>> {
5934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5935 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5936 }
5937
5938 pub fn compose_completion(
5939 &mut self,
5940 action: &ComposeCompletion,
5941 window: &mut Window,
5942 cx: &mut Context<Self>,
5943 ) -> Option<Task<Result<()>>> {
5944 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5945 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5946 }
5947
5948 fn do_completion(
5949 &mut self,
5950 item_ix: Option<usize>,
5951 intent: CompletionIntent,
5952 window: &mut Window,
5953 cx: &mut Context<Editor>,
5954 ) -> Option<Task<Result<()>>> {
5955 use language::ToOffset as _;
5956
5957 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5958 else {
5959 return None;
5960 };
5961
5962 let candidate_id = {
5963 let entries = completions_menu.entries.borrow();
5964 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5965 if self.show_edit_predictions_in_menu() {
5966 self.discard_edit_prediction(true, cx);
5967 }
5968 mat.candidate_id
5969 };
5970
5971 let completion = completions_menu
5972 .completions
5973 .borrow()
5974 .get(candidate_id)?
5975 .clone();
5976 cx.stop_propagation();
5977
5978 let buffer_handle = completions_menu.buffer.clone();
5979
5980 let CompletionEdit {
5981 new_text,
5982 snippet,
5983 replace_range,
5984 } = process_completion_for_edit(
5985 &completion,
5986 intent,
5987 &buffer_handle,
5988 &completions_menu.initial_position.text_anchor,
5989 cx,
5990 );
5991
5992 let buffer = buffer_handle.read(cx);
5993 let snapshot = self.buffer.read(cx).snapshot(cx);
5994 let newest_anchor = self.selections.newest_anchor();
5995 let replace_range_multibuffer = {
5996 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5997 excerpt.map_range_from_buffer(replace_range.clone())
5998 };
5999 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6000 return None;
6001 }
6002
6003 let old_text = buffer
6004 .text_for_range(replace_range.clone())
6005 .collect::<String>();
6006 let lookbehind = newest_anchor
6007 .start
6008 .text_anchor
6009 .to_offset(buffer)
6010 .saturating_sub(replace_range.start);
6011 let lookahead = replace_range
6012 .end
6013 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6014 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6015 let suffix = &old_text[lookbehind.min(old_text.len())..];
6016
6017 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6018 let mut ranges = Vec::new();
6019 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6020
6021 for selection in &selections {
6022 let range = if selection.id == newest_anchor.id {
6023 replace_range_multibuffer.clone()
6024 } else {
6025 let mut range = selection.range();
6026
6027 // if prefix is present, don't duplicate it
6028 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6029 range.start = range.start.saturating_sub(lookbehind);
6030
6031 // if suffix is also present, mimic the newest cursor and replace it
6032 if selection.id != newest_anchor.id
6033 && snapshot.contains_str_at(range.end, suffix)
6034 {
6035 range.end += lookahead;
6036 }
6037 }
6038 range
6039 };
6040
6041 ranges.push(range.clone());
6042
6043 if !self.linked_edit_ranges.is_empty() {
6044 let start_anchor = snapshot.anchor_before(range.start);
6045 let end_anchor = snapshot.anchor_after(range.end);
6046 if let Some(ranges) = self
6047 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6048 {
6049 for (buffer, edits) in ranges {
6050 linked_edits
6051 .entry(buffer.clone())
6052 .or_default()
6053 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6054 }
6055 }
6056 }
6057 }
6058
6059 let common_prefix_len = old_text
6060 .chars()
6061 .zip(new_text.chars())
6062 .take_while(|(a, b)| a == b)
6063 .map(|(a, _)| a.len_utf8())
6064 .sum::<usize>();
6065
6066 cx.emit(EditorEvent::InputHandled {
6067 utf16_range_to_replace: None,
6068 text: new_text[common_prefix_len..].into(),
6069 });
6070
6071 self.transact(window, cx, |editor, window, cx| {
6072 if let Some(mut snippet) = snippet {
6073 snippet.text = new_text.to_string();
6074 editor
6075 .insert_snippet(&ranges, snippet, window, cx)
6076 .log_err();
6077 } else {
6078 editor.buffer.update(cx, |multi_buffer, cx| {
6079 let auto_indent = match completion.insert_text_mode {
6080 Some(InsertTextMode::AS_IS) => None,
6081 _ => editor.autoindent_mode.clone(),
6082 };
6083 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6084 multi_buffer.edit(edits, auto_indent, cx);
6085 });
6086 }
6087 for (buffer, edits) in linked_edits {
6088 buffer.update(cx, |buffer, cx| {
6089 let snapshot = buffer.snapshot();
6090 let edits = edits
6091 .into_iter()
6092 .map(|(range, text)| {
6093 use text::ToPoint as TP;
6094 let end_point = TP::to_point(&range.end, &snapshot);
6095 let start_point = TP::to_point(&range.start, &snapshot);
6096 (start_point..end_point, text)
6097 })
6098 .sorted_by_key(|(range, _)| range.start);
6099 buffer.edit(edits, None, cx);
6100 })
6101 }
6102
6103 editor.refresh_edit_prediction(true, false, window, cx);
6104 });
6105 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6106
6107 let show_new_completions_on_confirm = completion
6108 .confirm
6109 .as_ref()
6110 .is_some_and(|confirm| confirm(intent, window, cx));
6111 if show_new_completions_on_confirm {
6112 self.open_or_update_completions_menu(None, None, false, window, cx);
6113 }
6114
6115 let provider = self.completion_provider.as_ref()?;
6116 drop(completion);
6117 let apply_edits = provider.apply_additional_edits_for_completion(
6118 buffer_handle,
6119 completions_menu.completions.clone(),
6120 candidate_id,
6121 true,
6122 cx,
6123 );
6124
6125 let editor_settings = EditorSettings::get_global(cx);
6126 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6127 // After the code completion is finished, users often want to know what signatures are needed.
6128 // so we should automatically call signature_help
6129 self.show_signature_help(&ShowSignatureHelp, window, cx);
6130 }
6131
6132 Some(cx.foreground_executor().spawn(async move {
6133 apply_edits.await?;
6134 Ok(())
6135 }))
6136 }
6137
6138 pub fn toggle_code_actions(
6139 &mut self,
6140 action: &ToggleCodeActions,
6141 window: &mut Window,
6142 cx: &mut Context<Self>,
6143 ) {
6144 let quick_launch = action.quick_launch;
6145 let mut context_menu = self.context_menu.borrow_mut();
6146 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6147 if code_actions.deployed_from == action.deployed_from {
6148 // Toggle if we're selecting the same one
6149 *context_menu = None;
6150 cx.notify();
6151 return;
6152 } else {
6153 // Otherwise, clear it and start a new one
6154 *context_menu = None;
6155 cx.notify();
6156 }
6157 }
6158 drop(context_menu);
6159 let snapshot = self.snapshot(window, cx);
6160 let deployed_from = action.deployed_from.clone();
6161 let action = action.clone();
6162 self.completion_tasks.clear();
6163 self.discard_edit_prediction(false, cx);
6164
6165 let multibuffer_point = match &action.deployed_from {
6166 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6167 DisplayPoint::new(*row, 0).to_point(&snapshot)
6168 }
6169 _ => self
6170 .selections
6171 .newest::<Point>(&snapshot.display_snapshot)
6172 .head(),
6173 };
6174 let Some((buffer, buffer_row)) = snapshot
6175 .buffer_snapshot()
6176 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6177 .and_then(|(buffer_snapshot, range)| {
6178 self.buffer()
6179 .read(cx)
6180 .buffer(buffer_snapshot.remote_id())
6181 .map(|buffer| (buffer, range.start.row))
6182 })
6183 else {
6184 return;
6185 };
6186 let buffer_id = buffer.read(cx).remote_id();
6187 let tasks = self
6188 .tasks
6189 .get(&(buffer_id, buffer_row))
6190 .map(|t| Arc::new(t.to_owned()));
6191
6192 if !self.focus_handle.is_focused(window) {
6193 return;
6194 }
6195 let project = self.project.clone();
6196
6197 let code_actions_task = match deployed_from {
6198 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6199 _ => self.code_actions(buffer_row, window, cx),
6200 };
6201
6202 let runnable_task = match deployed_from {
6203 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6204 _ => {
6205 let mut task_context_task = Task::ready(None);
6206 if let Some(tasks) = &tasks
6207 && let Some(project) = project
6208 {
6209 task_context_task =
6210 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6211 }
6212
6213 cx.spawn_in(window, {
6214 let buffer = buffer.clone();
6215 async move |editor, cx| {
6216 let task_context = task_context_task.await;
6217
6218 let resolved_tasks =
6219 tasks
6220 .zip(task_context.clone())
6221 .map(|(tasks, task_context)| ResolvedTasks {
6222 templates: tasks.resolve(&task_context).collect(),
6223 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6224 multibuffer_point.row,
6225 tasks.column,
6226 )),
6227 });
6228 let debug_scenarios = editor
6229 .update(cx, |editor, cx| {
6230 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6231 })?
6232 .await;
6233 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6234 }
6235 })
6236 }
6237 };
6238
6239 cx.spawn_in(window, async move |editor, cx| {
6240 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6241 let code_actions = code_actions_task.await;
6242 let spawn_straight_away = quick_launch
6243 && resolved_tasks
6244 .as_ref()
6245 .is_some_and(|tasks| tasks.templates.len() == 1)
6246 && code_actions
6247 .as_ref()
6248 .is_none_or(|actions| actions.is_empty())
6249 && debug_scenarios.is_empty();
6250
6251 editor.update_in(cx, |editor, window, cx| {
6252 crate::hover_popover::hide_hover(editor, cx);
6253 let actions = CodeActionContents::new(
6254 resolved_tasks,
6255 code_actions,
6256 debug_scenarios,
6257 task_context.unwrap_or_default(),
6258 );
6259
6260 // Don't show the menu if there are no actions available
6261 if actions.is_empty() {
6262 cx.notify();
6263 return Task::ready(Ok(()));
6264 }
6265
6266 *editor.context_menu.borrow_mut() =
6267 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6268 buffer,
6269 actions,
6270 selected_item: Default::default(),
6271 scroll_handle: UniformListScrollHandle::default(),
6272 deployed_from,
6273 }));
6274 cx.notify();
6275 if spawn_straight_away
6276 && let Some(task) = editor.confirm_code_action(
6277 &ConfirmCodeAction { item_ix: Some(0) },
6278 window,
6279 cx,
6280 )
6281 {
6282 return task;
6283 }
6284
6285 Task::ready(Ok(()))
6286 })
6287 })
6288 .detach_and_log_err(cx);
6289 }
6290
6291 fn debug_scenarios(
6292 &mut self,
6293 resolved_tasks: &Option<ResolvedTasks>,
6294 buffer: &Entity<Buffer>,
6295 cx: &mut App,
6296 ) -> Task<Vec<task::DebugScenario>> {
6297 maybe!({
6298 let project = self.project()?;
6299 let dap_store = project.read(cx).dap_store();
6300 let mut scenarios = vec![];
6301 let resolved_tasks = resolved_tasks.as_ref()?;
6302 let buffer = buffer.read(cx);
6303 let language = buffer.language()?;
6304 let file = buffer.file();
6305 let debug_adapter = language_settings(language.name().into(), file, cx)
6306 .debuggers
6307 .first()
6308 .map(SharedString::from)
6309 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6310
6311 dap_store.update(cx, |dap_store, cx| {
6312 for (_, task) in &resolved_tasks.templates {
6313 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6314 task.original_task().clone(),
6315 debug_adapter.clone().into(),
6316 task.display_label().to_owned().into(),
6317 cx,
6318 );
6319 scenarios.push(maybe_scenario);
6320 }
6321 });
6322 Some(cx.background_spawn(async move {
6323 futures::future::join_all(scenarios)
6324 .await
6325 .into_iter()
6326 .flatten()
6327 .collect::<Vec<_>>()
6328 }))
6329 })
6330 .unwrap_or_else(|| Task::ready(vec![]))
6331 }
6332
6333 fn code_actions(
6334 &mut self,
6335 buffer_row: u32,
6336 window: &mut Window,
6337 cx: &mut Context<Self>,
6338 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6339 let mut task = self.code_actions_task.take();
6340 cx.spawn_in(window, async move |editor, cx| {
6341 while let Some(prev_task) = task {
6342 prev_task.await.log_err();
6343 task = editor
6344 .update(cx, |this, _| this.code_actions_task.take())
6345 .ok()?;
6346 }
6347
6348 editor
6349 .update(cx, |editor, cx| {
6350 editor
6351 .available_code_actions
6352 .clone()
6353 .and_then(|(location, code_actions)| {
6354 let snapshot = location.buffer.read(cx).snapshot();
6355 let point_range = location.range.to_point(&snapshot);
6356 let point_range = point_range.start.row..=point_range.end.row;
6357 if point_range.contains(&buffer_row) {
6358 Some(code_actions)
6359 } else {
6360 None
6361 }
6362 })
6363 })
6364 .ok()
6365 .flatten()
6366 })
6367 }
6368
6369 pub fn confirm_code_action(
6370 &mut self,
6371 action: &ConfirmCodeAction,
6372 window: &mut Window,
6373 cx: &mut Context<Self>,
6374 ) -> Option<Task<Result<()>>> {
6375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6376
6377 let actions_menu =
6378 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6379 menu
6380 } else {
6381 return None;
6382 };
6383
6384 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6385 let action = actions_menu.actions.get(action_ix)?;
6386 let title = action.label();
6387 let buffer = actions_menu.buffer;
6388 let workspace = self.workspace()?;
6389
6390 match action {
6391 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6392 workspace.update(cx, |workspace, cx| {
6393 workspace.schedule_resolved_task(
6394 task_source_kind,
6395 resolved_task,
6396 false,
6397 window,
6398 cx,
6399 );
6400
6401 Some(Task::ready(Ok(())))
6402 })
6403 }
6404 CodeActionsItem::CodeAction {
6405 excerpt_id,
6406 action,
6407 provider,
6408 } => {
6409 let apply_code_action =
6410 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6411 let workspace = workspace.downgrade();
6412 Some(cx.spawn_in(window, async move |editor, cx| {
6413 let project_transaction = apply_code_action.await?;
6414 Self::open_project_transaction(
6415 &editor,
6416 workspace,
6417 project_transaction,
6418 title,
6419 cx,
6420 )
6421 .await
6422 }))
6423 }
6424 CodeActionsItem::DebugScenario(scenario) => {
6425 let context = actions_menu.actions.context;
6426
6427 workspace.update(cx, |workspace, cx| {
6428 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6429 workspace.start_debug_session(
6430 scenario,
6431 context,
6432 Some(buffer),
6433 None,
6434 window,
6435 cx,
6436 );
6437 });
6438 Some(Task::ready(Ok(())))
6439 }
6440 }
6441 }
6442
6443 pub async fn open_project_transaction(
6444 editor: &WeakEntity<Editor>,
6445 workspace: WeakEntity<Workspace>,
6446 transaction: ProjectTransaction,
6447 title: String,
6448 cx: &mut AsyncWindowContext,
6449 ) -> Result<()> {
6450 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6451 cx.update(|_, cx| {
6452 entries.sort_unstable_by_key(|(buffer, _)| {
6453 buffer.read(cx).file().map(|f| f.path().clone())
6454 });
6455 })?;
6456 if entries.is_empty() {
6457 return Ok(());
6458 }
6459
6460 // If the project transaction's edits are all contained within this editor, then
6461 // avoid opening a new editor to display them.
6462
6463 if let [(buffer, transaction)] = &*entries {
6464 let excerpt = editor.update(cx, |editor, cx| {
6465 editor
6466 .buffer()
6467 .read(cx)
6468 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6469 })?;
6470 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6471 && excerpted_buffer == *buffer
6472 {
6473 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6474 let excerpt_range = excerpt_range.to_offset(buffer);
6475 buffer
6476 .edited_ranges_for_transaction::<usize>(transaction)
6477 .all(|range| {
6478 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6479 })
6480 })?;
6481
6482 if all_edits_within_excerpt {
6483 return Ok(());
6484 }
6485 }
6486 }
6487
6488 let mut ranges_to_highlight = Vec::new();
6489 let excerpt_buffer = cx.new(|cx| {
6490 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6491 for (buffer_handle, transaction) in &entries {
6492 let edited_ranges = buffer_handle
6493 .read(cx)
6494 .edited_ranges_for_transaction::<Point>(transaction)
6495 .collect::<Vec<_>>();
6496 let (ranges, _) = multibuffer.set_excerpts_for_path(
6497 PathKey::for_buffer(buffer_handle, cx),
6498 buffer_handle.clone(),
6499 edited_ranges,
6500 multibuffer_context_lines(cx),
6501 cx,
6502 );
6503
6504 ranges_to_highlight.extend(ranges);
6505 }
6506 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6507 multibuffer
6508 })?;
6509
6510 workspace.update_in(cx, |workspace, window, cx| {
6511 let project = workspace.project().clone();
6512 let editor =
6513 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6514 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6515 editor.update(cx, |editor, cx| {
6516 editor.highlight_background::<Self>(
6517 &ranges_to_highlight,
6518 |theme| theme.colors().editor_highlighted_line_background,
6519 cx,
6520 );
6521 });
6522 })?;
6523
6524 Ok(())
6525 }
6526
6527 pub fn clear_code_action_providers(&mut self) {
6528 self.code_action_providers.clear();
6529 self.available_code_actions.take();
6530 }
6531
6532 pub fn add_code_action_provider(
6533 &mut self,
6534 provider: Rc<dyn CodeActionProvider>,
6535 window: &mut Window,
6536 cx: &mut Context<Self>,
6537 ) {
6538 if self
6539 .code_action_providers
6540 .iter()
6541 .any(|existing_provider| existing_provider.id() == provider.id())
6542 {
6543 return;
6544 }
6545
6546 self.code_action_providers.push(provider);
6547 self.refresh_code_actions(window, cx);
6548 }
6549
6550 pub fn remove_code_action_provider(
6551 &mut self,
6552 id: Arc<str>,
6553 window: &mut Window,
6554 cx: &mut Context<Self>,
6555 ) {
6556 self.code_action_providers
6557 .retain(|provider| provider.id() != id);
6558 self.refresh_code_actions(window, cx);
6559 }
6560
6561 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6562 !self.code_action_providers.is_empty()
6563 && EditorSettings::get_global(cx).toolbar.code_actions
6564 }
6565
6566 pub fn has_available_code_actions(&self) -> bool {
6567 self.available_code_actions
6568 .as_ref()
6569 .is_some_and(|(_, actions)| !actions.is_empty())
6570 }
6571
6572 fn render_inline_code_actions(
6573 &self,
6574 icon_size: ui::IconSize,
6575 display_row: DisplayRow,
6576 is_active: bool,
6577 cx: &mut Context<Self>,
6578 ) -> AnyElement {
6579 let show_tooltip = !self.context_menu_visible();
6580 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6581 .icon_size(icon_size)
6582 .shape(ui::IconButtonShape::Square)
6583 .icon_color(ui::Color::Hidden)
6584 .toggle_state(is_active)
6585 .when(show_tooltip, |this| {
6586 this.tooltip({
6587 let focus_handle = self.focus_handle.clone();
6588 move |_window, cx| {
6589 Tooltip::for_action_in(
6590 "Toggle Code Actions",
6591 &ToggleCodeActions {
6592 deployed_from: None,
6593 quick_launch: false,
6594 },
6595 &focus_handle,
6596 cx,
6597 )
6598 }
6599 })
6600 })
6601 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6602 window.focus(&editor.focus_handle(cx));
6603 editor.toggle_code_actions(
6604 &crate::actions::ToggleCodeActions {
6605 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6606 display_row,
6607 )),
6608 quick_launch: false,
6609 },
6610 window,
6611 cx,
6612 );
6613 }))
6614 .into_any_element()
6615 }
6616
6617 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6618 &self.context_menu
6619 }
6620
6621 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6622 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6623 cx.background_executor()
6624 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6625 .await;
6626
6627 let (start_buffer, start, _, end, newest_selection) = this
6628 .update(cx, |this, cx| {
6629 let newest_selection = this.selections.newest_anchor().clone();
6630 if newest_selection.head().diff_base_anchor.is_some() {
6631 return None;
6632 }
6633 let display_snapshot = this.display_snapshot(cx);
6634 let newest_selection_adjusted =
6635 this.selections.newest_adjusted(&display_snapshot);
6636 let buffer = this.buffer.read(cx);
6637
6638 let (start_buffer, start) =
6639 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6640 let (end_buffer, end) =
6641 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6642
6643 Some((start_buffer, start, end_buffer, end, newest_selection))
6644 })?
6645 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6646 .context(
6647 "Expected selection to lie in a single buffer when refreshing code actions",
6648 )?;
6649 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6650 let providers = this.code_action_providers.clone();
6651 let tasks = this
6652 .code_action_providers
6653 .iter()
6654 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6655 .collect::<Vec<_>>();
6656 (providers, tasks)
6657 })?;
6658
6659 let mut actions = Vec::new();
6660 for (provider, provider_actions) in
6661 providers.into_iter().zip(future::join_all(tasks).await)
6662 {
6663 if let Some(provider_actions) = provider_actions.log_err() {
6664 actions.extend(provider_actions.into_iter().map(|action| {
6665 AvailableCodeAction {
6666 excerpt_id: newest_selection.start.excerpt_id,
6667 action,
6668 provider: provider.clone(),
6669 }
6670 }));
6671 }
6672 }
6673
6674 this.update(cx, |this, cx| {
6675 this.available_code_actions = if actions.is_empty() {
6676 None
6677 } else {
6678 Some((
6679 Location {
6680 buffer: start_buffer,
6681 range: start..end,
6682 },
6683 actions.into(),
6684 ))
6685 };
6686 cx.notify();
6687 })
6688 }));
6689 }
6690
6691 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6692 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6693 self.show_git_blame_inline = false;
6694
6695 self.show_git_blame_inline_delay_task =
6696 Some(cx.spawn_in(window, async move |this, cx| {
6697 cx.background_executor().timer(delay).await;
6698
6699 this.update(cx, |this, cx| {
6700 this.show_git_blame_inline = true;
6701 cx.notify();
6702 })
6703 .log_err();
6704 }));
6705 }
6706 }
6707
6708 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6709 let snapshot = self.snapshot(window, cx);
6710 let cursor = self
6711 .selections
6712 .newest::<Point>(&snapshot.display_snapshot)
6713 .head();
6714 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6715 else {
6716 return;
6717 };
6718
6719 let Some(blame) = self.blame.as_ref() else {
6720 return;
6721 };
6722
6723 let row_info = RowInfo {
6724 buffer_id: Some(buffer.remote_id()),
6725 buffer_row: Some(point.row),
6726 ..Default::default()
6727 };
6728 let Some((buffer, blame_entry)) = blame
6729 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6730 .flatten()
6731 else {
6732 return;
6733 };
6734
6735 let anchor = self.selections.newest_anchor().head();
6736 let position = self.to_pixel_point(anchor, &snapshot, window);
6737 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6738 self.show_blame_popover(
6739 buffer,
6740 &blame_entry,
6741 position + last_bounds.origin,
6742 true,
6743 cx,
6744 );
6745 };
6746 }
6747
6748 fn show_blame_popover(
6749 &mut self,
6750 buffer: BufferId,
6751 blame_entry: &BlameEntry,
6752 position: gpui::Point<Pixels>,
6753 ignore_timeout: bool,
6754 cx: &mut Context<Self>,
6755 ) {
6756 if let Some(state) = &mut self.inline_blame_popover {
6757 state.hide_task.take();
6758 } else {
6759 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6760 let blame_entry = blame_entry.clone();
6761 let show_task = cx.spawn(async move |editor, cx| {
6762 if !ignore_timeout {
6763 cx.background_executor()
6764 .timer(std::time::Duration::from_millis(blame_popover_delay))
6765 .await;
6766 }
6767 editor
6768 .update(cx, |editor, cx| {
6769 editor.inline_blame_popover_show_task.take();
6770 let Some(blame) = editor.blame.as_ref() else {
6771 return;
6772 };
6773 let blame = blame.read(cx);
6774 let details = blame.details_for_entry(buffer, &blame_entry);
6775 let markdown = cx.new(|cx| {
6776 Markdown::new(
6777 details
6778 .as_ref()
6779 .map(|message| message.message.clone())
6780 .unwrap_or_default(),
6781 None,
6782 None,
6783 cx,
6784 )
6785 });
6786 editor.inline_blame_popover = Some(InlineBlamePopover {
6787 position,
6788 hide_task: None,
6789 popover_bounds: None,
6790 popover_state: InlineBlamePopoverState {
6791 scroll_handle: ScrollHandle::new(),
6792 commit_message: details,
6793 markdown,
6794 },
6795 keyboard_grace: ignore_timeout,
6796 });
6797 cx.notify();
6798 })
6799 .ok();
6800 });
6801 self.inline_blame_popover_show_task = Some(show_task);
6802 }
6803 }
6804
6805 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6806 self.inline_blame_popover_show_task.take();
6807 if let Some(state) = &mut self.inline_blame_popover {
6808 let hide_task = cx.spawn(async move |editor, cx| {
6809 if !ignore_timeout {
6810 cx.background_executor()
6811 .timer(std::time::Duration::from_millis(100))
6812 .await;
6813 }
6814 editor
6815 .update(cx, |editor, cx| {
6816 editor.inline_blame_popover.take();
6817 cx.notify();
6818 })
6819 .ok();
6820 });
6821 state.hide_task = Some(hide_task);
6822 true
6823 } else {
6824 false
6825 }
6826 }
6827
6828 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6829 if self.pending_rename.is_some() {
6830 return None;
6831 }
6832
6833 let provider = self.semantics_provider.clone()?;
6834 let buffer = self.buffer.read(cx);
6835 let newest_selection = self.selections.newest_anchor().clone();
6836 let cursor_position = newest_selection.head();
6837 let (cursor_buffer, cursor_buffer_position) =
6838 buffer.text_anchor_for_position(cursor_position, cx)?;
6839 let (tail_buffer, tail_buffer_position) =
6840 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6841 if cursor_buffer != tail_buffer {
6842 return None;
6843 }
6844
6845 let snapshot = cursor_buffer.read(cx).snapshot();
6846 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6847 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6848 if start_word_range != end_word_range {
6849 self.document_highlights_task.take();
6850 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6851 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6852 return None;
6853 }
6854
6855 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6856 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6857 cx.background_executor()
6858 .timer(Duration::from_millis(debounce))
6859 .await;
6860
6861 let highlights = if let Some(highlights) = cx
6862 .update(|cx| {
6863 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6864 })
6865 .ok()
6866 .flatten()
6867 {
6868 highlights.await.log_err()
6869 } else {
6870 None
6871 };
6872
6873 if let Some(highlights) = highlights {
6874 this.update(cx, |this, cx| {
6875 if this.pending_rename.is_some() {
6876 return;
6877 }
6878
6879 let buffer = this.buffer.read(cx);
6880 if buffer
6881 .text_anchor_for_position(cursor_position, cx)
6882 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6883 {
6884 return;
6885 }
6886
6887 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6888 let mut write_ranges = Vec::new();
6889 let mut read_ranges = Vec::new();
6890 for highlight in highlights {
6891 let buffer_id = cursor_buffer.read(cx).remote_id();
6892 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6893 {
6894 let start = highlight
6895 .range
6896 .start
6897 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6898 let end = highlight
6899 .range
6900 .end
6901 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6902 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6903 continue;
6904 }
6905
6906 let range =
6907 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6908 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6909 write_ranges.push(range);
6910 } else {
6911 read_ranges.push(range);
6912 }
6913 }
6914 }
6915
6916 this.highlight_background::<DocumentHighlightRead>(
6917 &read_ranges,
6918 |theme| theme.colors().editor_document_highlight_read_background,
6919 cx,
6920 );
6921 this.highlight_background::<DocumentHighlightWrite>(
6922 &write_ranges,
6923 |theme| theme.colors().editor_document_highlight_write_background,
6924 cx,
6925 );
6926 cx.notify();
6927 })
6928 .log_err();
6929 }
6930 }));
6931 None
6932 }
6933
6934 fn prepare_highlight_query_from_selection(
6935 &mut self,
6936 window: &Window,
6937 cx: &mut Context<Editor>,
6938 ) -> Option<(String, Range<Anchor>)> {
6939 if matches!(self.mode, EditorMode::SingleLine) {
6940 return None;
6941 }
6942 if !EditorSettings::get_global(cx).selection_highlight {
6943 return None;
6944 }
6945 if self.selections.count() != 1 || self.selections.line_mode() {
6946 return None;
6947 }
6948 let snapshot = self.snapshot(window, cx);
6949 let selection = self.selections.newest::<Point>(&snapshot);
6950 // If the selection spans multiple rows OR it is empty
6951 if selection.start.row != selection.end.row
6952 || selection.start.column == selection.end.column
6953 {
6954 return None;
6955 }
6956 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6957 let query = snapshot
6958 .buffer_snapshot()
6959 .text_for_range(selection_anchor_range.clone())
6960 .collect::<String>();
6961 if query.trim().is_empty() {
6962 return None;
6963 }
6964 Some((query, selection_anchor_range))
6965 }
6966
6967 fn update_selection_occurrence_highlights(
6968 &mut self,
6969 query_text: String,
6970 query_range: Range<Anchor>,
6971 multi_buffer_range_to_query: Range<Point>,
6972 use_debounce: bool,
6973 window: &mut Window,
6974 cx: &mut Context<Editor>,
6975 ) -> Task<()> {
6976 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6977 cx.spawn_in(window, async move |editor, cx| {
6978 if use_debounce {
6979 cx.background_executor()
6980 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6981 .await;
6982 }
6983 let match_task = cx.background_spawn(async move {
6984 let buffer_ranges = multi_buffer_snapshot
6985 .range_to_buffer_ranges(multi_buffer_range_to_query)
6986 .into_iter()
6987 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6988 let mut match_ranges = Vec::new();
6989 let Ok(regex) = project::search::SearchQuery::text(
6990 query_text.clone(),
6991 false,
6992 false,
6993 false,
6994 Default::default(),
6995 Default::default(),
6996 false,
6997 None,
6998 ) else {
6999 return Vec::default();
7000 };
7001 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7002 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7003 match_ranges.extend(
7004 regex
7005 .search(buffer_snapshot, Some(search_range.clone()))
7006 .await
7007 .into_iter()
7008 .filter_map(|match_range| {
7009 let match_start = buffer_snapshot
7010 .anchor_after(search_range.start + match_range.start);
7011 let match_end = buffer_snapshot
7012 .anchor_before(search_range.start + match_range.end);
7013 let match_anchor_range = Anchor::range_in_buffer(
7014 excerpt_id,
7015 buffer_snapshot.remote_id(),
7016 match_start..match_end,
7017 );
7018 (match_anchor_range != query_range).then_some(match_anchor_range)
7019 }),
7020 );
7021 }
7022 match_ranges
7023 });
7024 let match_ranges = match_task.await;
7025 editor
7026 .update_in(cx, |editor, _, cx| {
7027 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7028 if !match_ranges.is_empty() {
7029 editor.highlight_background::<SelectedTextHighlight>(
7030 &match_ranges,
7031 |theme| theme.colors().editor_document_highlight_bracket_background,
7032 cx,
7033 )
7034 }
7035 })
7036 .log_err();
7037 })
7038 }
7039
7040 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7041 struct NewlineFold;
7042 let type_id = std::any::TypeId::of::<NewlineFold>();
7043 if !self.mode.is_single_line() {
7044 return;
7045 }
7046 let snapshot = self.snapshot(window, cx);
7047 if snapshot.buffer_snapshot().max_point().row == 0 {
7048 return;
7049 }
7050 let task = cx.background_spawn(async move {
7051 let new_newlines = snapshot
7052 .buffer_chars_at(0)
7053 .filter_map(|(c, i)| {
7054 if c == '\n' {
7055 Some(
7056 snapshot.buffer_snapshot().anchor_after(i)
7057 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7058 )
7059 } else {
7060 None
7061 }
7062 })
7063 .collect::<Vec<_>>();
7064 let existing_newlines = snapshot
7065 .folds_in_range(0..snapshot.buffer_snapshot().len())
7066 .filter_map(|fold| {
7067 if fold.placeholder.type_tag == Some(type_id) {
7068 Some(fold.range.start..fold.range.end)
7069 } else {
7070 None
7071 }
7072 })
7073 .collect::<Vec<_>>();
7074
7075 (new_newlines, existing_newlines)
7076 });
7077 self.folding_newlines = cx.spawn(async move |this, cx| {
7078 let (new_newlines, existing_newlines) = task.await;
7079 if new_newlines == existing_newlines {
7080 return;
7081 }
7082 let placeholder = FoldPlaceholder {
7083 render: Arc::new(move |_, _, cx| {
7084 div()
7085 .bg(cx.theme().status().hint_background)
7086 .border_b_1()
7087 .size_full()
7088 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7089 .border_color(cx.theme().status().hint)
7090 .child("\\n")
7091 .into_any()
7092 }),
7093 constrain_width: false,
7094 merge_adjacent: false,
7095 type_tag: Some(type_id),
7096 };
7097 let creases = new_newlines
7098 .into_iter()
7099 .map(|range| Crease::simple(range, placeholder.clone()))
7100 .collect();
7101 this.update(cx, |this, cx| {
7102 this.display_map.update(cx, |display_map, cx| {
7103 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7104 display_map.fold(creases, cx);
7105 });
7106 })
7107 .ok();
7108 });
7109 }
7110
7111 fn refresh_selected_text_highlights(
7112 &mut self,
7113 on_buffer_edit: bool,
7114 window: &mut Window,
7115 cx: &mut Context<Editor>,
7116 ) {
7117 let Some((query_text, query_range)) =
7118 self.prepare_highlight_query_from_selection(window, cx)
7119 else {
7120 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7121 self.quick_selection_highlight_task.take();
7122 self.debounced_selection_highlight_task.take();
7123 return;
7124 };
7125 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7126 if on_buffer_edit
7127 || self
7128 .quick_selection_highlight_task
7129 .as_ref()
7130 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7131 {
7132 let multi_buffer_visible_start = self
7133 .scroll_manager
7134 .anchor()
7135 .anchor
7136 .to_point(&multi_buffer_snapshot);
7137 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7138 multi_buffer_visible_start
7139 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7140 Bias::Left,
7141 );
7142 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7143 self.quick_selection_highlight_task = Some((
7144 query_range.clone(),
7145 self.update_selection_occurrence_highlights(
7146 query_text.clone(),
7147 query_range.clone(),
7148 multi_buffer_visible_range,
7149 false,
7150 window,
7151 cx,
7152 ),
7153 ));
7154 }
7155 if on_buffer_edit
7156 || self
7157 .debounced_selection_highlight_task
7158 .as_ref()
7159 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7160 {
7161 let multi_buffer_start = multi_buffer_snapshot
7162 .anchor_before(0)
7163 .to_point(&multi_buffer_snapshot);
7164 let multi_buffer_end = multi_buffer_snapshot
7165 .anchor_after(multi_buffer_snapshot.len())
7166 .to_point(&multi_buffer_snapshot);
7167 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7168 self.debounced_selection_highlight_task = Some((
7169 query_range.clone(),
7170 self.update_selection_occurrence_highlights(
7171 query_text,
7172 query_range,
7173 multi_buffer_full_range,
7174 true,
7175 window,
7176 cx,
7177 ),
7178 ));
7179 }
7180 }
7181
7182 pub fn refresh_edit_prediction(
7183 &mut self,
7184 debounce: bool,
7185 user_requested: bool,
7186 window: &mut Window,
7187 cx: &mut Context<Self>,
7188 ) -> Option<()> {
7189 if DisableAiSettings::get_global(cx).disable_ai {
7190 return None;
7191 }
7192
7193 let provider = self.edit_prediction_provider()?;
7194 let cursor = self.selections.newest_anchor().head();
7195 let (buffer, cursor_buffer_position) =
7196 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7197
7198 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7199 self.discard_edit_prediction(false, cx);
7200 return None;
7201 }
7202
7203 self.update_visible_edit_prediction(window, cx);
7204
7205 if !user_requested
7206 && (!self.should_show_edit_predictions()
7207 || !self.is_focused(window)
7208 || buffer.read(cx).is_empty())
7209 {
7210 self.discard_edit_prediction(false, cx);
7211 return None;
7212 }
7213
7214 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7215 Some(())
7216 }
7217
7218 fn show_edit_predictions_in_menu(&self) -> bool {
7219 match self.edit_prediction_settings {
7220 EditPredictionSettings::Disabled => false,
7221 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7222 }
7223 }
7224
7225 pub fn edit_predictions_enabled(&self) -> bool {
7226 match self.edit_prediction_settings {
7227 EditPredictionSettings::Disabled => false,
7228 EditPredictionSettings::Enabled { .. } => true,
7229 }
7230 }
7231
7232 fn edit_prediction_requires_modifier(&self) -> bool {
7233 match self.edit_prediction_settings {
7234 EditPredictionSettings::Disabled => false,
7235 EditPredictionSettings::Enabled {
7236 preview_requires_modifier,
7237 ..
7238 } => preview_requires_modifier,
7239 }
7240 }
7241
7242 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7243 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7244 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7245 self.discard_edit_prediction(false, cx);
7246 } else {
7247 let selection = self.selections.newest_anchor();
7248 let cursor = selection.head();
7249
7250 if let Some((buffer, cursor_buffer_position)) =
7251 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7252 {
7253 self.edit_prediction_settings =
7254 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7255 }
7256 }
7257 }
7258
7259 fn edit_prediction_settings_at_position(
7260 &self,
7261 buffer: &Entity<Buffer>,
7262 buffer_position: language::Anchor,
7263 cx: &App,
7264 ) -> EditPredictionSettings {
7265 if !self.mode.is_full()
7266 || !self.show_edit_predictions_override.unwrap_or(true)
7267 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7268 {
7269 return EditPredictionSettings::Disabled;
7270 }
7271
7272 let buffer = buffer.read(cx);
7273
7274 let file = buffer.file();
7275
7276 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7277 return EditPredictionSettings::Disabled;
7278 };
7279
7280 let by_provider = matches!(
7281 self.menu_edit_predictions_policy,
7282 MenuEditPredictionsPolicy::ByProvider
7283 );
7284
7285 let show_in_menu = by_provider
7286 && self
7287 .edit_prediction_provider
7288 .as_ref()
7289 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7290
7291 let preview_requires_modifier =
7292 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7293
7294 EditPredictionSettings::Enabled {
7295 show_in_menu,
7296 preview_requires_modifier,
7297 }
7298 }
7299
7300 fn should_show_edit_predictions(&self) -> bool {
7301 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7302 }
7303
7304 pub fn edit_prediction_preview_is_active(&self) -> bool {
7305 matches!(
7306 self.edit_prediction_preview,
7307 EditPredictionPreview::Active { .. }
7308 )
7309 }
7310
7311 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7312 let cursor = self.selections.newest_anchor().head();
7313 if let Some((buffer, cursor_position)) =
7314 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7315 {
7316 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7317 } else {
7318 false
7319 }
7320 }
7321
7322 pub fn supports_minimap(&self, cx: &App) -> bool {
7323 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7324 }
7325
7326 fn edit_predictions_enabled_in_buffer(
7327 &self,
7328 buffer: &Entity<Buffer>,
7329 buffer_position: language::Anchor,
7330 cx: &App,
7331 ) -> bool {
7332 maybe!({
7333 if self.read_only(cx) {
7334 return Some(false);
7335 }
7336 let provider = self.edit_prediction_provider()?;
7337 if !provider.is_enabled(buffer, buffer_position, cx) {
7338 return Some(false);
7339 }
7340 let buffer = buffer.read(cx);
7341 let Some(file) = buffer.file() else {
7342 return Some(true);
7343 };
7344 let settings = all_language_settings(Some(file), cx);
7345 Some(settings.edit_predictions_enabled_for_file(file, cx))
7346 })
7347 .unwrap_or(false)
7348 }
7349
7350 fn cycle_edit_prediction(
7351 &mut self,
7352 direction: Direction,
7353 window: &mut Window,
7354 cx: &mut Context<Self>,
7355 ) -> Option<()> {
7356 let provider = self.edit_prediction_provider()?;
7357 let cursor = self.selections.newest_anchor().head();
7358 let (buffer, cursor_buffer_position) =
7359 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7360 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7361 return None;
7362 }
7363
7364 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7365 self.update_visible_edit_prediction(window, cx);
7366
7367 Some(())
7368 }
7369
7370 pub fn show_edit_prediction(
7371 &mut self,
7372 _: &ShowEditPrediction,
7373 window: &mut Window,
7374 cx: &mut Context<Self>,
7375 ) {
7376 if !self.has_active_edit_prediction() {
7377 self.refresh_edit_prediction(false, true, window, cx);
7378 return;
7379 }
7380
7381 self.update_visible_edit_prediction(window, cx);
7382 }
7383
7384 pub fn display_cursor_names(
7385 &mut self,
7386 _: &DisplayCursorNames,
7387 window: &mut Window,
7388 cx: &mut Context<Self>,
7389 ) {
7390 self.show_cursor_names(window, cx);
7391 }
7392
7393 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7394 self.show_cursor_names = true;
7395 cx.notify();
7396 cx.spawn_in(window, async move |this, cx| {
7397 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7398 this.update(cx, |this, cx| {
7399 this.show_cursor_names = false;
7400 cx.notify()
7401 })
7402 .ok()
7403 })
7404 .detach();
7405 }
7406
7407 pub fn next_edit_prediction(
7408 &mut self,
7409 _: &NextEditPrediction,
7410 window: &mut Window,
7411 cx: &mut Context<Self>,
7412 ) {
7413 if self.has_active_edit_prediction() {
7414 self.cycle_edit_prediction(Direction::Next, window, cx);
7415 } else {
7416 let is_copilot_disabled = self
7417 .refresh_edit_prediction(false, true, window, cx)
7418 .is_none();
7419 if is_copilot_disabled {
7420 cx.propagate();
7421 }
7422 }
7423 }
7424
7425 pub fn previous_edit_prediction(
7426 &mut self,
7427 _: &PreviousEditPrediction,
7428 window: &mut Window,
7429 cx: &mut Context<Self>,
7430 ) {
7431 if self.has_active_edit_prediction() {
7432 self.cycle_edit_prediction(Direction::Prev, window, cx);
7433 } else {
7434 let is_copilot_disabled = self
7435 .refresh_edit_prediction(false, true, window, cx)
7436 .is_none();
7437 if is_copilot_disabled {
7438 cx.propagate();
7439 }
7440 }
7441 }
7442
7443 pub fn accept_edit_prediction(
7444 &mut self,
7445 _: &AcceptEditPrediction,
7446 window: &mut Window,
7447 cx: &mut Context<Self>,
7448 ) {
7449 if self.show_edit_predictions_in_menu() {
7450 self.hide_context_menu(window, cx);
7451 }
7452
7453 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7454 return;
7455 };
7456
7457 match &active_edit_prediction.completion {
7458 EditPrediction::MoveWithin { target, .. } => {
7459 let target = *target;
7460
7461 if let Some(position_map) = &self.last_position_map {
7462 if position_map
7463 .visible_row_range
7464 .contains(&target.to_display_point(&position_map.snapshot).row())
7465 || !self.edit_prediction_requires_modifier()
7466 {
7467 self.unfold_ranges(&[target..target], true, false, cx);
7468 // Note that this is also done in vim's handler of the Tab action.
7469 self.change_selections(
7470 SelectionEffects::scroll(Autoscroll::newest()),
7471 window,
7472 cx,
7473 |selections| {
7474 selections.select_anchor_ranges([target..target]);
7475 },
7476 );
7477 self.clear_row_highlights::<EditPredictionPreview>();
7478
7479 self.edit_prediction_preview
7480 .set_previous_scroll_position(None);
7481 } else {
7482 self.edit_prediction_preview
7483 .set_previous_scroll_position(Some(
7484 position_map.snapshot.scroll_anchor,
7485 ));
7486
7487 self.highlight_rows::<EditPredictionPreview>(
7488 target..target,
7489 cx.theme().colors().editor_highlighted_line_background,
7490 RowHighlightOptions {
7491 autoscroll: true,
7492 ..Default::default()
7493 },
7494 cx,
7495 );
7496 self.request_autoscroll(Autoscroll::fit(), cx);
7497 }
7498 }
7499 }
7500 EditPrediction::MoveOutside { snapshot, target } => {
7501 if let Some(workspace) = self.workspace() {
7502 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7503 .detach_and_log_err(cx);
7504 }
7505 }
7506 EditPrediction::Edit { edits, .. } => {
7507 self.report_edit_prediction_event(
7508 active_edit_prediction.completion_id.clone(),
7509 true,
7510 cx,
7511 );
7512
7513 if let Some(provider) = self.edit_prediction_provider() {
7514 provider.accept(cx);
7515 }
7516
7517 // Store the transaction ID and selections before applying the edit
7518 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7519
7520 let snapshot = self.buffer.read(cx).snapshot(cx);
7521 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7522
7523 self.buffer.update(cx, |buffer, cx| {
7524 buffer.edit(edits.iter().cloned(), None, cx)
7525 });
7526
7527 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7528 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7529 });
7530
7531 let selections = self.selections.disjoint_anchors_arc();
7532 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7533 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7534 if has_new_transaction {
7535 self.selection_history
7536 .insert_transaction(transaction_id_now, selections);
7537 }
7538 }
7539
7540 self.update_visible_edit_prediction(window, cx);
7541 if self.active_edit_prediction.is_none() {
7542 self.refresh_edit_prediction(true, true, window, cx);
7543 }
7544
7545 cx.notify();
7546 }
7547 }
7548
7549 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7550 }
7551
7552 pub fn accept_partial_edit_prediction(
7553 &mut self,
7554 _: &AcceptPartialEditPrediction,
7555 window: &mut Window,
7556 cx: &mut Context<Self>,
7557 ) {
7558 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7559 return;
7560 };
7561 if self.selections.count() != 1 {
7562 return;
7563 }
7564
7565 match &active_edit_prediction.completion {
7566 EditPrediction::MoveWithin { target, .. } => {
7567 let target = *target;
7568 self.change_selections(
7569 SelectionEffects::scroll(Autoscroll::newest()),
7570 window,
7571 cx,
7572 |selections| {
7573 selections.select_anchor_ranges([target..target]);
7574 },
7575 );
7576 }
7577 EditPrediction::MoveOutside { snapshot, target } => {
7578 if let Some(workspace) = self.workspace() {
7579 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7580 .detach_and_log_err(cx);
7581 }
7582 }
7583 EditPrediction::Edit { edits, .. } => {
7584 self.report_edit_prediction_event(
7585 active_edit_prediction.completion_id.clone(),
7586 true,
7587 cx,
7588 );
7589
7590 // Find an insertion that starts at the cursor position.
7591 let snapshot = self.buffer.read(cx).snapshot(cx);
7592 let cursor_offset = self
7593 .selections
7594 .newest::<usize>(&self.display_snapshot(cx))
7595 .head();
7596 let insertion = edits.iter().find_map(|(range, text)| {
7597 let range = range.to_offset(&snapshot);
7598 if range.is_empty() && range.start == cursor_offset {
7599 Some(text)
7600 } else {
7601 None
7602 }
7603 });
7604
7605 if let Some(text) = insertion {
7606 let mut partial_completion = text
7607 .chars()
7608 .by_ref()
7609 .take_while(|c| c.is_alphabetic())
7610 .collect::<String>();
7611 if partial_completion.is_empty() {
7612 partial_completion = text
7613 .chars()
7614 .by_ref()
7615 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7616 .collect::<String>();
7617 }
7618
7619 cx.emit(EditorEvent::InputHandled {
7620 utf16_range_to_replace: None,
7621 text: partial_completion.clone().into(),
7622 });
7623
7624 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7625
7626 self.refresh_edit_prediction(true, true, window, cx);
7627 cx.notify();
7628 } else {
7629 self.accept_edit_prediction(&Default::default(), window, cx);
7630 }
7631 }
7632 }
7633 }
7634
7635 fn discard_edit_prediction(
7636 &mut self,
7637 should_report_edit_prediction_event: bool,
7638 cx: &mut Context<Self>,
7639 ) -> bool {
7640 if should_report_edit_prediction_event {
7641 let completion_id = self
7642 .active_edit_prediction
7643 .as_ref()
7644 .and_then(|active_completion| active_completion.completion_id.clone());
7645
7646 self.report_edit_prediction_event(completion_id, false, cx);
7647 }
7648
7649 if let Some(provider) = self.edit_prediction_provider() {
7650 provider.discard(cx);
7651 }
7652
7653 self.take_active_edit_prediction(cx)
7654 }
7655
7656 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7657 let Some(provider) = self.edit_prediction_provider() else {
7658 return;
7659 };
7660
7661 let Some((_, buffer, _)) = self
7662 .buffer
7663 .read(cx)
7664 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7665 else {
7666 return;
7667 };
7668
7669 let extension = buffer
7670 .read(cx)
7671 .file()
7672 .and_then(|file| Some(file.path().extension()?.to_string()));
7673
7674 let event_type = match accepted {
7675 true => "Edit Prediction Accepted",
7676 false => "Edit Prediction Discarded",
7677 };
7678 telemetry::event!(
7679 event_type,
7680 provider = provider.name(),
7681 prediction_id = id,
7682 suggestion_accepted = accepted,
7683 file_extension = extension,
7684 );
7685 }
7686
7687 fn open_editor_at_anchor(
7688 snapshot: &language::BufferSnapshot,
7689 target: language::Anchor,
7690 workspace: &Entity<Workspace>,
7691 window: &mut Window,
7692 cx: &mut App,
7693 ) -> Task<Result<()>> {
7694 workspace.update(cx, |workspace, cx| {
7695 let path = snapshot.file().map(|file| file.full_path(cx));
7696 let Some(path) =
7697 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7698 else {
7699 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7700 };
7701 let target = text::ToPoint::to_point(&target, snapshot);
7702 let item = workspace.open_path(path, None, true, window, cx);
7703 window.spawn(cx, async move |cx| {
7704 let Some(editor) = item.await?.downcast::<Editor>() else {
7705 return Ok(());
7706 };
7707 editor
7708 .update_in(cx, |editor, window, cx| {
7709 editor.go_to_singleton_buffer_point(target, window, cx);
7710 })
7711 .ok();
7712 anyhow::Ok(())
7713 })
7714 })
7715 }
7716
7717 pub fn has_active_edit_prediction(&self) -> bool {
7718 self.active_edit_prediction.is_some()
7719 }
7720
7721 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7722 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7723 return false;
7724 };
7725
7726 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7727 self.clear_highlights::<EditPredictionHighlight>(cx);
7728 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7729 true
7730 }
7731
7732 /// Returns true when we're displaying the edit prediction popover below the cursor
7733 /// like we are not previewing and the LSP autocomplete menu is visible
7734 /// or we are in `when_holding_modifier` mode.
7735 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7736 if self.edit_prediction_preview_is_active()
7737 || !self.show_edit_predictions_in_menu()
7738 || !self.edit_predictions_enabled()
7739 {
7740 return false;
7741 }
7742
7743 if self.has_visible_completions_menu() {
7744 return true;
7745 }
7746
7747 has_completion && self.edit_prediction_requires_modifier()
7748 }
7749
7750 fn handle_modifiers_changed(
7751 &mut self,
7752 modifiers: Modifiers,
7753 position_map: &PositionMap,
7754 window: &mut Window,
7755 cx: &mut Context<Self>,
7756 ) {
7757 // Ensure that the edit prediction preview is updated, even when not
7758 // enabled, if there's an active edit prediction preview.
7759 if self.show_edit_predictions_in_menu()
7760 || matches!(
7761 self.edit_prediction_preview,
7762 EditPredictionPreview::Active { .. }
7763 )
7764 {
7765 self.update_edit_prediction_preview(&modifiers, window, cx);
7766 }
7767
7768 self.update_selection_mode(&modifiers, position_map, window, cx);
7769
7770 let mouse_position = window.mouse_position();
7771 if !position_map.text_hitbox.is_hovered(window) {
7772 return;
7773 }
7774
7775 self.update_hovered_link(
7776 position_map.point_for_position(mouse_position),
7777 &position_map.snapshot,
7778 modifiers,
7779 window,
7780 cx,
7781 )
7782 }
7783
7784 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7785 match EditorSettings::get_global(cx).multi_cursor_modifier {
7786 MultiCursorModifier::Alt => modifiers.secondary(),
7787 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7788 }
7789 }
7790
7791 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7792 match EditorSettings::get_global(cx).multi_cursor_modifier {
7793 MultiCursorModifier::Alt => modifiers.alt,
7794 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7795 }
7796 }
7797
7798 fn columnar_selection_mode(
7799 modifiers: &Modifiers,
7800 cx: &mut Context<Self>,
7801 ) -> Option<ColumnarMode> {
7802 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7803 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7804 Some(ColumnarMode::FromMouse)
7805 } else if Self::is_alt_pressed(modifiers, cx) {
7806 Some(ColumnarMode::FromSelection)
7807 } else {
7808 None
7809 }
7810 } else {
7811 None
7812 }
7813 }
7814
7815 fn update_selection_mode(
7816 &mut self,
7817 modifiers: &Modifiers,
7818 position_map: &PositionMap,
7819 window: &mut Window,
7820 cx: &mut Context<Self>,
7821 ) {
7822 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7823 return;
7824 };
7825 if self.selections.pending_anchor().is_none() {
7826 return;
7827 }
7828
7829 let mouse_position = window.mouse_position();
7830 let point_for_position = position_map.point_for_position(mouse_position);
7831 let position = point_for_position.previous_valid;
7832
7833 self.select(
7834 SelectPhase::BeginColumnar {
7835 position,
7836 reset: false,
7837 mode,
7838 goal_column: point_for_position.exact_unclipped.column(),
7839 },
7840 window,
7841 cx,
7842 );
7843 }
7844
7845 fn update_edit_prediction_preview(
7846 &mut self,
7847 modifiers: &Modifiers,
7848 window: &mut Window,
7849 cx: &mut Context<Self>,
7850 ) {
7851 let mut modifiers_held = false;
7852 if let Some(accept_keystroke) = self
7853 .accept_edit_prediction_keybind(false, window, cx)
7854 .keystroke()
7855 {
7856 modifiers_held = modifiers_held
7857 || (accept_keystroke.modifiers() == modifiers
7858 && accept_keystroke.modifiers().modified());
7859 };
7860 if let Some(accept_partial_keystroke) = self
7861 .accept_edit_prediction_keybind(true, window, cx)
7862 .keystroke()
7863 {
7864 modifiers_held = modifiers_held
7865 || (accept_partial_keystroke.modifiers() == modifiers
7866 && accept_partial_keystroke.modifiers().modified());
7867 }
7868
7869 if modifiers_held {
7870 if matches!(
7871 self.edit_prediction_preview,
7872 EditPredictionPreview::Inactive { .. }
7873 ) {
7874 self.edit_prediction_preview = EditPredictionPreview::Active {
7875 previous_scroll_position: None,
7876 since: Instant::now(),
7877 };
7878
7879 self.update_visible_edit_prediction(window, cx);
7880 cx.notify();
7881 }
7882 } else if let EditPredictionPreview::Active {
7883 previous_scroll_position,
7884 since,
7885 } = self.edit_prediction_preview
7886 {
7887 if let (Some(previous_scroll_position), Some(position_map)) =
7888 (previous_scroll_position, self.last_position_map.as_ref())
7889 {
7890 self.set_scroll_position(
7891 previous_scroll_position
7892 .scroll_position(&position_map.snapshot.display_snapshot),
7893 window,
7894 cx,
7895 );
7896 }
7897
7898 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7899 released_too_fast: since.elapsed() < Duration::from_millis(200),
7900 };
7901 self.clear_row_highlights::<EditPredictionPreview>();
7902 self.update_visible_edit_prediction(window, cx);
7903 cx.notify();
7904 }
7905 }
7906
7907 fn update_visible_edit_prediction(
7908 &mut self,
7909 _window: &mut Window,
7910 cx: &mut Context<Self>,
7911 ) -> Option<()> {
7912 if DisableAiSettings::get_global(cx).disable_ai {
7913 return None;
7914 }
7915
7916 if self.ime_transaction.is_some() {
7917 self.discard_edit_prediction(false, cx);
7918 return None;
7919 }
7920
7921 let selection = self.selections.newest_anchor();
7922 let cursor = selection.head();
7923 let multibuffer = self.buffer.read(cx).snapshot(cx);
7924 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7925 let excerpt_id = cursor.excerpt_id;
7926
7927 let show_in_menu = self.show_edit_predictions_in_menu();
7928 let completions_menu_has_precedence = !show_in_menu
7929 && (self.context_menu.borrow().is_some()
7930 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7931
7932 if completions_menu_has_precedence
7933 || !offset_selection.is_empty()
7934 || self
7935 .active_edit_prediction
7936 .as_ref()
7937 .is_some_and(|completion| {
7938 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7939 return false;
7940 };
7941 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7942 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7943 !invalidation_range.contains(&offset_selection.head())
7944 })
7945 {
7946 self.discard_edit_prediction(false, cx);
7947 return None;
7948 }
7949
7950 self.take_active_edit_prediction(cx);
7951 let Some(provider) = self.edit_prediction_provider() else {
7952 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7953 return None;
7954 };
7955
7956 let (buffer, cursor_buffer_position) =
7957 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7958
7959 self.edit_prediction_settings =
7960 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7961
7962 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7963
7964 if self.edit_prediction_indent_conflict {
7965 let cursor_point = cursor.to_point(&multibuffer);
7966
7967 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7968
7969 if let Some((_, indent)) = indents.iter().next()
7970 && indent.len == cursor_point.column
7971 {
7972 self.edit_prediction_indent_conflict = false;
7973 }
7974 }
7975
7976 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7977
7978 let (completion_id, edits, edit_preview) = match edit_prediction {
7979 edit_prediction::EditPrediction::Local {
7980 id,
7981 edits,
7982 edit_preview,
7983 } => (id, edits, edit_preview),
7984 edit_prediction::EditPrediction::Jump {
7985 id,
7986 snapshot,
7987 target,
7988 } => {
7989 self.stale_edit_prediction_in_menu = None;
7990 self.active_edit_prediction = Some(EditPredictionState {
7991 inlay_ids: vec![],
7992 completion: EditPrediction::MoveOutside { snapshot, target },
7993 completion_id: id,
7994 invalidation_range: None,
7995 });
7996 cx.notify();
7997 return Some(());
7998 }
7999 };
8000
8001 let edits = edits
8002 .into_iter()
8003 .flat_map(|(range, new_text)| {
8004 Some((
8005 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8006 new_text,
8007 ))
8008 })
8009 .collect::<Vec<_>>();
8010 if edits.is_empty() {
8011 return None;
8012 }
8013
8014 let first_edit_start = edits.first().unwrap().0.start;
8015 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8016 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8017
8018 let last_edit_end = edits.last().unwrap().0.end;
8019 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8020 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8021
8022 let cursor_row = cursor.to_point(&multibuffer).row;
8023
8024 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8025
8026 let mut inlay_ids = Vec::new();
8027 let invalidation_row_range;
8028 let move_invalidation_row_range = if cursor_row < edit_start_row {
8029 Some(cursor_row..edit_end_row)
8030 } else if cursor_row > edit_end_row {
8031 Some(edit_start_row..cursor_row)
8032 } else {
8033 None
8034 };
8035 let supports_jump = self
8036 .edit_prediction_provider
8037 .as_ref()
8038 .map(|provider| provider.provider.supports_jump_to_edit())
8039 .unwrap_or(true);
8040
8041 let is_move = supports_jump
8042 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8043 let completion = if is_move {
8044 invalidation_row_range =
8045 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8046 let target = first_edit_start;
8047 EditPrediction::MoveWithin { target, snapshot }
8048 } else {
8049 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8050 && !self.edit_predictions_hidden_for_vim_mode;
8051
8052 if show_completions_in_buffer {
8053 if edits
8054 .iter()
8055 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8056 {
8057 let mut inlays = Vec::new();
8058 for (range, new_text) in &edits {
8059 let inlay = Inlay::edit_prediction(
8060 post_inc(&mut self.next_inlay_id),
8061 range.start,
8062 new_text.as_ref(),
8063 );
8064 inlay_ids.push(inlay.id);
8065 inlays.push(inlay);
8066 }
8067
8068 self.splice_inlays(&[], inlays, cx);
8069 } else {
8070 let background_color = cx.theme().status().deleted_background;
8071 self.highlight_text::<EditPredictionHighlight>(
8072 edits.iter().map(|(range, _)| range.clone()).collect(),
8073 HighlightStyle {
8074 background_color: Some(background_color),
8075 ..Default::default()
8076 },
8077 cx,
8078 );
8079 }
8080 }
8081
8082 invalidation_row_range = edit_start_row..edit_end_row;
8083
8084 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8085 if provider.show_tab_accept_marker() {
8086 EditDisplayMode::TabAccept
8087 } else {
8088 EditDisplayMode::Inline
8089 }
8090 } else {
8091 EditDisplayMode::DiffPopover
8092 };
8093
8094 EditPrediction::Edit {
8095 edits,
8096 edit_preview,
8097 display_mode,
8098 snapshot,
8099 }
8100 };
8101
8102 let invalidation_range = multibuffer
8103 .anchor_before(Point::new(invalidation_row_range.start, 0))
8104 ..multibuffer.anchor_after(Point::new(
8105 invalidation_row_range.end,
8106 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8107 ));
8108
8109 self.stale_edit_prediction_in_menu = None;
8110 self.active_edit_prediction = Some(EditPredictionState {
8111 inlay_ids,
8112 completion,
8113 completion_id,
8114 invalidation_range: Some(invalidation_range),
8115 });
8116
8117 cx.notify();
8118
8119 Some(())
8120 }
8121
8122 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8123 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8124 }
8125
8126 fn clear_tasks(&mut self) {
8127 self.tasks.clear()
8128 }
8129
8130 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8131 if self.tasks.insert(key, value).is_some() {
8132 // This case should hopefully be rare, but just in case...
8133 log::error!(
8134 "multiple different run targets found on a single line, only the last target will be rendered"
8135 )
8136 }
8137 }
8138
8139 /// Get all display points of breakpoints that will be rendered within editor
8140 ///
8141 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8142 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8143 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8144 fn active_breakpoints(
8145 &self,
8146 range: Range<DisplayRow>,
8147 window: &mut Window,
8148 cx: &mut Context<Self>,
8149 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8150 let mut breakpoint_display_points = HashMap::default();
8151
8152 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8153 return breakpoint_display_points;
8154 };
8155
8156 let snapshot = self.snapshot(window, cx);
8157
8158 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8159 let Some(project) = self.project() else {
8160 return breakpoint_display_points;
8161 };
8162
8163 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8164 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8165
8166 for (buffer_snapshot, range, excerpt_id) in
8167 multi_buffer_snapshot.range_to_buffer_ranges(range)
8168 {
8169 let Some(buffer) = project
8170 .read(cx)
8171 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8172 else {
8173 continue;
8174 };
8175 let breakpoints = breakpoint_store.read(cx).breakpoints(
8176 &buffer,
8177 Some(
8178 buffer_snapshot.anchor_before(range.start)
8179 ..buffer_snapshot.anchor_after(range.end),
8180 ),
8181 buffer_snapshot,
8182 cx,
8183 );
8184 for (breakpoint, state) in breakpoints {
8185 let multi_buffer_anchor =
8186 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8187 let position = multi_buffer_anchor
8188 .to_point(&multi_buffer_snapshot)
8189 .to_display_point(&snapshot);
8190
8191 breakpoint_display_points.insert(
8192 position.row(),
8193 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8194 );
8195 }
8196 }
8197
8198 breakpoint_display_points
8199 }
8200
8201 fn breakpoint_context_menu(
8202 &self,
8203 anchor: Anchor,
8204 window: &mut Window,
8205 cx: &mut Context<Self>,
8206 ) -> Entity<ui::ContextMenu> {
8207 let weak_editor = cx.weak_entity();
8208 let focus_handle = self.focus_handle(cx);
8209
8210 let row = self
8211 .buffer
8212 .read(cx)
8213 .snapshot(cx)
8214 .summary_for_anchor::<Point>(&anchor)
8215 .row;
8216
8217 let breakpoint = self
8218 .breakpoint_at_row(row, window, cx)
8219 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8220
8221 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8222 "Edit Log Breakpoint"
8223 } else {
8224 "Set Log Breakpoint"
8225 };
8226
8227 let condition_breakpoint_msg = if breakpoint
8228 .as_ref()
8229 .is_some_and(|bp| bp.1.condition.is_some())
8230 {
8231 "Edit Condition Breakpoint"
8232 } else {
8233 "Set Condition Breakpoint"
8234 };
8235
8236 let hit_condition_breakpoint_msg = if breakpoint
8237 .as_ref()
8238 .is_some_and(|bp| bp.1.hit_condition.is_some())
8239 {
8240 "Edit Hit Condition Breakpoint"
8241 } else {
8242 "Set Hit Condition Breakpoint"
8243 };
8244
8245 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8246 "Unset Breakpoint"
8247 } else {
8248 "Set Breakpoint"
8249 };
8250
8251 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8252
8253 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8254 BreakpointState::Enabled => Some("Disable"),
8255 BreakpointState::Disabled => Some("Enable"),
8256 });
8257
8258 let (anchor, breakpoint) =
8259 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8260
8261 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8262 menu.on_blur_subscription(Subscription::new(|| {}))
8263 .context(focus_handle)
8264 .when(run_to_cursor, |this| {
8265 let weak_editor = weak_editor.clone();
8266 this.entry("Run to cursor", None, move |window, cx| {
8267 weak_editor
8268 .update(cx, |editor, cx| {
8269 editor.change_selections(
8270 SelectionEffects::no_scroll(),
8271 window,
8272 cx,
8273 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8274 );
8275 })
8276 .ok();
8277
8278 window.dispatch_action(Box::new(RunToCursor), cx);
8279 })
8280 .separator()
8281 })
8282 .when_some(toggle_state_msg, |this, msg| {
8283 this.entry(msg, None, {
8284 let weak_editor = weak_editor.clone();
8285 let breakpoint = breakpoint.clone();
8286 move |_window, cx| {
8287 weak_editor
8288 .update(cx, |this, cx| {
8289 this.edit_breakpoint_at_anchor(
8290 anchor,
8291 breakpoint.as_ref().clone(),
8292 BreakpointEditAction::InvertState,
8293 cx,
8294 );
8295 })
8296 .log_err();
8297 }
8298 })
8299 })
8300 .entry(set_breakpoint_msg, None, {
8301 let weak_editor = weak_editor.clone();
8302 let breakpoint = breakpoint.clone();
8303 move |_window, cx| {
8304 weak_editor
8305 .update(cx, |this, cx| {
8306 this.edit_breakpoint_at_anchor(
8307 anchor,
8308 breakpoint.as_ref().clone(),
8309 BreakpointEditAction::Toggle,
8310 cx,
8311 );
8312 })
8313 .log_err();
8314 }
8315 })
8316 .entry(log_breakpoint_msg, None, {
8317 let breakpoint = breakpoint.clone();
8318 let weak_editor = weak_editor.clone();
8319 move |window, cx| {
8320 weak_editor
8321 .update(cx, |this, cx| {
8322 this.add_edit_breakpoint_block(
8323 anchor,
8324 breakpoint.as_ref(),
8325 BreakpointPromptEditAction::Log,
8326 window,
8327 cx,
8328 );
8329 })
8330 .log_err();
8331 }
8332 })
8333 .entry(condition_breakpoint_msg, None, {
8334 let breakpoint = breakpoint.clone();
8335 let weak_editor = weak_editor.clone();
8336 move |window, cx| {
8337 weak_editor
8338 .update(cx, |this, cx| {
8339 this.add_edit_breakpoint_block(
8340 anchor,
8341 breakpoint.as_ref(),
8342 BreakpointPromptEditAction::Condition,
8343 window,
8344 cx,
8345 );
8346 })
8347 .log_err();
8348 }
8349 })
8350 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8351 weak_editor
8352 .update(cx, |this, cx| {
8353 this.add_edit_breakpoint_block(
8354 anchor,
8355 breakpoint.as_ref(),
8356 BreakpointPromptEditAction::HitCondition,
8357 window,
8358 cx,
8359 );
8360 })
8361 .log_err();
8362 })
8363 })
8364 }
8365
8366 fn render_breakpoint(
8367 &self,
8368 position: Anchor,
8369 row: DisplayRow,
8370 breakpoint: &Breakpoint,
8371 state: Option<BreakpointSessionState>,
8372 cx: &mut Context<Self>,
8373 ) -> IconButton {
8374 let is_rejected = state.is_some_and(|s| !s.verified);
8375 // Is it a breakpoint that shows up when hovering over gutter?
8376 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8377 (false, false),
8378 |PhantomBreakpointIndicator {
8379 is_active,
8380 display_row,
8381 collides_with_existing_breakpoint,
8382 }| {
8383 (
8384 is_active && display_row == row,
8385 collides_with_existing_breakpoint,
8386 )
8387 },
8388 );
8389
8390 let (color, icon) = {
8391 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8392 (false, false) => ui::IconName::DebugBreakpoint,
8393 (true, false) => ui::IconName::DebugLogBreakpoint,
8394 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8395 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8396 };
8397
8398 let color = if is_phantom {
8399 Color::Hint
8400 } else if is_rejected {
8401 Color::Disabled
8402 } else {
8403 Color::Debugger
8404 };
8405
8406 (color, icon)
8407 };
8408
8409 let breakpoint = Arc::from(breakpoint.clone());
8410
8411 let alt_as_text = gpui::Keystroke {
8412 modifiers: Modifiers::secondary_key(),
8413 ..Default::default()
8414 };
8415 let primary_action_text = if breakpoint.is_disabled() {
8416 "Enable breakpoint"
8417 } else if is_phantom && !collides_with_existing {
8418 "Set breakpoint"
8419 } else {
8420 "Unset breakpoint"
8421 };
8422 let focus_handle = self.focus_handle.clone();
8423
8424 let meta = if is_rejected {
8425 SharedString::from("No executable code is associated with this line.")
8426 } else if collides_with_existing && !breakpoint.is_disabled() {
8427 SharedString::from(format!(
8428 "{alt_as_text}-click to disable,\nright-click for more options."
8429 ))
8430 } else {
8431 SharedString::from("Right-click for more options.")
8432 };
8433 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8434 .icon_size(IconSize::XSmall)
8435 .size(ui::ButtonSize::None)
8436 .when(is_rejected, |this| {
8437 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8438 })
8439 .icon_color(color)
8440 .style(ButtonStyle::Transparent)
8441 .on_click(cx.listener({
8442 move |editor, event: &ClickEvent, window, cx| {
8443 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8444 BreakpointEditAction::InvertState
8445 } else {
8446 BreakpointEditAction::Toggle
8447 };
8448
8449 window.focus(&editor.focus_handle(cx));
8450 editor.edit_breakpoint_at_anchor(
8451 position,
8452 breakpoint.as_ref().clone(),
8453 edit_action,
8454 cx,
8455 );
8456 }
8457 }))
8458 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8459 editor.set_breakpoint_context_menu(
8460 row,
8461 Some(position),
8462 event.position(),
8463 window,
8464 cx,
8465 );
8466 }))
8467 .tooltip(move |_window, cx| {
8468 Tooltip::with_meta_in(
8469 primary_action_text,
8470 Some(&ToggleBreakpoint),
8471 meta.clone(),
8472 &focus_handle,
8473 cx,
8474 )
8475 })
8476 }
8477
8478 fn build_tasks_context(
8479 project: &Entity<Project>,
8480 buffer: &Entity<Buffer>,
8481 buffer_row: u32,
8482 tasks: &Arc<RunnableTasks>,
8483 cx: &mut Context<Self>,
8484 ) -> Task<Option<task::TaskContext>> {
8485 let position = Point::new(buffer_row, tasks.column);
8486 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8487 let location = Location {
8488 buffer: buffer.clone(),
8489 range: range_start..range_start,
8490 };
8491 // Fill in the environmental variables from the tree-sitter captures
8492 let mut captured_task_variables = TaskVariables::default();
8493 for (capture_name, value) in tasks.extra_variables.clone() {
8494 captured_task_variables.insert(
8495 task::VariableName::Custom(capture_name.into()),
8496 value.clone(),
8497 );
8498 }
8499 project.update(cx, |project, cx| {
8500 project.task_store().update(cx, |task_store, cx| {
8501 task_store.task_context_for_location(captured_task_variables, location, cx)
8502 })
8503 })
8504 }
8505
8506 pub fn spawn_nearest_task(
8507 &mut self,
8508 action: &SpawnNearestTask,
8509 window: &mut Window,
8510 cx: &mut Context<Self>,
8511 ) {
8512 let Some((workspace, _)) = self.workspace.clone() else {
8513 return;
8514 };
8515 let Some(project) = self.project.clone() else {
8516 return;
8517 };
8518
8519 // Try to find a closest, enclosing node using tree-sitter that has a task
8520 let Some((buffer, buffer_row, tasks)) = self
8521 .find_enclosing_node_task(cx)
8522 // Or find the task that's closest in row-distance.
8523 .or_else(|| self.find_closest_task(cx))
8524 else {
8525 return;
8526 };
8527
8528 let reveal_strategy = action.reveal;
8529 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8530 cx.spawn_in(window, async move |_, cx| {
8531 let context = task_context.await?;
8532 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8533
8534 let resolved = &mut resolved_task.resolved;
8535 resolved.reveal = reveal_strategy;
8536
8537 workspace
8538 .update_in(cx, |workspace, window, cx| {
8539 workspace.schedule_resolved_task(
8540 task_source_kind,
8541 resolved_task,
8542 false,
8543 window,
8544 cx,
8545 );
8546 })
8547 .ok()
8548 })
8549 .detach();
8550 }
8551
8552 fn find_closest_task(
8553 &mut self,
8554 cx: &mut Context<Self>,
8555 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8556 let cursor_row = self
8557 .selections
8558 .newest_adjusted(&self.display_snapshot(cx))
8559 .head()
8560 .row;
8561
8562 let ((buffer_id, row), tasks) = self
8563 .tasks
8564 .iter()
8565 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8566
8567 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8568 let tasks = Arc::new(tasks.to_owned());
8569 Some((buffer, *row, tasks))
8570 }
8571
8572 fn find_enclosing_node_task(
8573 &mut self,
8574 cx: &mut Context<Self>,
8575 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8576 let snapshot = self.buffer.read(cx).snapshot(cx);
8577 let offset = self
8578 .selections
8579 .newest::<usize>(&self.display_snapshot(cx))
8580 .head();
8581 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8582 let buffer_id = excerpt.buffer().remote_id();
8583
8584 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8585 let mut cursor = layer.node().walk();
8586
8587 while cursor.goto_first_child_for_byte(offset).is_some() {
8588 if cursor.node().end_byte() == offset {
8589 cursor.goto_next_sibling();
8590 }
8591 }
8592
8593 // Ascend to the smallest ancestor that contains the range and has a task.
8594 loop {
8595 let node = cursor.node();
8596 let node_range = node.byte_range();
8597 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8598
8599 // Check if this node contains our offset
8600 if node_range.start <= offset && node_range.end >= offset {
8601 // If it contains offset, check for task
8602 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8603 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8604 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8605 }
8606 }
8607
8608 if !cursor.goto_parent() {
8609 break;
8610 }
8611 }
8612 None
8613 }
8614
8615 fn render_run_indicator(
8616 &self,
8617 _style: &EditorStyle,
8618 is_active: bool,
8619 row: DisplayRow,
8620 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8621 cx: &mut Context<Self>,
8622 ) -> IconButton {
8623 let color = Color::Muted;
8624 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8625
8626 IconButton::new(
8627 ("run_indicator", row.0 as usize),
8628 ui::IconName::PlayOutlined,
8629 )
8630 .shape(ui::IconButtonShape::Square)
8631 .icon_size(IconSize::XSmall)
8632 .icon_color(color)
8633 .toggle_state(is_active)
8634 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8635 let quick_launch = match e {
8636 ClickEvent::Keyboard(_) => true,
8637 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8638 };
8639
8640 window.focus(&editor.focus_handle(cx));
8641 editor.toggle_code_actions(
8642 &ToggleCodeActions {
8643 deployed_from: Some(CodeActionSource::RunMenu(row)),
8644 quick_launch,
8645 },
8646 window,
8647 cx,
8648 );
8649 }))
8650 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8651 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8652 }))
8653 }
8654
8655 pub fn context_menu_visible(&self) -> bool {
8656 !self.edit_prediction_preview_is_active()
8657 && self
8658 .context_menu
8659 .borrow()
8660 .as_ref()
8661 .is_some_and(|menu| menu.visible())
8662 }
8663
8664 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8665 self.context_menu
8666 .borrow()
8667 .as_ref()
8668 .map(|menu| menu.origin())
8669 }
8670
8671 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8672 self.context_menu_options = Some(options);
8673 }
8674
8675 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8676 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8677
8678 fn render_edit_prediction_popover(
8679 &mut self,
8680 text_bounds: &Bounds<Pixels>,
8681 content_origin: gpui::Point<Pixels>,
8682 right_margin: Pixels,
8683 editor_snapshot: &EditorSnapshot,
8684 visible_row_range: Range<DisplayRow>,
8685 scroll_top: ScrollOffset,
8686 scroll_bottom: ScrollOffset,
8687 line_layouts: &[LineWithInvisibles],
8688 line_height: Pixels,
8689 scroll_position: gpui::Point<ScrollOffset>,
8690 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8691 newest_selection_head: Option<DisplayPoint>,
8692 editor_width: Pixels,
8693 style: &EditorStyle,
8694 window: &mut Window,
8695 cx: &mut App,
8696 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8697 if self.mode().is_minimap() {
8698 return None;
8699 }
8700 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8701
8702 if self.edit_prediction_visible_in_cursor_popover(true) {
8703 return None;
8704 }
8705
8706 match &active_edit_prediction.completion {
8707 EditPrediction::MoveWithin { target, .. } => {
8708 let target_display_point = target.to_display_point(editor_snapshot);
8709
8710 if self.edit_prediction_requires_modifier() {
8711 if !self.edit_prediction_preview_is_active() {
8712 return None;
8713 }
8714
8715 self.render_edit_prediction_modifier_jump_popover(
8716 text_bounds,
8717 content_origin,
8718 visible_row_range,
8719 line_layouts,
8720 line_height,
8721 scroll_pixel_position,
8722 newest_selection_head,
8723 target_display_point,
8724 window,
8725 cx,
8726 )
8727 } else {
8728 self.render_edit_prediction_eager_jump_popover(
8729 text_bounds,
8730 content_origin,
8731 editor_snapshot,
8732 visible_row_range,
8733 scroll_top,
8734 scroll_bottom,
8735 line_height,
8736 scroll_pixel_position,
8737 target_display_point,
8738 editor_width,
8739 window,
8740 cx,
8741 )
8742 }
8743 }
8744 EditPrediction::Edit {
8745 display_mode: EditDisplayMode::Inline,
8746 ..
8747 } => None,
8748 EditPrediction::Edit {
8749 display_mode: EditDisplayMode::TabAccept,
8750 edits,
8751 ..
8752 } => {
8753 let range = &edits.first()?.0;
8754 let target_display_point = range.end.to_display_point(editor_snapshot);
8755
8756 self.render_edit_prediction_end_of_line_popover(
8757 "Accept",
8758 editor_snapshot,
8759 visible_row_range,
8760 target_display_point,
8761 line_height,
8762 scroll_pixel_position,
8763 content_origin,
8764 editor_width,
8765 window,
8766 cx,
8767 )
8768 }
8769 EditPrediction::Edit {
8770 edits,
8771 edit_preview,
8772 display_mode: EditDisplayMode::DiffPopover,
8773 snapshot,
8774 } => self.render_edit_prediction_diff_popover(
8775 text_bounds,
8776 content_origin,
8777 right_margin,
8778 editor_snapshot,
8779 visible_row_range,
8780 line_layouts,
8781 line_height,
8782 scroll_position,
8783 scroll_pixel_position,
8784 newest_selection_head,
8785 editor_width,
8786 style,
8787 edits,
8788 edit_preview,
8789 snapshot,
8790 window,
8791 cx,
8792 ),
8793 EditPrediction::MoveOutside { snapshot, .. } => {
8794 let file_name = snapshot
8795 .file()
8796 .map(|file| file.file_name(cx))
8797 .unwrap_or("untitled");
8798 let mut element = self
8799 .render_edit_prediction_line_popover(
8800 format!("Jump to {file_name}"),
8801 Some(IconName::ZedPredict),
8802 window,
8803 cx,
8804 )
8805 .into_any();
8806
8807 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8808 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8809 let origin_y = text_bounds.size.height - size.height - px(30.);
8810 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8811 element.prepaint_at(origin, window, cx);
8812
8813 Some((element, origin))
8814 }
8815 }
8816 }
8817
8818 fn render_edit_prediction_modifier_jump_popover(
8819 &mut self,
8820 text_bounds: &Bounds<Pixels>,
8821 content_origin: gpui::Point<Pixels>,
8822 visible_row_range: Range<DisplayRow>,
8823 line_layouts: &[LineWithInvisibles],
8824 line_height: Pixels,
8825 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8826 newest_selection_head: Option<DisplayPoint>,
8827 target_display_point: DisplayPoint,
8828 window: &mut Window,
8829 cx: &mut App,
8830 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8831 let scrolled_content_origin =
8832 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8833
8834 const SCROLL_PADDING_Y: Pixels = px(12.);
8835
8836 if target_display_point.row() < visible_row_range.start {
8837 return self.render_edit_prediction_scroll_popover(
8838 |_| SCROLL_PADDING_Y,
8839 IconName::ArrowUp,
8840 visible_row_range,
8841 line_layouts,
8842 newest_selection_head,
8843 scrolled_content_origin,
8844 window,
8845 cx,
8846 );
8847 } else if target_display_point.row() >= visible_row_range.end {
8848 return self.render_edit_prediction_scroll_popover(
8849 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8850 IconName::ArrowDown,
8851 visible_row_range,
8852 line_layouts,
8853 newest_selection_head,
8854 scrolled_content_origin,
8855 window,
8856 cx,
8857 );
8858 }
8859
8860 const POLE_WIDTH: Pixels = px(2.);
8861
8862 let line_layout =
8863 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8864 let target_column = target_display_point.column() as usize;
8865
8866 let target_x = line_layout.x_for_index(target_column);
8867 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8868 - scroll_pixel_position.y;
8869
8870 let flag_on_right = target_x < text_bounds.size.width / 2.;
8871
8872 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8873 border_color.l += 0.001;
8874
8875 let mut element = v_flex()
8876 .items_end()
8877 .when(flag_on_right, |el| el.items_start())
8878 .child(if flag_on_right {
8879 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8880 .rounded_bl(px(0.))
8881 .rounded_tl(px(0.))
8882 .border_l_2()
8883 .border_color(border_color)
8884 } else {
8885 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8886 .rounded_br(px(0.))
8887 .rounded_tr(px(0.))
8888 .border_r_2()
8889 .border_color(border_color)
8890 })
8891 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8892 .into_any();
8893
8894 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8895
8896 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8897 - point(
8898 if flag_on_right {
8899 POLE_WIDTH
8900 } else {
8901 size.width - POLE_WIDTH
8902 },
8903 size.height - line_height,
8904 );
8905
8906 origin.x = origin.x.max(content_origin.x);
8907
8908 element.prepaint_at(origin, window, cx);
8909
8910 Some((element, origin))
8911 }
8912
8913 fn render_edit_prediction_scroll_popover(
8914 &mut self,
8915 to_y: impl Fn(Size<Pixels>) -> Pixels,
8916 scroll_icon: IconName,
8917 visible_row_range: Range<DisplayRow>,
8918 line_layouts: &[LineWithInvisibles],
8919 newest_selection_head: Option<DisplayPoint>,
8920 scrolled_content_origin: gpui::Point<Pixels>,
8921 window: &mut Window,
8922 cx: &mut App,
8923 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8924 let mut element = self
8925 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8926 .into_any();
8927
8928 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8929
8930 let cursor = newest_selection_head?;
8931 let cursor_row_layout =
8932 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8933 let cursor_column = cursor.column() as usize;
8934
8935 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8936
8937 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8938
8939 element.prepaint_at(origin, window, cx);
8940 Some((element, origin))
8941 }
8942
8943 fn render_edit_prediction_eager_jump_popover(
8944 &mut self,
8945 text_bounds: &Bounds<Pixels>,
8946 content_origin: gpui::Point<Pixels>,
8947 editor_snapshot: &EditorSnapshot,
8948 visible_row_range: Range<DisplayRow>,
8949 scroll_top: ScrollOffset,
8950 scroll_bottom: ScrollOffset,
8951 line_height: Pixels,
8952 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8953 target_display_point: DisplayPoint,
8954 editor_width: Pixels,
8955 window: &mut Window,
8956 cx: &mut App,
8957 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8958 if target_display_point.row().as_f64() < scroll_top {
8959 let mut element = self
8960 .render_edit_prediction_line_popover(
8961 "Jump to Edit",
8962 Some(IconName::ArrowUp),
8963 window,
8964 cx,
8965 )
8966 .into_any();
8967
8968 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8969 let offset = point(
8970 (text_bounds.size.width - size.width) / 2.,
8971 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8972 );
8973
8974 let origin = text_bounds.origin + offset;
8975 element.prepaint_at(origin, window, cx);
8976 Some((element, origin))
8977 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8978 let mut element = self
8979 .render_edit_prediction_line_popover(
8980 "Jump to Edit",
8981 Some(IconName::ArrowDown),
8982 window,
8983 cx,
8984 )
8985 .into_any();
8986
8987 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8988 let offset = point(
8989 (text_bounds.size.width - size.width) / 2.,
8990 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8991 );
8992
8993 let origin = text_bounds.origin + offset;
8994 element.prepaint_at(origin, window, cx);
8995 Some((element, origin))
8996 } else {
8997 self.render_edit_prediction_end_of_line_popover(
8998 "Jump to Edit",
8999 editor_snapshot,
9000 visible_row_range,
9001 target_display_point,
9002 line_height,
9003 scroll_pixel_position,
9004 content_origin,
9005 editor_width,
9006 window,
9007 cx,
9008 )
9009 }
9010 }
9011
9012 fn render_edit_prediction_end_of_line_popover(
9013 self: &mut Editor,
9014 label: &'static str,
9015 editor_snapshot: &EditorSnapshot,
9016 visible_row_range: Range<DisplayRow>,
9017 target_display_point: DisplayPoint,
9018 line_height: Pixels,
9019 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9020 content_origin: gpui::Point<Pixels>,
9021 editor_width: Pixels,
9022 window: &mut Window,
9023 cx: &mut App,
9024 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9025 let target_line_end = DisplayPoint::new(
9026 target_display_point.row(),
9027 editor_snapshot.line_len(target_display_point.row()),
9028 );
9029
9030 let mut element = self
9031 .render_edit_prediction_line_popover(label, None, window, cx)
9032 .into_any();
9033
9034 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9035
9036 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9037
9038 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9039 let mut origin = start_point
9040 + line_origin
9041 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9042 origin.x = origin.x.max(content_origin.x);
9043
9044 let max_x = content_origin.x + editor_width - size.width;
9045
9046 if origin.x > max_x {
9047 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9048
9049 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9050 origin.y += offset;
9051 IconName::ArrowUp
9052 } else {
9053 origin.y -= offset;
9054 IconName::ArrowDown
9055 };
9056
9057 element = self
9058 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9059 .into_any();
9060
9061 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9062
9063 origin.x = content_origin.x + editor_width - size.width - px(2.);
9064 }
9065
9066 element.prepaint_at(origin, window, cx);
9067 Some((element, origin))
9068 }
9069
9070 fn render_edit_prediction_diff_popover(
9071 self: &Editor,
9072 text_bounds: &Bounds<Pixels>,
9073 content_origin: gpui::Point<Pixels>,
9074 right_margin: Pixels,
9075 editor_snapshot: &EditorSnapshot,
9076 visible_row_range: Range<DisplayRow>,
9077 line_layouts: &[LineWithInvisibles],
9078 line_height: Pixels,
9079 scroll_position: gpui::Point<ScrollOffset>,
9080 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9081 newest_selection_head: Option<DisplayPoint>,
9082 editor_width: Pixels,
9083 style: &EditorStyle,
9084 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9085 edit_preview: &Option<language::EditPreview>,
9086 snapshot: &language::BufferSnapshot,
9087 window: &mut Window,
9088 cx: &mut App,
9089 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9090 let edit_start = edits
9091 .first()
9092 .unwrap()
9093 .0
9094 .start
9095 .to_display_point(editor_snapshot);
9096 let edit_end = edits
9097 .last()
9098 .unwrap()
9099 .0
9100 .end
9101 .to_display_point(editor_snapshot);
9102
9103 let is_visible = visible_row_range.contains(&edit_start.row())
9104 || visible_row_range.contains(&edit_end.row());
9105 if !is_visible {
9106 return None;
9107 }
9108
9109 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9110 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9111 } else {
9112 // Fallback for providers without edit_preview
9113 crate::edit_prediction_fallback_text(edits, cx)
9114 };
9115
9116 let styled_text = highlighted_edits.to_styled_text(&style.text);
9117 let line_count = highlighted_edits.text.lines().count();
9118
9119 const BORDER_WIDTH: Pixels = px(1.);
9120
9121 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9122 let has_keybind = keybind.is_some();
9123
9124 let mut element = h_flex()
9125 .items_start()
9126 .child(
9127 h_flex()
9128 .bg(cx.theme().colors().editor_background)
9129 .border(BORDER_WIDTH)
9130 .shadow_xs()
9131 .border_color(cx.theme().colors().border)
9132 .rounded_l_lg()
9133 .when(line_count > 1, |el| el.rounded_br_lg())
9134 .pr_1()
9135 .child(styled_text),
9136 )
9137 .child(
9138 h_flex()
9139 .h(line_height + BORDER_WIDTH * 2.)
9140 .px_1p5()
9141 .gap_1()
9142 // Workaround: For some reason, there's a gap if we don't do this
9143 .ml(-BORDER_WIDTH)
9144 .shadow(vec![gpui::BoxShadow {
9145 color: gpui::black().opacity(0.05),
9146 offset: point(px(1.), px(1.)),
9147 blur_radius: px(2.),
9148 spread_radius: px(0.),
9149 }])
9150 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9151 .border(BORDER_WIDTH)
9152 .border_color(cx.theme().colors().border)
9153 .rounded_r_lg()
9154 .id("edit_prediction_diff_popover_keybind")
9155 .when(!has_keybind, |el| {
9156 let status_colors = cx.theme().status();
9157
9158 el.bg(status_colors.error_background)
9159 .border_color(status_colors.error.opacity(0.6))
9160 .child(Icon::new(IconName::Info).color(Color::Error))
9161 .cursor_default()
9162 .hoverable_tooltip(move |_window, cx| {
9163 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9164 })
9165 })
9166 .children(keybind),
9167 )
9168 .into_any();
9169
9170 let longest_row =
9171 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9172 let longest_line_width = if visible_row_range.contains(&longest_row) {
9173 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9174 } else {
9175 layout_line(
9176 longest_row,
9177 editor_snapshot,
9178 style,
9179 editor_width,
9180 |_| false,
9181 window,
9182 cx,
9183 )
9184 .width
9185 };
9186
9187 let viewport_bounds =
9188 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9189 right: -right_margin,
9190 ..Default::default()
9191 });
9192
9193 let x_after_longest = Pixels::from(
9194 ScrollPixelOffset::from(
9195 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9196 ) - scroll_pixel_position.x,
9197 );
9198
9199 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9200
9201 // Fully visible if it can be displayed within the window (allow overlapping other
9202 // panes). However, this is only allowed if the popover starts within text_bounds.
9203 let can_position_to_the_right = x_after_longest < text_bounds.right()
9204 && x_after_longest + element_bounds.width < viewport_bounds.right();
9205
9206 let mut origin = if can_position_to_the_right {
9207 point(
9208 x_after_longest,
9209 text_bounds.origin.y
9210 + Pixels::from(
9211 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9212 - scroll_pixel_position.y,
9213 ),
9214 )
9215 } else {
9216 let cursor_row = newest_selection_head.map(|head| head.row());
9217 let above_edit = edit_start
9218 .row()
9219 .0
9220 .checked_sub(line_count as u32)
9221 .map(DisplayRow);
9222 let below_edit = Some(edit_end.row() + 1);
9223 let above_cursor =
9224 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9225 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9226
9227 // Place the edit popover adjacent to the edit if there is a location
9228 // available that is onscreen and does not obscure the cursor. Otherwise,
9229 // place it adjacent to the cursor.
9230 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9231 .into_iter()
9232 .flatten()
9233 .find(|&start_row| {
9234 let end_row = start_row + line_count as u32;
9235 visible_row_range.contains(&start_row)
9236 && visible_row_range.contains(&end_row)
9237 && cursor_row
9238 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9239 })?;
9240
9241 content_origin
9242 + point(
9243 Pixels::from(-scroll_pixel_position.x),
9244 Pixels::from(
9245 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9246 ),
9247 )
9248 };
9249
9250 origin.x -= BORDER_WIDTH;
9251
9252 window.defer_draw(element, origin, 1);
9253
9254 // Do not return an element, since it will already be drawn due to defer_draw.
9255 None
9256 }
9257
9258 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9259 px(30.)
9260 }
9261
9262 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9263 if self.read_only(cx) {
9264 cx.theme().players().read_only()
9265 } else {
9266 self.style.as_ref().unwrap().local_player
9267 }
9268 }
9269
9270 fn render_edit_prediction_accept_keybind(
9271 &self,
9272 window: &mut Window,
9273 cx: &mut App,
9274 ) -> Option<AnyElement> {
9275 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9276 let accept_keystroke = accept_binding.keystroke()?;
9277
9278 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9279
9280 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9281 Color::Accent
9282 } else {
9283 Color::Muted
9284 };
9285
9286 h_flex()
9287 .px_0p5()
9288 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9289 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9290 .text_size(TextSize::XSmall.rems(cx))
9291 .child(h_flex().children(ui::render_modifiers(
9292 accept_keystroke.modifiers(),
9293 PlatformStyle::platform(),
9294 Some(modifiers_color),
9295 Some(IconSize::XSmall.rems().into()),
9296 true,
9297 )))
9298 .when(is_platform_style_mac, |parent| {
9299 parent.child(accept_keystroke.key().to_string())
9300 })
9301 .when(!is_platform_style_mac, |parent| {
9302 parent.child(
9303 Key::new(
9304 util::capitalize(accept_keystroke.key()),
9305 Some(Color::Default),
9306 )
9307 .size(Some(IconSize::XSmall.rems().into())),
9308 )
9309 })
9310 .into_any()
9311 .into()
9312 }
9313
9314 fn render_edit_prediction_line_popover(
9315 &self,
9316 label: impl Into<SharedString>,
9317 icon: Option<IconName>,
9318 window: &mut Window,
9319 cx: &mut App,
9320 ) -> Stateful<Div> {
9321 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9322
9323 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9324 let has_keybind = keybind.is_some();
9325
9326 h_flex()
9327 .id("ep-line-popover")
9328 .py_0p5()
9329 .pl_1()
9330 .pr(padding_right)
9331 .gap_1()
9332 .rounded_md()
9333 .border_1()
9334 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9335 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9336 .shadow_xs()
9337 .when(!has_keybind, |el| {
9338 let status_colors = cx.theme().status();
9339
9340 el.bg(status_colors.error_background)
9341 .border_color(status_colors.error.opacity(0.6))
9342 .pl_2()
9343 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9344 .cursor_default()
9345 .hoverable_tooltip(move |_window, cx| {
9346 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9347 })
9348 })
9349 .children(keybind)
9350 .child(
9351 Label::new(label)
9352 .size(LabelSize::Small)
9353 .when(!has_keybind, |el| {
9354 el.color(cx.theme().status().error.into()).strikethrough()
9355 }),
9356 )
9357 .when(!has_keybind, |el| {
9358 el.child(
9359 h_flex().ml_1().child(
9360 Icon::new(IconName::Info)
9361 .size(IconSize::Small)
9362 .color(cx.theme().status().error.into()),
9363 ),
9364 )
9365 })
9366 .when_some(icon, |element, icon| {
9367 element.child(
9368 div()
9369 .mt(px(1.5))
9370 .child(Icon::new(icon).size(IconSize::Small)),
9371 )
9372 })
9373 }
9374
9375 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9376 let accent_color = cx.theme().colors().text_accent;
9377 let editor_bg_color = cx.theme().colors().editor_background;
9378 editor_bg_color.blend(accent_color.opacity(0.1))
9379 }
9380
9381 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9382 let accent_color = cx.theme().colors().text_accent;
9383 let editor_bg_color = cx.theme().colors().editor_background;
9384 editor_bg_color.blend(accent_color.opacity(0.6))
9385 }
9386 fn get_prediction_provider_icon_name(
9387 provider: &Option<RegisteredEditPredictionProvider>,
9388 ) -> IconName {
9389 match provider {
9390 Some(provider) => match provider.provider.name() {
9391 "copilot" => IconName::Copilot,
9392 "supermaven" => IconName::Supermaven,
9393 _ => IconName::ZedPredict,
9394 },
9395 None => IconName::ZedPredict,
9396 }
9397 }
9398
9399 fn render_edit_prediction_cursor_popover(
9400 &self,
9401 min_width: Pixels,
9402 max_width: Pixels,
9403 cursor_point: Point,
9404 style: &EditorStyle,
9405 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9406 _window: &Window,
9407 cx: &mut Context<Editor>,
9408 ) -> Option<AnyElement> {
9409 let provider = self.edit_prediction_provider.as_ref()?;
9410 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9411
9412 let is_refreshing = provider.provider.is_refreshing(cx);
9413
9414 fn pending_completion_container(icon: IconName) -> Div {
9415 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9416 }
9417
9418 let completion = match &self.active_edit_prediction {
9419 Some(prediction) => {
9420 if !self.has_visible_completions_menu() {
9421 const RADIUS: Pixels = px(6.);
9422 const BORDER_WIDTH: Pixels = px(1.);
9423
9424 return Some(
9425 h_flex()
9426 .elevation_2(cx)
9427 .border(BORDER_WIDTH)
9428 .border_color(cx.theme().colors().border)
9429 .when(accept_keystroke.is_none(), |el| {
9430 el.border_color(cx.theme().status().error)
9431 })
9432 .rounded(RADIUS)
9433 .rounded_tl(px(0.))
9434 .overflow_hidden()
9435 .child(div().px_1p5().child(match &prediction.completion {
9436 EditPrediction::MoveWithin { target, snapshot } => {
9437 use text::ToPoint as _;
9438 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9439 {
9440 Icon::new(IconName::ZedPredictDown)
9441 } else {
9442 Icon::new(IconName::ZedPredictUp)
9443 }
9444 }
9445 EditPrediction::MoveOutside { .. } => {
9446 // TODO [zeta2] custom icon for external jump?
9447 Icon::new(provider_icon)
9448 }
9449 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9450 }))
9451 .child(
9452 h_flex()
9453 .gap_1()
9454 .py_1()
9455 .px_2()
9456 .rounded_r(RADIUS - BORDER_WIDTH)
9457 .border_l_1()
9458 .border_color(cx.theme().colors().border)
9459 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9460 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9461 el.child(
9462 Label::new("Hold")
9463 .size(LabelSize::Small)
9464 .when(accept_keystroke.is_none(), |el| {
9465 el.strikethrough()
9466 })
9467 .line_height_style(LineHeightStyle::UiLabel),
9468 )
9469 })
9470 .id("edit_prediction_cursor_popover_keybind")
9471 .when(accept_keystroke.is_none(), |el| {
9472 let status_colors = cx.theme().status();
9473
9474 el.bg(status_colors.error_background)
9475 .border_color(status_colors.error.opacity(0.6))
9476 .child(Icon::new(IconName::Info).color(Color::Error))
9477 .cursor_default()
9478 .hoverable_tooltip(move |_window, cx| {
9479 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9480 .into()
9481 })
9482 })
9483 .when_some(
9484 accept_keystroke.as_ref(),
9485 |el, accept_keystroke| {
9486 el.child(h_flex().children(ui::render_modifiers(
9487 accept_keystroke.modifiers(),
9488 PlatformStyle::platform(),
9489 Some(Color::Default),
9490 Some(IconSize::XSmall.rems().into()),
9491 false,
9492 )))
9493 },
9494 ),
9495 )
9496 .into_any(),
9497 );
9498 }
9499
9500 self.render_edit_prediction_cursor_popover_preview(
9501 prediction,
9502 cursor_point,
9503 style,
9504 cx,
9505 )?
9506 }
9507
9508 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9509 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9510 stale_completion,
9511 cursor_point,
9512 style,
9513 cx,
9514 )?,
9515
9516 None => pending_completion_container(provider_icon)
9517 .child(Label::new("...").size(LabelSize::Small)),
9518 },
9519
9520 None => pending_completion_container(provider_icon)
9521 .child(Label::new("...").size(LabelSize::Small)),
9522 };
9523
9524 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9525 completion
9526 .with_animation(
9527 "loading-completion",
9528 Animation::new(Duration::from_secs(2))
9529 .repeat()
9530 .with_easing(pulsating_between(0.4, 0.8)),
9531 |label, delta| label.opacity(delta),
9532 )
9533 .into_any_element()
9534 } else {
9535 completion.into_any_element()
9536 };
9537
9538 let has_completion = self.active_edit_prediction.is_some();
9539
9540 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9541 Some(
9542 h_flex()
9543 .min_w(min_width)
9544 .max_w(max_width)
9545 .flex_1()
9546 .elevation_2(cx)
9547 .border_color(cx.theme().colors().border)
9548 .child(
9549 div()
9550 .flex_1()
9551 .py_1()
9552 .px_2()
9553 .overflow_hidden()
9554 .child(completion),
9555 )
9556 .when_some(accept_keystroke, |el, accept_keystroke| {
9557 if !accept_keystroke.modifiers().modified() {
9558 return el;
9559 }
9560
9561 el.child(
9562 h_flex()
9563 .h_full()
9564 .border_l_1()
9565 .rounded_r_lg()
9566 .border_color(cx.theme().colors().border)
9567 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9568 .gap_1()
9569 .py_1()
9570 .px_2()
9571 .child(
9572 h_flex()
9573 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9574 .when(is_platform_style_mac, |parent| parent.gap_1())
9575 .child(h_flex().children(ui::render_modifiers(
9576 accept_keystroke.modifiers(),
9577 PlatformStyle::platform(),
9578 Some(if !has_completion {
9579 Color::Muted
9580 } else {
9581 Color::Default
9582 }),
9583 None,
9584 false,
9585 ))),
9586 )
9587 .child(Label::new("Preview").into_any_element())
9588 .opacity(if has_completion { 1.0 } else { 0.4 }),
9589 )
9590 })
9591 .into_any(),
9592 )
9593 }
9594
9595 fn render_edit_prediction_cursor_popover_preview(
9596 &self,
9597 completion: &EditPredictionState,
9598 cursor_point: Point,
9599 style: &EditorStyle,
9600 cx: &mut Context<Editor>,
9601 ) -> Option<Div> {
9602 use text::ToPoint as _;
9603
9604 fn render_relative_row_jump(
9605 prefix: impl Into<String>,
9606 current_row: u32,
9607 target_row: u32,
9608 ) -> Div {
9609 let (row_diff, arrow) = if target_row < current_row {
9610 (current_row - target_row, IconName::ArrowUp)
9611 } else {
9612 (target_row - current_row, IconName::ArrowDown)
9613 };
9614
9615 h_flex()
9616 .child(
9617 Label::new(format!("{}{}", prefix.into(), row_diff))
9618 .color(Color::Muted)
9619 .size(LabelSize::Small),
9620 )
9621 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9622 }
9623
9624 let supports_jump = self
9625 .edit_prediction_provider
9626 .as_ref()
9627 .map(|provider| provider.provider.supports_jump_to_edit())
9628 .unwrap_or(true);
9629
9630 match &completion.completion {
9631 EditPrediction::MoveWithin {
9632 target, snapshot, ..
9633 } => {
9634 if !supports_jump {
9635 return None;
9636 }
9637
9638 Some(
9639 h_flex()
9640 .px_2()
9641 .gap_2()
9642 .flex_1()
9643 .child(
9644 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9645 Icon::new(IconName::ZedPredictDown)
9646 } else {
9647 Icon::new(IconName::ZedPredictUp)
9648 },
9649 )
9650 .child(Label::new("Jump to Edit")),
9651 )
9652 }
9653 EditPrediction::MoveOutside { snapshot, .. } => {
9654 let file_name = snapshot
9655 .file()
9656 .map(|file| file.file_name(cx))
9657 .unwrap_or("untitled");
9658 Some(
9659 h_flex()
9660 .px_2()
9661 .gap_2()
9662 .flex_1()
9663 .child(Icon::new(IconName::ZedPredict))
9664 .child(Label::new(format!("Jump to {file_name}"))),
9665 )
9666 }
9667 EditPrediction::Edit {
9668 edits,
9669 edit_preview,
9670 snapshot,
9671 display_mode: _,
9672 } => {
9673 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9674
9675 let (highlighted_edits, has_more_lines) =
9676 if let Some(edit_preview) = edit_preview.as_ref() {
9677 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9678 .first_line_preview()
9679 } else {
9680 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9681 };
9682
9683 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9684 .with_default_highlights(&style.text, highlighted_edits.highlights);
9685
9686 let preview = h_flex()
9687 .gap_1()
9688 .min_w_16()
9689 .child(styled_text)
9690 .when(has_more_lines, |parent| parent.child("…"));
9691
9692 let left = if supports_jump && first_edit_row != cursor_point.row {
9693 render_relative_row_jump("", cursor_point.row, first_edit_row)
9694 .into_any_element()
9695 } else {
9696 let icon_name =
9697 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9698 Icon::new(icon_name).into_any_element()
9699 };
9700
9701 Some(
9702 h_flex()
9703 .h_full()
9704 .flex_1()
9705 .gap_2()
9706 .pr_1()
9707 .overflow_x_hidden()
9708 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9709 .child(left)
9710 .child(preview),
9711 )
9712 }
9713 }
9714 }
9715
9716 pub fn render_context_menu(
9717 &self,
9718 style: &EditorStyle,
9719 max_height_in_lines: u32,
9720 window: &mut Window,
9721 cx: &mut Context<Editor>,
9722 ) -> Option<AnyElement> {
9723 let menu = self.context_menu.borrow();
9724 let menu = menu.as_ref()?;
9725 if !menu.visible() {
9726 return None;
9727 };
9728 Some(menu.render(style, max_height_in_lines, window, cx))
9729 }
9730
9731 fn render_context_menu_aside(
9732 &mut self,
9733 max_size: Size<Pixels>,
9734 window: &mut Window,
9735 cx: &mut Context<Editor>,
9736 ) -> Option<AnyElement> {
9737 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9738 if menu.visible() {
9739 menu.render_aside(max_size, window, cx)
9740 } else {
9741 None
9742 }
9743 })
9744 }
9745
9746 fn hide_context_menu(
9747 &mut self,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> Option<CodeContextMenu> {
9751 cx.notify();
9752 self.completion_tasks.clear();
9753 let context_menu = self.context_menu.borrow_mut().take();
9754 self.stale_edit_prediction_in_menu.take();
9755 self.update_visible_edit_prediction(window, cx);
9756 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9757 && let Some(completion_provider) = &self.completion_provider
9758 {
9759 completion_provider.selection_changed(None, window, cx);
9760 }
9761 context_menu
9762 }
9763
9764 fn show_snippet_choices(
9765 &mut self,
9766 choices: &Vec<String>,
9767 selection: Range<Anchor>,
9768 cx: &mut Context<Self>,
9769 ) {
9770 let Some((_, buffer, _)) = self
9771 .buffer()
9772 .read(cx)
9773 .excerpt_containing(selection.start, cx)
9774 else {
9775 return;
9776 };
9777 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9778 else {
9779 return;
9780 };
9781 if buffer != end_buffer {
9782 log::error!("expected anchor range to have matching buffer IDs");
9783 return;
9784 }
9785
9786 let id = post_inc(&mut self.next_completion_id);
9787 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9788 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9789 CompletionsMenu::new_snippet_choices(
9790 id,
9791 true,
9792 choices,
9793 selection,
9794 buffer,
9795 snippet_sort_order,
9796 ),
9797 ));
9798 }
9799
9800 pub fn insert_snippet(
9801 &mut self,
9802 insertion_ranges: &[Range<usize>],
9803 snippet: Snippet,
9804 window: &mut Window,
9805 cx: &mut Context<Self>,
9806 ) -> Result<()> {
9807 struct Tabstop<T> {
9808 is_end_tabstop: bool,
9809 ranges: Vec<Range<T>>,
9810 choices: Option<Vec<String>>,
9811 }
9812
9813 let tabstops = self.buffer.update(cx, |buffer, cx| {
9814 let snippet_text: Arc<str> = snippet.text.clone().into();
9815 let edits = insertion_ranges
9816 .iter()
9817 .cloned()
9818 .map(|range| (range, snippet_text.clone()));
9819 let autoindent_mode = AutoindentMode::Block {
9820 original_indent_columns: Vec::new(),
9821 };
9822 buffer.edit(edits, Some(autoindent_mode), cx);
9823
9824 let snapshot = &*buffer.read(cx);
9825 let snippet = &snippet;
9826 snippet
9827 .tabstops
9828 .iter()
9829 .map(|tabstop| {
9830 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9831 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9832 });
9833 let mut tabstop_ranges = tabstop
9834 .ranges
9835 .iter()
9836 .flat_map(|tabstop_range| {
9837 let mut delta = 0_isize;
9838 insertion_ranges.iter().map(move |insertion_range| {
9839 let insertion_start = insertion_range.start as isize + delta;
9840 delta +=
9841 snippet.text.len() as isize - insertion_range.len() as isize;
9842
9843 let start = ((insertion_start + tabstop_range.start) as usize)
9844 .min(snapshot.len());
9845 let end = ((insertion_start + tabstop_range.end) as usize)
9846 .min(snapshot.len());
9847 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9848 })
9849 })
9850 .collect::<Vec<_>>();
9851 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9852
9853 Tabstop {
9854 is_end_tabstop,
9855 ranges: tabstop_ranges,
9856 choices: tabstop.choices.clone(),
9857 }
9858 })
9859 .collect::<Vec<_>>()
9860 });
9861 if let Some(tabstop) = tabstops.first() {
9862 self.change_selections(Default::default(), window, cx, |s| {
9863 // Reverse order so that the first range is the newest created selection.
9864 // Completions will use it and autoscroll will prioritize it.
9865 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9866 });
9867
9868 if let Some(choices) = &tabstop.choices
9869 && let Some(selection) = tabstop.ranges.first()
9870 {
9871 self.show_snippet_choices(choices, selection.clone(), cx)
9872 }
9873
9874 // If we're already at the last tabstop and it's at the end of the snippet,
9875 // we're done, we don't need to keep the state around.
9876 if !tabstop.is_end_tabstop {
9877 let choices = tabstops
9878 .iter()
9879 .map(|tabstop| tabstop.choices.clone())
9880 .collect();
9881
9882 let ranges = tabstops
9883 .into_iter()
9884 .map(|tabstop| tabstop.ranges)
9885 .collect::<Vec<_>>();
9886
9887 self.snippet_stack.push(SnippetState {
9888 active_index: 0,
9889 ranges,
9890 choices,
9891 });
9892 }
9893
9894 // Check whether the just-entered snippet ends with an auto-closable bracket.
9895 if self.autoclose_regions.is_empty() {
9896 let snapshot = self.buffer.read(cx).snapshot(cx);
9897 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9898 let selection_head = selection.head();
9899 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9900 continue;
9901 };
9902
9903 let mut bracket_pair = None;
9904 let max_lookup_length = scope
9905 .brackets()
9906 .map(|(pair, _)| {
9907 pair.start
9908 .as_str()
9909 .chars()
9910 .count()
9911 .max(pair.end.as_str().chars().count())
9912 })
9913 .max();
9914 if let Some(max_lookup_length) = max_lookup_length {
9915 let next_text = snapshot
9916 .chars_at(selection_head)
9917 .take(max_lookup_length)
9918 .collect::<String>();
9919 let prev_text = snapshot
9920 .reversed_chars_at(selection_head)
9921 .take(max_lookup_length)
9922 .collect::<String>();
9923
9924 for (pair, enabled) in scope.brackets() {
9925 if enabled
9926 && pair.close
9927 && prev_text.starts_with(pair.start.as_str())
9928 && next_text.starts_with(pair.end.as_str())
9929 {
9930 bracket_pair = Some(pair.clone());
9931 break;
9932 }
9933 }
9934 }
9935
9936 if let Some(pair) = bracket_pair {
9937 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9938 let autoclose_enabled =
9939 self.use_autoclose && snapshot_settings.use_autoclose;
9940 if autoclose_enabled {
9941 let start = snapshot.anchor_after(selection_head);
9942 let end = snapshot.anchor_after(selection_head);
9943 self.autoclose_regions.push(AutocloseRegion {
9944 selection_id: selection.id,
9945 range: start..end,
9946 pair,
9947 });
9948 }
9949 }
9950 }
9951 }
9952 }
9953 Ok(())
9954 }
9955
9956 pub fn move_to_next_snippet_tabstop(
9957 &mut self,
9958 window: &mut Window,
9959 cx: &mut Context<Self>,
9960 ) -> bool {
9961 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9962 }
9963
9964 pub fn move_to_prev_snippet_tabstop(
9965 &mut self,
9966 window: &mut Window,
9967 cx: &mut Context<Self>,
9968 ) -> bool {
9969 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9970 }
9971
9972 pub fn move_to_snippet_tabstop(
9973 &mut self,
9974 bias: Bias,
9975 window: &mut Window,
9976 cx: &mut Context<Self>,
9977 ) -> bool {
9978 if let Some(mut snippet) = self.snippet_stack.pop() {
9979 match bias {
9980 Bias::Left => {
9981 if snippet.active_index > 0 {
9982 snippet.active_index -= 1;
9983 } else {
9984 self.snippet_stack.push(snippet);
9985 return false;
9986 }
9987 }
9988 Bias::Right => {
9989 if snippet.active_index + 1 < snippet.ranges.len() {
9990 snippet.active_index += 1;
9991 } else {
9992 self.snippet_stack.push(snippet);
9993 return false;
9994 }
9995 }
9996 }
9997 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9998 self.change_selections(Default::default(), window, cx, |s| {
9999 // Reverse order so that the first range is the newest created selection.
10000 // Completions will use it and autoscroll will prioritize it.
10001 s.select_ranges(current_ranges.iter().rev().cloned())
10002 });
10003
10004 if let Some(choices) = &snippet.choices[snippet.active_index]
10005 && let Some(selection) = current_ranges.first()
10006 {
10007 self.show_snippet_choices(choices, selection.clone(), cx);
10008 }
10009
10010 // If snippet state is not at the last tabstop, push it back on the stack
10011 if snippet.active_index + 1 < snippet.ranges.len() {
10012 self.snippet_stack.push(snippet);
10013 }
10014 return true;
10015 }
10016 }
10017
10018 false
10019 }
10020
10021 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10022 self.transact(window, cx, |this, window, cx| {
10023 this.select_all(&SelectAll, window, cx);
10024 this.insert("", window, cx);
10025 });
10026 }
10027
10028 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10029 if self.read_only(cx) {
10030 return;
10031 }
10032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10033 self.transact(window, cx, |this, window, cx| {
10034 this.select_autoclose_pair(window, cx);
10035
10036 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10037
10038 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10039 if !this.linked_edit_ranges.is_empty() {
10040 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10041 let snapshot = this.buffer.read(cx).snapshot(cx);
10042
10043 for selection in selections.iter() {
10044 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10045 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10046 if selection_start.buffer_id != selection_end.buffer_id {
10047 continue;
10048 }
10049 if let Some(ranges) =
10050 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10051 {
10052 for (buffer, entries) in ranges {
10053 linked_ranges.entry(buffer).or_default().extend(entries);
10054 }
10055 }
10056 }
10057 }
10058
10059 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10060 for selection in &mut selections {
10061 if selection.is_empty() {
10062 let old_head = selection.head();
10063 let mut new_head =
10064 movement::left(&display_map, old_head.to_display_point(&display_map))
10065 .to_point(&display_map);
10066 if let Some((buffer, line_buffer_range)) = display_map
10067 .buffer_snapshot()
10068 .buffer_line_for_row(MultiBufferRow(old_head.row))
10069 {
10070 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10071 let indent_len = match indent_size.kind {
10072 IndentKind::Space => {
10073 buffer.settings_at(line_buffer_range.start, cx).tab_size
10074 }
10075 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10076 };
10077 if old_head.column <= indent_size.len && old_head.column > 0 {
10078 let indent_len = indent_len.get();
10079 new_head = cmp::min(
10080 new_head,
10081 MultiBufferPoint::new(
10082 old_head.row,
10083 ((old_head.column - 1) / indent_len) * indent_len,
10084 ),
10085 );
10086 }
10087 }
10088
10089 selection.set_head(new_head, SelectionGoal::None);
10090 }
10091 }
10092
10093 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10094 this.insert("", window, cx);
10095 let empty_str: Arc<str> = Arc::from("");
10096 for (buffer, edits) in linked_ranges {
10097 let snapshot = buffer.read(cx).snapshot();
10098 use text::ToPoint as TP;
10099
10100 let edits = edits
10101 .into_iter()
10102 .map(|range| {
10103 let end_point = TP::to_point(&range.end, &snapshot);
10104 let mut start_point = TP::to_point(&range.start, &snapshot);
10105
10106 if end_point == start_point {
10107 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10108 .saturating_sub(1);
10109 start_point =
10110 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10111 };
10112
10113 (start_point..end_point, empty_str.clone())
10114 })
10115 .sorted_by_key(|(range, _)| range.start)
10116 .collect::<Vec<_>>();
10117 buffer.update(cx, |this, cx| {
10118 this.edit(edits, None, cx);
10119 })
10120 }
10121 this.refresh_edit_prediction(true, false, window, cx);
10122 refresh_linked_ranges(this, window, cx);
10123 });
10124 }
10125
10126 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10127 if self.read_only(cx) {
10128 return;
10129 }
10130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10131 self.transact(window, cx, |this, window, cx| {
10132 this.change_selections(Default::default(), window, cx, |s| {
10133 s.move_with(|map, selection| {
10134 if selection.is_empty() {
10135 let cursor = movement::right(map, selection.head());
10136 selection.end = cursor;
10137 selection.reversed = true;
10138 selection.goal = SelectionGoal::None;
10139 }
10140 })
10141 });
10142 this.insert("", window, cx);
10143 this.refresh_edit_prediction(true, false, window, cx);
10144 });
10145 }
10146
10147 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10148 if self.mode.is_single_line() {
10149 cx.propagate();
10150 return;
10151 }
10152
10153 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10154 if self.move_to_prev_snippet_tabstop(window, cx) {
10155 return;
10156 }
10157 self.outdent(&Outdent, window, cx);
10158 }
10159
10160 pub fn next_snippet_tabstop(
10161 &mut self,
10162 _: &NextSnippetTabstop,
10163 window: &mut Window,
10164 cx: &mut Context<Self>,
10165 ) {
10166 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10167 cx.propagate();
10168 return;
10169 }
10170
10171 if self.move_to_next_snippet_tabstop(window, cx) {
10172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10173 return;
10174 }
10175 cx.propagate();
10176 }
10177
10178 pub fn previous_snippet_tabstop(
10179 &mut self,
10180 _: &PreviousSnippetTabstop,
10181 window: &mut Window,
10182 cx: &mut Context<Self>,
10183 ) {
10184 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10185 cx.propagate();
10186 return;
10187 }
10188
10189 if self.move_to_prev_snippet_tabstop(window, cx) {
10190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10191 return;
10192 }
10193 cx.propagate();
10194 }
10195
10196 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10197 if self.mode.is_single_line() {
10198 cx.propagate();
10199 return;
10200 }
10201
10202 if self.move_to_next_snippet_tabstop(window, cx) {
10203 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10204 return;
10205 }
10206 if self.read_only(cx) {
10207 return;
10208 }
10209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10210 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10211 let buffer = self.buffer.read(cx);
10212 let snapshot = buffer.snapshot(cx);
10213 let rows_iter = selections.iter().map(|s| s.head().row);
10214 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10215
10216 let has_some_cursor_in_whitespace = selections
10217 .iter()
10218 .filter(|selection| selection.is_empty())
10219 .any(|selection| {
10220 let cursor = selection.head();
10221 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10222 cursor.column < current_indent.len
10223 });
10224
10225 let mut edits = Vec::new();
10226 let mut prev_edited_row = 0;
10227 let mut row_delta = 0;
10228 for selection in &mut selections {
10229 if selection.start.row != prev_edited_row {
10230 row_delta = 0;
10231 }
10232 prev_edited_row = selection.end.row;
10233
10234 // If the selection is non-empty, then increase the indentation of the selected lines.
10235 if !selection.is_empty() {
10236 row_delta =
10237 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10238 continue;
10239 }
10240
10241 let cursor = selection.head();
10242 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10243 if let Some(suggested_indent) =
10244 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10245 {
10246 // Don't do anything if already at suggested indent
10247 // and there is any other cursor which is not
10248 if has_some_cursor_in_whitespace
10249 && cursor.column == current_indent.len
10250 && current_indent.len == suggested_indent.len
10251 {
10252 continue;
10253 }
10254
10255 // Adjust line and move cursor to suggested indent
10256 // if cursor is not at suggested indent
10257 if cursor.column < suggested_indent.len
10258 && cursor.column <= current_indent.len
10259 && current_indent.len <= suggested_indent.len
10260 {
10261 selection.start = Point::new(cursor.row, suggested_indent.len);
10262 selection.end = selection.start;
10263 if row_delta == 0 {
10264 edits.extend(Buffer::edit_for_indent_size_adjustment(
10265 cursor.row,
10266 current_indent,
10267 suggested_indent,
10268 ));
10269 row_delta = suggested_indent.len - current_indent.len;
10270 }
10271 continue;
10272 }
10273
10274 // If current indent is more than suggested indent
10275 // only move cursor to current indent and skip indent
10276 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10277 selection.start = Point::new(cursor.row, current_indent.len);
10278 selection.end = selection.start;
10279 continue;
10280 }
10281 }
10282
10283 // Otherwise, insert a hard or soft tab.
10284 let settings = buffer.language_settings_at(cursor, cx);
10285 let tab_size = if settings.hard_tabs {
10286 IndentSize::tab()
10287 } else {
10288 let tab_size = settings.tab_size.get();
10289 let indent_remainder = snapshot
10290 .text_for_range(Point::new(cursor.row, 0)..cursor)
10291 .flat_map(str::chars)
10292 .fold(row_delta % tab_size, |counter: u32, c| {
10293 if c == '\t' {
10294 0
10295 } else {
10296 (counter + 1) % tab_size
10297 }
10298 });
10299
10300 let chars_to_next_tab_stop = tab_size - indent_remainder;
10301 IndentSize::spaces(chars_to_next_tab_stop)
10302 };
10303 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10304 selection.end = selection.start;
10305 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10306 row_delta += tab_size.len;
10307 }
10308
10309 self.transact(window, cx, |this, window, cx| {
10310 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10311 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10312 this.refresh_edit_prediction(true, false, window, cx);
10313 });
10314 }
10315
10316 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10317 if self.read_only(cx) {
10318 return;
10319 }
10320 if self.mode.is_single_line() {
10321 cx.propagate();
10322 return;
10323 }
10324
10325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10326 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10327 let mut prev_edited_row = 0;
10328 let mut row_delta = 0;
10329 let mut edits = Vec::new();
10330 let buffer = self.buffer.read(cx);
10331 let snapshot = buffer.snapshot(cx);
10332 for selection in &mut selections {
10333 if selection.start.row != prev_edited_row {
10334 row_delta = 0;
10335 }
10336 prev_edited_row = selection.end.row;
10337
10338 row_delta =
10339 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10340 }
10341
10342 self.transact(window, cx, |this, window, cx| {
10343 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10344 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10345 });
10346 }
10347
10348 fn indent_selection(
10349 buffer: &MultiBuffer,
10350 snapshot: &MultiBufferSnapshot,
10351 selection: &mut Selection<Point>,
10352 edits: &mut Vec<(Range<Point>, String)>,
10353 delta_for_start_row: u32,
10354 cx: &App,
10355 ) -> u32 {
10356 let settings = buffer.language_settings_at(selection.start, cx);
10357 let tab_size = settings.tab_size.get();
10358 let indent_kind = if settings.hard_tabs {
10359 IndentKind::Tab
10360 } else {
10361 IndentKind::Space
10362 };
10363 let mut start_row = selection.start.row;
10364 let mut end_row = selection.end.row + 1;
10365
10366 // If a selection ends at the beginning of a line, don't indent
10367 // that last line.
10368 if selection.end.column == 0 && selection.end.row > selection.start.row {
10369 end_row -= 1;
10370 }
10371
10372 // Avoid re-indenting a row that has already been indented by a
10373 // previous selection, but still update this selection's column
10374 // to reflect that indentation.
10375 if delta_for_start_row > 0 {
10376 start_row += 1;
10377 selection.start.column += delta_for_start_row;
10378 if selection.end.row == selection.start.row {
10379 selection.end.column += delta_for_start_row;
10380 }
10381 }
10382
10383 let mut delta_for_end_row = 0;
10384 let has_multiple_rows = start_row + 1 != end_row;
10385 for row in start_row..end_row {
10386 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10387 let indent_delta = match (current_indent.kind, indent_kind) {
10388 (IndentKind::Space, IndentKind::Space) => {
10389 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10390 IndentSize::spaces(columns_to_next_tab_stop)
10391 }
10392 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10393 (_, IndentKind::Tab) => IndentSize::tab(),
10394 };
10395
10396 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10397 0
10398 } else {
10399 selection.start.column
10400 };
10401 let row_start = Point::new(row, start);
10402 edits.push((
10403 row_start..row_start,
10404 indent_delta.chars().collect::<String>(),
10405 ));
10406
10407 // Update this selection's endpoints to reflect the indentation.
10408 if row == selection.start.row {
10409 selection.start.column += indent_delta.len;
10410 }
10411 if row == selection.end.row {
10412 selection.end.column += indent_delta.len;
10413 delta_for_end_row = indent_delta.len;
10414 }
10415 }
10416
10417 if selection.start.row == selection.end.row {
10418 delta_for_start_row + delta_for_end_row
10419 } else {
10420 delta_for_end_row
10421 }
10422 }
10423
10424 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10425 if self.read_only(cx) {
10426 return;
10427 }
10428 if self.mode.is_single_line() {
10429 cx.propagate();
10430 return;
10431 }
10432
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10435 let selections = self.selections.all::<Point>(&display_map);
10436 let mut deletion_ranges = Vec::new();
10437 let mut last_outdent = None;
10438 {
10439 let buffer = self.buffer.read(cx);
10440 let snapshot = buffer.snapshot(cx);
10441 for selection in &selections {
10442 let settings = buffer.language_settings_at(selection.start, cx);
10443 let tab_size = settings.tab_size.get();
10444 let mut rows = selection.spanned_rows(false, &display_map);
10445
10446 // Avoid re-outdenting a row that has already been outdented by a
10447 // previous selection.
10448 if let Some(last_row) = last_outdent
10449 && last_row == rows.start
10450 {
10451 rows.start = rows.start.next_row();
10452 }
10453 let has_multiple_rows = rows.len() > 1;
10454 for row in rows.iter_rows() {
10455 let indent_size = snapshot.indent_size_for_line(row);
10456 if indent_size.len > 0 {
10457 let deletion_len = match indent_size.kind {
10458 IndentKind::Space => {
10459 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10460 if columns_to_prev_tab_stop == 0 {
10461 tab_size
10462 } else {
10463 columns_to_prev_tab_stop
10464 }
10465 }
10466 IndentKind::Tab => 1,
10467 };
10468 let start = if has_multiple_rows
10469 || deletion_len > selection.start.column
10470 || indent_size.len < selection.start.column
10471 {
10472 0
10473 } else {
10474 selection.start.column - deletion_len
10475 };
10476 deletion_ranges.push(
10477 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10478 );
10479 last_outdent = Some(row);
10480 }
10481 }
10482 }
10483 }
10484
10485 self.transact(window, cx, |this, window, cx| {
10486 this.buffer.update(cx, |buffer, cx| {
10487 let empty_str: Arc<str> = Arc::default();
10488 buffer.edit(
10489 deletion_ranges
10490 .into_iter()
10491 .map(|range| (range, empty_str.clone())),
10492 None,
10493 cx,
10494 );
10495 });
10496 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10497 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10498 });
10499 }
10500
10501 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10502 if self.read_only(cx) {
10503 return;
10504 }
10505 if self.mode.is_single_line() {
10506 cx.propagate();
10507 return;
10508 }
10509
10510 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10511 let selections = self
10512 .selections
10513 .all::<usize>(&self.display_snapshot(cx))
10514 .into_iter()
10515 .map(|s| s.range());
10516
10517 self.transact(window, cx, |this, window, cx| {
10518 this.buffer.update(cx, |buffer, cx| {
10519 buffer.autoindent_ranges(selections, cx);
10520 });
10521 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10522 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10523 });
10524 }
10525
10526 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10529 let selections = self.selections.all::<Point>(&display_map);
10530
10531 let mut new_cursors = Vec::new();
10532 let mut edit_ranges = Vec::new();
10533 let mut selections = selections.iter().peekable();
10534 while let Some(selection) = selections.next() {
10535 let mut rows = selection.spanned_rows(false, &display_map);
10536
10537 // Accumulate contiguous regions of rows that we want to delete.
10538 while let Some(next_selection) = selections.peek() {
10539 let next_rows = next_selection.spanned_rows(false, &display_map);
10540 if next_rows.start <= rows.end {
10541 rows.end = next_rows.end;
10542 selections.next().unwrap();
10543 } else {
10544 break;
10545 }
10546 }
10547
10548 let buffer = display_map.buffer_snapshot();
10549 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10550 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10551 // If there's a line after the range, delete the \n from the end of the row range
10552 (
10553 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10554 rows.end,
10555 )
10556 } else {
10557 // If there isn't a line after the range, delete the \n from the line before the
10558 // start of the row range
10559 edit_start = edit_start.saturating_sub(1);
10560 (buffer.len(), rows.start.previous_row())
10561 };
10562
10563 let text_layout_details = self.text_layout_details(window);
10564 let x = display_map.x_for_display_point(
10565 selection.head().to_display_point(&display_map),
10566 &text_layout_details,
10567 );
10568 let row = Point::new(target_row.0, 0)
10569 .to_display_point(&display_map)
10570 .row();
10571 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10572
10573 new_cursors.push((
10574 selection.id,
10575 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10576 SelectionGoal::None,
10577 ));
10578 edit_ranges.push(edit_start..edit_end);
10579 }
10580
10581 self.transact(window, cx, |this, window, cx| {
10582 let buffer = this.buffer.update(cx, |buffer, cx| {
10583 let empty_str: Arc<str> = Arc::default();
10584 buffer.edit(
10585 edit_ranges
10586 .into_iter()
10587 .map(|range| (range, empty_str.clone())),
10588 None,
10589 cx,
10590 );
10591 buffer.snapshot(cx)
10592 });
10593 let new_selections = new_cursors
10594 .into_iter()
10595 .map(|(id, cursor, goal)| {
10596 let cursor = cursor.to_point(&buffer);
10597 Selection {
10598 id,
10599 start: cursor,
10600 end: cursor,
10601 reversed: false,
10602 goal,
10603 }
10604 })
10605 .collect();
10606
10607 this.change_selections(Default::default(), window, cx, |s| {
10608 s.select(new_selections);
10609 });
10610 });
10611 }
10612
10613 pub fn join_lines_impl(
10614 &mut self,
10615 insert_whitespace: bool,
10616 window: &mut Window,
10617 cx: &mut Context<Self>,
10618 ) {
10619 if self.read_only(cx) {
10620 return;
10621 }
10622 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10623 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10624 let start = MultiBufferRow(selection.start.row);
10625 // Treat single line selections as if they include the next line. Otherwise this action
10626 // would do nothing for single line selections individual cursors.
10627 let end = if selection.start.row == selection.end.row {
10628 MultiBufferRow(selection.start.row + 1)
10629 } else {
10630 MultiBufferRow(selection.end.row)
10631 };
10632
10633 if let Some(last_row_range) = row_ranges.last_mut()
10634 && start <= last_row_range.end
10635 {
10636 last_row_range.end = end;
10637 continue;
10638 }
10639 row_ranges.push(start..end);
10640 }
10641
10642 let snapshot = self.buffer.read(cx).snapshot(cx);
10643 let mut cursor_positions = Vec::new();
10644 for row_range in &row_ranges {
10645 let anchor = snapshot.anchor_before(Point::new(
10646 row_range.end.previous_row().0,
10647 snapshot.line_len(row_range.end.previous_row()),
10648 ));
10649 cursor_positions.push(anchor..anchor);
10650 }
10651
10652 self.transact(window, cx, |this, window, cx| {
10653 for row_range in row_ranges.into_iter().rev() {
10654 for row in row_range.iter_rows().rev() {
10655 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10656 let next_line_row = row.next_row();
10657 let indent = snapshot.indent_size_for_line(next_line_row);
10658 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10659
10660 let replace =
10661 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10662 " "
10663 } else {
10664 ""
10665 };
10666
10667 this.buffer.update(cx, |buffer, cx| {
10668 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10669 });
10670 }
10671 }
10672
10673 this.change_selections(Default::default(), window, cx, |s| {
10674 s.select_anchor_ranges(cursor_positions)
10675 });
10676 });
10677 }
10678
10679 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10680 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10681 self.join_lines_impl(true, window, cx);
10682 }
10683
10684 pub fn sort_lines_case_sensitive(
10685 &mut self,
10686 _: &SortLinesCaseSensitive,
10687 window: &mut Window,
10688 cx: &mut Context<Self>,
10689 ) {
10690 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10691 }
10692
10693 pub fn sort_lines_by_length(
10694 &mut self,
10695 _: &SortLinesByLength,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) {
10699 self.manipulate_immutable_lines(window, cx, |lines| {
10700 lines.sort_by_key(|&line| line.chars().count())
10701 })
10702 }
10703
10704 pub fn sort_lines_case_insensitive(
10705 &mut self,
10706 _: &SortLinesCaseInsensitive,
10707 window: &mut Window,
10708 cx: &mut Context<Self>,
10709 ) {
10710 self.manipulate_immutable_lines(window, cx, |lines| {
10711 lines.sort_by_key(|line| line.to_lowercase())
10712 })
10713 }
10714
10715 pub fn unique_lines_case_insensitive(
10716 &mut self,
10717 _: &UniqueLinesCaseInsensitive,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 self.manipulate_immutable_lines(window, cx, |lines| {
10722 let mut seen = HashSet::default();
10723 lines.retain(|line| seen.insert(line.to_lowercase()));
10724 })
10725 }
10726
10727 pub fn unique_lines_case_sensitive(
10728 &mut self,
10729 _: &UniqueLinesCaseSensitive,
10730 window: &mut Window,
10731 cx: &mut Context<Self>,
10732 ) {
10733 self.manipulate_immutable_lines(window, cx, |lines| {
10734 let mut seen = HashSet::default();
10735 lines.retain(|line| seen.insert(*line));
10736 })
10737 }
10738
10739 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10740 let snapshot = self.buffer.read(cx).snapshot(cx);
10741 for selection in self.selections.disjoint_anchors_arc().iter() {
10742 if snapshot
10743 .language_at(selection.start)
10744 .and_then(|lang| lang.config().wrap_characters.as_ref())
10745 .is_some()
10746 {
10747 return true;
10748 }
10749 }
10750 false
10751 }
10752
10753 fn wrap_selections_in_tag(
10754 &mut self,
10755 _: &WrapSelectionsInTag,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10760
10761 let snapshot = self.buffer.read(cx).snapshot(cx);
10762
10763 let mut edits = Vec::new();
10764 let mut boundaries = Vec::new();
10765
10766 for selection in self
10767 .selections
10768 .all_adjusted(&self.display_snapshot(cx))
10769 .iter()
10770 {
10771 let Some(wrap_config) = snapshot
10772 .language_at(selection.start)
10773 .and_then(|lang| lang.config().wrap_characters.clone())
10774 else {
10775 continue;
10776 };
10777
10778 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10779 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10780
10781 let start_before = snapshot.anchor_before(selection.start);
10782 let end_after = snapshot.anchor_after(selection.end);
10783
10784 edits.push((start_before..start_before, open_tag));
10785 edits.push((end_after..end_after, close_tag));
10786
10787 boundaries.push((
10788 start_before,
10789 end_after,
10790 wrap_config.start_prefix.len(),
10791 wrap_config.end_suffix.len(),
10792 ));
10793 }
10794
10795 if edits.is_empty() {
10796 return;
10797 }
10798
10799 self.transact(window, cx, |this, window, cx| {
10800 let buffer = this.buffer.update(cx, |buffer, cx| {
10801 buffer.edit(edits, None, cx);
10802 buffer.snapshot(cx)
10803 });
10804
10805 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10806 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10807 boundaries.into_iter()
10808 {
10809 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10810 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10811 new_selections.push(open_offset..open_offset);
10812 new_selections.push(close_offset..close_offset);
10813 }
10814
10815 this.change_selections(Default::default(), window, cx, |s| {
10816 s.select_ranges(new_selections);
10817 });
10818
10819 this.request_autoscroll(Autoscroll::fit(), cx);
10820 });
10821 }
10822
10823 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10824 let Some(project) = self.project.clone() else {
10825 return;
10826 };
10827 self.reload(project, window, cx)
10828 .detach_and_notify_err(window, cx);
10829 }
10830
10831 pub fn restore_file(
10832 &mut self,
10833 _: &::git::RestoreFile,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 ) {
10837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10838 let mut buffer_ids = HashSet::default();
10839 let snapshot = self.buffer().read(cx).snapshot(cx);
10840 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10841 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10842 }
10843
10844 let buffer = self.buffer().read(cx);
10845 let ranges = buffer_ids
10846 .into_iter()
10847 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10848 .collect::<Vec<_>>();
10849
10850 self.restore_hunks_in_ranges(ranges, window, cx);
10851 }
10852
10853 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10855 let selections = self
10856 .selections
10857 .all(&self.display_snapshot(cx))
10858 .into_iter()
10859 .map(|s| s.range())
10860 .collect();
10861 self.restore_hunks_in_ranges(selections, window, cx);
10862 }
10863
10864 pub fn restore_hunks_in_ranges(
10865 &mut self,
10866 ranges: Vec<Range<Point>>,
10867 window: &mut Window,
10868 cx: &mut Context<Editor>,
10869 ) {
10870 let mut revert_changes = HashMap::default();
10871 let chunk_by = self
10872 .snapshot(window, cx)
10873 .hunks_for_ranges(ranges)
10874 .into_iter()
10875 .chunk_by(|hunk| hunk.buffer_id);
10876 for (buffer_id, hunks) in &chunk_by {
10877 let hunks = hunks.collect::<Vec<_>>();
10878 for hunk in &hunks {
10879 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10880 }
10881 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10882 }
10883 drop(chunk_by);
10884 if !revert_changes.is_empty() {
10885 self.transact(window, cx, |editor, window, cx| {
10886 editor.restore(revert_changes, window, cx);
10887 });
10888 }
10889 }
10890
10891 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10892 if let Some(status) = self
10893 .addons
10894 .iter()
10895 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10896 {
10897 return Some(status);
10898 }
10899 self.project
10900 .as_ref()?
10901 .read(cx)
10902 .status_for_buffer_id(buffer_id, cx)
10903 }
10904
10905 pub fn open_active_item_in_terminal(
10906 &mut self,
10907 _: &OpenInTerminal,
10908 window: &mut Window,
10909 cx: &mut Context<Self>,
10910 ) {
10911 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10912 let project_path = buffer.read(cx).project_path(cx)?;
10913 let project = self.project()?.read(cx);
10914 let entry = project.entry_for_path(&project_path, cx)?;
10915 let parent = match &entry.canonical_path {
10916 Some(canonical_path) => canonical_path.to_path_buf(),
10917 None => project.absolute_path(&project_path, cx)?,
10918 }
10919 .parent()?
10920 .to_path_buf();
10921 Some(parent)
10922 }) {
10923 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10924 }
10925 }
10926
10927 fn set_breakpoint_context_menu(
10928 &mut self,
10929 display_row: DisplayRow,
10930 position: Option<Anchor>,
10931 clicked_point: gpui::Point<Pixels>,
10932 window: &mut Window,
10933 cx: &mut Context<Self>,
10934 ) {
10935 let source = self
10936 .buffer
10937 .read(cx)
10938 .snapshot(cx)
10939 .anchor_before(Point::new(display_row.0, 0u32));
10940
10941 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10942
10943 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10944 self,
10945 source,
10946 clicked_point,
10947 context_menu,
10948 window,
10949 cx,
10950 );
10951 }
10952
10953 fn add_edit_breakpoint_block(
10954 &mut self,
10955 anchor: Anchor,
10956 breakpoint: &Breakpoint,
10957 edit_action: BreakpointPromptEditAction,
10958 window: &mut Window,
10959 cx: &mut Context<Self>,
10960 ) {
10961 let weak_editor = cx.weak_entity();
10962 let bp_prompt = cx.new(|cx| {
10963 BreakpointPromptEditor::new(
10964 weak_editor,
10965 anchor,
10966 breakpoint.clone(),
10967 edit_action,
10968 window,
10969 cx,
10970 )
10971 });
10972
10973 let height = bp_prompt.update(cx, |this, cx| {
10974 this.prompt
10975 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10976 });
10977 let cloned_prompt = bp_prompt.clone();
10978 let blocks = vec![BlockProperties {
10979 style: BlockStyle::Sticky,
10980 placement: BlockPlacement::Above(anchor),
10981 height: Some(height),
10982 render: Arc::new(move |cx| {
10983 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10984 cloned_prompt.clone().into_any_element()
10985 }),
10986 priority: 0,
10987 }];
10988
10989 let focus_handle = bp_prompt.focus_handle(cx);
10990 window.focus(&focus_handle);
10991
10992 let block_ids = self.insert_blocks(blocks, None, cx);
10993 bp_prompt.update(cx, |prompt, _| {
10994 prompt.add_block_ids(block_ids);
10995 });
10996 }
10997
10998 pub(crate) fn breakpoint_at_row(
10999 &self,
11000 row: u32,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) -> Option<(Anchor, Breakpoint)> {
11004 let snapshot = self.snapshot(window, cx);
11005 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11006
11007 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11008 }
11009
11010 pub(crate) fn breakpoint_at_anchor(
11011 &self,
11012 breakpoint_position: Anchor,
11013 snapshot: &EditorSnapshot,
11014 cx: &mut Context<Self>,
11015 ) -> Option<(Anchor, Breakpoint)> {
11016 let buffer = self
11017 .buffer
11018 .read(cx)
11019 .buffer_for_anchor(breakpoint_position, cx)?;
11020
11021 let enclosing_excerpt = breakpoint_position.excerpt_id;
11022 let buffer_snapshot = buffer.read(cx).snapshot();
11023
11024 let row = buffer_snapshot
11025 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11026 .row;
11027
11028 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11029 let anchor_end = snapshot
11030 .buffer_snapshot()
11031 .anchor_after(Point::new(row, line_len));
11032
11033 self.breakpoint_store
11034 .as_ref()?
11035 .read_with(cx, |breakpoint_store, cx| {
11036 breakpoint_store
11037 .breakpoints(
11038 &buffer,
11039 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11040 &buffer_snapshot,
11041 cx,
11042 )
11043 .next()
11044 .and_then(|(bp, _)| {
11045 let breakpoint_row = buffer_snapshot
11046 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11047 .row;
11048
11049 if breakpoint_row == row {
11050 snapshot
11051 .buffer_snapshot()
11052 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11053 .map(|position| (position, bp.bp.clone()))
11054 } else {
11055 None
11056 }
11057 })
11058 })
11059 }
11060
11061 pub fn edit_log_breakpoint(
11062 &mut self,
11063 _: &EditLogBreakpoint,
11064 window: &mut Window,
11065 cx: &mut Context<Self>,
11066 ) {
11067 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11068 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11069 message: None,
11070 state: BreakpointState::Enabled,
11071 condition: None,
11072 hit_condition: None,
11073 });
11074
11075 self.add_edit_breakpoint_block(
11076 anchor,
11077 &breakpoint,
11078 BreakpointPromptEditAction::Log,
11079 window,
11080 cx,
11081 );
11082 }
11083 }
11084
11085 fn breakpoints_at_cursors(
11086 &self,
11087 window: &mut Window,
11088 cx: &mut Context<Self>,
11089 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11090 let snapshot = self.snapshot(window, cx);
11091 let cursors = self
11092 .selections
11093 .disjoint_anchors_arc()
11094 .iter()
11095 .map(|selection| {
11096 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11097
11098 let breakpoint_position = self
11099 .breakpoint_at_row(cursor_position.row, window, cx)
11100 .map(|bp| bp.0)
11101 .unwrap_or_else(|| {
11102 snapshot
11103 .display_snapshot
11104 .buffer_snapshot()
11105 .anchor_after(Point::new(cursor_position.row, 0))
11106 });
11107
11108 let breakpoint = self
11109 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11110 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11111
11112 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11113 })
11114 // 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.
11115 .collect::<HashMap<Anchor, _>>();
11116
11117 cursors.into_iter().collect()
11118 }
11119
11120 pub fn enable_breakpoint(
11121 &mut self,
11122 _: &crate::actions::EnableBreakpoint,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11127 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11128 continue;
11129 };
11130 self.edit_breakpoint_at_anchor(
11131 anchor,
11132 breakpoint,
11133 BreakpointEditAction::InvertState,
11134 cx,
11135 );
11136 }
11137 }
11138
11139 pub fn disable_breakpoint(
11140 &mut self,
11141 _: &crate::actions::DisableBreakpoint,
11142 window: &mut Window,
11143 cx: &mut Context<Self>,
11144 ) {
11145 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11146 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11147 continue;
11148 };
11149 self.edit_breakpoint_at_anchor(
11150 anchor,
11151 breakpoint,
11152 BreakpointEditAction::InvertState,
11153 cx,
11154 );
11155 }
11156 }
11157
11158 pub fn toggle_breakpoint(
11159 &mut self,
11160 _: &crate::actions::ToggleBreakpoint,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11165 if let Some(breakpoint) = breakpoint {
11166 self.edit_breakpoint_at_anchor(
11167 anchor,
11168 breakpoint,
11169 BreakpointEditAction::Toggle,
11170 cx,
11171 );
11172 } else {
11173 self.edit_breakpoint_at_anchor(
11174 anchor,
11175 Breakpoint::new_standard(),
11176 BreakpointEditAction::Toggle,
11177 cx,
11178 );
11179 }
11180 }
11181 }
11182
11183 pub fn edit_breakpoint_at_anchor(
11184 &mut self,
11185 breakpoint_position: Anchor,
11186 breakpoint: Breakpoint,
11187 edit_action: BreakpointEditAction,
11188 cx: &mut Context<Self>,
11189 ) {
11190 let Some(breakpoint_store) = &self.breakpoint_store else {
11191 return;
11192 };
11193
11194 let Some(buffer) = self
11195 .buffer
11196 .read(cx)
11197 .buffer_for_anchor(breakpoint_position, cx)
11198 else {
11199 return;
11200 };
11201
11202 breakpoint_store.update(cx, |breakpoint_store, cx| {
11203 breakpoint_store.toggle_breakpoint(
11204 buffer,
11205 BreakpointWithPosition {
11206 position: breakpoint_position.text_anchor,
11207 bp: breakpoint,
11208 },
11209 edit_action,
11210 cx,
11211 );
11212 });
11213
11214 cx.notify();
11215 }
11216
11217 #[cfg(any(test, feature = "test-support"))]
11218 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11219 self.breakpoint_store.clone()
11220 }
11221
11222 pub fn prepare_restore_change(
11223 &self,
11224 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11225 hunk: &MultiBufferDiffHunk,
11226 cx: &mut App,
11227 ) -> Option<()> {
11228 if hunk.is_created_file() {
11229 return None;
11230 }
11231 let buffer = self.buffer.read(cx);
11232 let diff = buffer.diff_for(hunk.buffer_id)?;
11233 let buffer = buffer.buffer(hunk.buffer_id)?;
11234 let buffer = buffer.read(cx);
11235 let original_text = diff
11236 .read(cx)
11237 .base_text()
11238 .as_rope()
11239 .slice(hunk.diff_base_byte_range.clone());
11240 let buffer_snapshot = buffer.snapshot();
11241 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11242 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11243 probe
11244 .0
11245 .start
11246 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11247 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11248 }) {
11249 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11250 Some(())
11251 } else {
11252 None
11253 }
11254 }
11255
11256 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11257 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11258 }
11259
11260 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11261 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11262 }
11263
11264 fn manipulate_lines<M>(
11265 &mut self,
11266 window: &mut Window,
11267 cx: &mut Context<Self>,
11268 mut manipulate: M,
11269 ) where
11270 M: FnMut(&str) -> LineManipulationResult,
11271 {
11272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11273
11274 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11275 let buffer = self.buffer.read(cx).snapshot(cx);
11276
11277 let mut edits = Vec::new();
11278
11279 let selections = self.selections.all::<Point>(&display_map);
11280 let mut selections = selections.iter().peekable();
11281 let mut contiguous_row_selections = Vec::new();
11282 let mut new_selections = Vec::new();
11283 let mut added_lines = 0;
11284 let mut removed_lines = 0;
11285
11286 while let Some(selection) = selections.next() {
11287 let (start_row, end_row) = consume_contiguous_rows(
11288 &mut contiguous_row_selections,
11289 selection,
11290 &display_map,
11291 &mut selections,
11292 );
11293
11294 let start_point = Point::new(start_row.0, 0);
11295 let end_point = Point::new(
11296 end_row.previous_row().0,
11297 buffer.line_len(end_row.previous_row()),
11298 );
11299 let text = buffer
11300 .text_for_range(start_point..end_point)
11301 .collect::<String>();
11302
11303 let LineManipulationResult {
11304 new_text,
11305 line_count_before,
11306 line_count_after,
11307 } = manipulate(&text);
11308
11309 edits.push((start_point..end_point, new_text));
11310
11311 // Selections must change based on added and removed line count
11312 let start_row =
11313 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11314 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11315 new_selections.push(Selection {
11316 id: selection.id,
11317 start: start_row,
11318 end: end_row,
11319 goal: SelectionGoal::None,
11320 reversed: selection.reversed,
11321 });
11322
11323 if line_count_after > line_count_before {
11324 added_lines += line_count_after - line_count_before;
11325 } else if line_count_before > line_count_after {
11326 removed_lines += line_count_before - line_count_after;
11327 }
11328 }
11329
11330 self.transact(window, cx, |this, window, cx| {
11331 let buffer = this.buffer.update(cx, |buffer, cx| {
11332 buffer.edit(edits, None, cx);
11333 buffer.snapshot(cx)
11334 });
11335
11336 // Recalculate offsets on newly edited buffer
11337 let new_selections = new_selections
11338 .iter()
11339 .map(|s| {
11340 let start_point = Point::new(s.start.0, 0);
11341 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11342 Selection {
11343 id: s.id,
11344 start: buffer.point_to_offset(start_point),
11345 end: buffer.point_to_offset(end_point),
11346 goal: s.goal,
11347 reversed: s.reversed,
11348 }
11349 })
11350 .collect();
11351
11352 this.change_selections(Default::default(), window, cx, |s| {
11353 s.select(new_selections);
11354 });
11355
11356 this.request_autoscroll(Autoscroll::fit(), cx);
11357 });
11358 }
11359
11360 fn manipulate_immutable_lines<Fn>(
11361 &mut self,
11362 window: &mut Window,
11363 cx: &mut Context<Self>,
11364 mut callback: Fn,
11365 ) where
11366 Fn: FnMut(&mut Vec<&str>),
11367 {
11368 self.manipulate_lines(window, cx, |text| {
11369 let mut lines: Vec<&str> = text.split('\n').collect();
11370 let line_count_before = lines.len();
11371
11372 callback(&mut lines);
11373
11374 LineManipulationResult {
11375 new_text: lines.join("\n"),
11376 line_count_before,
11377 line_count_after: lines.len(),
11378 }
11379 });
11380 }
11381
11382 fn manipulate_mutable_lines<Fn>(
11383 &mut self,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 mut callback: Fn,
11387 ) where
11388 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11389 {
11390 self.manipulate_lines(window, cx, |text| {
11391 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11392 let line_count_before = lines.len();
11393
11394 callback(&mut lines);
11395
11396 LineManipulationResult {
11397 new_text: lines.join("\n"),
11398 line_count_before,
11399 line_count_after: lines.len(),
11400 }
11401 });
11402 }
11403
11404 pub fn convert_indentation_to_spaces(
11405 &mut self,
11406 _: &ConvertIndentationToSpaces,
11407 window: &mut Window,
11408 cx: &mut Context<Self>,
11409 ) {
11410 let settings = self.buffer.read(cx).language_settings(cx);
11411 let tab_size = settings.tab_size.get() as usize;
11412
11413 self.manipulate_mutable_lines(window, cx, |lines| {
11414 // Allocates a reasonably sized scratch buffer once for the whole loop
11415 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11416 // Avoids recomputing spaces that could be inserted many times
11417 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11418 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11419 .collect();
11420
11421 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11422 let mut chars = line.as_ref().chars();
11423 let mut col = 0;
11424 let mut changed = false;
11425
11426 for ch in chars.by_ref() {
11427 match ch {
11428 ' ' => {
11429 reindented_line.push(' ');
11430 col += 1;
11431 }
11432 '\t' => {
11433 // \t are converted to spaces depending on the current column
11434 let spaces_len = tab_size - (col % tab_size);
11435 reindented_line.extend(&space_cache[spaces_len - 1]);
11436 col += spaces_len;
11437 changed = true;
11438 }
11439 _ => {
11440 // If we dont append before break, the character is consumed
11441 reindented_line.push(ch);
11442 break;
11443 }
11444 }
11445 }
11446
11447 if !changed {
11448 reindented_line.clear();
11449 continue;
11450 }
11451 // Append the rest of the line and replace old reference with new one
11452 reindented_line.extend(chars);
11453 *line = Cow::Owned(reindented_line.clone());
11454 reindented_line.clear();
11455 }
11456 });
11457 }
11458
11459 pub fn convert_indentation_to_tabs(
11460 &mut self,
11461 _: &ConvertIndentationToTabs,
11462 window: &mut Window,
11463 cx: &mut Context<Self>,
11464 ) {
11465 let settings = self.buffer.read(cx).language_settings(cx);
11466 let tab_size = settings.tab_size.get() as usize;
11467
11468 self.manipulate_mutable_lines(window, cx, |lines| {
11469 // Allocates a reasonably sized buffer once for the whole loop
11470 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11471 // Avoids recomputing spaces that could be inserted many times
11472 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11473 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11474 .collect();
11475
11476 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11477 let mut chars = line.chars();
11478 let mut spaces_count = 0;
11479 let mut first_non_indent_char = None;
11480 let mut changed = false;
11481
11482 for ch in chars.by_ref() {
11483 match ch {
11484 ' ' => {
11485 // Keep track of spaces. Append \t when we reach tab_size
11486 spaces_count += 1;
11487 changed = true;
11488 if spaces_count == tab_size {
11489 reindented_line.push('\t');
11490 spaces_count = 0;
11491 }
11492 }
11493 '\t' => {
11494 reindented_line.push('\t');
11495 spaces_count = 0;
11496 }
11497 _ => {
11498 // Dont append it yet, we might have remaining spaces
11499 first_non_indent_char = Some(ch);
11500 break;
11501 }
11502 }
11503 }
11504
11505 if !changed {
11506 reindented_line.clear();
11507 continue;
11508 }
11509 // Remaining spaces that didn't make a full tab stop
11510 if spaces_count > 0 {
11511 reindented_line.extend(&space_cache[spaces_count - 1]);
11512 }
11513 // If we consume an extra character that was not indentation, add it back
11514 if let Some(extra_char) = first_non_indent_char {
11515 reindented_line.push(extra_char);
11516 }
11517 // Append the rest of the line and replace old reference with new one
11518 reindented_line.extend(chars);
11519 *line = Cow::Owned(reindented_line.clone());
11520 reindented_line.clear();
11521 }
11522 });
11523 }
11524
11525 pub fn convert_to_upper_case(
11526 &mut self,
11527 _: &ConvertToUpperCase,
11528 window: &mut Window,
11529 cx: &mut Context<Self>,
11530 ) {
11531 self.manipulate_text(window, cx, |text| text.to_uppercase())
11532 }
11533
11534 pub fn convert_to_lower_case(
11535 &mut self,
11536 _: &ConvertToLowerCase,
11537 window: &mut Window,
11538 cx: &mut Context<Self>,
11539 ) {
11540 self.manipulate_text(window, cx, |text| text.to_lowercase())
11541 }
11542
11543 pub fn convert_to_title_case(
11544 &mut self,
11545 _: &ConvertToTitleCase,
11546 window: &mut Window,
11547 cx: &mut Context<Self>,
11548 ) {
11549 self.manipulate_text(window, cx, |text| {
11550 text.split('\n')
11551 .map(|line| line.to_case(Case::Title))
11552 .join("\n")
11553 })
11554 }
11555
11556 pub fn convert_to_snake_case(
11557 &mut self,
11558 _: &ConvertToSnakeCase,
11559 window: &mut Window,
11560 cx: &mut Context<Self>,
11561 ) {
11562 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11563 }
11564
11565 pub fn convert_to_kebab_case(
11566 &mut self,
11567 _: &ConvertToKebabCase,
11568 window: &mut Window,
11569 cx: &mut Context<Self>,
11570 ) {
11571 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11572 }
11573
11574 pub fn convert_to_upper_camel_case(
11575 &mut self,
11576 _: &ConvertToUpperCamelCase,
11577 window: &mut Window,
11578 cx: &mut Context<Self>,
11579 ) {
11580 self.manipulate_text(window, cx, |text| {
11581 text.split('\n')
11582 .map(|line| line.to_case(Case::UpperCamel))
11583 .join("\n")
11584 })
11585 }
11586
11587 pub fn convert_to_lower_camel_case(
11588 &mut self,
11589 _: &ConvertToLowerCamelCase,
11590 window: &mut Window,
11591 cx: &mut Context<Self>,
11592 ) {
11593 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11594 }
11595
11596 pub fn convert_to_opposite_case(
11597 &mut self,
11598 _: &ConvertToOppositeCase,
11599 window: &mut Window,
11600 cx: &mut Context<Self>,
11601 ) {
11602 self.manipulate_text(window, cx, |text| {
11603 text.chars()
11604 .fold(String::with_capacity(text.len()), |mut t, c| {
11605 if c.is_uppercase() {
11606 t.extend(c.to_lowercase());
11607 } else {
11608 t.extend(c.to_uppercase());
11609 }
11610 t
11611 })
11612 })
11613 }
11614
11615 pub fn convert_to_sentence_case(
11616 &mut self,
11617 _: &ConvertToSentenceCase,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11622 }
11623
11624 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11625 self.manipulate_text(window, cx, |text| {
11626 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11627 if has_upper_case_characters {
11628 text.to_lowercase()
11629 } else {
11630 text.to_uppercase()
11631 }
11632 })
11633 }
11634
11635 pub fn convert_to_rot13(
11636 &mut self,
11637 _: &ConvertToRot13,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 self.manipulate_text(window, cx, |text| {
11642 text.chars()
11643 .map(|c| match c {
11644 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11645 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11646 _ => c,
11647 })
11648 .collect()
11649 })
11650 }
11651
11652 pub fn convert_to_rot47(
11653 &mut self,
11654 _: &ConvertToRot47,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.manipulate_text(window, cx, |text| {
11659 text.chars()
11660 .map(|c| {
11661 let code_point = c as u32;
11662 if code_point >= 33 && code_point <= 126 {
11663 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11664 }
11665 c
11666 })
11667 .collect()
11668 })
11669 }
11670
11671 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11672 where
11673 Fn: FnMut(&str) -> String,
11674 {
11675 let buffer = self.buffer.read(cx).snapshot(cx);
11676
11677 let mut new_selections = Vec::new();
11678 let mut edits = Vec::new();
11679 let mut selection_adjustment = 0i32;
11680
11681 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11682 let selection_is_empty = selection.is_empty();
11683
11684 let (start, end) = if selection_is_empty {
11685 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11686 (word_range.start, word_range.end)
11687 } else {
11688 (
11689 buffer.point_to_offset(selection.start),
11690 buffer.point_to_offset(selection.end),
11691 )
11692 };
11693
11694 let text = buffer.text_for_range(start..end).collect::<String>();
11695 let old_length = text.len() as i32;
11696 let text = callback(&text);
11697
11698 new_selections.push(Selection {
11699 start: (start as i32 - selection_adjustment) as usize,
11700 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11701 goal: SelectionGoal::None,
11702 id: selection.id,
11703 reversed: selection.reversed,
11704 });
11705
11706 selection_adjustment += old_length - text.len() as i32;
11707
11708 edits.push((start..end, text));
11709 }
11710
11711 self.transact(window, cx, |this, window, cx| {
11712 this.buffer.update(cx, |buffer, cx| {
11713 buffer.edit(edits, None, cx);
11714 });
11715
11716 this.change_selections(Default::default(), window, cx, |s| {
11717 s.select(new_selections);
11718 });
11719
11720 this.request_autoscroll(Autoscroll::fit(), cx);
11721 });
11722 }
11723
11724 pub fn move_selection_on_drop(
11725 &mut self,
11726 selection: &Selection<Anchor>,
11727 target: DisplayPoint,
11728 is_cut: bool,
11729 window: &mut Window,
11730 cx: &mut Context<Self>,
11731 ) {
11732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11733 let buffer = display_map.buffer_snapshot();
11734 let mut edits = Vec::new();
11735 let insert_point = display_map
11736 .clip_point(target, Bias::Left)
11737 .to_point(&display_map);
11738 let text = buffer
11739 .text_for_range(selection.start..selection.end)
11740 .collect::<String>();
11741 if is_cut {
11742 edits.push(((selection.start..selection.end), String::new()));
11743 }
11744 let insert_anchor = buffer.anchor_before(insert_point);
11745 edits.push(((insert_anchor..insert_anchor), text));
11746 let last_edit_start = insert_anchor.bias_left(buffer);
11747 let last_edit_end = insert_anchor.bias_right(buffer);
11748 self.transact(window, cx, |this, window, cx| {
11749 this.buffer.update(cx, |buffer, cx| {
11750 buffer.edit(edits, None, cx);
11751 });
11752 this.change_selections(Default::default(), window, cx, |s| {
11753 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11754 });
11755 });
11756 }
11757
11758 pub fn clear_selection_drag_state(&mut self) {
11759 self.selection_drag_state = SelectionDragState::None;
11760 }
11761
11762 pub fn duplicate(
11763 &mut self,
11764 upwards: bool,
11765 whole_lines: bool,
11766 window: &mut Window,
11767 cx: &mut Context<Self>,
11768 ) {
11769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11770
11771 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11772 let buffer = display_map.buffer_snapshot();
11773 let selections = self.selections.all::<Point>(&display_map);
11774
11775 let mut edits = Vec::new();
11776 let mut selections_iter = selections.iter().peekable();
11777 while let Some(selection) = selections_iter.next() {
11778 let mut rows = selection.spanned_rows(false, &display_map);
11779 // duplicate line-wise
11780 if whole_lines || selection.start == selection.end {
11781 // Avoid duplicating the same lines twice.
11782 while let Some(next_selection) = selections_iter.peek() {
11783 let next_rows = next_selection.spanned_rows(false, &display_map);
11784 if next_rows.start < rows.end {
11785 rows.end = next_rows.end;
11786 selections_iter.next().unwrap();
11787 } else {
11788 break;
11789 }
11790 }
11791
11792 // Copy the text from the selected row region and splice it either at the start
11793 // or end of the region.
11794 let start = Point::new(rows.start.0, 0);
11795 let end = Point::new(
11796 rows.end.previous_row().0,
11797 buffer.line_len(rows.end.previous_row()),
11798 );
11799
11800 let mut text = buffer.text_for_range(start..end).collect::<String>();
11801
11802 let insert_location = if upwards {
11803 // When duplicating upward, we need to insert before the current line.
11804 // If we're on the last line and it doesn't end with a newline,
11805 // we need to add a newline before the duplicated content.
11806 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11807 && buffer.max_point().column > 0
11808 && !text.ends_with('\n');
11809
11810 if needs_leading_newline {
11811 text.insert(0, '\n');
11812 end
11813 } else {
11814 text.push('\n');
11815 Point::new(rows.start.0, 0)
11816 }
11817 } else {
11818 text.push('\n');
11819 start
11820 };
11821 edits.push((insert_location..insert_location, text));
11822 } else {
11823 // duplicate character-wise
11824 let start = selection.start;
11825 let end = selection.end;
11826 let text = buffer.text_for_range(start..end).collect::<String>();
11827 edits.push((selection.end..selection.end, text));
11828 }
11829 }
11830
11831 self.transact(window, cx, |this, window, cx| {
11832 this.buffer.update(cx, |buffer, cx| {
11833 buffer.edit(edits, None, cx);
11834 });
11835
11836 // When duplicating upward with whole lines, move the cursor to the duplicated line
11837 if upwards && whole_lines {
11838 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11839
11840 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11841 let mut new_ranges = Vec::new();
11842 let selections = s.all::<Point>(&display_map);
11843 let mut selections_iter = selections.iter().peekable();
11844
11845 while let Some(first_selection) = selections_iter.next() {
11846 // Group contiguous selections together to find the total row span
11847 let mut group_selections = vec![first_selection];
11848 let mut rows = first_selection.spanned_rows(false, &display_map);
11849
11850 while let Some(next_selection) = selections_iter.peek() {
11851 let next_rows = next_selection.spanned_rows(false, &display_map);
11852 if next_rows.start < rows.end {
11853 rows.end = next_rows.end;
11854 group_selections.push(selections_iter.next().unwrap());
11855 } else {
11856 break;
11857 }
11858 }
11859
11860 let row_count = rows.end.0 - rows.start.0;
11861
11862 // Move all selections in this group up by the total number of duplicated rows
11863 for selection in group_selections {
11864 let new_start = Point::new(
11865 selection.start.row.saturating_sub(row_count),
11866 selection.start.column,
11867 );
11868
11869 let new_end = Point::new(
11870 selection.end.row.saturating_sub(row_count),
11871 selection.end.column,
11872 );
11873
11874 new_ranges.push(new_start..new_end);
11875 }
11876 }
11877
11878 s.select_ranges(new_ranges);
11879 });
11880 }
11881
11882 this.request_autoscroll(Autoscroll::fit(), cx);
11883 });
11884 }
11885
11886 pub fn duplicate_line_up(
11887 &mut self,
11888 _: &DuplicateLineUp,
11889 window: &mut Window,
11890 cx: &mut Context<Self>,
11891 ) {
11892 self.duplicate(true, true, window, cx);
11893 }
11894
11895 pub fn duplicate_line_down(
11896 &mut self,
11897 _: &DuplicateLineDown,
11898 window: &mut Window,
11899 cx: &mut Context<Self>,
11900 ) {
11901 self.duplicate(false, true, window, cx);
11902 }
11903
11904 pub fn duplicate_selection(
11905 &mut self,
11906 _: &DuplicateSelection,
11907 window: &mut Window,
11908 cx: &mut Context<Self>,
11909 ) {
11910 self.duplicate(false, false, window, cx);
11911 }
11912
11913 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11914 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11915 if self.mode.is_single_line() {
11916 cx.propagate();
11917 return;
11918 }
11919
11920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11921 let buffer = self.buffer.read(cx).snapshot(cx);
11922
11923 let mut edits = Vec::new();
11924 let mut unfold_ranges = Vec::new();
11925 let mut refold_creases = Vec::new();
11926
11927 let selections = self.selections.all::<Point>(&display_map);
11928 let mut selections = selections.iter().peekable();
11929 let mut contiguous_row_selections = Vec::new();
11930 let mut new_selections = Vec::new();
11931
11932 while let Some(selection) = selections.next() {
11933 // Find all the selections that span a contiguous row range
11934 let (start_row, end_row) = consume_contiguous_rows(
11935 &mut contiguous_row_selections,
11936 selection,
11937 &display_map,
11938 &mut selections,
11939 );
11940
11941 // Move the text spanned by the row range to be before the line preceding the row range
11942 if start_row.0 > 0 {
11943 let range_to_move = Point::new(
11944 start_row.previous_row().0,
11945 buffer.line_len(start_row.previous_row()),
11946 )
11947 ..Point::new(
11948 end_row.previous_row().0,
11949 buffer.line_len(end_row.previous_row()),
11950 );
11951 let insertion_point = display_map
11952 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11953 .0;
11954
11955 // Don't move lines across excerpts
11956 if buffer
11957 .excerpt_containing(insertion_point..range_to_move.end)
11958 .is_some()
11959 {
11960 let text = buffer
11961 .text_for_range(range_to_move.clone())
11962 .flat_map(|s| s.chars())
11963 .skip(1)
11964 .chain(['\n'])
11965 .collect::<String>();
11966
11967 edits.push((
11968 buffer.anchor_after(range_to_move.start)
11969 ..buffer.anchor_before(range_to_move.end),
11970 String::new(),
11971 ));
11972 let insertion_anchor = buffer.anchor_after(insertion_point);
11973 edits.push((insertion_anchor..insertion_anchor, text));
11974
11975 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11976
11977 // Move selections up
11978 new_selections.extend(contiguous_row_selections.drain(..).map(
11979 |mut selection| {
11980 selection.start.row -= row_delta;
11981 selection.end.row -= row_delta;
11982 selection
11983 },
11984 ));
11985
11986 // Move folds up
11987 unfold_ranges.push(range_to_move.clone());
11988 for fold in display_map.folds_in_range(
11989 buffer.anchor_before(range_to_move.start)
11990 ..buffer.anchor_after(range_to_move.end),
11991 ) {
11992 let mut start = fold.range.start.to_point(&buffer);
11993 let mut end = fold.range.end.to_point(&buffer);
11994 start.row -= row_delta;
11995 end.row -= row_delta;
11996 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11997 }
11998 }
11999 }
12000
12001 // If we didn't move line(s), preserve the existing selections
12002 new_selections.append(&mut contiguous_row_selections);
12003 }
12004
12005 self.transact(window, cx, |this, window, cx| {
12006 this.unfold_ranges(&unfold_ranges, true, true, cx);
12007 this.buffer.update(cx, |buffer, cx| {
12008 for (range, text) in edits {
12009 buffer.edit([(range, text)], None, cx);
12010 }
12011 });
12012 this.fold_creases(refold_creases, true, window, cx);
12013 this.change_selections(Default::default(), window, cx, |s| {
12014 s.select(new_selections);
12015 })
12016 });
12017 }
12018
12019 pub fn move_line_down(
12020 &mut self,
12021 _: &MoveLineDown,
12022 window: &mut Window,
12023 cx: &mut Context<Self>,
12024 ) {
12025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12026 if self.mode.is_single_line() {
12027 cx.propagate();
12028 return;
12029 }
12030
12031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12032 let buffer = self.buffer.read(cx).snapshot(cx);
12033
12034 let mut edits = Vec::new();
12035 let mut unfold_ranges = Vec::new();
12036 let mut refold_creases = Vec::new();
12037
12038 let selections = self.selections.all::<Point>(&display_map);
12039 let mut selections = selections.iter().peekable();
12040 let mut contiguous_row_selections = Vec::new();
12041 let mut new_selections = Vec::new();
12042
12043 while let Some(selection) = selections.next() {
12044 // Find all the selections that span a contiguous row range
12045 let (start_row, end_row) = consume_contiguous_rows(
12046 &mut contiguous_row_selections,
12047 selection,
12048 &display_map,
12049 &mut selections,
12050 );
12051
12052 // Move the text spanned by the row range to be after the last line of the row range
12053 if end_row.0 <= buffer.max_point().row {
12054 let range_to_move =
12055 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12056 let insertion_point = display_map
12057 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12058 .0;
12059
12060 // Don't move lines across excerpt boundaries
12061 if buffer
12062 .excerpt_containing(range_to_move.start..insertion_point)
12063 .is_some()
12064 {
12065 let mut text = String::from("\n");
12066 text.extend(buffer.text_for_range(range_to_move.clone()));
12067 text.pop(); // Drop trailing newline
12068 edits.push((
12069 buffer.anchor_after(range_to_move.start)
12070 ..buffer.anchor_before(range_to_move.end),
12071 String::new(),
12072 ));
12073 let insertion_anchor = buffer.anchor_after(insertion_point);
12074 edits.push((insertion_anchor..insertion_anchor, text));
12075
12076 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12077
12078 // Move selections down
12079 new_selections.extend(contiguous_row_selections.drain(..).map(
12080 |mut selection| {
12081 selection.start.row += row_delta;
12082 selection.end.row += row_delta;
12083 selection
12084 },
12085 ));
12086
12087 // Move folds down
12088 unfold_ranges.push(range_to_move.clone());
12089 for fold in display_map.folds_in_range(
12090 buffer.anchor_before(range_to_move.start)
12091 ..buffer.anchor_after(range_to_move.end),
12092 ) {
12093 let mut start = fold.range.start.to_point(&buffer);
12094 let mut end = fold.range.end.to_point(&buffer);
12095 start.row += row_delta;
12096 end.row += row_delta;
12097 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12098 }
12099 }
12100 }
12101
12102 // If we didn't move line(s), preserve the existing selections
12103 new_selections.append(&mut contiguous_row_selections);
12104 }
12105
12106 self.transact(window, cx, |this, window, cx| {
12107 this.unfold_ranges(&unfold_ranges, true, true, cx);
12108 this.buffer.update(cx, |buffer, cx| {
12109 for (range, text) in edits {
12110 buffer.edit([(range, text)], None, cx);
12111 }
12112 });
12113 this.fold_creases(refold_creases, true, window, cx);
12114 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12115 });
12116 }
12117
12118 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12120 let text_layout_details = &self.text_layout_details(window);
12121 self.transact(window, cx, |this, window, cx| {
12122 let edits = this.change_selections(Default::default(), window, cx, |s| {
12123 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12124 s.move_with(|display_map, selection| {
12125 if !selection.is_empty() {
12126 return;
12127 }
12128
12129 let mut head = selection.head();
12130 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12131 if head.column() == display_map.line_len(head.row()) {
12132 transpose_offset = display_map
12133 .buffer_snapshot()
12134 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12135 }
12136
12137 if transpose_offset == 0 {
12138 return;
12139 }
12140
12141 *head.column_mut() += 1;
12142 head = display_map.clip_point(head, Bias::Right);
12143 let goal = SelectionGoal::HorizontalPosition(
12144 display_map
12145 .x_for_display_point(head, text_layout_details)
12146 .into(),
12147 );
12148 selection.collapse_to(head, goal);
12149
12150 let transpose_start = display_map
12151 .buffer_snapshot()
12152 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12153 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12154 let transpose_end = display_map
12155 .buffer_snapshot()
12156 .clip_offset(transpose_offset + 1, Bias::Right);
12157 if let Some(ch) = display_map
12158 .buffer_snapshot()
12159 .chars_at(transpose_start)
12160 .next()
12161 {
12162 edits.push((transpose_start..transpose_offset, String::new()));
12163 edits.push((transpose_end..transpose_end, ch.to_string()));
12164 }
12165 }
12166 });
12167 edits
12168 });
12169 this.buffer
12170 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12171 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12172 this.change_selections(Default::default(), window, cx, |s| {
12173 s.select(selections);
12174 });
12175 });
12176 }
12177
12178 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12180 if self.mode.is_single_line() {
12181 cx.propagate();
12182 return;
12183 }
12184
12185 self.rewrap_impl(RewrapOptions::default(), cx)
12186 }
12187
12188 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12189 let buffer = self.buffer.read(cx).snapshot(cx);
12190 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12191
12192 #[derive(Clone, Debug, PartialEq)]
12193 enum CommentFormat {
12194 /// single line comment, with prefix for line
12195 Line(String),
12196 /// single line within a block comment, with prefix for line
12197 BlockLine(String),
12198 /// a single line of a block comment that includes the initial delimiter
12199 BlockCommentWithStart(BlockCommentConfig),
12200 /// a single line of a block comment that includes the ending delimiter
12201 BlockCommentWithEnd(BlockCommentConfig),
12202 }
12203
12204 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12205 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12206 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12207 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12208 .peekable();
12209
12210 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12211 row
12212 } else {
12213 return Vec::new();
12214 };
12215
12216 let language_settings = buffer.language_settings_at(selection.head(), cx);
12217 let language_scope = buffer.language_scope_at(selection.head());
12218
12219 let indent_and_prefix_for_row =
12220 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12221 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12222 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12223 &language_scope
12224 {
12225 let indent_end = Point::new(row, indent.len);
12226 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12227 let line_text_after_indent = buffer
12228 .text_for_range(indent_end..line_end)
12229 .collect::<String>();
12230
12231 let is_within_comment_override = buffer
12232 .language_scope_at(indent_end)
12233 .is_some_and(|scope| scope.override_name() == Some("comment"));
12234 let comment_delimiters = if is_within_comment_override {
12235 // we are within a comment syntax node, but we don't
12236 // yet know what kind of comment: block, doc or line
12237 match (
12238 language_scope.documentation_comment(),
12239 language_scope.block_comment(),
12240 ) {
12241 (Some(config), _) | (_, Some(config))
12242 if buffer.contains_str_at(indent_end, &config.start) =>
12243 {
12244 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12245 }
12246 (Some(config), _) | (_, Some(config))
12247 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12248 {
12249 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12250 }
12251 (Some(config), _) | (_, Some(config))
12252 if buffer.contains_str_at(indent_end, &config.prefix) =>
12253 {
12254 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12255 }
12256 (_, _) => language_scope
12257 .line_comment_prefixes()
12258 .iter()
12259 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12260 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12261 }
12262 } else {
12263 // we not in an overridden comment node, but we may
12264 // be within a non-overridden line comment node
12265 language_scope
12266 .line_comment_prefixes()
12267 .iter()
12268 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12269 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12270 };
12271
12272 let rewrap_prefix = language_scope
12273 .rewrap_prefixes()
12274 .iter()
12275 .find_map(|prefix_regex| {
12276 prefix_regex.find(&line_text_after_indent).map(|mat| {
12277 if mat.start() == 0 {
12278 Some(mat.as_str().to_string())
12279 } else {
12280 None
12281 }
12282 })
12283 })
12284 .flatten();
12285 (comment_delimiters, rewrap_prefix)
12286 } else {
12287 (None, None)
12288 };
12289 (indent, comment_prefix, rewrap_prefix)
12290 };
12291
12292 let mut ranges = Vec::new();
12293 let from_empty_selection = selection.is_empty();
12294
12295 let mut current_range_start = first_row;
12296 let mut prev_row = first_row;
12297 let (
12298 mut current_range_indent,
12299 mut current_range_comment_delimiters,
12300 mut current_range_rewrap_prefix,
12301 ) = indent_and_prefix_for_row(first_row);
12302
12303 for row in non_blank_rows_iter.skip(1) {
12304 let has_paragraph_break = row > prev_row + 1;
12305
12306 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12307 indent_and_prefix_for_row(row);
12308
12309 let has_indent_change = row_indent != current_range_indent;
12310 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12311
12312 let has_boundary_change = has_comment_change
12313 || row_rewrap_prefix.is_some()
12314 || (has_indent_change && current_range_comment_delimiters.is_some());
12315
12316 if has_paragraph_break || has_boundary_change {
12317 ranges.push((
12318 language_settings.clone(),
12319 Point::new(current_range_start, 0)
12320 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12321 current_range_indent,
12322 current_range_comment_delimiters.clone(),
12323 current_range_rewrap_prefix.clone(),
12324 from_empty_selection,
12325 ));
12326 current_range_start = row;
12327 current_range_indent = row_indent;
12328 current_range_comment_delimiters = row_comment_delimiters;
12329 current_range_rewrap_prefix = row_rewrap_prefix;
12330 }
12331 prev_row = row;
12332 }
12333
12334 ranges.push((
12335 language_settings.clone(),
12336 Point::new(current_range_start, 0)
12337 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12338 current_range_indent,
12339 current_range_comment_delimiters,
12340 current_range_rewrap_prefix,
12341 from_empty_selection,
12342 ));
12343
12344 ranges
12345 });
12346
12347 let mut edits = Vec::new();
12348 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12349
12350 for (
12351 language_settings,
12352 wrap_range,
12353 mut indent_size,
12354 comment_prefix,
12355 rewrap_prefix,
12356 from_empty_selection,
12357 ) in wrap_ranges
12358 {
12359 let mut start_row = wrap_range.start.row;
12360 let mut end_row = wrap_range.end.row;
12361
12362 // Skip selections that overlap with a range that has already been rewrapped.
12363 let selection_range = start_row..end_row;
12364 if rewrapped_row_ranges
12365 .iter()
12366 .any(|range| range.overlaps(&selection_range))
12367 {
12368 continue;
12369 }
12370
12371 let tab_size = language_settings.tab_size;
12372
12373 let (line_prefix, inside_comment) = match &comment_prefix {
12374 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12375 (Some(prefix.as_str()), true)
12376 }
12377 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12378 (Some(prefix.as_ref()), true)
12379 }
12380 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12381 start: _,
12382 end: _,
12383 prefix,
12384 tab_size,
12385 })) => {
12386 indent_size.len += tab_size;
12387 (Some(prefix.as_ref()), true)
12388 }
12389 None => (None, false),
12390 };
12391 let indent_prefix = indent_size.chars().collect::<String>();
12392 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12393
12394 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12395 RewrapBehavior::InComments => inside_comment,
12396 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12397 RewrapBehavior::Anywhere => true,
12398 };
12399
12400 let should_rewrap = options.override_language_settings
12401 || allow_rewrap_based_on_language
12402 || self.hard_wrap.is_some();
12403 if !should_rewrap {
12404 continue;
12405 }
12406
12407 if from_empty_selection {
12408 'expand_upwards: while start_row > 0 {
12409 let prev_row = start_row - 1;
12410 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12411 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12412 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12413 {
12414 start_row = prev_row;
12415 } else {
12416 break 'expand_upwards;
12417 }
12418 }
12419
12420 'expand_downwards: while end_row < buffer.max_point().row {
12421 let next_row = end_row + 1;
12422 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12423 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12424 && !buffer.is_line_blank(MultiBufferRow(next_row))
12425 {
12426 end_row = next_row;
12427 } else {
12428 break 'expand_downwards;
12429 }
12430 }
12431 }
12432
12433 let start = Point::new(start_row, 0);
12434 let start_offset = ToOffset::to_offset(&start, &buffer);
12435 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12436 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12437 let mut first_line_delimiter = None;
12438 let mut last_line_delimiter = None;
12439 let Some(lines_without_prefixes) = selection_text
12440 .lines()
12441 .enumerate()
12442 .map(|(ix, line)| {
12443 let line_trimmed = line.trim_start();
12444 if rewrap_prefix.is_some() && ix > 0 {
12445 Ok(line_trimmed)
12446 } else if let Some(
12447 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12448 start,
12449 prefix,
12450 end,
12451 tab_size,
12452 })
12453 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12454 start,
12455 prefix,
12456 end,
12457 tab_size,
12458 }),
12459 ) = &comment_prefix
12460 {
12461 let line_trimmed = line_trimmed
12462 .strip_prefix(start.as_ref())
12463 .map(|s| {
12464 let mut indent_size = indent_size;
12465 indent_size.len -= tab_size;
12466 let indent_prefix: String = indent_size.chars().collect();
12467 first_line_delimiter = Some((indent_prefix, start));
12468 s.trim_start()
12469 })
12470 .unwrap_or(line_trimmed);
12471 let line_trimmed = line_trimmed
12472 .strip_suffix(end.as_ref())
12473 .map(|s| {
12474 last_line_delimiter = Some(end);
12475 s.trim_end()
12476 })
12477 .unwrap_or(line_trimmed);
12478 let line_trimmed = line_trimmed
12479 .strip_prefix(prefix.as_ref())
12480 .unwrap_or(line_trimmed);
12481 Ok(line_trimmed)
12482 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12483 line_trimmed.strip_prefix(prefix).with_context(|| {
12484 format!("line did not start with prefix {prefix:?}: {line:?}")
12485 })
12486 } else {
12487 line_trimmed
12488 .strip_prefix(&line_prefix.trim_start())
12489 .with_context(|| {
12490 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12491 })
12492 }
12493 })
12494 .collect::<Result<Vec<_>, _>>()
12495 .log_err()
12496 else {
12497 continue;
12498 };
12499
12500 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12501 buffer
12502 .language_settings_at(Point::new(start_row, 0), cx)
12503 .preferred_line_length as usize
12504 });
12505
12506 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12507 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12508 } else {
12509 line_prefix.clone()
12510 };
12511
12512 let wrapped_text = {
12513 let mut wrapped_text = wrap_with_prefix(
12514 line_prefix,
12515 subsequent_lines_prefix,
12516 lines_without_prefixes.join("\n"),
12517 wrap_column,
12518 tab_size,
12519 options.preserve_existing_whitespace,
12520 );
12521
12522 if let Some((indent, delimiter)) = first_line_delimiter {
12523 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12524 }
12525 if let Some(last_line) = last_line_delimiter {
12526 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12527 }
12528
12529 wrapped_text
12530 };
12531
12532 // TODO: should always use char-based diff while still supporting cursor behavior that
12533 // matches vim.
12534 let mut diff_options = DiffOptions::default();
12535 if options.override_language_settings {
12536 diff_options.max_word_diff_len = 0;
12537 diff_options.max_word_diff_line_count = 0;
12538 } else {
12539 diff_options.max_word_diff_len = usize::MAX;
12540 diff_options.max_word_diff_line_count = usize::MAX;
12541 }
12542
12543 for (old_range, new_text) in
12544 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12545 {
12546 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12547 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12548 edits.push((edit_start..edit_end, new_text));
12549 }
12550
12551 rewrapped_row_ranges.push(start_row..=end_row);
12552 }
12553
12554 self.buffer
12555 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12556 }
12557
12558 pub fn cut_common(
12559 &mut self,
12560 cut_no_selection_line: bool,
12561 window: &mut Window,
12562 cx: &mut Context<Self>,
12563 ) -> ClipboardItem {
12564 let mut text = String::new();
12565 let buffer = self.buffer.read(cx).snapshot(cx);
12566 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12567 let mut clipboard_selections = Vec::with_capacity(selections.len());
12568 {
12569 let max_point = buffer.max_point();
12570 let mut is_first = true;
12571 for selection in &mut selections {
12572 let is_entire_line =
12573 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12574 if is_entire_line {
12575 selection.start = Point::new(selection.start.row, 0);
12576 if !selection.is_empty() && selection.end.column == 0 {
12577 selection.end = cmp::min(max_point, selection.end);
12578 } else {
12579 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12580 }
12581 selection.goal = SelectionGoal::None;
12582 }
12583 if is_first {
12584 is_first = false;
12585 } else {
12586 text += "\n";
12587 }
12588 let mut len = 0;
12589 for chunk in buffer.text_for_range(selection.start..selection.end) {
12590 text.push_str(chunk);
12591 len += chunk.len();
12592 }
12593 clipboard_selections.push(ClipboardSelection {
12594 len,
12595 is_entire_line,
12596 first_line_indent: buffer
12597 .indent_size_for_line(MultiBufferRow(selection.start.row))
12598 .len,
12599 });
12600 }
12601 }
12602
12603 self.transact(window, cx, |this, window, cx| {
12604 this.change_selections(Default::default(), window, cx, |s| {
12605 s.select(selections);
12606 });
12607 this.insert("", window, cx);
12608 });
12609 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12610 }
12611
12612 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12614 let item = self.cut_common(true, window, cx);
12615 cx.write_to_clipboard(item);
12616 }
12617
12618 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12619 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12620 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12621 s.move_with(|snapshot, sel| {
12622 if sel.is_empty() {
12623 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12624 }
12625 if sel.is_empty() {
12626 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12627 }
12628 });
12629 });
12630 let item = self.cut_common(false, window, cx);
12631 cx.set_global(KillRing(item))
12632 }
12633
12634 pub fn kill_ring_yank(
12635 &mut self,
12636 _: &KillRingYank,
12637 window: &mut Window,
12638 cx: &mut Context<Self>,
12639 ) {
12640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12641 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12642 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12643 (kill_ring.text().to_string(), kill_ring.metadata_json())
12644 } else {
12645 return;
12646 }
12647 } else {
12648 return;
12649 };
12650 self.do_paste(&text, metadata, false, window, cx);
12651 }
12652
12653 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12654 self.do_copy(true, cx);
12655 }
12656
12657 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12658 self.do_copy(false, cx);
12659 }
12660
12661 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12662 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12663 let buffer = self.buffer.read(cx).read(cx);
12664 let mut text = String::new();
12665
12666 let mut clipboard_selections = Vec::with_capacity(selections.len());
12667 {
12668 let max_point = buffer.max_point();
12669 let mut is_first = true;
12670 for selection in &selections {
12671 let mut start = selection.start;
12672 let mut end = selection.end;
12673 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12674 let mut add_trailing_newline = false;
12675 if is_entire_line {
12676 start = Point::new(start.row, 0);
12677 let next_line_start = Point::new(end.row + 1, 0);
12678 if next_line_start <= max_point {
12679 end = next_line_start;
12680 } else {
12681 // We're on the last line without a trailing newline.
12682 // Copy to the end of the line and add a newline afterwards.
12683 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12684 add_trailing_newline = true;
12685 }
12686 }
12687
12688 let mut trimmed_selections = Vec::new();
12689 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12690 let row = MultiBufferRow(start.row);
12691 let first_indent = buffer.indent_size_for_line(row);
12692 if first_indent.len == 0 || start.column > first_indent.len {
12693 trimmed_selections.push(start..end);
12694 } else {
12695 trimmed_selections.push(
12696 Point::new(row.0, first_indent.len)
12697 ..Point::new(row.0, buffer.line_len(row)),
12698 );
12699 for row in start.row + 1..=end.row {
12700 let mut line_len = buffer.line_len(MultiBufferRow(row));
12701 if row == end.row {
12702 line_len = end.column;
12703 }
12704 if line_len == 0 {
12705 trimmed_selections
12706 .push(Point::new(row, 0)..Point::new(row, line_len));
12707 continue;
12708 }
12709 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12710 if row_indent_size.len >= first_indent.len {
12711 trimmed_selections.push(
12712 Point::new(row, first_indent.len)..Point::new(row, line_len),
12713 );
12714 } else {
12715 trimmed_selections.clear();
12716 trimmed_selections.push(start..end);
12717 break;
12718 }
12719 }
12720 }
12721 } else {
12722 trimmed_selections.push(start..end);
12723 }
12724
12725 for trimmed_range in trimmed_selections {
12726 if is_first {
12727 is_first = false;
12728 } else {
12729 text += "\n";
12730 }
12731 let mut len = 0;
12732 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12733 text.push_str(chunk);
12734 len += chunk.len();
12735 }
12736 if add_trailing_newline {
12737 text.push('\n');
12738 len += 1;
12739 }
12740 clipboard_selections.push(ClipboardSelection {
12741 len,
12742 is_entire_line,
12743 first_line_indent: buffer
12744 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12745 .len,
12746 });
12747 }
12748 }
12749 }
12750
12751 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12752 text,
12753 clipboard_selections,
12754 ));
12755 }
12756
12757 pub fn do_paste(
12758 &mut self,
12759 text: &String,
12760 clipboard_selections: Option<Vec<ClipboardSelection>>,
12761 handle_entire_lines: bool,
12762 window: &mut Window,
12763 cx: &mut Context<Self>,
12764 ) {
12765 if self.read_only(cx) {
12766 return;
12767 }
12768
12769 let clipboard_text = Cow::Borrowed(text.as_str());
12770
12771 self.transact(window, cx, |this, window, cx| {
12772 let had_active_edit_prediction = this.has_active_edit_prediction();
12773 let display_map = this.display_snapshot(cx);
12774 let old_selections = this.selections.all::<usize>(&display_map);
12775 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12776
12777 if let Some(mut clipboard_selections) = clipboard_selections {
12778 let all_selections_were_entire_line =
12779 clipboard_selections.iter().all(|s| s.is_entire_line);
12780 let first_selection_indent_column =
12781 clipboard_selections.first().map(|s| s.first_line_indent);
12782 if clipboard_selections.len() != old_selections.len() {
12783 clipboard_selections.drain(..);
12784 }
12785 let mut auto_indent_on_paste = true;
12786
12787 this.buffer.update(cx, |buffer, cx| {
12788 let snapshot = buffer.read(cx);
12789 auto_indent_on_paste = snapshot
12790 .language_settings_at(cursor_offset, cx)
12791 .auto_indent_on_paste;
12792
12793 let mut start_offset = 0;
12794 let mut edits = Vec::new();
12795 let mut original_indent_columns = Vec::new();
12796 for (ix, selection) in old_selections.iter().enumerate() {
12797 let to_insert;
12798 let entire_line;
12799 let original_indent_column;
12800 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12801 let end_offset = start_offset + clipboard_selection.len;
12802 to_insert = &clipboard_text[start_offset..end_offset];
12803 entire_line = clipboard_selection.is_entire_line;
12804 start_offset = end_offset + 1;
12805 original_indent_column = Some(clipboard_selection.first_line_indent);
12806 } else {
12807 to_insert = &*clipboard_text;
12808 entire_line = all_selections_were_entire_line;
12809 original_indent_column = first_selection_indent_column
12810 }
12811
12812 let (range, to_insert) =
12813 if selection.is_empty() && handle_entire_lines && entire_line {
12814 // If the corresponding selection was empty when this slice of the
12815 // clipboard text was written, then the entire line containing the
12816 // selection was copied. If this selection is also currently empty,
12817 // then paste the line before the current line of the buffer.
12818 let column = selection.start.to_point(&snapshot).column as usize;
12819 let line_start = selection.start - column;
12820 (line_start..line_start, Cow::Borrowed(to_insert))
12821 } else {
12822 let language = snapshot.language_at(selection.head());
12823 let range = selection.range();
12824 if let Some(language) = language
12825 && language.name() == "Markdown".into()
12826 {
12827 edit_for_markdown_paste(
12828 &snapshot,
12829 range,
12830 to_insert,
12831 url::Url::parse(to_insert).ok(),
12832 )
12833 } else {
12834 (range, Cow::Borrowed(to_insert))
12835 }
12836 };
12837
12838 edits.push((range, to_insert));
12839 original_indent_columns.push(original_indent_column);
12840 }
12841 drop(snapshot);
12842
12843 buffer.edit(
12844 edits,
12845 if auto_indent_on_paste {
12846 Some(AutoindentMode::Block {
12847 original_indent_columns,
12848 })
12849 } else {
12850 None
12851 },
12852 cx,
12853 );
12854 });
12855
12856 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12857 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12858 } else {
12859 let url = url::Url::parse(&clipboard_text).ok();
12860
12861 let auto_indent_mode = if !clipboard_text.is_empty() {
12862 Some(AutoindentMode::Block {
12863 original_indent_columns: Vec::new(),
12864 })
12865 } else {
12866 None
12867 };
12868
12869 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12870 let snapshot = buffer.snapshot(cx);
12871
12872 let anchors = old_selections
12873 .iter()
12874 .map(|s| {
12875 let anchor = snapshot.anchor_after(s.head());
12876 s.map(|_| anchor)
12877 })
12878 .collect::<Vec<_>>();
12879
12880 let mut edits = Vec::new();
12881
12882 for selection in old_selections.iter() {
12883 let language = snapshot.language_at(selection.head());
12884 let range = selection.range();
12885
12886 let (edit_range, edit_text) = if let Some(language) = language
12887 && language.name() == "Markdown".into()
12888 {
12889 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12890 } else {
12891 (range, clipboard_text.clone())
12892 };
12893
12894 edits.push((edit_range, edit_text));
12895 }
12896
12897 drop(snapshot);
12898 buffer.edit(edits, auto_indent_mode, cx);
12899
12900 anchors
12901 });
12902
12903 this.change_selections(Default::default(), window, cx, |s| {
12904 s.select_anchors(selection_anchors);
12905 });
12906 }
12907
12908 // 🤔 | .. | show_in_menu |
12909 // | .. | true true
12910 // | had_edit_prediction | false true
12911
12912 let trigger_in_words =
12913 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12914
12915 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12916 });
12917 }
12918
12919 pub fn diff_clipboard_with_selection(
12920 &mut self,
12921 _: &DiffClipboardWithSelection,
12922 window: &mut Window,
12923 cx: &mut Context<Self>,
12924 ) {
12925 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12926
12927 if selections.is_empty() {
12928 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12929 return;
12930 };
12931
12932 let clipboard_text = match cx.read_from_clipboard() {
12933 Some(item) => match item.entries().first() {
12934 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12935 _ => None,
12936 },
12937 None => None,
12938 };
12939
12940 let Some(clipboard_text) = clipboard_text else {
12941 log::warn!("Clipboard doesn't contain text.");
12942 return;
12943 };
12944
12945 window.dispatch_action(
12946 Box::new(DiffClipboardWithSelectionData {
12947 clipboard_text,
12948 editor: cx.entity(),
12949 }),
12950 cx,
12951 );
12952 }
12953
12954 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12956 if let Some(item) = cx.read_from_clipboard() {
12957 let entries = item.entries();
12958
12959 match entries.first() {
12960 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12961 // of all the pasted entries.
12962 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12963 .do_paste(
12964 clipboard_string.text(),
12965 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12966 true,
12967 window,
12968 cx,
12969 ),
12970 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12971 }
12972 }
12973 }
12974
12975 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12976 if self.read_only(cx) {
12977 return;
12978 }
12979
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12981
12982 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12983 if let Some((selections, _)) =
12984 self.selection_history.transaction(transaction_id).cloned()
12985 {
12986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12987 s.select_anchors(selections.to_vec());
12988 });
12989 } else {
12990 log::error!(
12991 "No entry in selection_history found for undo. \
12992 This may correspond to a bug where undo does not update the selection. \
12993 If this is occurring, please add details to \
12994 https://github.com/zed-industries/zed/issues/22692"
12995 );
12996 }
12997 self.request_autoscroll(Autoscroll::fit(), cx);
12998 self.unmark_text(window, cx);
12999 self.refresh_edit_prediction(true, false, window, cx);
13000 cx.emit(EditorEvent::Edited { transaction_id });
13001 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13002 }
13003 }
13004
13005 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13006 if self.read_only(cx) {
13007 return;
13008 }
13009
13010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13011
13012 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13013 if let Some((_, Some(selections))) =
13014 self.selection_history.transaction(transaction_id).cloned()
13015 {
13016 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13017 s.select_anchors(selections.to_vec());
13018 });
13019 } else {
13020 log::error!(
13021 "No entry in selection_history found for redo. \
13022 This may correspond to a bug where undo does not update the selection. \
13023 If this is occurring, please add details to \
13024 https://github.com/zed-industries/zed/issues/22692"
13025 );
13026 }
13027 self.request_autoscroll(Autoscroll::fit(), cx);
13028 self.unmark_text(window, cx);
13029 self.refresh_edit_prediction(true, false, window, cx);
13030 cx.emit(EditorEvent::Edited { transaction_id });
13031 }
13032 }
13033
13034 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13035 self.buffer
13036 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13037 }
13038
13039 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13040 self.buffer
13041 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13042 }
13043
13044 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13046 self.change_selections(Default::default(), window, cx, |s| {
13047 s.move_with(|map, selection| {
13048 let cursor = if selection.is_empty() {
13049 movement::left(map, selection.start)
13050 } else {
13051 selection.start
13052 };
13053 selection.collapse_to(cursor, SelectionGoal::None);
13054 });
13055 })
13056 }
13057
13058 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13059 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13060 self.change_selections(Default::default(), window, cx, |s| {
13061 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13062 })
13063 }
13064
13065 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13067 self.change_selections(Default::default(), window, cx, |s| {
13068 s.move_with(|map, selection| {
13069 let cursor = if selection.is_empty() {
13070 movement::right(map, selection.end)
13071 } else {
13072 selection.end
13073 };
13074 selection.collapse_to(cursor, SelectionGoal::None)
13075 });
13076 })
13077 }
13078
13079 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081 self.change_selections(Default::default(), window, cx, |s| {
13082 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13083 });
13084 }
13085
13086 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13087 if self.take_rename(true, window, cx).is_some() {
13088 return;
13089 }
13090
13091 if self.mode.is_single_line() {
13092 cx.propagate();
13093 return;
13094 }
13095
13096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13097
13098 let text_layout_details = &self.text_layout_details(window);
13099 let selection_count = self.selections.count();
13100 let first_selection = self.selections.first_anchor();
13101
13102 self.change_selections(Default::default(), window, cx, |s| {
13103 s.move_with(|map, selection| {
13104 if !selection.is_empty() {
13105 selection.goal = SelectionGoal::None;
13106 }
13107 let (cursor, goal) = movement::up(
13108 map,
13109 selection.start,
13110 selection.goal,
13111 false,
13112 text_layout_details,
13113 );
13114 selection.collapse_to(cursor, goal);
13115 });
13116 });
13117
13118 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13119 {
13120 cx.propagate();
13121 }
13122 }
13123
13124 pub fn move_up_by_lines(
13125 &mut self,
13126 action: &MoveUpByLines,
13127 window: &mut Window,
13128 cx: &mut Context<Self>,
13129 ) {
13130 if self.take_rename(true, window, cx).is_some() {
13131 return;
13132 }
13133
13134 if self.mode.is_single_line() {
13135 cx.propagate();
13136 return;
13137 }
13138
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140
13141 let text_layout_details = &self.text_layout_details(window);
13142
13143 self.change_selections(Default::default(), window, cx, |s| {
13144 s.move_with(|map, selection| {
13145 if !selection.is_empty() {
13146 selection.goal = SelectionGoal::None;
13147 }
13148 let (cursor, goal) = movement::up_by_rows(
13149 map,
13150 selection.start,
13151 action.lines,
13152 selection.goal,
13153 false,
13154 text_layout_details,
13155 );
13156 selection.collapse_to(cursor, goal);
13157 });
13158 })
13159 }
13160
13161 pub fn move_down_by_lines(
13162 &mut self,
13163 action: &MoveDownByLines,
13164 window: &mut Window,
13165 cx: &mut Context<Self>,
13166 ) {
13167 if self.take_rename(true, window, cx).is_some() {
13168 return;
13169 }
13170
13171 if self.mode.is_single_line() {
13172 cx.propagate();
13173 return;
13174 }
13175
13176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13177
13178 let text_layout_details = &self.text_layout_details(window);
13179
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_with(|map, selection| {
13182 if !selection.is_empty() {
13183 selection.goal = SelectionGoal::None;
13184 }
13185 let (cursor, goal) = movement::down_by_rows(
13186 map,
13187 selection.start,
13188 action.lines,
13189 selection.goal,
13190 false,
13191 text_layout_details,
13192 );
13193 selection.collapse_to(cursor, goal);
13194 });
13195 })
13196 }
13197
13198 pub fn select_down_by_lines(
13199 &mut self,
13200 action: &SelectDownByLines,
13201 window: &mut Window,
13202 cx: &mut Context<Self>,
13203 ) {
13204 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13205 let text_layout_details = &self.text_layout_details(window);
13206 self.change_selections(Default::default(), window, cx, |s| {
13207 s.move_heads_with(|map, head, goal| {
13208 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13209 })
13210 })
13211 }
13212
13213 pub fn select_up_by_lines(
13214 &mut self,
13215 action: &SelectUpByLines,
13216 window: &mut Window,
13217 cx: &mut Context<Self>,
13218 ) {
13219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13220 let text_layout_details = &self.text_layout_details(window);
13221 self.change_selections(Default::default(), window, cx, |s| {
13222 s.move_heads_with(|map, head, goal| {
13223 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13224 })
13225 })
13226 }
13227
13228 pub fn select_page_up(
13229 &mut self,
13230 _: &SelectPageUp,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) {
13234 let Some(row_count) = self.visible_row_count() else {
13235 return;
13236 };
13237
13238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13239
13240 let text_layout_details = &self.text_layout_details(window);
13241
13242 self.change_selections(Default::default(), window, cx, |s| {
13243 s.move_heads_with(|map, head, goal| {
13244 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13245 })
13246 })
13247 }
13248
13249 pub fn move_page_up(
13250 &mut self,
13251 action: &MovePageUp,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 if self.take_rename(true, window, cx).is_some() {
13256 return;
13257 }
13258
13259 if self
13260 .context_menu
13261 .borrow_mut()
13262 .as_mut()
13263 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13264 .unwrap_or(false)
13265 {
13266 return;
13267 }
13268
13269 if matches!(self.mode, EditorMode::SingleLine) {
13270 cx.propagate();
13271 return;
13272 }
13273
13274 let Some(row_count) = self.visible_row_count() else {
13275 return;
13276 };
13277
13278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13279
13280 let effects = if action.center_cursor {
13281 SelectionEffects::scroll(Autoscroll::center())
13282 } else {
13283 SelectionEffects::default()
13284 };
13285
13286 let text_layout_details = &self.text_layout_details(window);
13287
13288 self.change_selections(effects, window, cx, |s| {
13289 s.move_with(|map, selection| {
13290 if !selection.is_empty() {
13291 selection.goal = SelectionGoal::None;
13292 }
13293 let (cursor, goal) = movement::up_by_rows(
13294 map,
13295 selection.end,
13296 row_count,
13297 selection.goal,
13298 false,
13299 text_layout_details,
13300 );
13301 selection.collapse_to(cursor, goal);
13302 });
13303 });
13304 }
13305
13306 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13308 let text_layout_details = &self.text_layout_details(window);
13309 self.change_selections(Default::default(), window, cx, |s| {
13310 s.move_heads_with(|map, head, goal| {
13311 movement::up(map, head, goal, false, text_layout_details)
13312 })
13313 })
13314 }
13315
13316 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13317 self.take_rename(true, window, cx);
13318
13319 if self.mode.is_single_line() {
13320 cx.propagate();
13321 return;
13322 }
13323
13324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13325
13326 let text_layout_details = &self.text_layout_details(window);
13327 let selection_count = self.selections.count();
13328 let first_selection = self.selections.first_anchor();
13329
13330 self.change_selections(Default::default(), window, cx, |s| {
13331 s.move_with(|map, selection| {
13332 if !selection.is_empty() {
13333 selection.goal = SelectionGoal::None;
13334 }
13335 let (cursor, goal) = movement::down(
13336 map,
13337 selection.end,
13338 selection.goal,
13339 false,
13340 text_layout_details,
13341 );
13342 selection.collapse_to(cursor, goal);
13343 });
13344 });
13345
13346 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13347 {
13348 cx.propagate();
13349 }
13350 }
13351
13352 pub fn select_page_down(
13353 &mut self,
13354 _: &SelectPageDown,
13355 window: &mut Window,
13356 cx: &mut Context<Self>,
13357 ) {
13358 let Some(row_count) = self.visible_row_count() else {
13359 return;
13360 };
13361
13362 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13363
13364 let text_layout_details = &self.text_layout_details(window);
13365
13366 self.change_selections(Default::default(), window, cx, |s| {
13367 s.move_heads_with(|map, head, goal| {
13368 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13369 })
13370 })
13371 }
13372
13373 pub fn move_page_down(
13374 &mut self,
13375 action: &MovePageDown,
13376 window: &mut Window,
13377 cx: &mut Context<Self>,
13378 ) {
13379 if self.take_rename(true, window, cx).is_some() {
13380 return;
13381 }
13382
13383 if self
13384 .context_menu
13385 .borrow_mut()
13386 .as_mut()
13387 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13388 .unwrap_or(false)
13389 {
13390 return;
13391 }
13392
13393 if matches!(self.mode, EditorMode::SingleLine) {
13394 cx.propagate();
13395 return;
13396 }
13397
13398 let Some(row_count) = self.visible_row_count() else {
13399 return;
13400 };
13401
13402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13403
13404 let effects = if action.center_cursor {
13405 SelectionEffects::scroll(Autoscroll::center())
13406 } else {
13407 SelectionEffects::default()
13408 };
13409
13410 let text_layout_details = &self.text_layout_details(window);
13411 self.change_selections(effects, window, cx, |s| {
13412 s.move_with(|map, selection| {
13413 if !selection.is_empty() {
13414 selection.goal = SelectionGoal::None;
13415 }
13416 let (cursor, goal) = movement::down_by_rows(
13417 map,
13418 selection.end,
13419 row_count,
13420 selection.goal,
13421 false,
13422 text_layout_details,
13423 );
13424 selection.collapse_to(cursor, goal);
13425 });
13426 });
13427 }
13428
13429 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13431 let text_layout_details = &self.text_layout_details(window);
13432 self.change_selections(Default::default(), window, cx, |s| {
13433 s.move_heads_with(|map, head, goal| {
13434 movement::down(map, head, goal, false, text_layout_details)
13435 })
13436 });
13437 }
13438
13439 pub fn context_menu_first(
13440 &mut self,
13441 _: &ContextMenuFirst,
13442 window: &mut Window,
13443 cx: &mut Context<Self>,
13444 ) {
13445 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13446 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13447 }
13448 }
13449
13450 pub fn context_menu_prev(
13451 &mut self,
13452 _: &ContextMenuPrevious,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13457 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13458 }
13459 }
13460
13461 pub fn context_menu_next(
13462 &mut self,
13463 _: &ContextMenuNext,
13464 window: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) {
13467 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13468 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13469 }
13470 }
13471
13472 pub fn context_menu_last(
13473 &mut self,
13474 _: &ContextMenuLast,
13475 window: &mut Window,
13476 cx: &mut Context<Self>,
13477 ) {
13478 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13479 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13480 }
13481 }
13482
13483 pub fn signature_help_prev(
13484 &mut self,
13485 _: &SignatureHelpPrevious,
13486 _: &mut Window,
13487 cx: &mut Context<Self>,
13488 ) {
13489 if let Some(popover) = self.signature_help_state.popover_mut() {
13490 if popover.current_signature == 0 {
13491 popover.current_signature = popover.signatures.len() - 1;
13492 } else {
13493 popover.current_signature -= 1;
13494 }
13495 cx.notify();
13496 }
13497 }
13498
13499 pub fn signature_help_next(
13500 &mut self,
13501 _: &SignatureHelpNext,
13502 _: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) {
13505 if let Some(popover) = self.signature_help_state.popover_mut() {
13506 if popover.current_signature + 1 == popover.signatures.len() {
13507 popover.current_signature = 0;
13508 } else {
13509 popover.current_signature += 1;
13510 }
13511 cx.notify();
13512 }
13513 }
13514
13515 pub fn move_to_previous_word_start(
13516 &mut self,
13517 _: &MoveToPreviousWordStart,
13518 window: &mut Window,
13519 cx: &mut Context<Self>,
13520 ) {
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522 self.change_selections(Default::default(), window, cx, |s| {
13523 s.move_cursors_with(|map, head, _| {
13524 (
13525 movement::previous_word_start(map, head),
13526 SelectionGoal::None,
13527 )
13528 });
13529 })
13530 }
13531
13532 pub fn move_to_previous_subword_start(
13533 &mut self,
13534 _: &MoveToPreviousSubwordStart,
13535 window: &mut Window,
13536 cx: &mut Context<Self>,
13537 ) {
13538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13539 self.change_selections(Default::default(), window, cx, |s| {
13540 s.move_cursors_with(|map, head, _| {
13541 (
13542 movement::previous_subword_start(map, head),
13543 SelectionGoal::None,
13544 )
13545 });
13546 })
13547 }
13548
13549 pub fn select_to_previous_word_start(
13550 &mut self,
13551 _: &SelectToPreviousWordStart,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13556 self.change_selections(Default::default(), window, cx, |s| {
13557 s.move_heads_with(|map, head, _| {
13558 (
13559 movement::previous_word_start(map, head),
13560 SelectionGoal::None,
13561 )
13562 });
13563 })
13564 }
13565
13566 pub fn select_to_previous_subword_start(
13567 &mut self,
13568 _: &SelectToPreviousSubwordStart,
13569 window: &mut Window,
13570 cx: &mut Context<Self>,
13571 ) {
13572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.move_heads_with(|map, head, _| {
13575 (
13576 movement::previous_subword_start(map, head),
13577 SelectionGoal::None,
13578 )
13579 });
13580 })
13581 }
13582
13583 pub fn delete_to_previous_word_start(
13584 &mut self,
13585 action: &DeleteToPreviousWordStart,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13590 self.transact(window, cx, |this, window, cx| {
13591 this.select_autoclose_pair(window, cx);
13592 this.change_selections(Default::default(), window, cx, |s| {
13593 s.move_with(|map, selection| {
13594 if selection.is_empty() {
13595 let mut cursor = if action.ignore_newlines {
13596 movement::previous_word_start(map, selection.head())
13597 } else {
13598 movement::previous_word_start_or_newline(map, selection.head())
13599 };
13600 cursor = movement::adjust_greedy_deletion(
13601 map,
13602 selection.head(),
13603 cursor,
13604 action.ignore_brackets,
13605 );
13606 selection.set_head(cursor, SelectionGoal::None);
13607 }
13608 });
13609 });
13610 this.insert("", window, cx);
13611 });
13612 }
13613
13614 pub fn delete_to_previous_subword_start(
13615 &mut self,
13616 _: &DeleteToPreviousSubwordStart,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13621 self.transact(window, cx, |this, window, cx| {
13622 this.select_autoclose_pair(window, cx);
13623 this.change_selections(Default::default(), window, cx, |s| {
13624 s.move_with(|map, selection| {
13625 if selection.is_empty() {
13626 let mut cursor = movement::previous_subword_start(map, selection.head());
13627 cursor =
13628 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13629 selection.set_head(cursor, SelectionGoal::None);
13630 }
13631 });
13632 });
13633 this.insert("", window, cx);
13634 });
13635 }
13636
13637 pub fn move_to_next_word_end(
13638 &mut self,
13639 _: &MoveToNextWordEnd,
13640 window: &mut Window,
13641 cx: &mut Context<Self>,
13642 ) {
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_cursors_with(|map, head, _| {
13646 (movement::next_word_end(map, head), SelectionGoal::None)
13647 });
13648 })
13649 }
13650
13651 pub fn move_to_next_subword_end(
13652 &mut self,
13653 _: &MoveToNextSubwordEnd,
13654 window: &mut Window,
13655 cx: &mut Context<Self>,
13656 ) {
13657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13658 self.change_selections(Default::default(), window, cx, |s| {
13659 s.move_cursors_with(|map, head, _| {
13660 (movement::next_subword_end(map, head), SelectionGoal::None)
13661 });
13662 })
13663 }
13664
13665 pub fn select_to_next_word_end(
13666 &mut self,
13667 _: &SelectToNextWordEnd,
13668 window: &mut Window,
13669 cx: &mut Context<Self>,
13670 ) {
13671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13672 self.change_selections(Default::default(), window, cx, |s| {
13673 s.move_heads_with(|map, head, _| {
13674 (movement::next_word_end(map, head), SelectionGoal::None)
13675 });
13676 })
13677 }
13678
13679 pub fn select_to_next_subword_end(
13680 &mut self,
13681 _: &SelectToNextSubwordEnd,
13682 window: &mut Window,
13683 cx: &mut Context<Self>,
13684 ) {
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_heads_with(|map, head, _| {
13688 (movement::next_subword_end(map, head), SelectionGoal::None)
13689 });
13690 })
13691 }
13692
13693 pub fn delete_to_next_word_end(
13694 &mut self,
13695 action: &DeleteToNextWordEnd,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13700 self.transact(window, cx, |this, window, cx| {
13701 this.change_selections(Default::default(), window, cx, |s| {
13702 s.move_with(|map, selection| {
13703 if selection.is_empty() {
13704 let mut cursor = if action.ignore_newlines {
13705 movement::next_word_end(map, selection.head())
13706 } else {
13707 movement::next_word_end_or_newline(map, selection.head())
13708 };
13709 cursor = movement::adjust_greedy_deletion(
13710 map,
13711 selection.head(),
13712 cursor,
13713 action.ignore_brackets,
13714 );
13715 selection.set_head(cursor, SelectionGoal::None);
13716 }
13717 });
13718 });
13719 this.insert("", window, cx);
13720 });
13721 }
13722
13723 pub fn delete_to_next_subword_end(
13724 &mut self,
13725 _: &DeleteToNextSubwordEnd,
13726 window: &mut Window,
13727 cx: &mut Context<Self>,
13728 ) {
13729 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13730 self.transact(window, cx, |this, window, cx| {
13731 this.change_selections(Default::default(), window, cx, |s| {
13732 s.move_with(|map, selection| {
13733 if selection.is_empty() {
13734 let mut cursor = movement::next_subword_end(map, selection.head());
13735 cursor =
13736 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13737 selection.set_head(cursor, SelectionGoal::None);
13738 }
13739 });
13740 });
13741 this.insert("", window, cx);
13742 });
13743 }
13744
13745 pub fn move_to_beginning_of_line(
13746 &mut self,
13747 action: &MoveToBeginningOfLine,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13752 self.change_selections(Default::default(), window, cx, |s| {
13753 s.move_cursors_with(|map, head, _| {
13754 (
13755 movement::indented_line_beginning(
13756 map,
13757 head,
13758 action.stop_at_soft_wraps,
13759 action.stop_at_indent,
13760 ),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_beginning_of_line(
13768 &mut self,
13769 action: &SelectToBeginningOfLine,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::indented_line_beginning(
13778 map,
13779 head,
13780 action.stop_at_soft_wraps,
13781 action.stop_at_indent,
13782 ),
13783 SelectionGoal::None,
13784 )
13785 });
13786 });
13787 }
13788
13789 pub fn delete_to_beginning_of_line(
13790 &mut self,
13791 action: &DeleteToBeginningOfLine,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 ) {
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13796 self.transact(window, cx, |this, window, cx| {
13797 this.change_selections(Default::default(), window, cx, |s| {
13798 s.move_with(|_, selection| {
13799 selection.reversed = true;
13800 });
13801 });
13802
13803 this.select_to_beginning_of_line(
13804 &SelectToBeginningOfLine {
13805 stop_at_soft_wraps: false,
13806 stop_at_indent: action.stop_at_indent,
13807 },
13808 window,
13809 cx,
13810 );
13811 this.backspace(&Backspace, window, cx);
13812 });
13813 }
13814
13815 pub fn move_to_end_of_line(
13816 &mut self,
13817 action: &MoveToEndOfLine,
13818 window: &mut Window,
13819 cx: &mut Context<Self>,
13820 ) {
13821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13822 self.change_selections(Default::default(), window, cx, |s| {
13823 s.move_cursors_with(|map, head, _| {
13824 (
13825 movement::line_end(map, head, action.stop_at_soft_wraps),
13826 SelectionGoal::None,
13827 )
13828 });
13829 })
13830 }
13831
13832 pub fn select_to_end_of_line(
13833 &mut self,
13834 action: &SelectToEndOfLine,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13839 self.change_selections(Default::default(), window, cx, |s| {
13840 s.move_heads_with(|map, head, _| {
13841 (
13842 movement::line_end(map, head, action.stop_at_soft_wraps),
13843 SelectionGoal::None,
13844 )
13845 });
13846 })
13847 }
13848
13849 pub fn delete_to_end_of_line(
13850 &mut self,
13851 _: &DeleteToEndOfLine,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13856 self.transact(window, cx, |this, window, cx| {
13857 this.select_to_end_of_line(
13858 &SelectToEndOfLine {
13859 stop_at_soft_wraps: false,
13860 },
13861 window,
13862 cx,
13863 );
13864 this.delete(&Delete, window, cx);
13865 });
13866 }
13867
13868 pub fn cut_to_end_of_line(
13869 &mut self,
13870 action: &CutToEndOfLine,
13871 window: &mut Window,
13872 cx: &mut Context<Self>,
13873 ) {
13874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13875 self.transact(window, cx, |this, window, cx| {
13876 this.select_to_end_of_line(
13877 &SelectToEndOfLine {
13878 stop_at_soft_wraps: false,
13879 },
13880 window,
13881 cx,
13882 );
13883 if !action.stop_at_newlines {
13884 this.change_selections(Default::default(), window, cx, |s| {
13885 s.move_with(|_, sel| {
13886 if sel.is_empty() {
13887 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13888 }
13889 });
13890 });
13891 }
13892 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13893 let item = this.cut_common(false, window, cx);
13894 cx.write_to_clipboard(item);
13895 });
13896 }
13897
13898 pub fn move_to_start_of_paragraph(
13899 &mut self,
13900 _: &MoveToStartOfParagraph,
13901 window: &mut Window,
13902 cx: &mut Context<Self>,
13903 ) {
13904 if matches!(self.mode, EditorMode::SingleLine) {
13905 cx.propagate();
13906 return;
13907 }
13908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13909 self.change_selections(Default::default(), window, cx, |s| {
13910 s.move_with(|map, selection| {
13911 selection.collapse_to(
13912 movement::start_of_paragraph(map, selection.head(), 1),
13913 SelectionGoal::None,
13914 )
13915 });
13916 })
13917 }
13918
13919 pub fn move_to_end_of_paragraph(
13920 &mut self,
13921 _: &MoveToEndOfParagraph,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 if matches!(self.mode, EditorMode::SingleLine) {
13926 cx.propagate();
13927 return;
13928 }
13929 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13930 self.change_selections(Default::default(), window, cx, |s| {
13931 s.move_with(|map, selection| {
13932 selection.collapse_to(
13933 movement::end_of_paragraph(map, selection.head(), 1),
13934 SelectionGoal::None,
13935 )
13936 });
13937 })
13938 }
13939
13940 pub fn select_to_start_of_paragraph(
13941 &mut self,
13942 _: &SelectToStartOfParagraph,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 if matches!(self.mode, EditorMode::SingleLine) {
13947 cx.propagate();
13948 return;
13949 }
13950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13951 self.change_selections(Default::default(), window, cx, |s| {
13952 s.move_heads_with(|map, head, _| {
13953 (
13954 movement::start_of_paragraph(map, head, 1),
13955 SelectionGoal::None,
13956 )
13957 });
13958 })
13959 }
13960
13961 pub fn select_to_end_of_paragraph(
13962 &mut self,
13963 _: &SelectToEndOfParagraph,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) {
13967 if matches!(self.mode, EditorMode::SingleLine) {
13968 cx.propagate();
13969 return;
13970 }
13971 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13972 self.change_selections(Default::default(), window, cx, |s| {
13973 s.move_heads_with(|map, head, _| {
13974 (
13975 movement::end_of_paragraph(map, head, 1),
13976 SelectionGoal::None,
13977 )
13978 });
13979 })
13980 }
13981
13982 pub fn move_to_start_of_excerpt(
13983 &mut self,
13984 _: &MoveToStartOfExcerpt,
13985 window: &mut Window,
13986 cx: &mut Context<Self>,
13987 ) {
13988 if matches!(self.mode, EditorMode::SingleLine) {
13989 cx.propagate();
13990 return;
13991 }
13992 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13993 self.change_selections(Default::default(), window, cx, |s| {
13994 s.move_with(|map, selection| {
13995 selection.collapse_to(
13996 movement::start_of_excerpt(
13997 map,
13998 selection.head(),
13999 workspace::searchable::Direction::Prev,
14000 ),
14001 SelectionGoal::None,
14002 )
14003 });
14004 })
14005 }
14006
14007 pub fn move_to_start_of_next_excerpt(
14008 &mut self,
14009 _: &MoveToStartOfNextExcerpt,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 if matches!(self.mode, EditorMode::SingleLine) {
14014 cx.propagate();
14015 return;
14016 }
14017
14018 self.change_selections(Default::default(), window, cx, |s| {
14019 s.move_with(|map, selection| {
14020 selection.collapse_to(
14021 movement::start_of_excerpt(
14022 map,
14023 selection.head(),
14024 workspace::searchable::Direction::Next,
14025 ),
14026 SelectionGoal::None,
14027 )
14028 });
14029 })
14030 }
14031
14032 pub fn move_to_end_of_excerpt(
14033 &mut self,
14034 _: &MoveToEndOfExcerpt,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 if matches!(self.mode, EditorMode::SingleLine) {
14039 cx.propagate();
14040 return;
14041 }
14042 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.move_with(|map, selection| {
14045 selection.collapse_to(
14046 movement::end_of_excerpt(
14047 map,
14048 selection.head(),
14049 workspace::searchable::Direction::Next,
14050 ),
14051 SelectionGoal::None,
14052 )
14053 });
14054 })
14055 }
14056
14057 pub fn move_to_end_of_previous_excerpt(
14058 &mut self,
14059 _: &MoveToEndOfPreviousExcerpt,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) {
14063 if matches!(self.mode, EditorMode::SingleLine) {
14064 cx.propagate();
14065 return;
14066 }
14067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.move_with(|map, selection| {
14070 selection.collapse_to(
14071 movement::end_of_excerpt(
14072 map,
14073 selection.head(),
14074 workspace::searchable::Direction::Prev,
14075 ),
14076 SelectionGoal::None,
14077 )
14078 });
14079 })
14080 }
14081
14082 pub fn select_to_start_of_excerpt(
14083 &mut self,
14084 _: &SelectToStartOfExcerpt,
14085 window: &mut Window,
14086 cx: &mut Context<Self>,
14087 ) {
14088 if matches!(self.mode, EditorMode::SingleLine) {
14089 cx.propagate();
14090 return;
14091 }
14092 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14093 self.change_selections(Default::default(), window, cx, |s| {
14094 s.move_heads_with(|map, head, _| {
14095 (
14096 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14097 SelectionGoal::None,
14098 )
14099 });
14100 })
14101 }
14102
14103 pub fn select_to_start_of_next_excerpt(
14104 &mut self,
14105 _: &SelectToStartOfNextExcerpt,
14106 window: &mut Window,
14107 cx: &mut Context<Self>,
14108 ) {
14109 if matches!(self.mode, EditorMode::SingleLine) {
14110 cx.propagate();
14111 return;
14112 }
14113 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14114 self.change_selections(Default::default(), window, cx, |s| {
14115 s.move_heads_with(|map, head, _| {
14116 (
14117 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14118 SelectionGoal::None,
14119 )
14120 });
14121 })
14122 }
14123
14124 pub fn select_to_end_of_excerpt(
14125 &mut self,
14126 _: &SelectToEndOfExcerpt,
14127 window: &mut Window,
14128 cx: &mut Context<Self>,
14129 ) {
14130 if matches!(self.mode, EditorMode::SingleLine) {
14131 cx.propagate();
14132 return;
14133 }
14134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14135 self.change_selections(Default::default(), window, cx, |s| {
14136 s.move_heads_with(|map, head, _| {
14137 (
14138 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14139 SelectionGoal::None,
14140 )
14141 });
14142 })
14143 }
14144
14145 pub fn select_to_end_of_previous_excerpt(
14146 &mut self,
14147 _: &SelectToEndOfPreviousExcerpt,
14148 window: &mut Window,
14149 cx: &mut Context<Self>,
14150 ) {
14151 if matches!(self.mode, EditorMode::SingleLine) {
14152 cx.propagate();
14153 return;
14154 }
14155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14156 self.change_selections(Default::default(), window, cx, |s| {
14157 s.move_heads_with(|map, head, _| {
14158 (
14159 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14160 SelectionGoal::None,
14161 )
14162 });
14163 })
14164 }
14165
14166 pub fn move_to_beginning(
14167 &mut self,
14168 _: &MoveToBeginning,
14169 window: &mut Window,
14170 cx: &mut Context<Self>,
14171 ) {
14172 if matches!(self.mode, EditorMode::SingleLine) {
14173 cx.propagate();
14174 return;
14175 }
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14177 self.change_selections(Default::default(), window, cx, |s| {
14178 s.select_ranges(vec![0..0]);
14179 });
14180 }
14181
14182 pub fn select_to_beginning(
14183 &mut self,
14184 _: &SelectToBeginning,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) {
14188 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14189 selection.set_head(Point::zero(), SelectionGoal::None);
14190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14191 self.change_selections(Default::default(), window, cx, |s| {
14192 s.select(vec![selection]);
14193 });
14194 }
14195
14196 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14197 if matches!(self.mode, EditorMode::SingleLine) {
14198 cx.propagate();
14199 return;
14200 }
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14202 let cursor = self.buffer.read(cx).read(cx).len();
14203 self.change_selections(Default::default(), window, cx, |s| {
14204 s.select_ranges(vec![cursor..cursor])
14205 });
14206 }
14207
14208 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14209 self.nav_history = nav_history;
14210 }
14211
14212 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14213 self.nav_history.as_ref()
14214 }
14215
14216 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14217 self.push_to_nav_history(
14218 self.selections.newest_anchor().head(),
14219 None,
14220 false,
14221 true,
14222 cx,
14223 );
14224 }
14225
14226 fn push_to_nav_history(
14227 &mut self,
14228 cursor_anchor: Anchor,
14229 new_position: Option<Point>,
14230 is_deactivate: bool,
14231 always: bool,
14232 cx: &mut Context<Self>,
14233 ) {
14234 if let Some(nav_history) = self.nav_history.as_mut() {
14235 let buffer = self.buffer.read(cx).read(cx);
14236 let cursor_position = cursor_anchor.to_point(&buffer);
14237 let scroll_state = self.scroll_manager.anchor();
14238 let scroll_top_row = scroll_state.top_row(&buffer);
14239 drop(buffer);
14240
14241 if let Some(new_position) = new_position {
14242 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14243 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14244 return;
14245 }
14246 }
14247
14248 nav_history.push(
14249 Some(NavigationData {
14250 cursor_anchor,
14251 cursor_position,
14252 scroll_anchor: scroll_state,
14253 scroll_top_row,
14254 }),
14255 cx,
14256 );
14257 cx.emit(EditorEvent::PushedToNavHistory {
14258 anchor: cursor_anchor,
14259 is_deactivate,
14260 })
14261 }
14262 }
14263
14264 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14266 let buffer = self.buffer.read(cx).snapshot(cx);
14267 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14268 selection.set_head(buffer.len(), SelectionGoal::None);
14269 self.change_selections(Default::default(), window, cx, |s| {
14270 s.select(vec![selection]);
14271 });
14272 }
14273
14274 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14276 let end = self.buffer.read(cx).read(cx).len();
14277 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14278 s.select_ranges(vec![0..end]);
14279 });
14280 }
14281
14282 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14284 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14285 let mut selections = self.selections.all::<Point>(&display_map);
14286 let max_point = display_map.buffer_snapshot().max_point();
14287 for selection in &mut selections {
14288 let rows = selection.spanned_rows(true, &display_map);
14289 selection.start = Point::new(rows.start.0, 0);
14290 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14291 selection.reversed = false;
14292 }
14293 self.change_selections(Default::default(), window, cx, |s| {
14294 s.select(selections);
14295 });
14296 }
14297
14298 pub fn split_selection_into_lines(
14299 &mut self,
14300 action: &SplitSelectionIntoLines,
14301 window: &mut Window,
14302 cx: &mut Context<Self>,
14303 ) {
14304 let selections = self
14305 .selections
14306 .all::<Point>(&self.display_snapshot(cx))
14307 .into_iter()
14308 .map(|selection| selection.start..selection.end)
14309 .collect::<Vec<_>>();
14310 self.unfold_ranges(&selections, true, true, cx);
14311
14312 let mut new_selection_ranges = Vec::new();
14313 {
14314 let buffer = self.buffer.read(cx).read(cx);
14315 for selection in selections {
14316 for row in selection.start.row..selection.end.row {
14317 let line_start = Point::new(row, 0);
14318 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14319
14320 if action.keep_selections {
14321 // Keep the selection range for each line
14322 let selection_start = if row == selection.start.row {
14323 selection.start
14324 } else {
14325 line_start
14326 };
14327 new_selection_ranges.push(selection_start..line_end);
14328 } else {
14329 // Collapse to cursor at end of line
14330 new_selection_ranges.push(line_end..line_end);
14331 }
14332 }
14333
14334 let is_multiline_selection = selection.start.row != selection.end.row;
14335 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14336 // so this action feels more ergonomic when paired with other selection operations
14337 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14338 if !should_skip_last {
14339 if action.keep_selections {
14340 if is_multiline_selection {
14341 let line_start = Point::new(selection.end.row, 0);
14342 new_selection_ranges.push(line_start..selection.end);
14343 } else {
14344 new_selection_ranges.push(selection.start..selection.end);
14345 }
14346 } else {
14347 new_selection_ranges.push(selection.end..selection.end);
14348 }
14349 }
14350 }
14351 }
14352 self.change_selections(Default::default(), window, cx, |s| {
14353 s.select_ranges(new_selection_ranges);
14354 });
14355 }
14356
14357 pub fn add_selection_above(
14358 &mut self,
14359 action: &AddSelectionAbove,
14360 window: &mut Window,
14361 cx: &mut Context<Self>,
14362 ) {
14363 self.add_selection(true, action.skip_soft_wrap, window, cx);
14364 }
14365
14366 pub fn add_selection_below(
14367 &mut self,
14368 action: &AddSelectionBelow,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) {
14372 self.add_selection(false, action.skip_soft_wrap, window, cx);
14373 }
14374
14375 fn add_selection(
14376 &mut self,
14377 above: bool,
14378 skip_soft_wrap: bool,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14383
14384 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14385 let all_selections = self.selections.all::<Point>(&display_map);
14386 let text_layout_details = self.text_layout_details(window);
14387
14388 let (mut columnar_selections, new_selections_to_columnarize) = {
14389 if let Some(state) = self.add_selections_state.as_ref() {
14390 let columnar_selection_ids: HashSet<_> = state
14391 .groups
14392 .iter()
14393 .flat_map(|group| group.stack.iter())
14394 .copied()
14395 .collect();
14396
14397 all_selections
14398 .into_iter()
14399 .partition(|s| columnar_selection_ids.contains(&s.id))
14400 } else {
14401 (Vec::new(), all_selections)
14402 }
14403 };
14404
14405 let mut state = self
14406 .add_selections_state
14407 .take()
14408 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14409
14410 for selection in new_selections_to_columnarize {
14411 let range = selection.display_range(&display_map).sorted();
14412 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14413 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14414 let positions = start_x.min(end_x)..start_x.max(end_x);
14415 let mut stack = Vec::new();
14416 for row in range.start.row().0..=range.end.row().0 {
14417 if let Some(selection) = self.selections.build_columnar_selection(
14418 &display_map,
14419 DisplayRow(row),
14420 &positions,
14421 selection.reversed,
14422 &text_layout_details,
14423 ) {
14424 stack.push(selection.id);
14425 columnar_selections.push(selection);
14426 }
14427 }
14428 if !stack.is_empty() {
14429 if above {
14430 stack.reverse();
14431 }
14432 state.groups.push(AddSelectionsGroup { above, stack });
14433 }
14434 }
14435
14436 let mut final_selections = Vec::new();
14437 let end_row = if above {
14438 DisplayRow(0)
14439 } else {
14440 display_map.max_point().row()
14441 };
14442
14443 let mut last_added_item_per_group = HashMap::default();
14444 for group in state.groups.iter_mut() {
14445 if let Some(last_id) = group.stack.last() {
14446 last_added_item_per_group.insert(*last_id, group);
14447 }
14448 }
14449
14450 for selection in columnar_selections {
14451 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14452 if above == group.above {
14453 let range = selection.display_range(&display_map).sorted();
14454 debug_assert_eq!(range.start.row(), range.end.row());
14455 let mut row = range.start.row();
14456 let positions =
14457 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14458 Pixels::from(start)..Pixels::from(end)
14459 } else {
14460 let start_x =
14461 display_map.x_for_display_point(range.start, &text_layout_details);
14462 let end_x =
14463 display_map.x_for_display_point(range.end, &text_layout_details);
14464 start_x.min(end_x)..start_x.max(end_x)
14465 };
14466
14467 let mut maybe_new_selection = None;
14468 let direction = if above { -1 } else { 1 };
14469
14470 while row != end_row {
14471 if skip_soft_wrap {
14472 row = display_map
14473 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14474 .row();
14475 } else if above {
14476 row.0 -= 1;
14477 } else {
14478 row.0 += 1;
14479 }
14480
14481 if let Some(new_selection) = self.selections.build_columnar_selection(
14482 &display_map,
14483 row,
14484 &positions,
14485 selection.reversed,
14486 &text_layout_details,
14487 ) {
14488 maybe_new_selection = Some(new_selection);
14489 break;
14490 }
14491 }
14492
14493 if let Some(new_selection) = maybe_new_selection {
14494 group.stack.push(new_selection.id);
14495 if above {
14496 final_selections.push(new_selection);
14497 final_selections.push(selection);
14498 } else {
14499 final_selections.push(selection);
14500 final_selections.push(new_selection);
14501 }
14502 } else {
14503 final_selections.push(selection);
14504 }
14505 } else {
14506 group.stack.pop();
14507 }
14508 } else {
14509 final_selections.push(selection);
14510 }
14511 }
14512
14513 self.change_selections(Default::default(), window, cx, |s| {
14514 s.select(final_selections);
14515 });
14516
14517 let final_selection_ids: HashSet<_> = self
14518 .selections
14519 .all::<Point>(&display_map)
14520 .iter()
14521 .map(|s| s.id)
14522 .collect();
14523 state.groups.retain_mut(|group| {
14524 // selections might get merged above so we remove invalid items from stacks
14525 group.stack.retain(|id| final_selection_ids.contains(id));
14526
14527 // single selection in stack can be treated as initial state
14528 group.stack.len() > 1
14529 });
14530
14531 if !state.groups.is_empty() {
14532 self.add_selections_state = Some(state);
14533 }
14534 }
14535
14536 fn select_match_ranges(
14537 &mut self,
14538 range: Range<usize>,
14539 reversed: bool,
14540 replace_newest: bool,
14541 auto_scroll: Option<Autoscroll>,
14542 window: &mut Window,
14543 cx: &mut Context<Editor>,
14544 ) {
14545 self.unfold_ranges(
14546 std::slice::from_ref(&range),
14547 false,
14548 auto_scroll.is_some(),
14549 cx,
14550 );
14551 let effects = if let Some(scroll) = auto_scroll {
14552 SelectionEffects::scroll(scroll)
14553 } else {
14554 SelectionEffects::no_scroll()
14555 };
14556 self.change_selections(effects, window, cx, |s| {
14557 if replace_newest {
14558 s.delete(s.newest_anchor().id);
14559 }
14560 if reversed {
14561 s.insert_range(range.end..range.start);
14562 } else {
14563 s.insert_range(range);
14564 }
14565 });
14566 }
14567
14568 pub fn select_next_match_internal(
14569 &mut self,
14570 display_map: &DisplaySnapshot,
14571 replace_newest: bool,
14572 autoscroll: Option<Autoscroll>,
14573 window: &mut Window,
14574 cx: &mut Context<Self>,
14575 ) -> Result<()> {
14576 let buffer = display_map.buffer_snapshot();
14577 let mut selections = self.selections.all::<usize>(&display_map);
14578 if let Some(mut select_next_state) = self.select_next_state.take() {
14579 let query = &select_next_state.query;
14580 if !select_next_state.done {
14581 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14582 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14583 let mut next_selected_range = None;
14584
14585 let bytes_after_last_selection =
14586 buffer.bytes_in_range(last_selection.end..buffer.len());
14587 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14588 let query_matches = query
14589 .stream_find_iter(bytes_after_last_selection)
14590 .map(|result| (last_selection.end, result))
14591 .chain(
14592 query
14593 .stream_find_iter(bytes_before_first_selection)
14594 .map(|result| (0, result)),
14595 );
14596
14597 for (start_offset, query_match) in query_matches {
14598 let query_match = query_match.unwrap(); // can only fail due to I/O
14599 let offset_range =
14600 start_offset + query_match.start()..start_offset + query_match.end();
14601
14602 if !select_next_state.wordwise
14603 || (!buffer.is_inside_word(offset_range.start, None)
14604 && !buffer.is_inside_word(offset_range.end, None))
14605 {
14606 let idx = selections
14607 .partition_point(|selection| selection.end <= offset_range.start);
14608 let overlaps = selections
14609 .get(idx)
14610 .map_or(false, |selection| selection.start < offset_range.end);
14611
14612 if !overlaps {
14613 next_selected_range = Some(offset_range);
14614 break;
14615 }
14616 }
14617 }
14618
14619 if let Some(next_selected_range) = next_selected_range {
14620 self.select_match_ranges(
14621 next_selected_range,
14622 last_selection.reversed,
14623 replace_newest,
14624 autoscroll,
14625 window,
14626 cx,
14627 );
14628 } else {
14629 select_next_state.done = true;
14630 }
14631 }
14632
14633 self.select_next_state = Some(select_next_state);
14634 } else {
14635 let mut only_carets = true;
14636 let mut same_text_selected = true;
14637 let mut selected_text = None;
14638
14639 let mut selections_iter = selections.iter().peekable();
14640 while let Some(selection) = selections_iter.next() {
14641 if selection.start != selection.end {
14642 only_carets = false;
14643 }
14644
14645 if same_text_selected {
14646 if selected_text.is_none() {
14647 selected_text =
14648 Some(buffer.text_for_range(selection.range()).collect::<String>());
14649 }
14650
14651 if let Some(next_selection) = selections_iter.peek() {
14652 if next_selection.range().len() == selection.range().len() {
14653 let next_selected_text = buffer
14654 .text_for_range(next_selection.range())
14655 .collect::<String>();
14656 if Some(next_selected_text) != selected_text {
14657 same_text_selected = false;
14658 selected_text = None;
14659 }
14660 } else {
14661 same_text_selected = false;
14662 selected_text = None;
14663 }
14664 }
14665 }
14666 }
14667
14668 if only_carets {
14669 for selection in &mut selections {
14670 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14671 selection.start = word_range.start;
14672 selection.end = word_range.end;
14673 selection.goal = SelectionGoal::None;
14674 selection.reversed = false;
14675 self.select_match_ranges(
14676 selection.start..selection.end,
14677 selection.reversed,
14678 replace_newest,
14679 autoscroll,
14680 window,
14681 cx,
14682 );
14683 }
14684
14685 if selections.len() == 1 {
14686 let selection = selections
14687 .last()
14688 .expect("ensured that there's only one selection");
14689 let query = buffer
14690 .text_for_range(selection.start..selection.end)
14691 .collect::<String>();
14692 let is_empty = query.is_empty();
14693 let select_state = SelectNextState {
14694 query: self.build_query(&[query], cx)?,
14695 wordwise: true,
14696 done: is_empty,
14697 };
14698 self.select_next_state = Some(select_state);
14699 } else {
14700 self.select_next_state = None;
14701 }
14702 } else if let Some(selected_text) = selected_text {
14703 self.select_next_state = Some(SelectNextState {
14704 query: self.build_query(&[selected_text], cx)?,
14705 wordwise: false,
14706 done: false,
14707 });
14708 self.select_next_match_internal(
14709 display_map,
14710 replace_newest,
14711 autoscroll,
14712 window,
14713 cx,
14714 )?;
14715 }
14716 }
14717 Ok(())
14718 }
14719
14720 pub fn select_all_matches(
14721 &mut self,
14722 _action: &SelectAllMatches,
14723 window: &mut Window,
14724 cx: &mut Context<Self>,
14725 ) -> Result<()> {
14726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14727
14728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14729
14730 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14731 let Some(select_next_state) = self.select_next_state.as_mut() else {
14732 return Ok(());
14733 };
14734 if select_next_state.done {
14735 return Ok(());
14736 }
14737
14738 let mut new_selections = Vec::new();
14739
14740 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14741 let buffer = display_map.buffer_snapshot();
14742 let query_matches = select_next_state
14743 .query
14744 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14745
14746 for query_match in query_matches.into_iter() {
14747 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14748 let offset_range = if reversed {
14749 query_match.end()..query_match.start()
14750 } else {
14751 query_match.start()..query_match.end()
14752 };
14753
14754 if !select_next_state.wordwise
14755 || (!buffer.is_inside_word(offset_range.start, None)
14756 && !buffer.is_inside_word(offset_range.end, None))
14757 {
14758 new_selections.push(offset_range.start..offset_range.end);
14759 }
14760 }
14761
14762 select_next_state.done = true;
14763
14764 if new_selections.is_empty() {
14765 log::error!("bug: new_selections is empty in select_all_matches");
14766 return Ok(());
14767 }
14768
14769 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14770 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14771 selections.select_ranges(new_selections)
14772 });
14773
14774 Ok(())
14775 }
14776
14777 pub fn select_next(
14778 &mut self,
14779 action: &SelectNext,
14780 window: &mut Window,
14781 cx: &mut Context<Self>,
14782 ) -> Result<()> {
14783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14785 self.select_next_match_internal(
14786 &display_map,
14787 action.replace_newest,
14788 Some(Autoscroll::newest()),
14789 window,
14790 cx,
14791 )?;
14792 Ok(())
14793 }
14794
14795 pub fn select_previous(
14796 &mut self,
14797 action: &SelectPrevious,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) -> Result<()> {
14801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14802 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14803 let buffer = display_map.buffer_snapshot();
14804 let mut selections = self.selections.all::<usize>(&display_map);
14805 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14806 let query = &select_prev_state.query;
14807 if !select_prev_state.done {
14808 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14809 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14810 let mut next_selected_range = None;
14811 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14812 let bytes_before_last_selection =
14813 buffer.reversed_bytes_in_range(0..last_selection.start);
14814 let bytes_after_first_selection =
14815 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14816 let query_matches = query
14817 .stream_find_iter(bytes_before_last_selection)
14818 .map(|result| (last_selection.start, result))
14819 .chain(
14820 query
14821 .stream_find_iter(bytes_after_first_selection)
14822 .map(|result| (buffer.len(), result)),
14823 );
14824 for (end_offset, query_match) in query_matches {
14825 let query_match = query_match.unwrap(); // can only fail due to I/O
14826 let offset_range =
14827 end_offset - query_match.end()..end_offset - query_match.start();
14828
14829 if !select_prev_state.wordwise
14830 || (!buffer.is_inside_word(offset_range.start, None)
14831 && !buffer.is_inside_word(offset_range.end, None))
14832 {
14833 next_selected_range = Some(offset_range);
14834 break;
14835 }
14836 }
14837
14838 if let Some(next_selected_range) = next_selected_range {
14839 self.select_match_ranges(
14840 next_selected_range,
14841 last_selection.reversed,
14842 action.replace_newest,
14843 Some(Autoscroll::newest()),
14844 window,
14845 cx,
14846 );
14847 } else {
14848 select_prev_state.done = true;
14849 }
14850 }
14851
14852 self.select_prev_state = Some(select_prev_state);
14853 } else {
14854 let mut only_carets = true;
14855 let mut same_text_selected = true;
14856 let mut selected_text = None;
14857
14858 let mut selections_iter = selections.iter().peekable();
14859 while let Some(selection) = selections_iter.next() {
14860 if selection.start != selection.end {
14861 only_carets = false;
14862 }
14863
14864 if same_text_selected {
14865 if selected_text.is_none() {
14866 selected_text =
14867 Some(buffer.text_for_range(selection.range()).collect::<String>());
14868 }
14869
14870 if let Some(next_selection) = selections_iter.peek() {
14871 if next_selection.range().len() == selection.range().len() {
14872 let next_selected_text = buffer
14873 .text_for_range(next_selection.range())
14874 .collect::<String>();
14875 if Some(next_selected_text) != selected_text {
14876 same_text_selected = false;
14877 selected_text = None;
14878 }
14879 } else {
14880 same_text_selected = false;
14881 selected_text = None;
14882 }
14883 }
14884 }
14885 }
14886
14887 if only_carets {
14888 for selection in &mut selections {
14889 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14890 selection.start = word_range.start;
14891 selection.end = word_range.end;
14892 selection.goal = SelectionGoal::None;
14893 selection.reversed = false;
14894 self.select_match_ranges(
14895 selection.start..selection.end,
14896 selection.reversed,
14897 action.replace_newest,
14898 Some(Autoscroll::newest()),
14899 window,
14900 cx,
14901 );
14902 }
14903 if selections.len() == 1 {
14904 let selection = selections
14905 .last()
14906 .expect("ensured that there's only one selection");
14907 let query = buffer
14908 .text_for_range(selection.start..selection.end)
14909 .collect::<String>();
14910 let is_empty = query.is_empty();
14911 let select_state = SelectNextState {
14912 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14913 wordwise: true,
14914 done: is_empty,
14915 };
14916 self.select_prev_state = Some(select_state);
14917 } else {
14918 self.select_prev_state = None;
14919 }
14920 } else if let Some(selected_text) = selected_text {
14921 self.select_prev_state = Some(SelectNextState {
14922 query: self
14923 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14924 wordwise: false,
14925 done: false,
14926 });
14927 self.select_previous(action, window, cx)?;
14928 }
14929 }
14930 Ok(())
14931 }
14932
14933 /// Builds an `AhoCorasick` automaton from the provided patterns, while
14934 /// setting the case sensitivity based on the global
14935 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
14936 /// editor's settings.
14937 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
14938 where
14939 I: IntoIterator<Item = P>,
14940 P: AsRef<[u8]>,
14941 {
14942 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
14943 || EditorSettings::get_global(cx).search.case_sensitive,
14944 |value| value,
14945 );
14946
14947 let mut builder = AhoCorasickBuilder::new();
14948 builder.ascii_case_insensitive(!case_sensitive);
14949 builder.build(patterns)
14950 }
14951
14952 pub fn find_next_match(
14953 &mut self,
14954 _: &FindNextMatch,
14955 window: &mut Window,
14956 cx: &mut Context<Self>,
14957 ) -> Result<()> {
14958 let selections = self.selections.disjoint_anchors_arc();
14959 match selections.first() {
14960 Some(first) if selections.len() >= 2 => {
14961 self.change_selections(Default::default(), window, cx, |s| {
14962 s.select_ranges([first.range()]);
14963 });
14964 }
14965 _ => self.select_next(
14966 &SelectNext {
14967 replace_newest: true,
14968 },
14969 window,
14970 cx,
14971 )?,
14972 }
14973 Ok(())
14974 }
14975
14976 pub fn find_previous_match(
14977 &mut self,
14978 _: &FindPreviousMatch,
14979 window: &mut Window,
14980 cx: &mut Context<Self>,
14981 ) -> Result<()> {
14982 let selections = self.selections.disjoint_anchors_arc();
14983 match selections.last() {
14984 Some(last) if selections.len() >= 2 => {
14985 self.change_selections(Default::default(), window, cx, |s| {
14986 s.select_ranges([last.range()]);
14987 });
14988 }
14989 _ => self.select_previous(
14990 &SelectPrevious {
14991 replace_newest: true,
14992 },
14993 window,
14994 cx,
14995 )?,
14996 }
14997 Ok(())
14998 }
14999
15000 pub fn toggle_comments(
15001 &mut self,
15002 action: &ToggleComments,
15003 window: &mut Window,
15004 cx: &mut Context<Self>,
15005 ) {
15006 if self.read_only(cx) {
15007 return;
15008 }
15009 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15010 let text_layout_details = &self.text_layout_details(window);
15011 self.transact(window, cx, |this, window, cx| {
15012 let mut selections = this
15013 .selections
15014 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15015 let mut edits = Vec::new();
15016 let mut selection_edit_ranges = Vec::new();
15017 let mut last_toggled_row = None;
15018 let snapshot = this.buffer.read(cx).read(cx);
15019 let empty_str: Arc<str> = Arc::default();
15020 let mut suffixes_inserted = Vec::new();
15021 let ignore_indent = action.ignore_indent;
15022
15023 fn comment_prefix_range(
15024 snapshot: &MultiBufferSnapshot,
15025 row: MultiBufferRow,
15026 comment_prefix: &str,
15027 comment_prefix_whitespace: &str,
15028 ignore_indent: bool,
15029 ) -> Range<Point> {
15030 let indent_size = if ignore_indent {
15031 0
15032 } else {
15033 snapshot.indent_size_for_line(row).len
15034 };
15035
15036 let start = Point::new(row.0, indent_size);
15037
15038 let mut line_bytes = snapshot
15039 .bytes_in_range(start..snapshot.max_point())
15040 .flatten()
15041 .copied();
15042
15043 // If this line currently begins with the line comment prefix, then record
15044 // the range containing the prefix.
15045 if line_bytes
15046 .by_ref()
15047 .take(comment_prefix.len())
15048 .eq(comment_prefix.bytes())
15049 {
15050 // Include any whitespace that matches the comment prefix.
15051 let matching_whitespace_len = line_bytes
15052 .zip(comment_prefix_whitespace.bytes())
15053 .take_while(|(a, b)| a == b)
15054 .count() as u32;
15055 let end = Point::new(
15056 start.row,
15057 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15058 );
15059 start..end
15060 } else {
15061 start..start
15062 }
15063 }
15064
15065 fn comment_suffix_range(
15066 snapshot: &MultiBufferSnapshot,
15067 row: MultiBufferRow,
15068 comment_suffix: &str,
15069 comment_suffix_has_leading_space: bool,
15070 ) -> Range<Point> {
15071 let end = Point::new(row.0, snapshot.line_len(row));
15072 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15073
15074 let mut line_end_bytes = snapshot
15075 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15076 .flatten()
15077 .copied();
15078
15079 let leading_space_len = if suffix_start_column > 0
15080 && line_end_bytes.next() == Some(b' ')
15081 && comment_suffix_has_leading_space
15082 {
15083 1
15084 } else {
15085 0
15086 };
15087
15088 // If this line currently begins with the line comment prefix, then record
15089 // the range containing the prefix.
15090 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15091 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15092 start..end
15093 } else {
15094 end..end
15095 }
15096 }
15097
15098 // TODO: Handle selections that cross excerpts
15099 for selection in &mut selections {
15100 let start_column = snapshot
15101 .indent_size_for_line(MultiBufferRow(selection.start.row))
15102 .len;
15103 let language = if let Some(language) =
15104 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15105 {
15106 language
15107 } else {
15108 continue;
15109 };
15110
15111 selection_edit_ranges.clear();
15112
15113 // If multiple selections contain a given row, avoid processing that
15114 // row more than once.
15115 let mut start_row = MultiBufferRow(selection.start.row);
15116 if last_toggled_row == Some(start_row) {
15117 start_row = start_row.next_row();
15118 }
15119 let end_row =
15120 if selection.end.row > selection.start.row && selection.end.column == 0 {
15121 MultiBufferRow(selection.end.row - 1)
15122 } else {
15123 MultiBufferRow(selection.end.row)
15124 };
15125 last_toggled_row = Some(end_row);
15126
15127 if start_row > end_row {
15128 continue;
15129 }
15130
15131 // If the language has line comments, toggle those.
15132 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15133
15134 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15135 if ignore_indent {
15136 full_comment_prefixes = full_comment_prefixes
15137 .into_iter()
15138 .map(|s| Arc::from(s.trim_end()))
15139 .collect();
15140 }
15141
15142 if !full_comment_prefixes.is_empty() {
15143 let first_prefix = full_comment_prefixes
15144 .first()
15145 .expect("prefixes is non-empty");
15146 let prefix_trimmed_lengths = full_comment_prefixes
15147 .iter()
15148 .map(|p| p.trim_end_matches(' ').len())
15149 .collect::<SmallVec<[usize; 4]>>();
15150
15151 let mut all_selection_lines_are_comments = true;
15152
15153 for row in start_row.0..=end_row.0 {
15154 let row = MultiBufferRow(row);
15155 if start_row < end_row && snapshot.is_line_blank(row) {
15156 continue;
15157 }
15158
15159 let prefix_range = full_comment_prefixes
15160 .iter()
15161 .zip(prefix_trimmed_lengths.iter().copied())
15162 .map(|(prefix, trimmed_prefix_len)| {
15163 comment_prefix_range(
15164 snapshot.deref(),
15165 row,
15166 &prefix[..trimmed_prefix_len],
15167 &prefix[trimmed_prefix_len..],
15168 ignore_indent,
15169 )
15170 })
15171 .max_by_key(|range| range.end.column - range.start.column)
15172 .expect("prefixes is non-empty");
15173
15174 if prefix_range.is_empty() {
15175 all_selection_lines_are_comments = false;
15176 }
15177
15178 selection_edit_ranges.push(prefix_range);
15179 }
15180
15181 if all_selection_lines_are_comments {
15182 edits.extend(
15183 selection_edit_ranges
15184 .iter()
15185 .cloned()
15186 .map(|range| (range, empty_str.clone())),
15187 );
15188 } else {
15189 let min_column = selection_edit_ranges
15190 .iter()
15191 .map(|range| range.start.column)
15192 .min()
15193 .unwrap_or(0);
15194 edits.extend(selection_edit_ranges.iter().map(|range| {
15195 let position = Point::new(range.start.row, min_column);
15196 (position..position, first_prefix.clone())
15197 }));
15198 }
15199 } else if let Some(BlockCommentConfig {
15200 start: full_comment_prefix,
15201 end: comment_suffix,
15202 ..
15203 }) = language.block_comment()
15204 {
15205 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15206 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15207 let prefix_range = comment_prefix_range(
15208 snapshot.deref(),
15209 start_row,
15210 comment_prefix,
15211 comment_prefix_whitespace,
15212 ignore_indent,
15213 );
15214 let suffix_range = comment_suffix_range(
15215 snapshot.deref(),
15216 end_row,
15217 comment_suffix.trim_start_matches(' '),
15218 comment_suffix.starts_with(' '),
15219 );
15220
15221 if prefix_range.is_empty() || suffix_range.is_empty() {
15222 edits.push((
15223 prefix_range.start..prefix_range.start,
15224 full_comment_prefix.clone(),
15225 ));
15226 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15227 suffixes_inserted.push((end_row, comment_suffix.len()));
15228 } else {
15229 edits.push((prefix_range, empty_str.clone()));
15230 edits.push((suffix_range, empty_str.clone()));
15231 }
15232 } else {
15233 continue;
15234 }
15235 }
15236
15237 drop(snapshot);
15238 this.buffer.update(cx, |buffer, cx| {
15239 buffer.edit(edits, None, cx);
15240 });
15241
15242 // Adjust selections so that they end before any comment suffixes that
15243 // were inserted.
15244 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15245 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15246 let snapshot = this.buffer.read(cx).read(cx);
15247 for selection in &mut selections {
15248 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15249 match row.cmp(&MultiBufferRow(selection.end.row)) {
15250 Ordering::Less => {
15251 suffixes_inserted.next();
15252 continue;
15253 }
15254 Ordering::Greater => break,
15255 Ordering::Equal => {
15256 if selection.end.column == snapshot.line_len(row) {
15257 if selection.is_empty() {
15258 selection.start.column -= suffix_len as u32;
15259 }
15260 selection.end.column -= suffix_len as u32;
15261 }
15262 break;
15263 }
15264 }
15265 }
15266 }
15267
15268 drop(snapshot);
15269 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15270
15271 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15272 let selections_on_single_row = selections.windows(2).all(|selections| {
15273 selections[0].start.row == selections[1].start.row
15274 && selections[0].end.row == selections[1].end.row
15275 && selections[0].start.row == selections[0].end.row
15276 });
15277 let selections_selecting = selections
15278 .iter()
15279 .any(|selection| selection.start != selection.end);
15280 let advance_downwards = action.advance_downwards
15281 && selections_on_single_row
15282 && !selections_selecting
15283 && !matches!(this.mode, EditorMode::SingleLine);
15284
15285 if advance_downwards {
15286 let snapshot = this.buffer.read(cx).snapshot(cx);
15287
15288 this.change_selections(Default::default(), window, cx, |s| {
15289 s.move_cursors_with(|display_snapshot, display_point, _| {
15290 let mut point = display_point.to_point(display_snapshot);
15291 point.row += 1;
15292 point = snapshot.clip_point(point, Bias::Left);
15293 let display_point = point.to_display_point(display_snapshot);
15294 let goal = SelectionGoal::HorizontalPosition(
15295 display_snapshot
15296 .x_for_display_point(display_point, text_layout_details)
15297 .into(),
15298 );
15299 (display_point, goal)
15300 })
15301 });
15302 }
15303 });
15304 }
15305
15306 pub fn select_enclosing_symbol(
15307 &mut self,
15308 _: &SelectEnclosingSymbol,
15309 window: &mut Window,
15310 cx: &mut Context<Self>,
15311 ) {
15312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15313
15314 let buffer = self.buffer.read(cx).snapshot(cx);
15315 let old_selections = self
15316 .selections
15317 .all::<usize>(&self.display_snapshot(cx))
15318 .into_boxed_slice();
15319
15320 fn update_selection(
15321 selection: &Selection<usize>,
15322 buffer_snap: &MultiBufferSnapshot,
15323 ) -> Option<Selection<usize>> {
15324 let cursor = selection.head();
15325 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15326 for symbol in symbols.iter().rev() {
15327 let start = symbol.range.start.to_offset(buffer_snap);
15328 let end = symbol.range.end.to_offset(buffer_snap);
15329 let new_range = start..end;
15330 if start < selection.start || end > selection.end {
15331 return Some(Selection {
15332 id: selection.id,
15333 start: new_range.start,
15334 end: new_range.end,
15335 goal: SelectionGoal::None,
15336 reversed: selection.reversed,
15337 });
15338 }
15339 }
15340 None
15341 }
15342
15343 let mut selected_larger_symbol = false;
15344 let new_selections = old_selections
15345 .iter()
15346 .map(|selection| match update_selection(selection, &buffer) {
15347 Some(new_selection) => {
15348 if new_selection.range() != selection.range() {
15349 selected_larger_symbol = true;
15350 }
15351 new_selection
15352 }
15353 None => selection.clone(),
15354 })
15355 .collect::<Vec<_>>();
15356
15357 if selected_larger_symbol {
15358 self.change_selections(Default::default(), window, cx, |s| {
15359 s.select(new_selections);
15360 });
15361 }
15362 }
15363
15364 pub fn select_larger_syntax_node(
15365 &mut self,
15366 _: &SelectLargerSyntaxNode,
15367 window: &mut Window,
15368 cx: &mut Context<Self>,
15369 ) {
15370 let Some(visible_row_count) = self.visible_row_count() else {
15371 return;
15372 };
15373 let old_selections: Box<[_]> = self
15374 .selections
15375 .all::<usize>(&self.display_snapshot(cx))
15376 .into();
15377 if old_selections.is_empty() {
15378 return;
15379 }
15380
15381 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15382
15383 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15384 let buffer = self.buffer.read(cx).snapshot(cx);
15385
15386 let mut selected_larger_node = false;
15387 let mut new_selections = old_selections
15388 .iter()
15389 .map(|selection| {
15390 let old_range = selection.start..selection.end;
15391
15392 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15393 // manually select word at selection
15394 if ["string_content", "inline"].contains(&node.kind()) {
15395 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15396 // ignore if word is already selected
15397 if !word_range.is_empty() && old_range != word_range {
15398 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15399 // only select word if start and end point belongs to same word
15400 if word_range == last_word_range {
15401 selected_larger_node = true;
15402 return Selection {
15403 id: selection.id,
15404 start: word_range.start,
15405 end: word_range.end,
15406 goal: SelectionGoal::None,
15407 reversed: selection.reversed,
15408 };
15409 }
15410 }
15411 }
15412 }
15413
15414 let mut new_range = old_range.clone();
15415 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15416 new_range = range;
15417 if !node.is_named() {
15418 continue;
15419 }
15420 if !display_map.intersects_fold(new_range.start)
15421 && !display_map.intersects_fold(new_range.end)
15422 {
15423 break;
15424 }
15425 }
15426
15427 selected_larger_node |= new_range != old_range;
15428 Selection {
15429 id: selection.id,
15430 start: new_range.start,
15431 end: new_range.end,
15432 goal: SelectionGoal::None,
15433 reversed: selection.reversed,
15434 }
15435 })
15436 .collect::<Vec<_>>();
15437
15438 if !selected_larger_node {
15439 return; // don't put this call in the history
15440 }
15441
15442 // scroll based on transformation done to the last selection created by the user
15443 let (last_old, last_new) = old_selections
15444 .last()
15445 .zip(new_selections.last().cloned())
15446 .expect("old_selections isn't empty");
15447
15448 // revert selection
15449 let is_selection_reversed = {
15450 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15451 new_selections.last_mut().expect("checked above").reversed =
15452 should_newest_selection_be_reversed;
15453 should_newest_selection_be_reversed
15454 };
15455
15456 if selected_larger_node {
15457 self.select_syntax_node_history.disable_clearing = true;
15458 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15459 s.select(new_selections.clone());
15460 });
15461 self.select_syntax_node_history.disable_clearing = false;
15462 }
15463
15464 let start_row = last_new.start.to_display_point(&display_map).row().0;
15465 let end_row = last_new.end.to_display_point(&display_map).row().0;
15466 let selection_height = end_row - start_row + 1;
15467 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15468
15469 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15470 let scroll_behavior = if fits_on_the_screen {
15471 self.request_autoscroll(Autoscroll::fit(), cx);
15472 SelectSyntaxNodeScrollBehavior::FitSelection
15473 } else if is_selection_reversed {
15474 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15475 SelectSyntaxNodeScrollBehavior::CursorTop
15476 } else {
15477 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15478 SelectSyntaxNodeScrollBehavior::CursorBottom
15479 };
15480
15481 self.select_syntax_node_history.push((
15482 old_selections,
15483 scroll_behavior,
15484 is_selection_reversed,
15485 ));
15486 }
15487
15488 pub fn select_smaller_syntax_node(
15489 &mut self,
15490 _: &SelectSmallerSyntaxNode,
15491 window: &mut Window,
15492 cx: &mut Context<Self>,
15493 ) {
15494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15495
15496 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15497 self.select_syntax_node_history.pop()
15498 {
15499 if let Some(selection) = selections.last_mut() {
15500 selection.reversed = is_selection_reversed;
15501 }
15502
15503 self.select_syntax_node_history.disable_clearing = true;
15504 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15505 s.select(selections.to_vec());
15506 });
15507 self.select_syntax_node_history.disable_clearing = false;
15508
15509 match scroll_behavior {
15510 SelectSyntaxNodeScrollBehavior::CursorTop => {
15511 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15512 }
15513 SelectSyntaxNodeScrollBehavior::FitSelection => {
15514 self.request_autoscroll(Autoscroll::fit(), cx);
15515 }
15516 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15517 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15518 }
15519 }
15520 }
15521 }
15522
15523 pub fn unwrap_syntax_node(
15524 &mut self,
15525 _: &UnwrapSyntaxNode,
15526 window: &mut Window,
15527 cx: &mut Context<Self>,
15528 ) {
15529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15530
15531 let buffer = self.buffer.read(cx).snapshot(cx);
15532 let selections = self
15533 .selections
15534 .all::<usize>(&self.display_snapshot(cx))
15535 .into_iter()
15536 // subtracting the offset requires sorting
15537 .sorted_by_key(|i| i.start);
15538
15539 let full_edits = selections
15540 .into_iter()
15541 .filter_map(|selection| {
15542 let child = if selection.is_empty()
15543 && let Some((_, ancestor_range)) =
15544 buffer.syntax_ancestor(selection.start..selection.end)
15545 {
15546 ancestor_range
15547 } else {
15548 selection.range()
15549 };
15550
15551 let mut parent = child.clone();
15552 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15553 parent = ancestor_range;
15554 if parent.start < child.start || parent.end > child.end {
15555 break;
15556 }
15557 }
15558
15559 if parent == child {
15560 return None;
15561 }
15562 let text = buffer.text_for_range(child).collect::<String>();
15563 Some((selection.id, parent, text))
15564 })
15565 .collect::<Vec<_>>();
15566 if full_edits.is_empty() {
15567 return;
15568 }
15569
15570 self.transact(window, cx, |this, window, cx| {
15571 this.buffer.update(cx, |buffer, cx| {
15572 buffer.edit(
15573 full_edits
15574 .iter()
15575 .map(|(_, p, t)| (p.clone(), t.clone()))
15576 .collect::<Vec<_>>(),
15577 None,
15578 cx,
15579 );
15580 });
15581 this.change_selections(Default::default(), window, cx, |s| {
15582 let mut offset = 0;
15583 let mut selections = vec![];
15584 for (id, parent, text) in full_edits {
15585 let start = parent.start - offset;
15586 offset += parent.len() - text.len();
15587 selections.push(Selection {
15588 id,
15589 start,
15590 end: start + text.len(),
15591 reversed: false,
15592 goal: Default::default(),
15593 });
15594 }
15595 s.select(selections);
15596 });
15597 });
15598 }
15599
15600 pub fn select_next_syntax_node(
15601 &mut self,
15602 _: &SelectNextSyntaxNode,
15603 window: &mut Window,
15604 cx: &mut Context<Self>,
15605 ) {
15606 let old_selections: Box<[_]> = self
15607 .selections
15608 .all::<usize>(&self.display_snapshot(cx))
15609 .into();
15610 if old_selections.is_empty() {
15611 return;
15612 }
15613
15614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15615
15616 let buffer = self.buffer.read(cx).snapshot(cx);
15617 let mut selected_sibling = false;
15618
15619 let new_selections = old_selections
15620 .iter()
15621 .map(|selection| {
15622 let old_range = selection.start..selection.end;
15623
15624 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15625 let new_range = node.byte_range();
15626 selected_sibling = true;
15627 Selection {
15628 id: selection.id,
15629 start: new_range.start,
15630 end: new_range.end,
15631 goal: SelectionGoal::None,
15632 reversed: selection.reversed,
15633 }
15634 } else {
15635 selection.clone()
15636 }
15637 })
15638 .collect::<Vec<_>>();
15639
15640 if selected_sibling {
15641 self.change_selections(
15642 SelectionEffects::scroll(Autoscroll::fit()),
15643 window,
15644 cx,
15645 |s| {
15646 s.select(new_selections);
15647 },
15648 );
15649 }
15650 }
15651
15652 pub fn select_prev_syntax_node(
15653 &mut self,
15654 _: &SelectPreviousSyntaxNode,
15655 window: &mut Window,
15656 cx: &mut Context<Self>,
15657 ) {
15658 let old_selections: Box<[_]> = self
15659 .selections
15660 .all::<usize>(&self.display_snapshot(cx))
15661 .into();
15662 if old_selections.is_empty() {
15663 return;
15664 }
15665
15666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15667
15668 let buffer = self.buffer.read(cx).snapshot(cx);
15669 let mut selected_sibling = false;
15670
15671 let new_selections = old_selections
15672 .iter()
15673 .map(|selection| {
15674 let old_range = selection.start..selection.end;
15675
15676 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15677 let new_range = node.byte_range();
15678 selected_sibling = true;
15679 Selection {
15680 id: selection.id,
15681 start: new_range.start,
15682 end: new_range.end,
15683 goal: SelectionGoal::None,
15684 reversed: selection.reversed,
15685 }
15686 } else {
15687 selection.clone()
15688 }
15689 })
15690 .collect::<Vec<_>>();
15691
15692 if selected_sibling {
15693 self.change_selections(
15694 SelectionEffects::scroll(Autoscroll::fit()),
15695 window,
15696 cx,
15697 |s| {
15698 s.select(new_selections);
15699 },
15700 );
15701 }
15702 }
15703
15704 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15705 if !EditorSettings::get_global(cx).gutter.runnables {
15706 self.clear_tasks();
15707 return Task::ready(());
15708 }
15709 let project = self.project().map(Entity::downgrade);
15710 let task_sources = self.lsp_task_sources(cx);
15711 let multi_buffer = self.buffer.downgrade();
15712 cx.spawn_in(window, async move |editor, cx| {
15713 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15714 let Some(project) = project.and_then(|p| p.upgrade()) else {
15715 return;
15716 };
15717 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15718 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15719 }) else {
15720 return;
15721 };
15722
15723 let hide_runnables = project
15724 .update(cx, |project, _| project.is_via_collab())
15725 .unwrap_or(true);
15726 if hide_runnables {
15727 return;
15728 }
15729 let new_rows =
15730 cx.background_spawn({
15731 let snapshot = display_snapshot.clone();
15732 async move {
15733 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15734 }
15735 })
15736 .await;
15737 let Ok(lsp_tasks) =
15738 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15739 else {
15740 return;
15741 };
15742 let lsp_tasks = lsp_tasks.await;
15743
15744 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15745 lsp_tasks
15746 .into_iter()
15747 .flat_map(|(kind, tasks)| {
15748 tasks.into_iter().filter_map(move |(location, task)| {
15749 Some((kind.clone(), location?, task))
15750 })
15751 })
15752 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15753 let buffer = location.target.buffer;
15754 let buffer_snapshot = buffer.read(cx).snapshot();
15755 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15756 |(excerpt_id, snapshot, _)| {
15757 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15758 display_snapshot
15759 .buffer_snapshot()
15760 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15761 } else {
15762 None
15763 }
15764 },
15765 );
15766 if let Some(offset) = offset {
15767 let task_buffer_range =
15768 location.target.range.to_point(&buffer_snapshot);
15769 let context_buffer_range =
15770 task_buffer_range.to_offset(&buffer_snapshot);
15771 let context_range = BufferOffset(context_buffer_range.start)
15772 ..BufferOffset(context_buffer_range.end);
15773
15774 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15775 .or_insert_with(|| RunnableTasks {
15776 templates: Vec::new(),
15777 offset,
15778 column: task_buffer_range.start.column,
15779 extra_variables: HashMap::default(),
15780 context_range,
15781 })
15782 .templates
15783 .push((kind, task.original_task().clone()));
15784 }
15785
15786 acc
15787 })
15788 }) else {
15789 return;
15790 };
15791
15792 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15793 buffer.language_settings(cx).tasks.prefer_lsp
15794 }) else {
15795 return;
15796 };
15797
15798 let rows = Self::runnable_rows(
15799 project,
15800 display_snapshot,
15801 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15802 new_rows,
15803 cx.clone(),
15804 )
15805 .await;
15806 editor
15807 .update(cx, |editor, _| {
15808 editor.clear_tasks();
15809 for (key, mut value) in rows {
15810 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15811 value.templates.extend(lsp_tasks.templates);
15812 }
15813
15814 editor.insert_tasks(key, value);
15815 }
15816 for (key, value) in lsp_tasks_by_rows {
15817 editor.insert_tasks(key, value);
15818 }
15819 })
15820 .ok();
15821 })
15822 }
15823 fn fetch_runnable_ranges(
15824 snapshot: &DisplaySnapshot,
15825 range: Range<Anchor>,
15826 ) -> Vec<language::RunnableRange> {
15827 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15828 }
15829
15830 fn runnable_rows(
15831 project: Entity<Project>,
15832 snapshot: DisplaySnapshot,
15833 prefer_lsp: bool,
15834 runnable_ranges: Vec<RunnableRange>,
15835 cx: AsyncWindowContext,
15836 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15837 cx.spawn(async move |cx| {
15838 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15839 for mut runnable in runnable_ranges {
15840 let Some(tasks) = cx
15841 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15842 .ok()
15843 else {
15844 continue;
15845 };
15846 let mut tasks = tasks.await;
15847
15848 if prefer_lsp {
15849 tasks.retain(|(task_kind, _)| {
15850 !matches!(task_kind, TaskSourceKind::Language { .. })
15851 });
15852 }
15853 if tasks.is_empty() {
15854 continue;
15855 }
15856
15857 let point = runnable
15858 .run_range
15859 .start
15860 .to_point(&snapshot.buffer_snapshot());
15861 let Some(row) = snapshot
15862 .buffer_snapshot()
15863 .buffer_line_for_row(MultiBufferRow(point.row))
15864 .map(|(_, range)| range.start.row)
15865 else {
15866 continue;
15867 };
15868
15869 let context_range =
15870 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15871 runnable_rows.push((
15872 (runnable.buffer_id, row),
15873 RunnableTasks {
15874 templates: tasks,
15875 offset: snapshot
15876 .buffer_snapshot()
15877 .anchor_before(runnable.run_range.start),
15878 context_range,
15879 column: point.column,
15880 extra_variables: runnable.extra_captures,
15881 },
15882 ));
15883 }
15884 runnable_rows
15885 })
15886 }
15887
15888 fn templates_with_tags(
15889 project: &Entity<Project>,
15890 runnable: &mut Runnable,
15891 cx: &mut App,
15892 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15893 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15894 let (worktree_id, file) = project
15895 .buffer_for_id(runnable.buffer, cx)
15896 .and_then(|buffer| buffer.read(cx).file())
15897 .map(|file| (file.worktree_id(cx), file.clone()))
15898 .unzip();
15899
15900 (
15901 project.task_store().read(cx).task_inventory().cloned(),
15902 worktree_id,
15903 file,
15904 )
15905 });
15906
15907 let tags = mem::take(&mut runnable.tags);
15908 let language = runnable.language.clone();
15909 cx.spawn(async move |cx| {
15910 let mut templates_with_tags = Vec::new();
15911 if let Some(inventory) = inventory {
15912 for RunnableTag(tag) in tags {
15913 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15914 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15915 }) else {
15916 return templates_with_tags;
15917 };
15918 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15919 move |(_, template)| {
15920 template.tags.iter().any(|source_tag| source_tag == &tag)
15921 },
15922 ));
15923 }
15924 }
15925 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15926
15927 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15928 // Strongest source wins; if we have worktree tag binding, prefer that to
15929 // global and language bindings;
15930 // if we have a global binding, prefer that to language binding.
15931 let first_mismatch = templates_with_tags
15932 .iter()
15933 .position(|(tag_source, _)| tag_source != leading_tag_source);
15934 if let Some(index) = first_mismatch {
15935 templates_with_tags.truncate(index);
15936 }
15937 }
15938
15939 templates_with_tags
15940 })
15941 }
15942
15943 pub fn move_to_enclosing_bracket(
15944 &mut self,
15945 _: &MoveToEnclosingBracket,
15946 window: &mut Window,
15947 cx: &mut Context<Self>,
15948 ) {
15949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15950 self.change_selections(Default::default(), window, cx, |s| {
15951 s.move_offsets_with(|snapshot, selection| {
15952 let Some(enclosing_bracket_ranges) =
15953 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15954 else {
15955 return;
15956 };
15957
15958 let mut best_length = usize::MAX;
15959 let mut best_inside = false;
15960 let mut best_in_bracket_range = false;
15961 let mut best_destination = None;
15962 for (open, close) in enclosing_bracket_ranges {
15963 let close = close.to_inclusive();
15964 let length = close.end() - open.start;
15965 let inside = selection.start >= open.end && selection.end <= *close.start();
15966 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15967 || close.contains(&selection.head());
15968
15969 // If best is next to a bracket and current isn't, skip
15970 if !in_bracket_range && best_in_bracket_range {
15971 continue;
15972 }
15973
15974 // Prefer smaller lengths unless best is inside and current isn't
15975 if length > best_length && (best_inside || !inside) {
15976 continue;
15977 }
15978
15979 best_length = length;
15980 best_inside = inside;
15981 best_in_bracket_range = in_bracket_range;
15982 best_destination = Some(
15983 if close.contains(&selection.start) && close.contains(&selection.end) {
15984 if inside { open.end } else { open.start }
15985 } else if inside {
15986 *close.start()
15987 } else {
15988 *close.end()
15989 },
15990 );
15991 }
15992
15993 if let Some(destination) = best_destination {
15994 selection.collapse_to(destination, SelectionGoal::None);
15995 }
15996 })
15997 });
15998 }
15999
16000 pub fn undo_selection(
16001 &mut self,
16002 _: &UndoSelection,
16003 window: &mut Window,
16004 cx: &mut Context<Self>,
16005 ) {
16006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16007 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16008 self.selection_history.mode = SelectionHistoryMode::Undoing;
16009 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16010 this.end_selection(window, cx);
16011 this.change_selections(
16012 SelectionEffects::scroll(Autoscroll::newest()),
16013 window,
16014 cx,
16015 |s| s.select_anchors(entry.selections.to_vec()),
16016 );
16017 });
16018 self.selection_history.mode = SelectionHistoryMode::Normal;
16019
16020 self.select_next_state = entry.select_next_state;
16021 self.select_prev_state = entry.select_prev_state;
16022 self.add_selections_state = entry.add_selections_state;
16023 }
16024 }
16025
16026 pub fn redo_selection(
16027 &mut self,
16028 _: &RedoSelection,
16029 window: &mut Window,
16030 cx: &mut Context<Self>,
16031 ) {
16032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16033 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16034 self.selection_history.mode = SelectionHistoryMode::Redoing;
16035 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16036 this.end_selection(window, cx);
16037 this.change_selections(
16038 SelectionEffects::scroll(Autoscroll::newest()),
16039 window,
16040 cx,
16041 |s| s.select_anchors(entry.selections.to_vec()),
16042 );
16043 });
16044 self.selection_history.mode = SelectionHistoryMode::Normal;
16045
16046 self.select_next_state = entry.select_next_state;
16047 self.select_prev_state = entry.select_prev_state;
16048 self.add_selections_state = entry.add_selections_state;
16049 }
16050 }
16051
16052 pub fn expand_excerpts(
16053 &mut self,
16054 action: &ExpandExcerpts,
16055 _: &mut Window,
16056 cx: &mut Context<Self>,
16057 ) {
16058 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16059 }
16060
16061 pub fn expand_excerpts_down(
16062 &mut self,
16063 action: &ExpandExcerptsDown,
16064 _: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) {
16067 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16068 }
16069
16070 pub fn expand_excerpts_up(
16071 &mut self,
16072 action: &ExpandExcerptsUp,
16073 _: &mut Window,
16074 cx: &mut Context<Self>,
16075 ) {
16076 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16077 }
16078
16079 pub fn expand_excerpts_for_direction(
16080 &mut self,
16081 lines: u32,
16082 direction: ExpandExcerptDirection,
16083
16084 cx: &mut Context<Self>,
16085 ) {
16086 let selections = self.selections.disjoint_anchors_arc();
16087
16088 let lines = if lines == 0 {
16089 EditorSettings::get_global(cx).expand_excerpt_lines
16090 } else {
16091 lines
16092 };
16093
16094 self.buffer.update(cx, |buffer, cx| {
16095 let snapshot = buffer.snapshot(cx);
16096 let mut excerpt_ids = selections
16097 .iter()
16098 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16099 .collect::<Vec<_>>();
16100 excerpt_ids.sort();
16101 excerpt_ids.dedup();
16102 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16103 })
16104 }
16105
16106 pub fn expand_excerpt(
16107 &mut self,
16108 excerpt: ExcerptId,
16109 direction: ExpandExcerptDirection,
16110 window: &mut Window,
16111 cx: &mut Context<Self>,
16112 ) {
16113 let current_scroll_position = self.scroll_position(cx);
16114 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16115 let mut scroll = None;
16116
16117 if direction == ExpandExcerptDirection::Down {
16118 let multi_buffer = self.buffer.read(cx);
16119 let snapshot = multi_buffer.snapshot(cx);
16120 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16121 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16122 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16123 {
16124 let buffer_snapshot = buffer.read(cx).snapshot();
16125 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16126 let last_row = buffer_snapshot.max_point().row;
16127 let lines_below = last_row.saturating_sub(excerpt_end_row);
16128 if lines_below >= lines_to_expand {
16129 scroll = Some(
16130 current_scroll_position
16131 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16132 );
16133 }
16134 }
16135 }
16136 if direction == ExpandExcerptDirection::Up
16137 && self
16138 .buffer
16139 .read(cx)
16140 .snapshot(cx)
16141 .excerpt_before(excerpt)
16142 .is_none()
16143 {
16144 scroll = Some(current_scroll_position);
16145 }
16146
16147 self.buffer.update(cx, |buffer, cx| {
16148 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16149 });
16150
16151 if let Some(new_scroll_position) = scroll {
16152 self.set_scroll_position(new_scroll_position, window, cx);
16153 }
16154 }
16155
16156 pub fn go_to_singleton_buffer_point(
16157 &mut self,
16158 point: Point,
16159 window: &mut Window,
16160 cx: &mut Context<Self>,
16161 ) {
16162 self.go_to_singleton_buffer_range(point..point, window, cx);
16163 }
16164
16165 pub fn go_to_singleton_buffer_range(
16166 &mut self,
16167 range: Range<Point>,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) {
16171 let multibuffer = self.buffer().read(cx);
16172 let Some(buffer) = multibuffer.as_singleton() else {
16173 return;
16174 };
16175 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16176 return;
16177 };
16178 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16179 return;
16180 };
16181 self.change_selections(
16182 SelectionEffects::default().nav_history(true),
16183 window,
16184 cx,
16185 |s| s.select_anchor_ranges([start..end]),
16186 );
16187 }
16188
16189 pub fn go_to_diagnostic(
16190 &mut self,
16191 action: &GoToDiagnostic,
16192 window: &mut Window,
16193 cx: &mut Context<Self>,
16194 ) {
16195 if !self.diagnostics_enabled() {
16196 return;
16197 }
16198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16199 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16200 }
16201
16202 pub fn go_to_prev_diagnostic(
16203 &mut self,
16204 action: &GoToPreviousDiagnostic,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) {
16208 if !self.diagnostics_enabled() {
16209 return;
16210 }
16211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16212 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16213 }
16214
16215 pub fn go_to_diagnostic_impl(
16216 &mut self,
16217 direction: Direction,
16218 severity: GoToDiagnosticSeverityFilter,
16219 window: &mut Window,
16220 cx: &mut Context<Self>,
16221 ) {
16222 let buffer = self.buffer.read(cx).snapshot(cx);
16223 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16224
16225 let mut active_group_id = None;
16226 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16227 && active_group.active_range.start.to_offset(&buffer) == selection.start
16228 {
16229 active_group_id = Some(active_group.group_id);
16230 }
16231
16232 fn filtered<'a>(
16233 severity: GoToDiagnosticSeverityFilter,
16234 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16235 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16236 diagnostics
16237 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16238 .filter(|entry| entry.range.start != entry.range.end)
16239 .filter(|entry| !entry.diagnostic.is_unnecessary)
16240 }
16241
16242 let before = filtered(
16243 severity,
16244 buffer
16245 .diagnostics_in_range(0..selection.start)
16246 .filter(|entry| entry.range.start <= selection.start),
16247 );
16248 let after = filtered(
16249 severity,
16250 buffer
16251 .diagnostics_in_range(selection.start..buffer.len())
16252 .filter(|entry| entry.range.start >= selection.start),
16253 );
16254
16255 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16256 if direction == Direction::Prev {
16257 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16258 {
16259 for diagnostic in prev_diagnostics.into_iter().rev() {
16260 if diagnostic.range.start != selection.start
16261 || active_group_id
16262 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16263 {
16264 found = Some(diagnostic);
16265 break 'outer;
16266 }
16267 }
16268 }
16269 } else {
16270 for diagnostic in after.chain(before) {
16271 if diagnostic.range.start != selection.start
16272 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16273 {
16274 found = Some(diagnostic);
16275 break;
16276 }
16277 }
16278 }
16279 let Some(next_diagnostic) = found else {
16280 return;
16281 };
16282
16283 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16284 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16285 return;
16286 };
16287 let snapshot = self.snapshot(window, cx);
16288 if snapshot.intersects_fold(next_diagnostic.range.start) {
16289 self.unfold_ranges(
16290 std::slice::from_ref(&next_diagnostic.range),
16291 true,
16292 false,
16293 cx,
16294 );
16295 }
16296 self.change_selections(Default::default(), window, cx, |s| {
16297 s.select_ranges(vec![
16298 next_diagnostic.range.start..next_diagnostic.range.start,
16299 ])
16300 });
16301 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16302 self.refresh_edit_prediction(false, true, window, cx);
16303 }
16304
16305 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16307 let snapshot = self.snapshot(window, cx);
16308 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16309 self.go_to_hunk_before_or_after_position(
16310 &snapshot,
16311 selection.head(),
16312 Direction::Next,
16313 window,
16314 cx,
16315 );
16316 }
16317
16318 pub fn go_to_hunk_before_or_after_position(
16319 &mut self,
16320 snapshot: &EditorSnapshot,
16321 position: Point,
16322 direction: Direction,
16323 window: &mut Window,
16324 cx: &mut Context<Editor>,
16325 ) {
16326 let row = if direction == Direction::Next {
16327 self.hunk_after_position(snapshot, position)
16328 .map(|hunk| hunk.row_range.start)
16329 } else {
16330 self.hunk_before_position(snapshot, position)
16331 };
16332
16333 if let Some(row) = row {
16334 let destination = Point::new(row.0, 0);
16335 let autoscroll = Autoscroll::center();
16336
16337 self.unfold_ranges(&[destination..destination], false, false, cx);
16338 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16339 s.select_ranges([destination..destination]);
16340 });
16341 }
16342 }
16343
16344 fn hunk_after_position(
16345 &mut self,
16346 snapshot: &EditorSnapshot,
16347 position: Point,
16348 ) -> Option<MultiBufferDiffHunk> {
16349 snapshot
16350 .buffer_snapshot()
16351 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16352 .find(|hunk| hunk.row_range.start.0 > position.row)
16353 .or_else(|| {
16354 snapshot
16355 .buffer_snapshot()
16356 .diff_hunks_in_range(Point::zero()..position)
16357 .find(|hunk| hunk.row_range.end.0 < position.row)
16358 })
16359 }
16360
16361 fn go_to_prev_hunk(
16362 &mut self,
16363 _: &GoToPreviousHunk,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) {
16367 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16368 let snapshot = self.snapshot(window, cx);
16369 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16370 self.go_to_hunk_before_or_after_position(
16371 &snapshot,
16372 selection.head(),
16373 Direction::Prev,
16374 window,
16375 cx,
16376 );
16377 }
16378
16379 fn hunk_before_position(
16380 &mut self,
16381 snapshot: &EditorSnapshot,
16382 position: Point,
16383 ) -> Option<MultiBufferRow> {
16384 snapshot
16385 .buffer_snapshot()
16386 .diff_hunk_before(position)
16387 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16388 }
16389
16390 fn go_to_next_change(
16391 &mut self,
16392 _: &GoToNextChange,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) {
16396 if let Some(selections) = self
16397 .change_list
16398 .next_change(1, Direction::Next)
16399 .map(|s| s.to_vec())
16400 {
16401 self.change_selections(Default::default(), window, cx, |s| {
16402 let map = s.display_snapshot();
16403 s.select_display_ranges(selections.iter().map(|a| {
16404 let point = a.to_display_point(&map);
16405 point..point
16406 }))
16407 })
16408 }
16409 }
16410
16411 fn go_to_previous_change(
16412 &mut self,
16413 _: &GoToPreviousChange,
16414 window: &mut Window,
16415 cx: &mut Context<Self>,
16416 ) {
16417 if let Some(selections) = self
16418 .change_list
16419 .next_change(1, Direction::Prev)
16420 .map(|s| s.to_vec())
16421 {
16422 self.change_selections(Default::default(), window, cx, |s| {
16423 let map = s.display_snapshot();
16424 s.select_display_ranges(selections.iter().map(|a| {
16425 let point = a.to_display_point(&map);
16426 point..point
16427 }))
16428 })
16429 }
16430 }
16431
16432 pub fn go_to_next_document_highlight(
16433 &mut self,
16434 _: &GoToNextDocumentHighlight,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) {
16438 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16439 }
16440
16441 pub fn go_to_prev_document_highlight(
16442 &mut self,
16443 _: &GoToPreviousDocumentHighlight,
16444 window: &mut Window,
16445 cx: &mut Context<Self>,
16446 ) {
16447 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16448 }
16449
16450 pub fn go_to_document_highlight_before_or_after_position(
16451 &mut self,
16452 direction: Direction,
16453 window: &mut Window,
16454 cx: &mut Context<Editor>,
16455 ) {
16456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16457 let snapshot = self.snapshot(window, cx);
16458 let buffer = &snapshot.buffer_snapshot();
16459 let position = self
16460 .selections
16461 .newest::<Point>(&snapshot.display_snapshot)
16462 .head();
16463 let anchor_position = buffer.anchor_after(position);
16464
16465 // Get all document highlights (both read and write)
16466 let mut all_highlights = Vec::new();
16467
16468 if let Some((_, read_highlights)) = self
16469 .background_highlights
16470 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16471 {
16472 all_highlights.extend(read_highlights.iter());
16473 }
16474
16475 if let Some((_, write_highlights)) = self
16476 .background_highlights
16477 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16478 {
16479 all_highlights.extend(write_highlights.iter());
16480 }
16481
16482 if all_highlights.is_empty() {
16483 return;
16484 }
16485
16486 // Sort highlights by position
16487 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16488
16489 let target_highlight = match direction {
16490 Direction::Next => {
16491 // Find the first highlight after the current position
16492 all_highlights
16493 .iter()
16494 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16495 }
16496 Direction::Prev => {
16497 // Find the last highlight before the current position
16498 all_highlights
16499 .iter()
16500 .rev()
16501 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16502 }
16503 };
16504
16505 if let Some(highlight) = target_highlight {
16506 let destination = highlight.start.to_point(buffer);
16507 let autoscroll = Autoscroll::center();
16508
16509 self.unfold_ranges(&[destination..destination], false, false, cx);
16510 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16511 s.select_ranges([destination..destination]);
16512 });
16513 }
16514 }
16515
16516 fn go_to_line<T: 'static>(
16517 &mut self,
16518 position: Anchor,
16519 highlight_color: Option<Hsla>,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) {
16523 let snapshot = self.snapshot(window, cx).display_snapshot;
16524 let position = position.to_point(&snapshot.buffer_snapshot());
16525 let start = snapshot
16526 .buffer_snapshot()
16527 .clip_point(Point::new(position.row, 0), Bias::Left);
16528 let end = start + Point::new(1, 0);
16529 let start = snapshot.buffer_snapshot().anchor_before(start);
16530 let end = snapshot.buffer_snapshot().anchor_before(end);
16531
16532 self.highlight_rows::<T>(
16533 start..end,
16534 highlight_color
16535 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16536 Default::default(),
16537 cx,
16538 );
16539
16540 if self.buffer.read(cx).is_singleton() {
16541 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16542 }
16543 }
16544
16545 pub fn go_to_definition(
16546 &mut self,
16547 _: &GoToDefinition,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) -> Task<Result<Navigated>> {
16551 let definition =
16552 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16553 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16554 cx.spawn_in(window, async move |editor, cx| {
16555 if definition.await? == Navigated::Yes {
16556 return Ok(Navigated::Yes);
16557 }
16558 match fallback_strategy {
16559 GoToDefinitionFallback::None => Ok(Navigated::No),
16560 GoToDefinitionFallback::FindAllReferences => {
16561 match editor.update_in(cx, |editor, window, cx| {
16562 editor.find_all_references(&FindAllReferences, window, cx)
16563 })? {
16564 Some(references) => references.await,
16565 None => Ok(Navigated::No),
16566 }
16567 }
16568 }
16569 })
16570 }
16571
16572 pub fn go_to_declaration(
16573 &mut self,
16574 _: &GoToDeclaration,
16575 window: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) -> Task<Result<Navigated>> {
16578 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16579 }
16580
16581 pub fn go_to_declaration_split(
16582 &mut self,
16583 _: &GoToDeclaration,
16584 window: &mut Window,
16585 cx: &mut Context<Self>,
16586 ) -> Task<Result<Navigated>> {
16587 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16588 }
16589
16590 pub fn go_to_implementation(
16591 &mut self,
16592 _: &GoToImplementation,
16593 window: &mut Window,
16594 cx: &mut Context<Self>,
16595 ) -> Task<Result<Navigated>> {
16596 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16597 }
16598
16599 pub fn go_to_implementation_split(
16600 &mut self,
16601 _: &GoToImplementationSplit,
16602 window: &mut Window,
16603 cx: &mut Context<Self>,
16604 ) -> Task<Result<Navigated>> {
16605 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16606 }
16607
16608 pub fn go_to_type_definition(
16609 &mut self,
16610 _: &GoToTypeDefinition,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) -> Task<Result<Navigated>> {
16614 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16615 }
16616
16617 pub fn go_to_definition_split(
16618 &mut self,
16619 _: &GoToDefinitionSplit,
16620 window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) -> Task<Result<Navigated>> {
16623 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16624 }
16625
16626 pub fn go_to_type_definition_split(
16627 &mut self,
16628 _: &GoToTypeDefinitionSplit,
16629 window: &mut Window,
16630 cx: &mut Context<Self>,
16631 ) -> Task<Result<Navigated>> {
16632 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16633 }
16634
16635 fn go_to_definition_of_kind(
16636 &mut self,
16637 kind: GotoDefinitionKind,
16638 split: bool,
16639 window: &mut Window,
16640 cx: &mut Context<Self>,
16641 ) -> Task<Result<Navigated>> {
16642 let Some(provider) = self.semantics_provider.clone() else {
16643 return Task::ready(Ok(Navigated::No));
16644 };
16645 let head = self
16646 .selections
16647 .newest::<usize>(&self.display_snapshot(cx))
16648 .head();
16649 let buffer = self.buffer.read(cx);
16650 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16651 return Task::ready(Ok(Navigated::No));
16652 };
16653 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16654 return Task::ready(Ok(Navigated::No));
16655 };
16656
16657 cx.spawn_in(window, async move |editor, cx| {
16658 let Some(definitions) = definitions.await? else {
16659 return Ok(Navigated::No);
16660 };
16661 let navigated = editor
16662 .update_in(cx, |editor, window, cx| {
16663 editor.navigate_to_hover_links(
16664 Some(kind),
16665 definitions
16666 .into_iter()
16667 .filter(|location| {
16668 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16669 })
16670 .map(HoverLink::Text)
16671 .collect::<Vec<_>>(),
16672 split,
16673 window,
16674 cx,
16675 )
16676 })?
16677 .await?;
16678 anyhow::Ok(navigated)
16679 })
16680 }
16681
16682 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16683 let selection = self.selections.newest_anchor();
16684 let head = selection.head();
16685 let tail = selection.tail();
16686
16687 let Some((buffer, start_position)) =
16688 self.buffer.read(cx).text_anchor_for_position(head, cx)
16689 else {
16690 return;
16691 };
16692
16693 let end_position = if head != tail {
16694 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16695 return;
16696 };
16697 Some(pos)
16698 } else {
16699 None
16700 };
16701
16702 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16703 let url = if let Some(end_pos) = end_position {
16704 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16705 } else {
16706 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16707 };
16708
16709 if let Some(url) = url {
16710 cx.update(|window, cx| {
16711 if parse_zed_link(&url, cx).is_some() {
16712 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16713 } else {
16714 cx.open_url(&url);
16715 }
16716 })?;
16717 }
16718
16719 anyhow::Ok(())
16720 });
16721
16722 url_finder.detach();
16723 }
16724
16725 pub fn open_selected_filename(
16726 &mut self,
16727 _: &OpenSelectedFilename,
16728 window: &mut Window,
16729 cx: &mut Context<Self>,
16730 ) {
16731 let Some(workspace) = self.workspace() else {
16732 return;
16733 };
16734
16735 let position = self.selections.newest_anchor().head();
16736
16737 let Some((buffer, buffer_position)) =
16738 self.buffer.read(cx).text_anchor_for_position(position, cx)
16739 else {
16740 return;
16741 };
16742
16743 let project = self.project.clone();
16744
16745 cx.spawn_in(window, async move |_, cx| {
16746 let result = find_file(&buffer, project, buffer_position, cx).await;
16747
16748 if let Some((_, path)) = result {
16749 workspace
16750 .update_in(cx, |workspace, window, cx| {
16751 workspace.open_resolved_path(path, window, cx)
16752 })?
16753 .await?;
16754 }
16755 anyhow::Ok(())
16756 })
16757 .detach();
16758 }
16759
16760 pub(crate) fn navigate_to_hover_links(
16761 &mut self,
16762 kind: Option<GotoDefinitionKind>,
16763 definitions: Vec<HoverLink>,
16764 split: bool,
16765 window: &mut Window,
16766 cx: &mut Context<Editor>,
16767 ) -> Task<Result<Navigated>> {
16768 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16769 let mut first_url_or_file = None;
16770 let definitions: Vec<_> = definitions
16771 .into_iter()
16772 .filter_map(|def| match def {
16773 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16774 HoverLink::InlayHint(lsp_location, server_id) => {
16775 let computation =
16776 self.compute_target_location(lsp_location, server_id, window, cx);
16777 Some(cx.background_spawn(computation))
16778 }
16779 HoverLink::Url(url) => {
16780 first_url_or_file = Some(Either::Left(url));
16781 None
16782 }
16783 HoverLink::File(path) => {
16784 first_url_or_file = Some(Either::Right(path));
16785 None
16786 }
16787 })
16788 .collect();
16789
16790 let workspace = self.workspace();
16791
16792 cx.spawn_in(window, async move |editor, cx| {
16793 let locations: Vec<Location> = future::join_all(definitions)
16794 .await
16795 .into_iter()
16796 .filter_map(|location| location.transpose())
16797 .collect::<Result<_>>()
16798 .context("location tasks")?;
16799 let mut locations = cx.update(|_, cx| {
16800 locations
16801 .into_iter()
16802 .map(|location| {
16803 let buffer = location.buffer.read(cx);
16804 (location.buffer, location.range.to_point(buffer))
16805 })
16806 .into_group_map()
16807 })?;
16808 let mut num_locations = 0;
16809 for ranges in locations.values_mut() {
16810 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16811 ranges.dedup();
16812 num_locations += ranges.len();
16813 }
16814
16815 if num_locations > 1 {
16816 let Some(workspace) = workspace else {
16817 return Ok(Navigated::No);
16818 };
16819
16820 let tab_kind = match kind {
16821 Some(GotoDefinitionKind::Implementation) => "Implementations",
16822 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16823 Some(GotoDefinitionKind::Declaration) => "Declarations",
16824 Some(GotoDefinitionKind::Type) => "Types",
16825 };
16826 let title = editor
16827 .update_in(cx, |_, _, cx| {
16828 let target = locations
16829 .iter()
16830 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16831 .map(|(buffer, location)| {
16832 buffer
16833 .read(cx)
16834 .text_for_range(location.clone())
16835 .collect::<String>()
16836 })
16837 .filter(|text| !text.contains('\n'))
16838 .unique()
16839 .take(3)
16840 .join(", ");
16841 if target.is_empty() {
16842 tab_kind.to_owned()
16843 } else {
16844 format!("{tab_kind} for {target}")
16845 }
16846 })
16847 .context("buffer title")?;
16848
16849 let opened = workspace
16850 .update_in(cx, |workspace, window, cx| {
16851 Self::open_locations_in_multibuffer(
16852 workspace,
16853 locations,
16854 title,
16855 split,
16856 MultibufferSelectionMode::First,
16857 window,
16858 cx,
16859 )
16860 })
16861 .is_ok();
16862
16863 anyhow::Ok(Navigated::from_bool(opened))
16864 } else if num_locations == 0 {
16865 // If there is one url or file, open it directly
16866 match first_url_or_file {
16867 Some(Either::Left(url)) => {
16868 cx.update(|_, cx| cx.open_url(&url))?;
16869 Ok(Navigated::Yes)
16870 }
16871 Some(Either::Right(path)) => {
16872 let Some(workspace) = workspace else {
16873 return Ok(Navigated::No);
16874 };
16875
16876 workspace
16877 .update_in(cx, |workspace, window, cx| {
16878 workspace.open_resolved_path(path, window, cx)
16879 })?
16880 .await?;
16881 Ok(Navigated::Yes)
16882 }
16883 None => Ok(Navigated::No),
16884 }
16885 } else {
16886 let Some(workspace) = workspace else {
16887 return Ok(Navigated::No);
16888 };
16889
16890 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16891 let target_range = target_ranges.first().unwrap().clone();
16892
16893 editor.update_in(cx, |editor, window, cx| {
16894 let range = target_range.to_point(target_buffer.read(cx));
16895 let range = editor.range_for_match(&range);
16896 let range = collapse_multiline_range(range);
16897
16898 if !split
16899 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16900 {
16901 editor.go_to_singleton_buffer_range(range, window, cx);
16902 } else {
16903 let pane = workspace.read(cx).active_pane().clone();
16904 window.defer(cx, move |window, cx| {
16905 let target_editor: Entity<Self> =
16906 workspace.update(cx, |workspace, cx| {
16907 let pane = if split {
16908 workspace.adjacent_pane(window, cx)
16909 } else {
16910 workspace.active_pane().clone()
16911 };
16912
16913 workspace.open_project_item(
16914 pane,
16915 target_buffer.clone(),
16916 true,
16917 true,
16918 window,
16919 cx,
16920 )
16921 });
16922 target_editor.update(cx, |target_editor, cx| {
16923 // When selecting a definition in a different buffer, disable the nav history
16924 // to avoid creating a history entry at the previous cursor location.
16925 pane.update(cx, |pane, _| pane.disable_history());
16926 target_editor.go_to_singleton_buffer_range(range, window, cx);
16927 pane.update(cx, |pane, _| pane.enable_history());
16928 });
16929 });
16930 }
16931 Navigated::Yes
16932 })
16933 }
16934 })
16935 }
16936
16937 fn compute_target_location(
16938 &self,
16939 lsp_location: lsp::Location,
16940 server_id: LanguageServerId,
16941 window: &mut Window,
16942 cx: &mut Context<Self>,
16943 ) -> Task<anyhow::Result<Option<Location>>> {
16944 let Some(project) = self.project.clone() else {
16945 return Task::ready(Ok(None));
16946 };
16947
16948 cx.spawn_in(window, async move |editor, cx| {
16949 let location_task = editor.update(cx, |_, cx| {
16950 project.update(cx, |project, cx| {
16951 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16952 })
16953 })?;
16954 let location = Some({
16955 let target_buffer_handle = location_task.await.context("open local buffer")?;
16956 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16957 let target_start = target_buffer
16958 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16959 let target_end = target_buffer
16960 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16961 target_buffer.anchor_after(target_start)
16962 ..target_buffer.anchor_before(target_end)
16963 })?;
16964 Location {
16965 buffer: target_buffer_handle,
16966 range,
16967 }
16968 });
16969 Ok(location)
16970 })
16971 }
16972
16973 fn go_to_next_reference(
16974 &mut self,
16975 _: &GoToNextReference,
16976 window: &mut Window,
16977 cx: &mut Context<Self>,
16978 ) {
16979 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16980 if let Some(task) = task {
16981 task.detach();
16982 };
16983 }
16984
16985 fn go_to_prev_reference(
16986 &mut self,
16987 _: &GoToPreviousReference,
16988 window: &mut Window,
16989 cx: &mut Context<Self>,
16990 ) {
16991 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16992 if let Some(task) = task {
16993 task.detach();
16994 };
16995 }
16996
16997 pub fn go_to_reference_before_or_after_position(
16998 &mut self,
16999 direction: Direction,
17000 count: usize,
17001 window: &mut Window,
17002 cx: &mut Context<Self>,
17003 ) -> Option<Task<Result<()>>> {
17004 let selection = self.selections.newest_anchor();
17005 let head = selection.head();
17006
17007 let multi_buffer = self.buffer.read(cx);
17008
17009 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17010 let workspace = self.workspace()?;
17011 let project = workspace.read(cx).project().clone();
17012 let references =
17013 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17014 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17015 let Some(locations) = references.await? else {
17016 return Ok(());
17017 };
17018
17019 if locations.is_empty() {
17020 // totally normal - the cursor may be on something which is not
17021 // a symbol (e.g. a keyword)
17022 log::info!("no references found under cursor");
17023 return Ok(());
17024 }
17025
17026 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17027
17028 let multi_buffer_snapshot =
17029 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
17030
17031 let (locations, current_location_index) =
17032 multi_buffer.update(cx, |multi_buffer, cx| {
17033 let mut locations = locations
17034 .into_iter()
17035 .filter_map(|loc| {
17036 let start = multi_buffer.buffer_anchor_to_anchor(
17037 &loc.buffer,
17038 loc.range.start,
17039 cx,
17040 )?;
17041 let end = multi_buffer.buffer_anchor_to_anchor(
17042 &loc.buffer,
17043 loc.range.end,
17044 cx,
17045 )?;
17046 Some(start..end)
17047 })
17048 .collect::<Vec<_>>();
17049
17050 // There is an O(n) implementation, but given this list will be
17051 // small (usually <100 items), the extra O(log(n)) factor isn't
17052 // worth the (surprisingly large amount of) extra complexity.
17053 locations
17054 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17055
17056 let head_offset = head.to_offset(&multi_buffer_snapshot);
17057
17058 let current_location_index = locations.iter().position(|loc| {
17059 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17060 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17061 });
17062
17063 (locations, current_location_index)
17064 })?;
17065
17066 let Some(current_location_index) = current_location_index else {
17067 // This indicates something has gone wrong, because we already
17068 // handle the "no references" case above
17069 log::error!(
17070 "failed to find current reference under cursor. Total references: {}",
17071 locations.len()
17072 );
17073 return Ok(());
17074 };
17075
17076 let destination_location_index = match direction {
17077 Direction::Next => (current_location_index + count) % locations.len(),
17078 Direction::Prev => {
17079 (current_location_index + locations.len() - count % locations.len())
17080 % locations.len()
17081 }
17082 };
17083
17084 // TODO(cameron): is this needed?
17085 // the thinking is to avoid "jumping to the current location" (avoid
17086 // polluting "jumplist" in vim terms)
17087 if current_location_index == destination_location_index {
17088 return Ok(());
17089 }
17090
17091 let Range { start, end } = locations[destination_location_index];
17092
17093 editor.update_in(cx, |editor, window, cx| {
17094 let effects = SelectionEffects::default();
17095
17096 editor.unfold_ranges(&[start..end], false, false, cx);
17097 editor.change_selections(effects, window, cx, |s| {
17098 s.select_ranges([start..start]);
17099 });
17100 })?;
17101
17102 Ok(())
17103 }))
17104 }
17105
17106 pub fn find_all_references(
17107 &mut self,
17108 _: &FindAllReferences,
17109 window: &mut Window,
17110 cx: &mut Context<Self>,
17111 ) -> Option<Task<Result<Navigated>>> {
17112 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17113 let multi_buffer = self.buffer.read(cx);
17114 let head = selection.head();
17115
17116 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17117 let head_anchor = multi_buffer_snapshot.anchor_at(
17118 head,
17119 if head < selection.tail() {
17120 Bias::Right
17121 } else {
17122 Bias::Left
17123 },
17124 );
17125
17126 match self
17127 .find_all_references_task_sources
17128 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17129 {
17130 Ok(_) => {
17131 log::info!(
17132 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17133 );
17134 return None;
17135 }
17136 Err(i) => {
17137 self.find_all_references_task_sources.insert(i, head_anchor);
17138 }
17139 }
17140
17141 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17142 let workspace = self.workspace()?;
17143 let project = workspace.read(cx).project().clone();
17144 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17145 Some(cx.spawn_in(window, async move |editor, cx| {
17146 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17147 if let Ok(i) = editor
17148 .find_all_references_task_sources
17149 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17150 {
17151 editor.find_all_references_task_sources.remove(i);
17152 }
17153 });
17154
17155 let Some(locations) = references.await? else {
17156 return anyhow::Ok(Navigated::No);
17157 };
17158 let mut locations = cx.update(|_, cx| {
17159 locations
17160 .into_iter()
17161 .map(|location| {
17162 let buffer = location.buffer.read(cx);
17163 (location.buffer, location.range.to_point(buffer))
17164 })
17165 .into_group_map()
17166 })?;
17167 if locations.is_empty() {
17168 return anyhow::Ok(Navigated::No);
17169 }
17170 for ranges in locations.values_mut() {
17171 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17172 ranges.dedup();
17173 }
17174
17175 workspace.update_in(cx, |workspace, window, cx| {
17176 let target = locations
17177 .iter()
17178 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17179 .map(|(buffer, location)| {
17180 buffer
17181 .read(cx)
17182 .text_for_range(location.clone())
17183 .collect::<String>()
17184 })
17185 .filter(|text| !text.contains('\n'))
17186 .unique()
17187 .take(3)
17188 .join(", ");
17189 let title = if target.is_empty() {
17190 "References".to_owned()
17191 } else {
17192 format!("References to {target}")
17193 };
17194 Self::open_locations_in_multibuffer(
17195 workspace,
17196 locations,
17197 title,
17198 false,
17199 MultibufferSelectionMode::First,
17200 window,
17201 cx,
17202 );
17203 Navigated::Yes
17204 })
17205 }))
17206 }
17207
17208 /// Opens a multibuffer with the given project locations in it
17209 pub fn open_locations_in_multibuffer(
17210 workspace: &mut Workspace,
17211 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17212 title: String,
17213 split: bool,
17214 multibuffer_selection_mode: MultibufferSelectionMode,
17215 window: &mut Window,
17216 cx: &mut Context<Workspace>,
17217 ) {
17218 if locations.is_empty() {
17219 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17220 return;
17221 }
17222
17223 let capability = workspace.project().read(cx).capability();
17224 let mut ranges = <Vec<Range<Anchor>>>::new();
17225
17226 // a key to find existing multibuffer editors with the same set of locations
17227 // to prevent us from opening more and more multibuffer tabs for searches and the like
17228 let mut key = (title.clone(), vec![]);
17229 let excerpt_buffer = cx.new(|cx| {
17230 let key = &mut key.1;
17231 let mut multibuffer = MultiBuffer::new(capability);
17232 for (buffer, mut ranges_for_buffer) in locations {
17233 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17234 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17235 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17236 PathKey::for_buffer(&buffer, cx),
17237 buffer.clone(),
17238 ranges_for_buffer,
17239 multibuffer_context_lines(cx),
17240 cx,
17241 );
17242 ranges.extend(new_ranges)
17243 }
17244
17245 multibuffer.with_title(title)
17246 });
17247 let existing = workspace.active_pane().update(cx, |pane, cx| {
17248 pane.items()
17249 .filter_map(|item| item.downcast::<Editor>())
17250 .find(|editor| {
17251 editor
17252 .read(cx)
17253 .lookup_key
17254 .as_ref()
17255 .and_then(|it| {
17256 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17257 })
17258 .is_some_and(|it| *it == key)
17259 })
17260 });
17261 let editor = existing.unwrap_or_else(|| {
17262 cx.new(|cx| {
17263 let mut editor = Editor::for_multibuffer(
17264 excerpt_buffer,
17265 Some(workspace.project().clone()),
17266 window,
17267 cx,
17268 );
17269 editor.lookup_key = Some(Box::new(key));
17270 editor
17271 })
17272 });
17273 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17274 MultibufferSelectionMode::First => {
17275 if let Some(first_range) = ranges.first() {
17276 editor.change_selections(
17277 SelectionEffects::no_scroll(),
17278 window,
17279 cx,
17280 |selections| {
17281 selections.clear_disjoint();
17282 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17283 },
17284 );
17285 }
17286 editor.highlight_background::<Self>(
17287 &ranges,
17288 |theme| theme.colors().editor_highlighted_line_background,
17289 cx,
17290 );
17291 }
17292 MultibufferSelectionMode::All => {
17293 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17294 selections.clear_disjoint();
17295 selections.select_anchor_ranges(ranges);
17296 });
17297 }
17298 });
17299
17300 let item = Box::new(editor);
17301 let item_id = item.item_id();
17302
17303 if split {
17304 let pane = workspace.adjacent_pane(window, cx);
17305 workspace.add_item(pane, item, None, true, true, window, cx);
17306 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17307 let (preview_item_id, preview_item_idx) =
17308 workspace.active_pane().read_with(cx, |pane, _| {
17309 (pane.preview_item_id(), pane.preview_item_idx())
17310 });
17311
17312 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17313
17314 if let Some(preview_item_id) = preview_item_id {
17315 workspace.active_pane().update(cx, |pane, cx| {
17316 pane.remove_item(preview_item_id, false, false, window, cx);
17317 });
17318 }
17319 } else {
17320 workspace.add_item_to_active_pane(item, None, true, window, cx);
17321 }
17322 workspace.active_pane().update(cx, |pane, cx| {
17323 pane.set_preview_item_id(Some(item_id), cx);
17324 });
17325 }
17326
17327 pub fn rename(
17328 &mut self,
17329 _: &Rename,
17330 window: &mut Window,
17331 cx: &mut Context<Self>,
17332 ) -> Option<Task<Result<()>>> {
17333 use language::ToOffset as _;
17334
17335 let provider = self.semantics_provider.clone()?;
17336 let selection = self.selections.newest_anchor().clone();
17337 let (cursor_buffer, cursor_buffer_position) = self
17338 .buffer
17339 .read(cx)
17340 .text_anchor_for_position(selection.head(), cx)?;
17341 let (tail_buffer, cursor_buffer_position_end) = self
17342 .buffer
17343 .read(cx)
17344 .text_anchor_for_position(selection.tail(), cx)?;
17345 if tail_buffer != cursor_buffer {
17346 return None;
17347 }
17348
17349 let snapshot = cursor_buffer.read(cx).snapshot();
17350 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17351 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17352 let prepare_rename = provider
17353 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17354 .unwrap_or_else(|| Task::ready(Ok(None)));
17355 drop(snapshot);
17356
17357 Some(cx.spawn_in(window, async move |this, cx| {
17358 let rename_range = if let Some(range) = prepare_rename.await? {
17359 Some(range)
17360 } else {
17361 this.update(cx, |this, cx| {
17362 let buffer = this.buffer.read(cx).snapshot(cx);
17363 let mut buffer_highlights = this
17364 .document_highlights_for_position(selection.head(), &buffer)
17365 .filter(|highlight| {
17366 highlight.start.excerpt_id == selection.head().excerpt_id
17367 && highlight.end.excerpt_id == selection.head().excerpt_id
17368 });
17369 buffer_highlights
17370 .next()
17371 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17372 })?
17373 };
17374 if let Some(rename_range) = rename_range {
17375 this.update_in(cx, |this, window, cx| {
17376 let snapshot = cursor_buffer.read(cx).snapshot();
17377 let rename_buffer_range = rename_range.to_offset(&snapshot);
17378 let cursor_offset_in_rename_range =
17379 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17380 let cursor_offset_in_rename_range_end =
17381 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17382
17383 this.take_rename(false, window, cx);
17384 let buffer = this.buffer.read(cx).read(cx);
17385 let cursor_offset = selection.head().to_offset(&buffer);
17386 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17387 let rename_end = rename_start + rename_buffer_range.len();
17388 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17389 let mut old_highlight_id = None;
17390 let old_name: Arc<str> = buffer
17391 .chunks(rename_start..rename_end, true)
17392 .map(|chunk| {
17393 if old_highlight_id.is_none() {
17394 old_highlight_id = chunk.syntax_highlight_id;
17395 }
17396 chunk.text
17397 })
17398 .collect::<String>()
17399 .into();
17400
17401 drop(buffer);
17402
17403 // Position the selection in the rename editor so that it matches the current selection.
17404 this.show_local_selections = false;
17405 let rename_editor = cx.new(|cx| {
17406 let mut editor = Editor::single_line(window, cx);
17407 editor.buffer.update(cx, |buffer, cx| {
17408 buffer.edit([(0..0, old_name.clone())], None, cx)
17409 });
17410 let rename_selection_range = match cursor_offset_in_rename_range
17411 .cmp(&cursor_offset_in_rename_range_end)
17412 {
17413 Ordering::Equal => {
17414 editor.select_all(&SelectAll, window, cx);
17415 return editor;
17416 }
17417 Ordering::Less => {
17418 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17419 }
17420 Ordering::Greater => {
17421 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17422 }
17423 };
17424 if rename_selection_range.end > old_name.len() {
17425 editor.select_all(&SelectAll, window, cx);
17426 } else {
17427 editor.change_selections(Default::default(), window, cx, |s| {
17428 s.select_ranges([rename_selection_range]);
17429 });
17430 }
17431 editor
17432 });
17433 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17434 if e == &EditorEvent::Focused {
17435 cx.emit(EditorEvent::FocusedIn)
17436 }
17437 })
17438 .detach();
17439
17440 let write_highlights =
17441 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17442 let read_highlights =
17443 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17444 let ranges = write_highlights
17445 .iter()
17446 .flat_map(|(_, ranges)| ranges.iter())
17447 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17448 .cloned()
17449 .collect();
17450
17451 this.highlight_text::<Rename>(
17452 ranges,
17453 HighlightStyle {
17454 fade_out: Some(0.6),
17455 ..Default::default()
17456 },
17457 cx,
17458 );
17459 let rename_focus_handle = rename_editor.focus_handle(cx);
17460 window.focus(&rename_focus_handle);
17461 let block_id = this.insert_blocks(
17462 [BlockProperties {
17463 style: BlockStyle::Flex,
17464 placement: BlockPlacement::Below(range.start),
17465 height: Some(1),
17466 render: Arc::new({
17467 let rename_editor = rename_editor.clone();
17468 move |cx: &mut BlockContext| {
17469 let mut text_style = cx.editor_style.text.clone();
17470 if let Some(highlight_style) = old_highlight_id
17471 .and_then(|h| h.style(&cx.editor_style.syntax))
17472 {
17473 text_style = text_style.highlight(highlight_style);
17474 }
17475 div()
17476 .block_mouse_except_scroll()
17477 .pl(cx.anchor_x)
17478 .child(EditorElement::new(
17479 &rename_editor,
17480 EditorStyle {
17481 background: cx.theme().system().transparent,
17482 local_player: cx.editor_style.local_player,
17483 text: text_style,
17484 scrollbar_width: cx.editor_style.scrollbar_width,
17485 syntax: cx.editor_style.syntax.clone(),
17486 status: cx.editor_style.status.clone(),
17487 inlay_hints_style: HighlightStyle {
17488 font_weight: Some(FontWeight::BOLD),
17489 ..make_inlay_hints_style(cx.app)
17490 },
17491 edit_prediction_styles: make_suggestion_styles(
17492 cx.app,
17493 ),
17494 ..EditorStyle::default()
17495 },
17496 ))
17497 .into_any_element()
17498 }
17499 }),
17500 priority: 0,
17501 }],
17502 Some(Autoscroll::fit()),
17503 cx,
17504 )[0];
17505 this.pending_rename = Some(RenameState {
17506 range,
17507 old_name,
17508 editor: rename_editor,
17509 block_id,
17510 });
17511 })?;
17512 }
17513
17514 Ok(())
17515 }))
17516 }
17517
17518 pub fn confirm_rename(
17519 &mut self,
17520 _: &ConfirmRename,
17521 window: &mut Window,
17522 cx: &mut Context<Self>,
17523 ) -> Option<Task<Result<()>>> {
17524 let rename = self.take_rename(false, window, cx)?;
17525 let workspace = self.workspace()?.downgrade();
17526 let (buffer, start) = self
17527 .buffer
17528 .read(cx)
17529 .text_anchor_for_position(rename.range.start, cx)?;
17530 let (end_buffer, _) = self
17531 .buffer
17532 .read(cx)
17533 .text_anchor_for_position(rename.range.end, cx)?;
17534 if buffer != end_buffer {
17535 return None;
17536 }
17537
17538 let old_name = rename.old_name;
17539 let new_name = rename.editor.read(cx).text(cx);
17540
17541 let rename = self.semantics_provider.as_ref()?.perform_rename(
17542 &buffer,
17543 start,
17544 new_name.clone(),
17545 cx,
17546 )?;
17547
17548 Some(cx.spawn_in(window, async move |editor, cx| {
17549 let project_transaction = rename.await?;
17550 Self::open_project_transaction(
17551 &editor,
17552 workspace,
17553 project_transaction,
17554 format!("Rename: {} → {}", old_name, new_name),
17555 cx,
17556 )
17557 .await?;
17558
17559 editor.update(cx, |editor, cx| {
17560 editor.refresh_document_highlights(cx);
17561 })?;
17562 Ok(())
17563 }))
17564 }
17565
17566 fn take_rename(
17567 &mut self,
17568 moving_cursor: bool,
17569 window: &mut Window,
17570 cx: &mut Context<Self>,
17571 ) -> Option<RenameState> {
17572 let rename = self.pending_rename.take()?;
17573 if rename.editor.focus_handle(cx).is_focused(window) {
17574 window.focus(&self.focus_handle);
17575 }
17576
17577 self.remove_blocks(
17578 [rename.block_id].into_iter().collect(),
17579 Some(Autoscroll::fit()),
17580 cx,
17581 );
17582 self.clear_highlights::<Rename>(cx);
17583 self.show_local_selections = true;
17584
17585 if moving_cursor {
17586 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17587 editor
17588 .selections
17589 .newest::<usize>(&editor.display_snapshot(cx))
17590 .head()
17591 });
17592
17593 // Update the selection to match the position of the selection inside
17594 // the rename editor.
17595 let snapshot = self.buffer.read(cx).read(cx);
17596 let rename_range = rename.range.to_offset(&snapshot);
17597 let cursor_in_editor = snapshot
17598 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17599 .min(rename_range.end);
17600 drop(snapshot);
17601
17602 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17603 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17604 });
17605 } else {
17606 self.refresh_document_highlights(cx);
17607 }
17608
17609 Some(rename)
17610 }
17611
17612 pub fn pending_rename(&self) -> Option<&RenameState> {
17613 self.pending_rename.as_ref()
17614 }
17615
17616 fn format(
17617 &mut self,
17618 _: &Format,
17619 window: &mut Window,
17620 cx: &mut Context<Self>,
17621 ) -> Option<Task<Result<()>>> {
17622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17623
17624 let project = match &self.project {
17625 Some(project) => project.clone(),
17626 None => return None,
17627 };
17628
17629 Some(self.perform_format(
17630 project,
17631 FormatTrigger::Manual,
17632 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17633 window,
17634 cx,
17635 ))
17636 }
17637
17638 fn format_selections(
17639 &mut self,
17640 _: &FormatSelections,
17641 window: &mut Window,
17642 cx: &mut Context<Self>,
17643 ) -> Option<Task<Result<()>>> {
17644 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17645
17646 let project = match &self.project {
17647 Some(project) => project.clone(),
17648 None => return None,
17649 };
17650
17651 let ranges = self
17652 .selections
17653 .all_adjusted(&self.display_snapshot(cx))
17654 .into_iter()
17655 .map(|selection| selection.range())
17656 .collect_vec();
17657
17658 Some(self.perform_format(
17659 project,
17660 FormatTrigger::Manual,
17661 FormatTarget::Ranges(ranges),
17662 window,
17663 cx,
17664 ))
17665 }
17666
17667 fn perform_format(
17668 &mut self,
17669 project: Entity<Project>,
17670 trigger: FormatTrigger,
17671 target: FormatTarget,
17672 window: &mut Window,
17673 cx: &mut Context<Self>,
17674 ) -> Task<Result<()>> {
17675 let buffer = self.buffer.clone();
17676 let (buffers, target) = match target {
17677 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17678 FormatTarget::Ranges(selection_ranges) => {
17679 let multi_buffer = buffer.read(cx);
17680 let snapshot = multi_buffer.read(cx);
17681 let mut buffers = HashSet::default();
17682 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17683 BTreeMap::new();
17684 for selection_range in selection_ranges {
17685 for (buffer, buffer_range, _) in
17686 snapshot.range_to_buffer_ranges(selection_range)
17687 {
17688 let buffer_id = buffer.remote_id();
17689 let start = buffer.anchor_before(buffer_range.start);
17690 let end = buffer.anchor_after(buffer_range.end);
17691 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17692 buffer_id_to_ranges
17693 .entry(buffer_id)
17694 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17695 .or_insert_with(|| vec![start..end]);
17696 }
17697 }
17698 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17699 }
17700 };
17701
17702 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17703 let selections_prev = transaction_id_prev
17704 .and_then(|transaction_id_prev| {
17705 // default to selections as they were after the last edit, if we have them,
17706 // instead of how they are now.
17707 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17708 // will take you back to where you made the last edit, instead of staying where you scrolled
17709 self.selection_history
17710 .transaction(transaction_id_prev)
17711 .map(|t| t.0.clone())
17712 })
17713 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17714
17715 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17716 let format = project.update(cx, |project, cx| {
17717 project.format(buffers, target, true, trigger, cx)
17718 });
17719
17720 cx.spawn_in(window, async move |editor, cx| {
17721 let transaction = futures::select_biased! {
17722 transaction = format.log_err().fuse() => transaction,
17723 () = timeout => {
17724 log::warn!("timed out waiting for formatting");
17725 None
17726 }
17727 };
17728
17729 buffer
17730 .update(cx, |buffer, cx| {
17731 if let Some(transaction) = transaction
17732 && !buffer.is_singleton()
17733 {
17734 buffer.push_transaction(&transaction.0, cx);
17735 }
17736 cx.notify();
17737 })
17738 .ok();
17739
17740 if let Some(transaction_id_now) =
17741 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17742 {
17743 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17744 if has_new_transaction {
17745 _ = editor.update(cx, |editor, _| {
17746 editor
17747 .selection_history
17748 .insert_transaction(transaction_id_now, selections_prev);
17749 });
17750 }
17751 }
17752
17753 Ok(())
17754 })
17755 }
17756
17757 fn organize_imports(
17758 &mut self,
17759 _: &OrganizeImports,
17760 window: &mut Window,
17761 cx: &mut Context<Self>,
17762 ) -> Option<Task<Result<()>>> {
17763 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17764 let project = match &self.project {
17765 Some(project) => project.clone(),
17766 None => return None,
17767 };
17768 Some(self.perform_code_action_kind(
17769 project,
17770 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17771 window,
17772 cx,
17773 ))
17774 }
17775
17776 fn perform_code_action_kind(
17777 &mut self,
17778 project: Entity<Project>,
17779 kind: CodeActionKind,
17780 window: &mut Window,
17781 cx: &mut Context<Self>,
17782 ) -> Task<Result<()>> {
17783 let buffer = self.buffer.clone();
17784 let buffers = buffer.read(cx).all_buffers();
17785 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17786 let apply_action = project.update(cx, |project, cx| {
17787 project.apply_code_action_kind(buffers, kind, true, cx)
17788 });
17789 cx.spawn_in(window, async move |_, cx| {
17790 let transaction = futures::select_biased! {
17791 () = timeout => {
17792 log::warn!("timed out waiting for executing code action");
17793 None
17794 }
17795 transaction = apply_action.log_err().fuse() => transaction,
17796 };
17797 buffer
17798 .update(cx, |buffer, cx| {
17799 // check if we need this
17800 if let Some(transaction) = transaction
17801 && !buffer.is_singleton()
17802 {
17803 buffer.push_transaction(&transaction.0, cx);
17804 }
17805 cx.notify();
17806 })
17807 .ok();
17808 Ok(())
17809 })
17810 }
17811
17812 pub fn restart_language_server(
17813 &mut self,
17814 _: &RestartLanguageServer,
17815 _: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) {
17818 if let Some(project) = self.project.clone() {
17819 self.buffer.update(cx, |multi_buffer, cx| {
17820 project.update(cx, |project, cx| {
17821 project.restart_language_servers_for_buffers(
17822 multi_buffer.all_buffers().into_iter().collect(),
17823 HashSet::default(),
17824 cx,
17825 );
17826 });
17827 })
17828 }
17829 }
17830
17831 pub fn stop_language_server(
17832 &mut self,
17833 _: &StopLanguageServer,
17834 _: &mut Window,
17835 cx: &mut Context<Self>,
17836 ) {
17837 if let Some(project) = self.project.clone() {
17838 self.buffer.update(cx, |multi_buffer, cx| {
17839 project.update(cx, |project, cx| {
17840 project.stop_language_servers_for_buffers(
17841 multi_buffer.all_buffers().into_iter().collect(),
17842 HashSet::default(),
17843 cx,
17844 );
17845 });
17846 });
17847 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17848 }
17849 }
17850
17851 fn cancel_language_server_work(
17852 workspace: &mut Workspace,
17853 _: &actions::CancelLanguageServerWork,
17854 _: &mut Window,
17855 cx: &mut Context<Workspace>,
17856 ) {
17857 let project = workspace.project();
17858 let buffers = workspace
17859 .active_item(cx)
17860 .and_then(|item| item.act_as::<Editor>(cx))
17861 .map_or(HashSet::default(), |editor| {
17862 editor.read(cx).buffer.read(cx).all_buffers()
17863 });
17864 project.update(cx, |project, cx| {
17865 project.cancel_language_server_work_for_buffers(buffers, cx);
17866 });
17867 }
17868
17869 fn show_character_palette(
17870 &mut self,
17871 _: &ShowCharacterPalette,
17872 window: &mut Window,
17873 _: &mut Context<Self>,
17874 ) {
17875 window.show_character_palette();
17876 }
17877
17878 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17879 if !self.diagnostics_enabled() {
17880 return;
17881 }
17882
17883 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17884 let buffer = self.buffer.read(cx).snapshot(cx);
17885 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17886 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17887 let is_valid = buffer
17888 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17889 .any(|entry| {
17890 entry.diagnostic.is_primary
17891 && !entry.range.is_empty()
17892 && entry.range.start == primary_range_start
17893 && entry.diagnostic.message == active_diagnostics.active_message
17894 });
17895
17896 if !is_valid {
17897 self.dismiss_diagnostics(cx);
17898 }
17899 }
17900 }
17901
17902 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17903 match &self.active_diagnostics {
17904 ActiveDiagnostic::Group(group) => Some(group),
17905 _ => None,
17906 }
17907 }
17908
17909 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17910 if !self.diagnostics_enabled() {
17911 return;
17912 }
17913 self.dismiss_diagnostics(cx);
17914 self.active_diagnostics = ActiveDiagnostic::All;
17915 }
17916
17917 fn activate_diagnostics(
17918 &mut self,
17919 buffer_id: BufferId,
17920 diagnostic: DiagnosticEntryRef<'_, usize>,
17921 window: &mut Window,
17922 cx: &mut Context<Self>,
17923 ) {
17924 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17925 return;
17926 }
17927 self.dismiss_diagnostics(cx);
17928 let snapshot = self.snapshot(window, cx);
17929 let buffer = self.buffer.read(cx).snapshot(cx);
17930 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17931 return;
17932 };
17933
17934 let diagnostic_group = buffer
17935 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17936 .collect::<Vec<_>>();
17937
17938 let blocks =
17939 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17940
17941 let blocks = self.display_map.update(cx, |display_map, cx| {
17942 display_map.insert_blocks(blocks, cx).into_iter().collect()
17943 });
17944 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17945 active_range: buffer.anchor_before(diagnostic.range.start)
17946 ..buffer.anchor_after(diagnostic.range.end),
17947 active_message: diagnostic.diagnostic.message.clone(),
17948 group_id: diagnostic.diagnostic.group_id,
17949 blocks,
17950 });
17951 cx.notify();
17952 }
17953
17954 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17955 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17956 return;
17957 };
17958
17959 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17960 if let ActiveDiagnostic::Group(group) = prev {
17961 self.display_map.update(cx, |display_map, cx| {
17962 display_map.remove_blocks(group.blocks, cx);
17963 });
17964 cx.notify();
17965 }
17966 }
17967
17968 /// Disable inline diagnostics rendering for this editor.
17969 pub fn disable_inline_diagnostics(&mut self) {
17970 self.inline_diagnostics_enabled = false;
17971 self.inline_diagnostics_update = Task::ready(());
17972 self.inline_diagnostics.clear();
17973 }
17974
17975 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17976 self.diagnostics_enabled = false;
17977 self.dismiss_diagnostics(cx);
17978 self.inline_diagnostics_update = Task::ready(());
17979 self.inline_diagnostics.clear();
17980 }
17981
17982 pub fn disable_word_completions(&mut self) {
17983 self.word_completions_enabled = false;
17984 }
17985
17986 pub fn diagnostics_enabled(&self) -> bool {
17987 self.diagnostics_enabled && self.mode.is_full()
17988 }
17989
17990 pub fn inline_diagnostics_enabled(&self) -> bool {
17991 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17992 }
17993
17994 pub fn show_inline_diagnostics(&self) -> bool {
17995 self.show_inline_diagnostics
17996 }
17997
17998 pub fn toggle_inline_diagnostics(
17999 &mut self,
18000 _: &ToggleInlineDiagnostics,
18001 window: &mut Window,
18002 cx: &mut Context<Editor>,
18003 ) {
18004 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18005 self.refresh_inline_diagnostics(false, window, cx);
18006 }
18007
18008 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18009 self.diagnostics_max_severity = severity;
18010 self.display_map.update(cx, |display_map, _| {
18011 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18012 });
18013 }
18014
18015 pub fn toggle_diagnostics(
18016 &mut self,
18017 _: &ToggleDiagnostics,
18018 window: &mut Window,
18019 cx: &mut Context<Editor>,
18020 ) {
18021 if !self.diagnostics_enabled() {
18022 return;
18023 }
18024
18025 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18026 EditorSettings::get_global(cx)
18027 .diagnostics_max_severity
18028 .filter(|severity| severity != &DiagnosticSeverity::Off)
18029 .unwrap_or(DiagnosticSeverity::Hint)
18030 } else {
18031 DiagnosticSeverity::Off
18032 };
18033 self.set_max_diagnostics_severity(new_severity, cx);
18034 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18035 self.active_diagnostics = ActiveDiagnostic::None;
18036 self.inline_diagnostics_update = Task::ready(());
18037 self.inline_diagnostics.clear();
18038 } else {
18039 self.refresh_inline_diagnostics(false, window, cx);
18040 }
18041
18042 cx.notify();
18043 }
18044
18045 pub fn toggle_minimap(
18046 &mut self,
18047 _: &ToggleMinimap,
18048 window: &mut Window,
18049 cx: &mut Context<Editor>,
18050 ) {
18051 if self.supports_minimap(cx) {
18052 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18053 }
18054 }
18055
18056 fn refresh_inline_diagnostics(
18057 &mut self,
18058 debounce: bool,
18059 window: &mut Window,
18060 cx: &mut Context<Self>,
18061 ) {
18062 let max_severity = ProjectSettings::get_global(cx)
18063 .diagnostics
18064 .inline
18065 .max_severity
18066 .unwrap_or(self.diagnostics_max_severity);
18067
18068 if !self.inline_diagnostics_enabled()
18069 || !self.diagnostics_enabled()
18070 || !self.show_inline_diagnostics
18071 || max_severity == DiagnosticSeverity::Off
18072 {
18073 self.inline_diagnostics_update = Task::ready(());
18074 self.inline_diagnostics.clear();
18075 return;
18076 }
18077
18078 let debounce_ms = ProjectSettings::get_global(cx)
18079 .diagnostics
18080 .inline
18081 .update_debounce_ms;
18082 let debounce = if debounce && debounce_ms > 0 {
18083 Some(Duration::from_millis(debounce_ms))
18084 } else {
18085 None
18086 };
18087 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18088 if let Some(debounce) = debounce {
18089 cx.background_executor().timer(debounce).await;
18090 }
18091 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18092 editor
18093 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18094 .ok()
18095 }) else {
18096 return;
18097 };
18098
18099 let new_inline_diagnostics = cx
18100 .background_spawn(async move {
18101 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18102 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18103 let message = diagnostic_entry
18104 .diagnostic
18105 .message
18106 .split_once('\n')
18107 .map(|(line, _)| line)
18108 .map(SharedString::new)
18109 .unwrap_or_else(|| {
18110 SharedString::new(&*diagnostic_entry.diagnostic.message)
18111 });
18112 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18113 let (Ok(i) | Err(i)) = inline_diagnostics
18114 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18115 inline_diagnostics.insert(
18116 i,
18117 (
18118 start_anchor,
18119 InlineDiagnostic {
18120 message,
18121 group_id: diagnostic_entry.diagnostic.group_id,
18122 start: diagnostic_entry.range.start.to_point(&snapshot),
18123 is_primary: diagnostic_entry.diagnostic.is_primary,
18124 severity: diagnostic_entry.diagnostic.severity,
18125 },
18126 ),
18127 );
18128 }
18129 inline_diagnostics
18130 })
18131 .await;
18132
18133 editor
18134 .update(cx, |editor, cx| {
18135 editor.inline_diagnostics = new_inline_diagnostics;
18136 cx.notify();
18137 })
18138 .ok();
18139 });
18140 }
18141
18142 fn pull_diagnostics(
18143 &mut self,
18144 buffer_id: Option<BufferId>,
18145 window: &Window,
18146 cx: &mut Context<Self>,
18147 ) -> Option<()> {
18148 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18149 return None;
18150 }
18151 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18152 .diagnostics
18153 .lsp_pull_diagnostics;
18154 if !pull_diagnostics_settings.enabled {
18155 return None;
18156 }
18157 let project = self.project()?.downgrade();
18158 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18159 let mut buffers = self.buffer.read(cx).all_buffers();
18160 buffers.retain(|buffer| {
18161 let buffer_id_to_retain = buffer.read(cx).remote_id();
18162 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18163 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18164 });
18165 if buffers.is_empty() {
18166 self.pull_diagnostics_task = Task::ready(());
18167 return None;
18168 }
18169
18170 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18171 cx.background_executor().timer(debounce).await;
18172
18173 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18174 buffers
18175 .into_iter()
18176 .filter_map(|buffer| {
18177 project
18178 .update(cx, |project, cx| {
18179 project.lsp_store().update(cx, |lsp_store, cx| {
18180 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18181 })
18182 })
18183 .ok()
18184 })
18185 .collect::<FuturesUnordered<_>>()
18186 }) else {
18187 return;
18188 };
18189
18190 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18191 match pull_task {
18192 Ok(()) => {
18193 if editor
18194 .update_in(cx, |editor, window, cx| {
18195 editor.update_diagnostics_state(window, cx);
18196 })
18197 .is_err()
18198 {
18199 return;
18200 }
18201 }
18202 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18203 }
18204 }
18205 });
18206
18207 Some(())
18208 }
18209
18210 pub fn set_selections_from_remote(
18211 &mut self,
18212 selections: Vec<Selection<Anchor>>,
18213 pending_selection: Option<Selection<Anchor>>,
18214 window: &mut Window,
18215 cx: &mut Context<Self>,
18216 ) {
18217 let old_cursor_position = self.selections.newest_anchor().head();
18218 self.selections
18219 .change_with(&self.display_snapshot(cx), |s| {
18220 s.select_anchors(selections);
18221 if let Some(pending_selection) = pending_selection {
18222 s.set_pending(pending_selection, SelectMode::Character);
18223 } else {
18224 s.clear_pending();
18225 }
18226 });
18227 self.selections_did_change(
18228 false,
18229 &old_cursor_position,
18230 SelectionEffects::default(),
18231 window,
18232 cx,
18233 );
18234 }
18235
18236 pub fn transact(
18237 &mut self,
18238 window: &mut Window,
18239 cx: &mut Context<Self>,
18240 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18241 ) -> Option<TransactionId> {
18242 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18243 this.start_transaction_at(Instant::now(), window, cx);
18244 update(this, window, cx);
18245 this.end_transaction_at(Instant::now(), cx)
18246 })
18247 }
18248
18249 pub fn start_transaction_at(
18250 &mut self,
18251 now: Instant,
18252 window: &mut Window,
18253 cx: &mut Context<Self>,
18254 ) -> Option<TransactionId> {
18255 self.end_selection(window, cx);
18256 if let Some(tx_id) = self
18257 .buffer
18258 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18259 {
18260 self.selection_history
18261 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18262 cx.emit(EditorEvent::TransactionBegun {
18263 transaction_id: tx_id,
18264 });
18265 Some(tx_id)
18266 } else {
18267 None
18268 }
18269 }
18270
18271 pub fn end_transaction_at(
18272 &mut self,
18273 now: Instant,
18274 cx: &mut Context<Self>,
18275 ) -> Option<TransactionId> {
18276 if let Some(transaction_id) = self
18277 .buffer
18278 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18279 {
18280 if let Some((_, end_selections)) =
18281 self.selection_history.transaction_mut(transaction_id)
18282 {
18283 *end_selections = Some(self.selections.disjoint_anchors_arc());
18284 } else {
18285 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18286 }
18287
18288 cx.emit(EditorEvent::Edited { transaction_id });
18289 Some(transaction_id)
18290 } else {
18291 None
18292 }
18293 }
18294
18295 pub fn modify_transaction_selection_history(
18296 &mut self,
18297 transaction_id: TransactionId,
18298 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18299 ) -> bool {
18300 self.selection_history
18301 .transaction_mut(transaction_id)
18302 .map(modify)
18303 .is_some()
18304 }
18305
18306 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18307 if self.selection_mark_mode {
18308 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18309 s.move_with(|_, sel| {
18310 sel.collapse_to(sel.head(), SelectionGoal::None);
18311 });
18312 })
18313 }
18314 self.selection_mark_mode = true;
18315 cx.notify();
18316 }
18317
18318 pub fn swap_selection_ends(
18319 &mut self,
18320 _: &actions::SwapSelectionEnds,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) {
18324 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18325 s.move_with(|_, sel| {
18326 if sel.start != sel.end {
18327 sel.reversed = !sel.reversed
18328 }
18329 });
18330 });
18331 self.request_autoscroll(Autoscroll::newest(), cx);
18332 cx.notify();
18333 }
18334
18335 pub fn toggle_focus(
18336 workspace: &mut Workspace,
18337 _: &actions::ToggleFocus,
18338 window: &mut Window,
18339 cx: &mut Context<Workspace>,
18340 ) {
18341 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18342 return;
18343 };
18344 workspace.activate_item(&item, true, true, window, cx);
18345 }
18346
18347 pub fn toggle_fold(
18348 &mut self,
18349 _: &actions::ToggleFold,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18354 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18355 let selection = self.selections.newest::<Point>(&display_map);
18356
18357 let range = if selection.is_empty() {
18358 let point = selection.head().to_display_point(&display_map);
18359 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18360 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18361 .to_point(&display_map);
18362 start..end
18363 } else {
18364 selection.range()
18365 };
18366 if display_map.folds_in_range(range).next().is_some() {
18367 self.unfold_lines(&Default::default(), window, cx)
18368 } else {
18369 self.fold(&Default::default(), window, cx)
18370 }
18371 } else {
18372 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18373 let buffer_ids: HashSet<_> = self
18374 .selections
18375 .disjoint_anchor_ranges()
18376 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18377 .collect();
18378
18379 let should_unfold = buffer_ids
18380 .iter()
18381 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18382
18383 for buffer_id in buffer_ids {
18384 if should_unfold {
18385 self.unfold_buffer(buffer_id, cx);
18386 } else {
18387 self.fold_buffer(buffer_id, cx);
18388 }
18389 }
18390 }
18391 }
18392
18393 pub fn toggle_fold_recursive(
18394 &mut self,
18395 _: &actions::ToggleFoldRecursive,
18396 window: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18400
18401 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18402 let range = if selection.is_empty() {
18403 let point = selection.head().to_display_point(&display_map);
18404 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18405 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18406 .to_point(&display_map);
18407 start..end
18408 } else {
18409 selection.range()
18410 };
18411 if display_map.folds_in_range(range).next().is_some() {
18412 self.unfold_recursive(&Default::default(), window, cx)
18413 } else {
18414 self.fold_recursive(&Default::default(), window, cx)
18415 }
18416 }
18417
18418 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18419 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18420 let mut to_fold = Vec::new();
18421 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18422 let selections = self.selections.all_adjusted(&display_map);
18423
18424 for selection in selections {
18425 let range = selection.range().sorted();
18426 let buffer_start_row = range.start.row;
18427
18428 if range.start.row != range.end.row {
18429 let mut found = false;
18430 let mut row = range.start.row;
18431 while row <= range.end.row {
18432 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18433 {
18434 found = true;
18435 row = crease.range().end.row + 1;
18436 to_fold.push(crease);
18437 } else {
18438 row += 1
18439 }
18440 }
18441 if found {
18442 continue;
18443 }
18444 }
18445
18446 for row in (0..=range.start.row).rev() {
18447 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18448 && crease.range().end.row >= buffer_start_row
18449 {
18450 to_fold.push(crease);
18451 if row <= range.start.row {
18452 break;
18453 }
18454 }
18455 }
18456 }
18457
18458 self.fold_creases(to_fold, true, window, cx);
18459 } else {
18460 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18461 let buffer_ids = self
18462 .selections
18463 .disjoint_anchor_ranges()
18464 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18465 .collect::<HashSet<_>>();
18466 for buffer_id in buffer_ids {
18467 self.fold_buffer(buffer_id, cx);
18468 }
18469 }
18470 }
18471
18472 pub fn toggle_fold_all(
18473 &mut self,
18474 _: &actions::ToggleFoldAll,
18475 window: &mut Window,
18476 cx: &mut Context<Self>,
18477 ) {
18478 if self.buffer.read(cx).is_singleton() {
18479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18480 let has_folds = display_map
18481 .folds_in_range(0..display_map.buffer_snapshot().len())
18482 .next()
18483 .is_some();
18484
18485 if has_folds {
18486 self.unfold_all(&actions::UnfoldAll, window, cx);
18487 } else {
18488 self.fold_all(&actions::FoldAll, window, cx);
18489 }
18490 } else {
18491 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18492 let should_unfold = buffer_ids
18493 .iter()
18494 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18495
18496 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18497 editor
18498 .update_in(cx, |editor, _, cx| {
18499 for buffer_id in buffer_ids {
18500 if should_unfold {
18501 editor.unfold_buffer(buffer_id, cx);
18502 } else {
18503 editor.fold_buffer(buffer_id, cx);
18504 }
18505 }
18506 })
18507 .ok();
18508 });
18509 }
18510 }
18511
18512 fn fold_at_level(
18513 &mut self,
18514 fold_at: &FoldAtLevel,
18515 window: &mut Window,
18516 cx: &mut Context<Self>,
18517 ) {
18518 if !self.buffer.read(cx).is_singleton() {
18519 return;
18520 }
18521
18522 let fold_at_level = fold_at.0;
18523 let snapshot = self.buffer.read(cx).snapshot(cx);
18524 let mut to_fold = Vec::new();
18525 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18526
18527 let row_ranges_to_keep: Vec<Range<u32>> = self
18528 .selections
18529 .all::<Point>(&self.display_snapshot(cx))
18530 .into_iter()
18531 .map(|sel| sel.start.row..sel.end.row)
18532 .collect();
18533
18534 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18535 while start_row < end_row {
18536 match self
18537 .snapshot(window, cx)
18538 .crease_for_buffer_row(MultiBufferRow(start_row))
18539 {
18540 Some(crease) => {
18541 let nested_start_row = crease.range().start.row + 1;
18542 let nested_end_row = crease.range().end.row;
18543
18544 if current_level < fold_at_level {
18545 stack.push((nested_start_row, nested_end_row, current_level + 1));
18546 } else if current_level == fold_at_level {
18547 // Fold iff there is no selection completely contained within the fold region
18548 if !row_ranges_to_keep.iter().any(|selection| {
18549 selection.end >= nested_start_row
18550 && selection.start <= nested_end_row
18551 }) {
18552 to_fold.push(crease);
18553 }
18554 }
18555
18556 start_row = nested_end_row + 1;
18557 }
18558 None => start_row += 1,
18559 }
18560 }
18561 }
18562
18563 self.fold_creases(to_fold, true, window, cx);
18564 }
18565
18566 pub fn fold_at_level_1(
18567 &mut self,
18568 _: &actions::FoldAtLevel1,
18569 window: &mut Window,
18570 cx: &mut Context<Self>,
18571 ) {
18572 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18573 }
18574
18575 pub fn fold_at_level_2(
18576 &mut self,
18577 _: &actions::FoldAtLevel2,
18578 window: &mut Window,
18579 cx: &mut Context<Self>,
18580 ) {
18581 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18582 }
18583
18584 pub fn fold_at_level_3(
18585 &mut self,
18586 _: &actions::FoldAtLevel3,
18587 window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) {
18590 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18591 }
18592
18593 pub fn fold_at_level_4(
18594 &mut self,
18595 _: &actions::FoldAtLevel4,
18596 window: &mut Window,
18597 cx: &mut Context<Self>,
18598 ) {
18599 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18600 }
18601
18602 pub fn fold_at_level_5(
18603 &mut self,
18604 _: &actions::FoldAtLevel5,
18605 window: &mut Window,
18606 cx: &mut Context<Self>,
18607 ) {
18608 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18609 }
18610
18611 pub fn fold_at_level_6(
18612 &mut self,
18613 _: &actions::FoldAtLevel6,
18614 window: &mut Window,
18615 cx: &mut Context<Self>,
18616 ) {
18617 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18618 }
18619
18620 pub fn fold_at_level_7(
18621 &mut self,
18622 _: &actions::FoldAtLevel7,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18627 }
18628
18629 pub fn fold_at_level_8(
18630 &mut self,
18631 _: &actions::FoldAtLevel8,
18632 window: &mut Window,
18633 cx: &mut Context<Self>,
18634 ) {
18635 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18636 }
18637
18638 pub fn fold_at_level_9(
18639 &mut self,
18640 _: &actions::FoldAtLevel9,
18641 window: &mut Window,
18642 cx: &mut Context<Self>,
18643 ) {
18644 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18645 }
18646
18647 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18648 if self.buffer.read(cx).is_singleton() {
18649 let mut fold_ranges = Vec::new();
18650 let snapshot = self.buffer.read(cx).snapshot(cx);
18651
18652 for row in 0..snapshot.max_row().0 {
18653 if let Some(foldable_range) = self
18654 .snapshot(window, cx)
18655 .crease_for_buffer_row(MultiBufferRow(row))
18656 {
18657 fold_ranges.push(foldable_range);
18658 }
18659 }
18660
18661 self.fold_creases(fold_ranges, true, window, cx);
18662 } else {
18663 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18664 editor
18665 .update_in(cx, |editor, _, cx| {
18666 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18667 editor.fold_buffer(buffer_id, cx);
18668 }
18669 })
18670 .ok();
18671 });
18672 }
18673 }
18674
18675 pub fn fold_function_bodies(
18676 &mut self,
18677 _: &actions::FoldFunctionBodies,
18678 window: &mut Window,
18679 cx: &mut Context<Self>,
18680 ) {
18681 let snapshot = self.buffer.read(cx).snapshot(cx);
18682
18683 let ranges = snapshot
18684 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18685 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18686 .collect::<Vec<_>>();
18687
18688 let creases = ranges
18689 .into_iter()
18690 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18691 .collect();
18692
18693 self.fold_creases(creases, true, window, cx);
18694 }
18695
18696 pub fn fold_recursive(
18697 &mut self,
18698 _: &actions::FoldRecursive,
18699 window: &mut Window,
18700 cx: &mut Context<Self>,
18701 ) {
18702 let mut to_fold = Vec::new();
18703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18704 let selections = self.selections.all_adjusted(&display_map);
18705
18706 for selection in selections {
18707 let range = selection.range().sorted();
18708 let buffer_start_row = range.start.row;
18709
18710 if range.start.row != range.end.row {
18711 let mut found = false;
18712 for row in range.start.row..=range.end.row {
18713 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18714 found = true;
18715 to_fold.push(crease);
18716 }
18717 }
18718 if found {
18719 continue;
18720 }
18721 }
18722
18723 for row in (0..=range.start.row).rev() {
18724 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18725 if crease.range().end.row >= buffer_start_row {
18726 to_fold.push(crease);
18727 } else {
18728 break;
18729 }
18730 }
18731 }
18732 }
18733
18734 self.fold_creases(to_fold, true, window, cx);
18735 }
18736
18737 pub fn fold_at(
18738 &mut self,
18739 buffer_row: MultiBufferRow,
18740 window: &mut Window,
18741 cx: &mut Context<Self>,
18742 ) {
18743 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18744
18745 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18746 let autoscroll = self
18747 .selections
18748 .all::<Point>(&display_map)
18749 .iter()
18750 .any(|selection| crease.range().overlaps(&selection.range()));
18751
18752 self.fold_creases(vec![crease], autoscroll, window, cx);
18753 }
18754 }
18755
18756 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18757 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18758 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18759 let buffer = display_map.buffer_snapshot();
18760 let selections = self.selections.all::<Point>(&display_map);
18761 let ranges = selections
18762 .iter()
18763 .map(|s| {
18764 let range = s.display_range(&display_map).sorted();
18765 let mut start = range.start.to_point(&display_map);
18766 let mut end = range.end.to_point(&display_map);
18767 start.column = 0;
18768 end.column = buffer.line_len(MultiBufferRow(end.row));
18769 start..end
18770 })
18771 .collect::<Vec<_>>();
18772
18773 self.unfold_ranges(&ranges, true, true, cx);
18774 } else {
18775 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18776 let buffer_ids = self
18777 .selections
18778 .disjoint_anchor_ranges()
18779 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18780 .collect::<HashSet<_>>();
18781 for buffer_id in buffer_ids {
18782 self.unfold_buffer(buffer_id, cx);
18783 }
18784 }
18785 }
18786
18787 pub fn unfold_recursive(
18788 &mut self,
18789 _: &UnfoldRecursive,
18790 _window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18794 let selections = self.selections.all::<Point>(&display_map);
18795 let ranges = selections
18796 .iter()
18797 .map(|s| {
18798 let mut range = s.display_range(&display_map).sorted();
18799 *range.start.column_mut() = 0;
18800 *range.end.column_mut() = display_map.line_len(range.end.row());
18801 let start = range.start.to_point(&display_map);
18802 let end = range.end.to_point(&display_map);
18803 start..end
18804 })
18805 .collect::<Vec<_>>();
18806
18807 self.unfold_ranges(&ranges, true, true, cx);
18808 }
18809
18810 pub fn unfold_at(
18811 &mut self,
18812 buffer_row: MultiBufferRow,
18813 _window: &mut Window,
18814 cx: &mut Context<Self>,
18815 ) {
18816 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18817
18818 let intersection_range = Point::new(buffer_row.0, 0)
18819 ..Point::new(
18820 buffer_row.0,
18821 display_map.buffer_snapshot().line_len(buffer_row),
18822 );
18823
18824 let autoscroll = self
18825 .selections
18826 .all::<Point>(&display_map)
18827 .iter()
18828 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18829
18830 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18831 }
18832
18833 pub fn unfold_all(
18834 &mut self,
18835 _: &actions::UnfoldAll,
18836 _window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) {
18839 if self.buffer.read(cx).is_singleton() {
18840 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18841 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18842 } else {
18843 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18844 editor
18845 .update(cx, |editor, cx| {
18846 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18847 editor.unfold_buffer(buffer_id, cx);
18848 }
18849 })
18850 .ok();
18851 });
18852 }
18853 }
18854
18855 pub fn fold_selected_ranges(
18856 &mut self,
18857 _: &FoldSelectedRanges,
18858 window: &mut Window,
18859 cx: &mut Context<Self>,
18860 ) {
18861 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18862 let selections = self.selections.all_adjusted(&display_map);
18863 let ranges = selections
18864 .into_iter()
18865 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18866 .collect::<Vec<_>>();
18867 self.fold_creases(ranges, true, window, cx);
18868 }
18869
18870 pub fn fold_ranges<T: ToOffset + Clone>(
18871 &mut self,
18872 ranges: Vec<Range<T>>,
18873 auto_scroll: bool,
18874 window: &mut Window,
18875 cx: &mut Context<Self>,
18876 ) {
18877 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18878 let ranges = ranges
18879 .into_iter()
18880 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18881 .collect::<Vec<_>>();
18882 self.fold_creases(ranges, auto_scroll, window, cx);
18883 }
18884
18885 pub fn fold_creases<T: ToOffset + Clone>(
18886 &mut self,
18887 creases: Vec<Crease<T>>,
18888 auto_scroll: bool,
18889 _window: &mut Window,
18890 cx: &mut Context<Self>,
18891 ) {
18892 if creases.is_empty() {
18893 return;
18894 }
18895
18896 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18897
18898 if auto_scroll {
18899 self.request_autoscroll(Autoscroll::fit(), cx);
18900 }
18901
18902 cx.notify();
18903
18904 self.scrollbar_marker_state.dirty = true;
18905 self.folds_did_change(cx);
18906 }
18907
18908 /// Removes any folds whose ranges intersect any of the given ranges.
18909 pub fn unfold_ranges<T: ToOffset + Clone>(
18910 &mut self,
18911 ranges: &[Range<T>],
18912 inclusive: bool,
18913 auto_scroll: bool,
18914 cx: &mut Context<Self>,
18915 ) {
18916 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18917 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18918 });
18919 self.folds_did_change(cx);
18920 }
18921
18922 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18923 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18924 return;
18925 }
18926
18927 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18928 self.display_map.update(cx, |display_map, cx| {
18929 display_map.fold_buffers([buffer_id], cx)
18930 });
18931
18932 let snapshot = self.display_snapshot(cx);
18933 self.selections.change_with(&snapshot, |selections| {
18934 selections.remove_selections_from_buffer(buffer_id);
18935 });
18936
18937 cx.emit(EditorEvent::BufferFoldToggled {
18938 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18939 folded: true,
18940 });
18941 cx.notify();
18942 }
18943
18944 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18945 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18946 return;
18947 }
18948 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18949 self.display_map.update(cx, |display_map, cx| {
18950 display_map.unfold_buffers([buffer_id], cx);
18951 });
18952 cx.emit(EditorEvent::BufferFoldToggled {
18953 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18954 folded: false,
18955 });
18956 cx.notify();
18957 }
18958
18959 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18960 self.display_map.read(cx).is_buffer_folded(buffer)
18961 }
18962
18963 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18964 self.display_map.read(cx).folded_buffers()
18965 }
18966
18967 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18968 self.display_map.update(cx, |display_map, cx| {
18969 display_map.disable_header_for_buffer(buffer_id, cx);
18970 });
18971 cx.notify();
18972 }
18973
18974 /// Removes any folds with the given ranges.
18975 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18976 &mut self,
18977 ranges: &[Range<T>],
18978 type_id: TypeId,
18979 auto_scroll: bool,
18980 cx: &mut Context<Self>,
18981 ) {
18982 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18983 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18984 });
18985 self.folds_did_change(cx);
18986 }
18987
18988 fn remove_folds_with<T: ToOffset + Clone>(
18989 &mut self,
18990 ranges: &[Range<T>],
18991 auto_scroll: bool,
18992 cx: &mut Context<Self>,
18993 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18994 ) {
18995 if ranges.is_empty() {
18996 return;
18997 }
18998
18999 let mut buffers_affected = HashSet::default();
19000 let multi_buffer = self.buffer().read(cx);
19001 for range in ranges {
19002 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19003 buffers_affected.insert(buffer.read(cx).remote_id());
19004 };
19005 }
19006
19007 self.display_map.update(cx, update);
19008
19009 if auto_scroll {
19010 self.request_autoscroll(Autoscroll::fit(), cx);
19011 }
19012
19013 cx.notify();
19014 self.scrollbar_marker_state.dirty = true;
19015 self.active_indent_guides_state.dirty = true;
19016 }
19017
19018 pub fn update_renderer_widths(
19019 &mut self,
19020 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19021 cx: &mut Context<Self>,
19022 ) -> bool {
19023 self.display_map
19024 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19025 }
19026
19027 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19028 self.display_map.read(cx).fold_placeholder.clone()
19029 }
19030
19031 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19032 self.buffer.update(cx, |buffer, cx| {
19033 buffer.set_all_diff_hunks_expanded(cx);
19034 });
19035 }
19036
19037 pub fn expand_all_diff_hunks(
19038 &mut self,
19039 _: &ExpandAllDiffHunks,
19040 _window: &mut Window,
19041 cx: &mut Context<Self>,
19042 ) {
19043 self.buffer.update(cx, |buffer, cx| {
19044 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19045 });
19046 }
19047
19048 pub fn collapse_all_diff_hunks(
19049 &mut self,
19050 _: &CollapseAllDiffHunks,
19051 _window: &mut Window,
19052 cx: &mut Context<Self>,
19053 ) {
19054 self.buffer.update(cx, |buffer, cx| {
19055 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19056 });
19057 }
19058
19059 pub fn toggle_selected_diff_hunks(
19060 &mut self,
19061 _: &ToggleSelectedDiffHunks,
19062 _window: &mut Window,
19063 cx: &mut Context<Self>,
19064 ) {
19065 let ranges: Vec<_> = self
19066 .selections
19067 .disjoint_anchors()
19068 .iter()
19069 .map(|s| s.range())
19070 .collect();
19071 self.toggle_diff_hunks_in_ranges(ranges, cx);
19072 }
19073
19074 pub fn diff_hunks_in_ranges<'a>(
19075 &'a self,
19076 ranges: &'a [Range<Anchor>],
19077 buffer: &'a MultiBufferSnapshot,
19078 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19079 ranges.iter().flat_map(move |range| {
19080 let end_excerpt_id = range.end.excerpt_id;
19081 let range = range.to_point(buffer);
19082 let mut peek_end = range.end;
19083 if range.end.row < buffer.max_row().0 {
19084 peek_end = Point::new(range.end.row + 1, 0);
19085 }
19086 buffer
19087 .diff_hunks_in_range(range.start..peek_end)
19088 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19089 })
19090 }
19091
19092 pub fn has_stageable_diff_hunks_in_ranges(
19093 &self,
19094 ranges: &[Range<Anchor>],
19095 snapshot: &MultiBufferSnapshot,
19096 ) -> bool {
19097 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19098 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19099 }
19100
19101 pub fn toggle_staged_selected_diff_hunks(
19102 &mut self,
19103 _: &::git::ToggleStaged,
19104 _: &mut Window,
19105 cx: &mut Context<Self>,
19106 ) {
19107 let snapshot = self.buffer.read(cx).snapshot(cx);
19108 let ranges: Vec<_> = self
19109 .selections
19110 .disjoint_anchors()
19111 .iter()
19112 .map(|s| s.range())
19113 .collect();
19114 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19115 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19116 }
19117
19118 pub fn set_render_diff_hunk_controls(
19119 &mut self,
19120 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19121 cx: &mut Context<Self>,
19122 ) {
19123 self.render_diff_hunk_controls = render_diff_hunk_controls;
19124 cx.notify();
19125 }
19126
19127 pub fn stage_and_next(
19128 &mut self,
19129 _: &::git::StageAndNext,
19130 window: &mut Window,
19131 cx: &mut Context<Self>,
19132 ) {
19133 self.do_stage_or_unstage_and_next(true, window, cx);
19134 }
19135
19136 pub fn unstage_and_next(
19137 &mut self,
19138 _: &::git::UnstageAndNext,
19139 window: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 self.do_stage_or_unstage_and_next(false, window, cx);
19143 }
19144
19145 pub fn stage_or_unstage_diff_hunks(
19146 &mut self,
19147 stage: bool,
19148 ranges: Vec<Range<Anchor>>,
19149 cx: &mut Context<Self>,
19150 ) {
19151 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19152 cx.spawn(async move |this, cx| {
19153 task.await?;
19154 this.update(cx, |this, cx| {
19155 let snapshot = this.buffer.read(cx).snapshot(cx);
19156 let chunk_by = this
19157 .diff_hunks_in_ranges(&ranges, &snapshot)
19158 .chunk_by(|hunk| hunk.buffer_id);
19159 for (buffer_id, hunks) in &chunk_by {
19160 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19161 }
19162 })
19163 })
19164 .detach_and_log_err(cx);
19165 }
19166
19167 fn save_buffers_for_ranges_if_needed(
19168 &mut self,
19169 ranges: &[Range<Anchor>],
19170 cx: &mut Context<Editor>,
19171 ) -> Task<Result<()>> {
19172 let multibuffer = self.buffer.read(cx);
19173 let snapshot = multibuffer.read(cx);
19174 let buffer_ids: HashSet<_> = ranges
19175 .iter()
19176 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19177 .collect();
19178 drop(snapshot);
19179
19180 let mut buffers = HashSet::default();
19181 for buffer_id in buffer_ids {
19182 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19183 let buffer = buffer_entity.read(cx);
19184 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19185 {
19186 buffers.insert(buffer_entity);
19187 }
19188 }
19189 }
19190
19191 if let Some(project) = &self.project {
19192 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19193 } else {
19194 Task::ready(Ok(()))
19195 }
19196 }
19197
19198 fn do_stage_or_unstage_and_next(
19199 &mut self,
19200 stage: bool,
19201 window: &mut Window,
19202 cx: &mut Context<Self>,
19203 ) {
19204 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19205
19206 if ranges.iter().any(|range| range.start != range.end) {
19207 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19208 return;
19209 }
19210
19211 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19212 let snapshot = self.snapshot(window, cx);
19213 let position = self
19214 .selections
19215 .newest::<Point>(&snapshot.display_snapshot)
19216 .head();
19217 let mut row = snapshot
19218 .buffer_snapshot()
19219 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19220 .find(|hunk| hunk.row_range.start.0 > position.row)
19221 .map(|hunk| hunk.row_range.start);
19222
19223 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19224 // Outside of the project diff editor, wrap around to the beginning.
19225 if !all_diff_hunks_expanded {
19226 row = row.or_else(|| {
19227 snapshot
19228 .buffer_snapshot()
19229 .diff_hunks_in_range(Point::zero()..position)
19230 .find(|hunk| hunk.row_range.end.0 < position.row)
19231 .map(|hunk| hunk.row_range.start)
19232 });
19233 }
19234
19235 if let Some(row) = row {
19236 let destination = Point::new(row.0, 0);
19237 let autoscroll = Autoscroll::center();
19238
19239 self.unfold_ranges(&[destination..destination], false, false, cx);
19240 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19241 s.select_ranges([destination..destination]);
19242 });
19243 }
19244 }
19245
19246 fn do_stage_or_unstage(
19247 &self,
19248 stage: bool,
19249 buffer_id: BufferId,
19250 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19251 cx: &mut App,
19252 ) -> Option<()> {
19253 let project = self.project()?;
19254 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19255 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19256 let buffer_snapshot = buffer.read(cx).snapshot();
19257 let file_exists = buffer_snapshot
19258 .file()
19259 .is_some_and(|file| file.disk_state().exists());
19260 diff.update(cx, |diff, cx| {
19261 diff.stage_or_unstage_hunks(
19262 stage,
19263 &hunks
19264 .map(|hunk| buffer_diff::DiffHunk {
19265 buffer_range: hunk.buffer_range,
19266 diff_base_byte_range: hunk.diff_base_byte_range,
19267 secondary_status: hunk.secondary_status,
19268 range: Point::zero()..Point::zero(), // unused
19269 })
19270 .collect::<Vec<_>>(),
19271 &buffer_snapshot,
19272 file_exists,
19273 cx,
19274 )
19275 });
19276 None
19277 }
19278
19279 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19280 let ranges: Vec<_> = self
19281 .selections
19282 .disjoint_anchors()
19283 .iter()
19284 .map(|s| s.range())
19285 .collect();
19286 self.buffer
19287 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19288 }
19289
19290 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19291 self.buffer.update(cx, |buffer, cx| {
19292 let ranges = vec![Anchor::min()..Anchor::max()];
19293 if !buffer.all_diff_hunks_expanded()
19294 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19295 {
19296 buffer.collapse_diff_hunks(ranges, cx);
19297 true
19298 } else {
19299 false
19300 }
19301 })
19302 }
19303
19304 fn toggle_diff_hunks_in_ranges(
19305 &mut self,
19306 ranges: Vec<Range<Anchor>>,
19307 cx: &mut Context<Editor>,
19308 ) {
19309 self.buffer.update(cx, |buffer, cx| {
19310 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19311 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19312 })
19313 }
19314
19315 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19316 self.buffer.update(cx, |buffer, cx| {
19317 let snapshot = buffer.snapshot(cx);
19318 let excerpt_id = range.end.excerpt_id;
19319 let point_range = range.to_point(&snapshot);
19320 let expand = !buffer.single_hunk_is_expanded(range, cx);
19321 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19322 })
19323 }
19324
19325 pub(crate) fn apply_all_diff_hunks(
19326 &mut self,
19327 _: &ApplyAllDiffHunks,
19328 window: &mut Window,
19329 cx: &mut Context<Self>,
19330 ) {
19331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19332
19333 let buffers = self.buffer.read(cx).all_buffers();
19334 for branch_buffer in buffers {
19335 branch_buffer.update(cx, |branch_buffer, cx| {
19336 branch_buffer.merge_into_base(Vec::new(), cx);
19337 });
19338 }
19339
19340 if let Some(project) = self.project.clone() {
19341 self.save(
19342 SaveOptions {
19343 format: true,
19344 autosave: false,
19345 },
19346 project,
19347 window,
19348 cx,
19349 )
19350 .detach_and_log_err(cx);
19351 }
19352 }
19353
19354 pub(crate) fn apply_selected_diff_hunks(
19355 &mut self,
19356 _: &ApplyDiffHunk,
19357 window: &mut Window,
19358 cx: &mut Context<Self>,
19359 ) {
19360 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19361 let snapshot = self.snapshot(window, cx);
19362 let hunks = snapshot.hunks_for_ranges(
19363 self.selections
19364 .all(&snapshot.display_snapshot)
19365 .into_iter()
19366 .map(|selection| selection.range()),
19367 );
19368 let mut ranges_by_buffer = HashMap::default();
19369 self.transact(window, cx, |editor, _window, cx| {
19370 for hunk in hunks {
19371 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19372 ranges_by_buffer
19373 .entry(buffer.clone())
19374 .or_insert_with(Vec::new)
19375 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19376 }
19377 }
19378
19379 for (buffer, ranges) in ranges_by_buffer {
19380 buffer.update(cx, |buffer, cx| {
19381 buffer.merge_into_base(ranges, cx);
19382 });
19383 }
19384 });
19385
19386 if let Some(project) = self.project.clone() {
19387 self.save(
19388 SaveOptions {
19389 format: true,
19390 autosave: false,
19391 },
19392 project,
19393 window,
19394 cx,
19395 )
19396 .detach_and_log_err(cx);
19397 }
19398 }
19399
19400 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19401 if hovered != self.gutter_hovered {
19402 self.gutter_hovered = hovered;
19403 cx.notify();
19404 }
19405 }
19406
19407 pub fn insert_blocks(
19408 &mut self,
19409 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19410 autoscroll: Option<Autoscroll>,
19411 cx: &mut Context<Self>,
19412 ) -> Vec<CustomBlockId> {
19413 let blocks = self
19414 .display_map
19415 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19416 if let Some(autoscroll) = autoscroll {
19417 self.request_autoscroll(autoscroll, cx);
19418 }
19419 cx.notify();
19420 blocks
19421 }
19422
19423 pub fn resize_blocks(
19424 &mut self,
19425 heights: HashMap<CustomBlockId, u32>,
19426 autoscroll: Option<Autoscroll>,
19427 cx: &mut Context<Self>,
19428 ) {
19429 self.display_map
19430 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19431 if let Some(autoscroll) = autoscroll {
19432 self.request_autoscroll(autoscroll, cx);
19433 }
19434 cx.notify();
19435 }
19436
19437 pub fn replace_blocks(
19438 &mut self,
19439 renderers: HashMap<CustomBlockId, RenderBlock>,
19440 autoscroll: Option<Autoscroll>,
19441 cx: &mut Context<Self>,
19442 ) {
19443 self.display_map
19444 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19445 if let Some(autoscroll) = autoscroll {
19446 self.request_autoscroll(autoscroll, cx);
19447 }
19448 cx.notify();
19449 }
19450
19451 pub fn remove_blocks(
19452 &mut self,
19453 block_ids: HashSet<CustomBlockId>,
19454 autoscroll: Option<Autoscroll>,
19455 cx: &mut Context<Self>,
19456 ) {
19457 self.display_map.update(cx, |display_map, cx| {
19458 display_map.remove_blocks(block_ids, cx)
19459 });
19460 if let Some(autoscroll) = autoscroll {
19461 self.request_autoscroll(autoscroll, cx);
19462 }
19463 cx.notify();
19464 }
19465
19466 pub fn row_for_block(
19467 &self,
19468 block_id: CustomBlockId,
19469 cx: &mut Context<Self>,
19470 ) -> Option<DisplayRow> {
19471 self.display_map
19472 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19473 }
19474
19475 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19476 self.focused_block = Some(focused_block);
19477 }
19478
19479 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19480 self.focused_block.take()
19481 }
19482
19483 pub fn insert_creases(
19484 &mut self,
19485 creases: impl IntoIterator<Item = Crease<Anchor>>,
19486 cx: &mut Context<Self>,
19487 ) -> Vec<CreaseId> {
19488 self.display_map
19489 .update(cx, |map, cx| map.insert_creases(creases, cx))
19490 }
19491
19492 pub fn remove_creases(
19493 &mut self,
19494 ids: impl IntoIterator<Item = CreaseId>,
19495 cx: &mut Context<Self>,
19496 ) -> Vec<(CreaseId, Range<Anchor>)> {
19497 self.display_map
19498 .update(cx, |map, cx| map.remove_creases(ids, cx))
19499 }
19500
19501 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19502 self.display_map
19503 .update(cx, |map, cx| map.snapshot(cx))
19504 .longest_row()
19505 }
19506
19507 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19508 self.display_map
19509 .update(cx, |map, cx| map.snapshot(cx))
19510 .max_point()
19511 }
19512
19513 pub fn text(&self, cx: &App) -> String {
19514 self.buffer.read(cx).read(cx).text()
19515 }
19516
19517 pub fn is_empty(&self, cx: &App) -> bool {
19518 self.buffer.read(cx).read(cx).is_empty()
19519 }
19520
19521 pub fn text_option(&self, cx: &App) -> Option<String> {
19522 let text = self.text(cx);
19523 let text = text.trim();
19524
19525 if text.is_empty() {
19526 return None;
19527 }
19528
19529 Some(text.to_string())
19530 }
19531
19532 pub fn set_text(
19533 &mut self,
19534 text: impl Into<Arc<str>>,
19535 window: &mut Window,
19536 cx: &mut Context<Self>,
19537 ) {
19538 self.transact(window, cx, |this, _, cx| {
19539 this.buffer
19540 .read(cx)
19541 .as_singleton()
19542 .expect("you can only call set_text on editors for singleton buffers")
19543 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19544 });
19545 }
19546
19547 pub fn display_text(&self, cx: &mut App) -> String {
19548 self.display_map
19549 .update(cx, |map, cx| map.snapshot(cx))
19550 .text()
19551 }
19552
19553 fn create_minimap(
19554 &self,
19555 minimap_settings: MinimapSettings,
19556 window: &mut Window,
19557 cx: &mut Context<Self>,
19558 ) -> Option<Entity<Self>> {
19559 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19560 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19561 }
19562
19563 fn initialize_new_minimap(
19564 &self,
19565 minimap_settings: MinimapSettings,
19566 window: &mut Window,
19567 cx: &mut Context<Self>,
19568 ) -> Entity<Self> {
19569 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19570
19571 let mut minimap = Editor::new_internal(
19572 EditorMode::Minimap {
19573 parent: cx.weak_entity(),
19574 },
19575 self.buffer.clone(),
19576 None,
19577 Some(self.display_map.clone()),
19578 window,
19579 cx,
19580 );
19581 minimap.scroll_manager.clone_state(&self.scroll_manager);
19582 minimap.set_text_style_refinement(TextStyleRefinement {
19583 font_size: Some(MINIMAP_FONT_SIZE),
19584 font_weight: Some(MINIMAP_FONT_WEIGHT),
19585 ..Default::default()
19586 });
19587 minimap.update_minimap_configuration(minimap_settings, cx);
19588 cx.new(|_| minimap)
19589 }
19590
19591 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19592 let current_line_highlight = minimap_settings
19593 .current_line_highlight
19594 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19595 self.set_current_line_highlight(Some(current_line_highlight));
19596 }
19597
19598 pub fn minimap(&self) -> Option<&Entity<Self>> {
19599 self.minimap
19600 .as_ref()
19601 .filter(|_| self.minimap_visibility.visible())
19602 }
19603
19604 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19605 let mut wrap_guides = smallvec![];
19606
19607 if self.show_wrap_guides == Some(false) {
19608 return wrap_guides;
19609 }
19610
19611 let settings = self.buffer.read(cx).language_settings(cx);
19612 if settings.show_wrap_guides {
19613 match self.soft_wrap_mode(cx) {
19614 SoftWrap::Column(soft_wrap) => {
19615 wrap_guides.push((soft_wrap as usize, true));
19616 }
19617 SoftWrap::Bounded(soft_wrap) => {
19618 wrap_guides.push((soft_wrap as usize, true));
19619 }
19620 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19621 }
19622 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19623 }
19624
19625 wrap_guides
19626 }
19627
19628 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19629 let settings = self.buffer.read(cx).language_settings(cx);
19630 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19631 match mode {
19632 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19633 SoftWrap::None
19634 }
19635 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19636 language_settings::SoftWrap::PreferredLineLength => {
19637 SoftWrap::Column(settings.preferred_line_length)
19638 }
19639 language_settings::SoftWrap::Bounded => {
19640 SoftWrap::Bounded(settings.preferred_line_length)
19641 }
19642 }
19643 }
19644
19645 pub fn set_soft_wrap_mode(
19646 &mut self,
19647 mode: language_settings::SoftWrap,
19648
19649 cx: &mut Context<Self>,
19650 ) {
19651 self.soft_wrap_mode_override = Some(mode);
19652 cx.notify();
19653 }
19654
19655 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19656 self.hard_wrap = hard_wrap;
19657 cx.notify();
19658 }
19659
19660 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19661 self.text_style_refinement = Some(style);
19662 }
19663
19664 /// called by the Element so we know what style we were most recently rendered with.
19665 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19666 // We intentionally do not inform the display map about the minimap style
19667 // so that wrapping is not recalculated and stays consistent for the editor
19668 // and its linked minimap.
19669 if !self.mode.is_minimap() {
19670 let font = style.text.font();
19671 let font_size = style.text.font_size.to_pixels(window.rem_size());
19672 let display_map = self
19673 .placeholder_display_map
19674 .as_ref()
19675 .filter(|_| self.is_empty(cx))
19676 .unwrap_or(&self.display_map);
19677
19678 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19679 }
19680 self.style = Some(style);
19681 }
19682
19683 pub fn style(&self) -> Option<&EditorStyle> {
19684 self.style.as_ref()
19685 }
19686
19687 // Called by the element. This method is not designed to be called outside of the editor
19688 // element's layout code because it does not notify when rewrapping is computed synchronously.
19689 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19690 if self.is_empty(cx) {
19691 self.placeholder_display_map
19692 .as_ref()
19693 .map_or(false, |display_map| {
19694 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19695 })
19696 } else {
19697 self.display_map
19698 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19699 }
19700 }
19701
19702 pub fn set_soft_wrap(&mut self) {
19703 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19704 }
19705
19706 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19707 if self.soft_wrap_mode_override.is_some() {
19708 self.soft_wrap_mode_override.take();
19709 } else {
19710 let soft_wrap = match self.soft_wrap_mode(cx) {
19711 SoftWrap::GitDiff => return,
19712 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19713 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19714 language_settings::SoftWrap::None
19715 }
19716 };
19717 self.soft_wrap_mode_override = Some(soft_wrap);
19718 }
19719 cx.notify();
19720 }
19721
19722 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19723 let Some(workspace) = self.workspace() else {
19724 return;
19725 };
19726 let fs = workspace.read(cx).app_state().fs.clone();
19727 let current_show = TabBarSettings::get_global(cx).show;
19728 update_settings_file(fs, cx, move |setting, _| {
19729 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19730 });
19731 }
19732
19733 pub fn toggle_indent_guides(
19734 &mut self,
19735 _: &ToggleIndentGuides,
19736 _: &mut Window,
19737 cx: &mut Context<Self>,
19738 ) {
19739 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19740 self.buffer
19741 .read(cx)
19742 .language_settings(cx)
19743 .indent_guides
19744 .enabled
19745 });
19746 self.show_indent_guides = Some(!currently_enabled);
19747 cx.notify();
19748 }
19749
19750 fn should_show_indent_guides(&self) -> Option<bool> {
19751 self.show_indent_guides
19752 }
19753
19754 pub fn toggle_line_numbers(
19755 &mut self,
19756 _: &ToggleLineNumbers,
19757 _: &mut Window,
19758 cx: &mut Context<Self>,
19759 ) {
19760 let mut editor_settings = EditorSettings::get_global(cx).clone();
19761 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19762 EditorSettings::override_global(editor_settings, cx);
19763 }
19764
19765 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19766 if let Some(show_line_numbers) = self.show_line_numbers {
19767 return show_line_numbers;
19768 }
19769 EditorSettings::get_global(cx).gutter.line_numbers
19770 }
19771
19772 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19773 match (
19774 self.use_relative_line_numbers,
19775 EditorSettings::get_global(cx).relative_line_numbers,
19776 ) {
19777 (None, setting) => setting,
19778 (Some(false), _) => RelativeLineNumbers::Disabled,
19779 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19780 (Some(true), _) => RelativeLineNumbers::Enabled,
19781 }
19782 }
19783
19784 pub fn toggle_relative_line_numbers(
19785 &mut self,
19786 _: &ToggleRelativeLineNumbers,
19787 _: &mut Window,
19788 cx: &mut Context<Self>,
19789 ) {
19790 let is_relative = self.relative_line_numbers(cx);
19791 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19792 }
19793
19794 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19795 self.use_relative_line_numbers = is_relative;
19796 cx.notify();
19797 }
19798
19799 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19800 self.show_gutter = show_gutter;
19801 cx.notify();
19802 }
19803
19804 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19805 self.show_scrollbars = ScrollbarAxes {
19806 horizontal: show,
19807 vertical: show,
19808 };
19809 cx.notify();
19810 }
19811
19812 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19813 self.show_scrollbars.vertical = show;
19814 cx.notify();
19815 }
19816
19817 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19818 self.show_scrollbars.horizontal = show;
19819 cx.notify();
19820 }
19821
19822 pub fn set_minimap_visibility(
19823 &mut self,
19824 minimap_visibility: MinimapVisibility,
19825 window: &mut Window,
19826 cx: &mut Context<Self>,
19827 ) {
19828 if self.minimap_visibility != minimap_visibility {
19829 if minimap_visibility.visible() && self.minimap.is_none() {
19830 let minimap_settings = EditorSettings::get_global(cx).minimap;
19831 self.minimap =
19832 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19833 }
19834 self.minimap_visibility = minimap_visibility;
19835 cx.notify();
19836 }
19837 }
19838
19839 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19840 self.set_show_scrollbars(false, cx);
19841 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19842 }
19843
19844 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19845 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19846 }
19847
19848 /// Normally the text in full mode and auto height editors is padded on the
19849 /// left side by roughly half a character width for improved hit testing.
19850 ///
19851 /// Use this method to disable this for cases where this is not wanted (e.g.
19852 /// if you want to align the editor text with some other text above or below)
19853 /// or if you want to add this padding to single-line editors.
19854 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19855 self.offset_content = offset_content;
19856 cx.notify();
19857 }
19858
19859 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19860 self.show_line_numbers = Some(show_line_numbers);
19861 cx.notify();
19862 }
19863
19864 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19865 self.disable_expand_excerpt_buttons = true;
19866 cx.notify();
19867 }
19868
19869 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19870 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19871 cx.notify();
19872 }
19873
19874 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19875 self.show_code_actions = Some(show_code_actions);
19876 cx.notify();
19877 }
19878
19879 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19880 self.show_runnables = Some(show_runnables);
19881 cx.notify();
19882 }
19883
19884 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19885 self.show_breakpoints = Some(show_breakpoints);
19886 cx.notify();
19887 }
19888
19889 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19890 if self.display_map.read(cx).masked != masked {
19891 self.display_map.update(cx, |map, _| map.masked = masked);
19892 }
19893 cx.notify()
19894 }
19895
19896 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19897 self.show_wrap_guides = Some(show_wrap_guides);
19898 cx.notify();
19899 }
19900
19901 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19902 self.show_indent_guides = Some(show_indent_guides);
19903 cx.notify();
19904 }
19905
19906 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19907 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19908 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19909 && let Some(dir) = file.abs_path(cx).parent()
19910 {
19911 return Some(dir.to_owned());
19912 }
19913 }
19914
19915 None
19916 }
19917
19918 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19919 self.active_excerpt(cx)?
19920 .1
19921 .read(cx)
19922 .file()
19923 .and_then(|f| f.as_local())
19924 }
19925
19926 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19927 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19928 let buffer = buffer.read(cx);
19929 if let Some(project_path) = buffer.project_path(cx) {
19930 let project = self.project()?.read(cx);
19931 project.absolute_path(&project_path, cx)
19932 } else {
19933 buffer
19934 .file()
19935 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19936 }
19937 })
19938 }
19939
19940 pub fn reveal_in_finder(
19941 &mut self,
19942 _: &RevealInFileManager,
19943 _window: &mut Window,
19944 cx: &mut Context<Self>,
19945 ) {
19946 if let Some(target) = self.target_file(cx) {
19947 cx.reveal_path(&target.abs_path(cx));
19948 }
19949 }
19950
19951 pub fn copy_path(
19952 &mut self,
19953 _: &zed_actions::workspace::CopyPath,
19954 _window: &mut Window,
19955 cx: &mut Context<Self>,
19956 ) {
19957 if let Some(path) = self.target_file_abs_path(cx)
19958 && let Some(path) = path.to_str()
19959 {
19960 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19961 } else {
19962 cx.propagate();
19963 }
19964 }
19965
19966 pub fn copy_relative_path(
19967 &mut self,
19968 _: &zed_actions::workspace::CopyRelativePath,
19969 _window: &mut Window,
19970 cx: &mut Context<Self>,
19971 ) {
19972 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19973 let project = self.project()?.read(cx);
19974 let path = buffer.read(cx).file()?.path();
19975 let path = path.display(project.path_style(cx));
19976 Some(path)
19977 }) {
19978 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19979 } else {
19980 cx.propagate();
19981 }
19982 }
19983
19984 /// Returns the project path for the editor's buffer, if any buffer is
19985 /// opened in the editor.
19986 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19987 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19988 buffer.read(cx).project_path(cx)
19989 } else {
19990 None
19991 }
19992 }
19993
19994 // Returns true if the editor handled a go-to-line request
19995 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19996 maybe!({
19997 let breakpoint_store = self.breakpoint_store.as_ref()?;
19998
19999 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20000 else {
20001 self.clear_row_highlights::<ActiveDebugLine>();
20002 return None;
20003 };
20004
20005 let position = active_stack_frame.position;
20006 let buffer_id = position.buffer_id?;
20007 let snapshot = self
20008 .project
20009 .as_ref()?
20010 .read(cx)
20011 .buffer_for_id(buffer_id, cx)?
20012 .read(cx)
20013 .snapshot();
20014
20015 let mut handled = false;
20016 for (id, ExcerptRange { context, .. }) in
20017 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20018 {
20019 if context.start.cmp(&position, &snapshot).is_ge()
20020 || context.end.cmp(&position, &snapshot).is_lt()
20021 {
20022 continue;
20023 }
20024 let snapshot = self.buffer.read(cx).snapshot(cx);
20025 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20026
20027 handled = true;
20028 self.clear_row_highlights::<ActiveDebugLine>();
20029
20030 self.go_to_line::<ActiveDebugLine>(
20031 multibuffer_anchor,
20032 Some(cx.theme().colors().editor_debugger_active_line_background),
20033 window,
20034 cx,
20035 );
20036
20037 cx.notify();
20038 }
20039
20040 handled.then_some(())
20041 })
20042 .is_some()
20043 }
20044
20045 pub fn copy_file_name_without_extension(
20046 &mut self,
20047 _: &CopyFileNameWithoutExtension,
20048 _: &mut Window,
20049 cx: &mut Context<Self>,
20050 ) {
20051 if let Some(file) = self.target_file(cx)
20052 && let Some(file_stem) = file.path().file_stem()
20053 {
20054 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20055 }
20056 }
20057
20058 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20059 if let Some(file) = self.target_file(cx)
20060 && let Some(name) = file.path().file_name()
20061 {
20062 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20063 }
20064 }
20065
20066 pub fn toggle_git_blame(
20067 &mut self,
20068 _: &::git::Blame,
20069 window: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20073
20074 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20075 self.start_git_blame(true, window, cx);
20076 }
20077
20078 cx.notify();
20079 }
20080
20081 pub fn toggle_git_blame_inline(
20082 &mut self,
20083 _: &ToggleGitBlameInline,
20084 window: &mut Window,
20085 cx: &mut Context<Self>,
20086 ) {
20087 self.toggle_git_blame_inline_internal(true, window, cx);
20088 cx.notify();
20089 }
20090
20091 pub fn open_git_blame_commit(
20092 &mut self,
20093 _: &OpenGitBlameCommit,
20094 window: &mut Window,
20095 cx: &mut Context<Self>,
20096 ) {
20097 self.open_git_blame_commit_internal(window, cx);
20098 }
20099
20100 fn open_git_blame_commit_internal(
20101 &mut self,
20102 window: &mut Window,
20103 cx: &mut Context<Self>,
20104 ) -> Option<()> {
20105 let blame = self.blame.as_ref()?;
20106 let snapshot = self.snapshot(window, cx);
20107 let cursor = self
20108 .selections
20109 .newest::<Point>(&snapshot.display_snapshot)
20110 .head();
20111 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20112 let (_, blame_entry) = blame
20113 .update(cx, |blame, cx| {
20114 blame
20115 .blame_for_rows(
20116 &[RowInfo {
20117 buffer_id: Some(buffer.remote_id()),
20118 buffer_row: Some(point.row),
20119 ..Default::default()
20120 }],
20121 cx,
20122 )
20123 .next()
20124 })
20125 .flatten()?;
20126 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20127 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20128 let workspace = self.workspace()?.downgrade();
20129 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20130 None
20131 }
20132
20133 pub fn git_blame_inline_enabled(&self) -> bool {
20134 self.git_blame_inline_enabled
20135 }
20136
20137 pub fn toggle_selection_menu(
20138 &mut self,
20139 _: &ToggleSelectionMenu,
20140 _: &mut Window,
20141 cx: &mut Context<Self>,
20142 ) {
20143 self.show_selection_menu = self
20144 .show_selection_menu
20145 .map(|show_selections_menu| !show_selections_menu)
20146 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20147
20148 cx.notify();
20149 }
20150
20151 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20152 self.show_selection_menu
20153 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20154 }
20155
20156 fn start_git_blame(
20157 &mut self,
20158 user_triggered: bool,
20159 window: &mut Window,
20160 cx: &mut Context<Self>,
20161 ) {
20162 if let Some(project) = self.project() {
20163 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20164 && buffer.read(cx).file().is_none()
20165 {
20166 return;
20167 }
20168
20169 let focused = self.focus_handle(cx).contains_focused(window, cx);
20170
20171 let project = project.clone();
20172 let blame = cx
20173 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20174 self.blame_subscription =
20175 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20176 self.blame = Some(blame);
20177 }
20178 }
20179
20180 fn toggle_git_blame_inline_internal(
20181 &mut self,
20182 user_triggered: bool,
20183 window: &mut Window,
20184 cx: &mut Context<Self>,
20185 ) {
20186 if self.git_blame_inline_enabled {
20187 self.git_blame_inline_enabled = false;
20188 self.show_git_blame_inline = false;
20189 self.show_git_blame_inline_delay_task.take();
20190 } else {
20191 self.git_blame_inline_enabled = true;
20192 self.start_git_blame_inline(user_triggered, window, cx);
20193 }
20194
20195 cx.notify();
20196 }
20197
20198 fn start_git_blame_inline(
20199 &mut self,
20200 user_triggered: bool,
20201 window: &mut Window,
20202 cx: &mut Context<Self>,
20203 ) {
20204 self.start_git_blame(user_triggered, window, cx);
20205
20206 if ProjectSettings::get_global(cx)
20207 .git
20208 .inline_blame_delay()
20209 .is_some()
20210 {
20211 self.start_inline_blame_timer(window, cx);
20212 } else {
20213 self.show_git_blame_inline = true
20214 }
20215 }
20216
20217 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20218 self.blame.as_ref()
20219 }
20220
20221 pub fn show_git_blame_gutter(&self) -> bool {
20222 self.show_git_blame_gutter
20223 }
20224
20225 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20226 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20227 }
20228
20229 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20230 self.show_git_blame_inline
20231 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20232 && !self.newest_selection_head_on_empty_line(cx)
20233 && self.has_blame_entries(cx)
20234 }
20235
20236 fn has_blame_entries(&self, cx: &App) -> bool {
20237 self.blame()
20238 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20239 }
20240
20241 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20242 let cursor_anchor = self.selections.newest_anchor().head();
20243
20244 let snapshot = self.buffer.read(cx).snapshot(cx);
20245 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20246
20247 snapshot.line_len(buffer_row) == 0
20248 }
20249
20250 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20251 let buffer_and_selection = maybe!({
20252 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20253 let selection_range = selection.range();
20254
20255 let multi_buffer = self.buffer().read(cx);
20256 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20257 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20258
20259 let (buffer, range, _) = if selection.reversed {
20260 buffer_ranges.first()
20261 } else {
20262 buffer_ranges.last()
20263 }?;
20264
20265 let selection = text::ToPoint::to_point(&range.start, buffer).row
20266 ..text::ToPoint::to_point(&range.end, buffer).row;
20267 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20268 });
20269
20270 let Some((buffer, selection)) = buffer_and_selection else {
20271 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20272 };
20273
20274 let Some(project) = self.project() else {
20275 return Task::ready(Err(anyhow!("editor does not have project")));
20276 };
20277
20278 project.update(cx, |project, cx| {
20279 project.get_permalink_to_line(&buffer, selection, cx)
20280 })
20281 }
20282
20283 pub fn copy_permalink_to_line(
20284 &mut self,
20285 _: &CopyPermalinkToLine,
20286 window: &mut Window,
20287 cx: &mut Context<Self>,
20288 ) {
20289 let permalink_task = self.get_permalink_to_line(cx);
20290 let workspace = self.workspace();
20291
20292 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20293 Ok(permalink) => {
20294 cx.update(|_, cx| {
20295 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20296 })
20297 .ok();
20298 }
20299 Err(err) => {
20300 let message = format!("Failed to copy permalink: {err}");
20301
20302 anyhow::Result::<()>::Err(err).log_err();
20303
20304 if let Some(workspace) = workspace {
20305 workspace
20306 .update_in(cx, |workspace, _, cx| {
20307 struct CopyPermalinkToLine;
20308
20309 workspace.show_toast(
20310 Toast::new(
20311 NotificationId::unique::<CopyPermalinkToLine>(),
20312 message,
20313 ),
20314 cx,
20315 )
20316 })
20317 .ok();
20318 }
20319 }
20320 })
20321 .detach();
20322 }
20323
20324 pub fn copy_file_location(
20325 &mut self,
20326 _: &CopyFileLocation,
20327 _: &mut Window,
20328 cx: &mut Context<Self>,
20329 ) {
20330 let selection = self
20331 .selections
20332 .newest::<Point>(&self.display_snapshot(cx))
20333 .start
20334 .row
20335 + 1;
20336 if let Some(file) = self.target_file(cx) {
20337 let path = file.path().display(file.path_style(cx));
20338 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20339 }
20340 }
20341
20342 pub fn open_permalink_to_line(
20343 &mut self,
20344 _: &OpenPermalinkToLine,
20345 window: &mut Window,
20346 cx: &mut Context<Self>,
20347 ) {
20348 let permalink_task = self.get_permalink_to_line(cx);
20349 let workspace = self.workspace();
20350
20351 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20352 Ok(permalink) => {
20353 cx.update(|_, cx| {
20354 cx.open_url(permalink.as_ref());
20355 })
20356 .ok();
20357 }
20358 Err(err) => {
20359 let message = format!("Failed to open permalink: {err}");
20360
20361 anyhow::Result::<()>::Err(err).log_err();
20362
20363 if let Some(workspace) = workspace {
20364 workspace
20365 .update(cx, |workspace, cx| {
20366 struct OpenPermalinkToLine;
20367
20368 workspace.show_toast(
20369 Toast::new(
20370 NotificationId::unique::<OpenPermalinkToLine>(),
20371 message,
20372 ),
20373 cx,
20374 )
20375 })
20376 .ok();
20377 }
20378 }
20379 })
20380 .detach();
20381 }
20382
20383 pub fn insert_uuid_v4(
20384 &mut self,
20385 _: &InsertUuidV4,
20386 window: &mut Window,
20387 cx: &mut Context<Self>,
20388 ) {
20389 self.insert_uuid(UuidVersion::V4, window, cx);
20390 }
20391
20392 pub fn insert_uuid_v7(
20393 &mut self,
20394 _: &InsertUuidV7,
20395 window: &mut Window,
20396 cx: &mut Context<Self>,
20397 ) {
20398 self.insert_uuid(UuidVersion::V7, window, cx);
20399 }
20400
20401 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20403 self.transact(window, cx, |this, window, cx| {
20404 let edits = this
20405 .selections
20406 .all::<Point>(&this.display_snapshot(cx))
20407 .into_iter()
20408 .map(|selection| {
20409 let uuid = match version {
20410 UuidVersion::V4 => uuid::Uuid::new_v4(),
20411 UuidVersion::V7 => uuid::Uuid::now_v7(),
20412 };
20413
20414 (selection.range(), uuid.to_string())
20415 });
20416 this.edit(edits, cx);
20417 this.refresh_edit_prediction(true, false, window, cx);
20418 });
20419 }
20420
20421 pub fn open_selections_in_multibuffer(
20422 &mut self,
20423 _: &OpenSelectionsInMultibuffer,
20424 window: &mut Window,
20425 cx: &mut Context<Self>,
20426 ) {
20427 let multibuffer = self.buffer.read(cx);
20428
20429 let Some(buffer) = multibuffer.as_singleton() else {
20430 return;
20431 };
20432
20433 let Some(workspace) = self.workspace() else {
20434 return;
20435 };
20436
20437 let title = multibuffer.title(cx).to_string();
20438
20439 let locations = self
20440 .selections
20441 .all_anchors(&self.display_snapshot(cx))
20442 .iter()
20443 .map(|selection| {
20444 (
20445 buffer.clone(),
20446 (selection.start.text_anchor..selection.end.text_anchor)
20447 .to_point(buffer.read(cx)),
20448 )
20449 })
20450 .into_group_map();
20451
20452 cx.spawn_in(window, async move |_, cx| {
20453 workspace.update_in(cx, |workspace, window, cx| {
20454 Self::open_locations_in_multibuffer(
20455 workspace,
20456 locations,
20457 format!("Selections for '{title}'"),
20458 false,
20459 MultibufferSelectionMode::All,
20460 window,
20461 cx,
20462 );
20463 })
20464 })
20465 .detach();
20466 }
20467
20468 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20469 /// last highlight added will be used.
20470 ///
20471 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20472 pub fn highlight_rows<T: 'static>(
20473 &mut self,
20474 range: Range<Anchor>,
20475 color: Hsla,
20476 options: RowHighlightOptions,
20477 cx: &mut Context<Self>,
20478 ) {
20479 let snapshot = self.buffer().read(cx).snapshot(cx);
20480 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20481 let ix = row_highlights.binary_search_by(|highlight| {
20482 Ordering::Equal
20483 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20484 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20485 });
20486
20487 if let Err(mut ix) = ix {
20488 let index = post_inc(&mut self.highlight_order);
20489
20490 // If this range intersects with the preceding highlight, then merge it with
20491 // the preceding highlight. Otherwise insert a new highlight.
20492 let mut merged = false;
20493 if ix > 0 {
20494 let prev_highlight = &mut row_highlights[ix - 1];
20495 if prev_highlight
20496 .range
20497 .end
20498 .cmp(&range.start, &snapshot)
20499 .is_ge()
20500 {
20501 ix -= 1;
20502 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20503 prev_highlight.range.end = range.end;
20504 }
20505 merged = true;
20506 prev_highlight.index = index;
20507 prev_highlight.color = color;
20508 prev_highlight.options = options;
20509 }
20510 }
20511
20512 if !merged {
20513 row_highlights.insert(
20514 ix,
20515 RowHighlight {
20516 range,
20517 index,
20518 color,
20519 options,
20520 type_id: TypeId::of::<T>(),
20521 },
20522 );
20523 }
20524
20525 // If any of the following highlights intersect with this one, merge them.
20526 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20527 let highlight = &row_highlights[ix];
20528 if next_highlight
20529 .range
20530 .start
20531 .cmp(&highlight.range.end, &snapshot)
20532 .is_le()
20533 {
20534 if next_highlight
20535 .range
20536 .end
20537 .cmp(&highlight.range.end, &snapshot)
20538 .is_gt()
20539 {
20540 row_highlights[ix].range.end = next_highlight.range.end;
20541 }
20542 row_highlights.remove(ix + 1);
20543 } else {
20544 break;
20545 }
20546 }
20547 }
20548 }
20549
20550 /// Remove any highlighted row ranges of the given type that intersect the
20551 /// given ranges.
20552 pub fn remove_highlighted_rows<T: 'static>(
20553 &mut self,
20554 ranges_to_remove: Vec<Range<Anchor>>,
20555 cx: &mut Context<Self>,
20556 ) {
20557 let snapshot = self.buffer().read(cx).snapshot(cx);
20558 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20559 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20560 row_highlights.retain(|highlight| {
20561 while let Some(range_to_remove) = ranges_to_remove.peek() {
20562 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20563 Ordering::Less | Ordering::Equal => {
20564 ranges_to_remove.next();
20565 }
20566 Ordering::Greater => {
20567 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20568 Ordering::Less | Ordering::Equal => {
20569 return false;
20570 }
20571 Ordering::Greater => break,
20572 }
20573 }
20574 }
20575 }
20576
20577 true
20578 })
20579 }
20580
20581 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20582 pub fn clear_row_highlights<T: 'static>(&mut self) {
20583 self.highlighted_rows.remove(&TypeId::of::<T>());
20584 }
20585
20586 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20587 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20588 self.highlighted_rows
20589 .get(&TypeId::of::<T>())
20590 .map_or(&[] as &[_], |vec| vec.as_slice())
20591 .iter()
20592 .map(|highlight| (highlight.range.clone(), highlight.color))
20593 }
20594
20595 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20596 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20597 /// Allows to ignore certain kinds of highlights.
20598 pub fn highlighted_display_rows(
20599 &self,
20600 window: &mut Window,
20601 cx: &mut App,
20602 ) -> BTreeMap<DisplayRow, LineHighlight> {
20603 let snapshot = self.snapshot(window, cx);
20604 let mut used_highlight_orders = HashMap::default();
20605 self.highlighted_rows
20606 .iter()
20607 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20608 .fold(
20609 BTreeMap::<DisplayRow, LineHighlight>::new(),
20610 |mut unique_rows, highlight| {
20611 let start = highlight.range.start.to_display_point(&snapshot);
20612 let end = highlight.range.end.to_display_point(&snapshot);
20613 let start_row = start.row().0;
20614 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20615 && end.column() == 0
20616 {
20617 end.row().0.saturating_sub(1)
20618 } else {
20619 end.row().0
20620 };
20621 for row in start_row..=end_row {
20622 let used_index =
20623 used_highlight_orders.entry(row).or_insert(highlight.index);
20624 if highlight.index >= *used_index {
20625 *used_index = highlight.index;
20626 unique_rows.insert(
20627 DisplayRow(row),
20628 LineHighlight {
20629 include_gutter: highlight.options.include_gutter,
20630 border: None,
20631 background: highlight.color.into(),
20632 type_id: Some(highlight.type_id),
20633 },
20634 );
20635 }
20636 }
20637 unique_rows
20638 },
20639 )
20640 }
20641
20642 pub fn highlighted_display_row_for_autoscroll(
20643 &self,
20644 snapshot: &DisplaySnapshot,
20645 ) -> Option<DisplayRow> {
20646 self.highlighted_rows
20647 .values()
20648 .flat_map(|highlighted_rows| highlighted_rows.iter())
20649 .filter_map(|highlight| {
20650 if highlight.options.autoscroll {
20651 Some(highlight.range.start.to_display_point(snapshot).row())
20652 } else {
20653 None
20654 }
20655 })
20656 .min()
20657 }
20658
20659 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20660 self.highlight_background::<SearchWithinRange>(
20661 ranges,
20662 |colors| colors.colors().editor_document_highlight_read_background,
20663 cx,
20664 )
20665 }
20666
20667 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20668 self.breadcrumb_header = Some(new_header);
20669 }
20670
20671 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20672 self.clear_background_highlights::<SearchWithinRange>(cx);
20673 }
20674
20675 pub fn highlight_background<T: 'static>(
20676 &mut self,
20677 ranges: &[Range<Anchor>],
20678 color_fetcher: fn(&Theme) -> Hsla,
20679 cx: &mut Context<Self>,
20680 ) {
20681 self.background_highlights.insert(
20682 HighlightKey::Type(TypeId::of::<T>()),
20683 (color_fetcher, Arc::from(ranges)),
20684 );
20685 self.scrollbar_marker_state.dirty = true;
20686 cx.notify();
20687 }
20688
20689 pub fn highlight_background_key<T: 'static>(
20690 &mut self,
20691 key: usize,
20692 ranges: &[Range<Anchor>],
20693 color_fetcher: fn(&Theme) -> Hsla,
20694 cx: &mut Context<Self>,
20695 ) {
20696 self.background_highlights.insert(
20697 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20698 (color_fetcher, Arc::from(ranges)),
20699 );
20700 self.scrollbar_marker_state.dirty = true;
20701 cx.notify();
20702 }
20703
20704 pub fn clear_background_highlights<T: 'static>(
20705 &mut self,
20706 cx: &mut Context<Self>,
20707 ) -> Option<BackgroundHighlight> {
20708 let text_highlights = self
20709 .background_highlights
20710 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20711 if !text_highlights.1.is_empty() {
20712 self.scrollbar_marker_state.dirty = true;
20713 cx.notify();
20714 }
20715 Some(text_highlights)
20716 }
20717
20718 pub fn highlight_gutter<T: 'static>(
20719 &mut self,
20720 ranges: impl Into<Vec<Range<Anchor>>>,
20721 color_fetcher: fn(&App) -> Hsla,
20722 cx: &mut Context<Self>,
20723 ) {
20724 self.gutter_highlights
20725 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20726 cx.notify();
20727 }
20728
20729 pub fn clear_gutter_highlights<T: 'static>(
20730 &mut self,
20731 cx: &mut Context<Self>,
20732 ) -> Option<GutterHighlight> {
20733 cx.notify();
20734 self.gutter_highlights.remove(&TypeId::of::<T>())
20735 }
20736
20737 pub fn insert_gutter_highlight<T: 'static>(
20738 &mut self,
20739 range: Range<Anchor>,
20740 color_fetcher: fn(&App) -> Hsla,
20741 cx: &mut Context<Self>,
20742 ) {
20743 let snapshot = self.buffer().read(cx).snapshot(cx);
20744 let mut highlights = self
20745 .gutter_highlights
20746 .remove(&TypeId::of::<T>())
20747 .map(|(_, highlights)| highlights)
20748 .unwrap_or_default();
20749 let ix = highlights.binary_search_by(|highlight| {
20750 Ordering::Equal
20751 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20752 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20753 });
20754 if let Err(ix) = ix {
20755 highlights.insert(ix, range);
20756 }
20757 self.gutter_highlights
20758 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20759 }
20760
20761 pub fn remove_gutter_highlights<T: 'static>(
20762 &mut self,
20763 ranges_to_remove: Vec<Range<Anchor>>,
20764 cx: &mut Context<Self>,
20765 ) {
20766 let snapshot = self.buffer().read(cx).snapshot(cx);
20767 let Some((color_fetcher, mut gutter_highlights)) =
20768 self.gutter_highlights.remove(&TypeId::of::<T>())
20769 else {
20770 return;
20771 };
20772 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20773 gutter_highlights.retain(|highlight| {
20774 while let Some(range_to_remove) = ranges_to_remove.peek() {
20775 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20776 Ordering::Less | Ordering::Equal => {
20777 ranges_to_remove.next();
20778 }
20779 Ordering::Greater => {
20780 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20781 Ordering::Less | Ordering::Equal => {
20782 return false;
20783 }
20784 Ordering::Greater => break,
20785 }
20786 }
20787 }
20788 }
20789
20790 true
20791 });
20792 self.gutter_highlights
20793 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20794 }
20795
20796 #[cfg(feature = "test-support")]
20797 pub fn all_text_highlights(
20798 &self,
20799 window: &mut Window,
20800 cx: &mut Context<Self>,
20801 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20802 let snapshot = self.snapshot(window, cx);
20803 self.display_map.update(cx, |display_map, _| {
20804 display_map
20805 .all_text_highlights()
20806 .map(|highlight| {
20807 let (style, ranges) = highlight.as_ref();
20808 (
20809 *style,
20810 ranges
20811 .iter()
20812 .map(|range| range.clone().to_display_points(&snapshot))
20813 .collect(),
20814 )
20815 })
20816 .collect()
20817 })
20818 }
20819
20820 #[cfg(feature = "test-support")]
20821 pub fn all_text_background_highlights(
20822 &self,
20823 window: &mut Window,
20824 cx: &mut Context<Self>,
20825 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20826 let snapshot = self.snapshot(window, cx);
20827 let buffer = &snapshot.buffer_snapshot();
20828 let start = buffer.anchor_before(0);
20829 let end = buffer.anchor_after(buffer.len());
20830 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20831 }
20832
20833 #[cfg(any(test, feature = "test-support"))]
20834 pub fn sorted_background_highlights_in_range(
20835 &self,
20836 search_range: Range<Anchor>,
20837 display_snapshot: &DisplaySnapshot,
20838 theme: &Theme,
20839 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20840 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20841 res.sort_by(|a, b| {
20842 a.0.start
20843 .cmp(&b.0.start)
20844 .then_with(|| a.0.end.cmp(&b.0.end))
20845 .then_with(|| a.1.cmp(&b.1))
20846 });
20847 res
20848 }
20849
20850 #[cfg(feature = "test-support")]
20851 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20852 let snapshot = self.buffer().read(cx).snapshot(cx);
20853
20854 let highlights = self
20855 .background_highlights
20856 .get(&HighlightKey::Type(TypeId::of::<
20857 items::BufferSearchHighlights,
20858 >()));
20859
20860 if let Some((_color, ranges)) = highlights {
20861 ranges
20862 .iter()
20863 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20864 .collect_vec()
20865 } else {
20866 vec![]
20867 }
20868 }
20869
20870 fn document_highlights_for_position<'a>(
20871 &'a self,
20872 position: Anchor,
20873 buffer: &'a MultiBufferSnapshot,
20874 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20875 let read_highlights = self
20876 .background_highlights
20877 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20878 .map(|h| &h.1);
20879 let write_highlights = self
20880 .background_highlights
20881 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20882 .map(|h| &h.1);
20883 let left_position = position.bias_left(buffer);
20884 let right_position = position.bias_right(buffer);
20885 read_highlights
20886 .into_iter()
20887 .chain(write_highlights)
20888 .flat_map(move |ranges| {
20889 let start_ix = match ranges.binary_search_by(|probe| {
20890 let cmp = probe.end.cmp(&left_position, buffer);
20891 if cmp.is_ge() {
20892 Ordering::Greater
20893 } else {
20894 Ordering::Less
20895 }
20896 }) {
20897 Ok(i) | Err(i) => i,
20898 };
20899
20900 ranges[start_ix..]
20901 .iter()
20902 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20903 })
20904 }
20905
20906 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20907 self.background_highlights
20908 .get(&HighlightKey::Type(TypeId::of::<T>()))
20909 .is_some_and(|(_, highlights)| !highlights.is_empty())
20910 }
20911
20912 /// Returns all background highlights for a given range.
20913 ///
20914 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20915 pub fn background_highlights_in_range(
20916 &self,
20917 search_range: Range<Anchor>,
20918 display_snapshot: &DisplaySnapshot,
20919 theme: &Theme,
20920 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20921 let mut results = Vec::new();
20922 for (color_fetcher, ranges) in self.background_highlights.values() {
20923 let color = color_fetcher(theme);
20924 let start_ix = match ranges.binary_search_by(|probe| {
20925 let cmp = probe
20926 .end
20927 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20928 if cmp.is_gt() {
20929 Ordering::Greater
20930 } else {
20931 Ordering::Less
20932 }
20933 }) {
20934 Ok(i) | Err(i) => i,
20935 };
20936 for range in &ranges[start_ix..] {
20937 if range
20938 .start
20939 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20940 .is_ge()
20941 {
20942 break;
20943 }
20944
20945 let start = range.start.to_display_point(display_snapshot);
20946 let end = range.end.to_display_point(display_snapshot);
20947 results.push((start..end, color))
20948 }
20949 }
20950 results
20951 }
20952
20953 pub fn gutter_highlights_in_range(
20954 &self,
20955 search_range: Range<Anchor>,
20956 display_snapshot: &DisplaySnapshot,
20957 cx: &App,
20958 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20959 let mut results = Vec::new();
20960 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20961 let color = color_fetcher(cx);
20962 let start_ix = match ranges.binary_search_by(|probe| {
20963 let cmp = probe
20964 .end
20965 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20966 if cmp.is_gt() {
20967 Ordering::Greater
20968 } else {
20969 Ordering::Less
20970 }
20971 }) {
20972 Ok(i) | Err(i) => i,
20973 };
20974 for range in &ranges[start_ix..] {
20975 if range
20976 .start
20977 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20978 .is_ge()
20979 {
20980 break;
20981 }
20982
20983 let start = range.start.to_display_point(display_snapshot);
20984 let end = range.end.to_display_point(display_snapshot);
20985 results.push((start..end, color))
20986 }
20987 }
20988 results
20989 }
20990
20991 /// Get the text ranges corresponding to the redaction query
20992 pub fn redacted_ranges(
20993 &self,
20994 search_range: Range<Anchor>,
20995 display_snapshot: &DisplaySnapshot,
20996 cx: &App,
20997 ) -> Vec<Range<DisplayPoint>> {
20998 display_snapshot
20999 .buffer_snapshot()
21000 .redacted_ranges(search_range, |file| {
21001 if let Some(file) = file {
21002 file.is_private()
21003 && EditorSettings::get(
21004 Some(SettingsLocation {
21005 worktree_id: file.worktree_id(cx),
21006 path: file.path().as_ref(),
21007 }),
21008 cx,
21009 )
21010 .redact_private_values
21011 } else {
21012 false
21013 }
21014 })
21015 .map(|range| {
21016 range.start.to_display_point(display_snapshot)
21017 ..range.end.to_display_point(display_snapshot)
21018 })
21019 .collect()
21020 }
21021
21022 pub fn highlight_text_key<T: 'static>(
21023 &mut self,
21024 key: usize,
21025 ranges: Vec<Range<Anchor>>,
21026 style: HighlightStyle,
21027 cx: &mut Context<Self>,
21028 ) {
21029 self.display_map.update(cx, |map, _| {
21030 map.highlight_text(
21031 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21032 ranges,
21033 style,
21034 );
21035 });
21036 cx.notify();
21037 }
21038
21039 pub fn highlight_text<T: 'static>(
21040 &mut self,
21041 ranges: Vec<Range<Anchor>>,
21042 style: HighlightStyle,
21043 cx: &mut Context<Self>,
21044 ) {
21045 self.display_map.update(cx, |map, _| {
21046 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
21047 });
21048 cx.notify();
21049 }
21050
21051 pub fn text_highlights<'a, T: 'static>(
21052 &'a self,
21053 cx: &'a App,
21054 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21055 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21056 }
21057
21058 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21059 let cleared = self
21060 .display_map
21061 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21062 if cleared {
21063 cx.notify();
21064 }
21065 }
21066
21067 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21068 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21069 && self.focus_handle.is_focused(window)
21070 }
21071
21072 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21073 self.show_cursor_when_unfocused = is_enabled;
21074 cx.notify();
21075 }
21076
21077 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21078 cx.notify();
21079 }
21080
21081 fn on_debug_session_event(
21082 &mut self,
21083 _session: Entity<Session>,
21084 event: &SessionEvent,
21085 cx: &mut Context<Self>,
21086 ) {
21087 if let SessionEvent::InvalidateInlineValue = event {
21088 self.refresh_inline_values(cx);
21089 }
21090 }
21091
21092 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21093 let Some(project) = self.project.clone() else {
21094 return;
21095 };
21096
21097 if !self.inline_value_cache.enabled {
21098 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21099 self.splice_inlays(&inlays, Vec::new(), cx);
21100 return;
21101 }
21102
21103 let current_execution_position = self
21104 .highlighted_rows
21105 .get(&TypeId::of::<ActiveDebugLine>())
21106 .and_then(|lines| lines.last().map(|line| line.range.end));
21107
21108 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21109 let inline_values = editor
21110 .update(cx, |editor, cx| {
21111 let Some(current_execution_position) = current_execution_position else {
21112 return Some(Task::ready(Ok(Vec::new())));
21113 };
21114
21115 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21116 let snapshot = buffer.snapshot(cx);
21117
21118 let excerpt = snapshot.excerpt_containing(
21119 current_execution_position..current_execution_position,
21120 )?;
21121
21122 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21123 })?;
21124
21125 let range =
21126 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21127
21128 project.inline_values(buffer, range, cx)
21129 })
21130 .ok()
21131 .flatten()?
21132 .await
21133 .context("refreshing debugger inlays")
21134 .log_err()?;
21135
21136 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21137
21138 for (buffer_id, inline_value) in inline_values
21139 .into_iter()
21140 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21141 {
21142 buffer_inline_values
21143 .entry(buffer_id)
21144 .or_default()
21145 .push(inline_value);
21146 }
21147
21148 editor
21149 .update(cx, |editor, cx| {
21150 let snapshot = editor.buffer.read(cx).snapshot(cx);
21151 let mut new_inlays = Vec::default();
21152
21153 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21154 let buffer_id = buffer_snapshot.remote_id();
21155 buffer_inline_values
21156 .get(&buffer_id)
21157 .into_iter()
21158 .flatten()
21159 .for_each(|hint| {
21160 let inlay = Inlay::debugger(
21161 post_inc(&mut editor.next_inlay_id),
21162 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21163 hint.text(),
21164 );
21165 if !inlay.text().chars().contains(&'\n') {
21166 new_inlays.push(inlay);
21167 }
21168 });
21169 }
21170
21171 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21172 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21173
21174 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21175 })
21176 .ok()?;
21177 Some(())
21178 });
21179 }
21180
21181 fn on_buffer_event(
21182 &mut self,
21183 multibuffer: &Entity<MultiBuffer>,
21184 event: &multi_buffer::Event,
21185 window: &mut Window,
21186 cx: &mut Context<Self>,
21187 ) {
21188 match event {
21189 multi_buffer::Event::Edited { edited_buffer } => {
21190 self.scrollbar_marker_state.dirty = true;
21191 self.active_indent_guides_state.dirty = true;
21192 self.refresh_active_diagnostics(cx);
21193 self.refresh_code_actions(window, cx);
21194 self.refresh_selected_text_highlights(true, window, cx);
21195 self.refresh_single_line_folds(window, cx);
21196 self.refresh_matching_bracket_highlights(window, cx);
21197 if self.has_active_edit_prediction() {
21198 self.update_visible_edit_prediction(window, cx);
21199 }
21200
21201 if let Some(buffer) = edited_buffer {
21202 if buffer.read(cx).file().is_none() {
21203 cx.emit(EditorEvent::TitleChanged);
21204 }
21205
21206 if self.project.is_some() {
21207 let buffer_id = buffer.read(cx).remote_id();
21208 self.register_buffer(buffer_id, cx);
21209 self.update_lsp_data(Some(buffer_id), window, cx);
21210 self.refresh_inlay_hints(
21211 InlayHintRefreshReason::BufferEdited(buffer_id),
21212 cx,
21213 );
21214 }
21215 }
21216
21217 cx.emit(EditorEvent::BufferEdited);
21218 cx.emit(SearchEvent::MatchesInvalidated);
21219
21220 let Some(project) = &self.project else { return };
21221 let (telemetry, is_via_ssh) = {
21222 let project = project.read(cx);
21223 let telemetry = project.client().telemetry().clone();
21224 let is_via_ssh = project.is_via_remote_server();
21225 (telemetry, is_via_ssh)
21226 };
21227 telemetry.log_edit_event("editor", is_via_ssh);
21228 }
21229 multi_buffer::Event::ExcerptsAdded {
21230 buffer,
21231 predecessor,
21232 excerpts,
21233 } => {
21234 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21235 let buffer_id = buffer.read(cx).remote_id();
21236 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21237 && let Some(project) = &self.project
21238 {
21239 update_uncommitted_diff_for_buffer(
21240 cx.entity(),
21241 project,
21242 [buffer.clone()],
21243 self.buffer.clone(),
21244 cx,
21245 )
21246 .detach();
21247 }
21248 self.update_lsp_data(Some(buffer_id), window, cx);
21249 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21250 cx.emit(EditorEvent::ExcerptsAdded {
21251 buffer: buffer.clone(),
21252 predecessor: *predecessor,
21253 excerpts: excerpts.clone(),
21254 });
21255 }
21256 multi_buffer::Event::ExcerptsRemoved {
21257 ids,
21258 removed_buffer_ids,
21259 } => {
21260 if let Some(inlay_hints) = &mut self.inlay_hints {
21261 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21262 }
21263 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21264 for buffer_id in removed_buffer_ids {
21265 self.registered_buffers.remove(buffer_id);
21266 }
21267 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21268 cx.emit(EditorEvent::ExcerptsRemoved {
21269 ids: ids.clone(),
21270 removed_buffer_ids: removed_buffer_ids.clone(),
21271 });
21272 }
21273 multi_buffer::Event::ExcerptsEdited {
21274 excerpt_ids,
21275 buffer_ids,
21276 } => {
21277 self.display_map.update(cx, |map, cx| {
21278 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21279 });
21280 cx.emit(EditorEvent::ExcerptsEdited {
21281 ids: excerpt_ids.clone(),
21282 });
21283 }
21284 multi_buffer::Event::ExcerptsExpanded { ids } => {
21285 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21286 self.refresh_document_highlights(cx);
21287 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21288 }
21289 multi_buffer::Event::Reparsed(buffer_id) => {
21290 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21291 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21292
21293 cx.emit(EditorEvent::Reparsed(*buffer_id));
21294 }
21295 multi_buffer::Event::DiffHunksToggled => {
21296 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21297 }
21298 multi_buffer::Event::LanguageChanged(buffer_id) => {
21299 self.registered_buffers.remove(&buffer_id);
21300 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21301 cx.emit(EditorEvent::Reparsed(*buffer_id));
21302 cx.notify();
21303 }
21304 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21305 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21306 multi_buffer::Event::FileHandleChanged
21307 | multi_buffer::Event::Reloaded
21308 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21309 multi_buffer::Event::DiagnosticsUpdated => {
21310 self.update_diagnostics_state(window, cx);
21311 }
21312 _ => {}
21313 };
21314 }
21315
21316 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21317 if !self.diagnostics_enabled() {
21318 return;
21319 }
21320 self.refresh_active_diagnostics(cx);
21321 self.refresh_inline_diagnostics(true, window, cx);
21322 self.scrollbar_marker_state.dirty = true;
21323 cx.notify();
21324 }
21325
21326 pub fn start_temporary_diff_override(&mut self) {
21327 self.load_diff_task.take();
21328 self.temporary_diff_override = true;
21329 }
21330
21331 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21332 self.temporary_diff_override = false;
21333 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21334 self.buffer.update(cx, |buffer, cx| {
21335 buffer.set_all_diff_hunks_collapsed(cx);
21336 });
21337
21338 if let Some(project) = self.project.clone() {
21339 self.load_diff_task = Some(
21340 update_uncommitted_diff_for_buffer(
21341 cx.entity(),
21342 &project,
21343 self.buffer.read(cx).all_buffers(),
21344 self.buffer.clone(),
21345 cx,
21346 )
21347 .shared(),
21348 );
21349 }
21350 }
21351
21352 fn on_display_map_changed(
21353 &mut self,
21354 _: Entity<DisplayMap>,
21355 _: &mut Window,
21356 cx: &mut Context<Self>,
21357 ) {
21358 cx.notify();
21359 }
21360
21361 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21362 if self.diagnostics_enabled() {
21363 let new_severity = EditorSettings::get_global(cx)
21364 .diagnostics_max_severity
21365 .unwrap_or(DiagnosticSeverity::Hint);
21366 self.set_max_diagnostics_severity(new_severity, cx);
21367 }
21368 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21369 self.update_edit_prediction_settings(cx);
21370 self.refresh_edit_prediction(true, false, window, cx);
21371 self.refresh_inline_values(cx);
21372 self.refresh_inlay_hints(
21373 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21374 self.selections.newest_anchor().head(),
21375 &self.buffer.read(cx).snapshot(cx),
21376 cx,
21377 )),
21378 cx,
21379 );
21380
21381 let old_cursor_shape = self.cursor_shape;
21382 let old_show_breadcrumbs = self.show_breadcrumbs;
21383
21384 {
21385 let editor_settings = EditorSettings::get_global(cx);
21386 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21387 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21388 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21389 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21390 }
21391
21392 if old_cursor_shape != self.cursor_shape {
21393 cx.emit(EditorEvent::CursorShapeChanged);
21394 }
21395
21396 if old_show_breadcrumbs != self.show_breadcrumbs {
21397 cx.emit(EditorEvent::BreadcrumbsChanged);
21398 }
21399
21400 let project_settings = ProjectSettings::get_global(cx);
21401 self.buffer_serialization = self
21402 .should_serialize_buffer()
21403 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21404
21405 if self.mode.is_full() {
21406 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21407 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21408 if self.show_inline_diagnostics != show_inline_diagnostics {
21409 self.show_inline_diagnostics = show_inline_diagnostics;
21410 self.refresh_inline_diagnostics(false, window, cx);
21411 }
21412
21413 if self.git_blame_inline_enabled != inline_blame_enabled {
21414 self.toggle_git_blame_inline_internal(false, window, cx);
21415 }
21416
21417 let minimap_settings = EditorSettings::get_global(cx).minimap;
21418 if self.minimap_visibility != MinimapVisibility::Disabled {
21419 if self.minimap_visibility.settings_visibility()
21420 != minimap_settings.minimap_enabled()
21421 {
21422 self.set_minimap_visibility(
21423 MinimapVisibility::for_mode(self.mode(), cx),
21424 window,
21425 cx,
21426 );
21427 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21428 minimap_entity.update(cx, |minimap_editor, cx| {
21429 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21430 })
21431 }
21432 }
21433 }
21434
21435 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21436 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21437 }) {
21438 if !inlay_splice.is_empty() {
21439 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21440 }
21441 self.refresh_colors_for_visible_range(None, window, cx);
21442 }
21443
21444 cx.notify();
21445 }
21446
21447 pub fn set_searchable(&mut self, searchable: bool) {
21448 self.searchable = searchable;
21449 }
21450
21451 pub fn searchable(&self) -> bool {
21452 self.searchable
21453 }
21454
21455 pub fn open_excerpts_in_split(
21456 &mut self,
21457 _: &OpenExcerptsSplit,
21458 window: &mut Window,
21459 cx: &mut Context<Self>,
21460 ) {
21461 self.open_excerpts_common(None, true, window, cx)
21462 }
21463
21464 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21465 self.open_excerpts_common(None, false, window, cx)
21466 }
21467
21468 fn open_excerpts_common(
21469 &mut self,
21470 jump_data: Option<JumpData>,
21471 split: bool,
21472 window: &mut Window,
21473 cx: &mut Context<Self>,
21474 ) {
21475 let Some(workspace) = self.workspace() else {
21476 cx.propagate();
21477 return;
21478 };
21479
21480 if self.buffer.read(cx).is_singleton() {
21481 cx.propagate();
21482 return;
21483 }
21484
21485 let mut new_selections_by_buffer = HashMap::default();
21486 match &jump_data {
21487 Some(JumpData::MultiBufferPoint {
21488 excerpt_id,
21489 position,
21490 anchor,
21491 line_offset_from_top,
21492 }) => {
21493 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21494 if let Some(buffer) = multi_buffer_snapshot
21495 .buffer_id_for_excerpt(*excerpt_id)
21496 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21497 {
21498 let buffer_snapshot = buffer.read(cx).snapshot();
21499 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21500 language::ToPoint::to_point(anchor, &buffer_snapshot)
21501 } else {
21502 buffer_snapshot.clip_point(*position, Bias::Left)
21503 };
21504 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21505 new_selections_by_buffer.insert(
21506 buffer,
21507 (
21508 vec![jump_to_offset..jump_to_offset],
21509 Some(*line_offset_from_top),
21510 ),
21511 );
21512 }
21513 }
21514 Some(JumpData::MultiBufferRow {
21515 row,
21516 line_offset_from_top,
21517 }) => {
21518 let point = MultiBufferPoint::new(row.0, 0);
21519 if let Some((buffer, buffer_point, _)) =
21520 self.buffer.read(cx).point_to_buffer_point(point, cx)
21521 {
21522 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21523 new_selections_by_buffer
21524 .entry(buffer)
21525 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21526 .0
21527 .push(buffer_offset..buffer_offset)
21528 }
21529 }
21530 None => {
21531 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21532 let multi_buffer = self.buffer.read(cx);
21533 for selection in selections {
21534 for (snapshot, range, _, anchor) in multi_buffer
21535 .snapshot(cx)
21536 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21537 {
21538 if let Some(anchor) = anchor {
21539 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21540 else {
21541 continue;
21542 };
21543 let offset = text::ToOffset::to_offset(
21544 &anchor.text_anchor,
21545 &buffer_handle.read(cx).snapshot(),
21546 );
21547 let range = offset..offset;
21548 new_selections_by_buffer
21549 .entry(buffer_handle)
21550 .or_insert((Vec::new(), None))
21551 .0
21552 .push(range)
21553 } else {
21554 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21555 else {
21556 continue;
21557 };
21558 new_selections_by_buffer
21559 .entry(buffer_handle)
21560 .or_insert((Vec::new(), None))
21561 .0
21562 .push(range)
21563 }
21564 }
21565 }
21566 }
21567 }
21568
21569 new_selections_by_buffer
21570 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21571
21572 if new_selections_by_buffer.is_empty() {
21573 return;
21574 }
21575
21576 // We defer the pane interaction because we ourselves are a workspace item
21577 // and activating a new item causes the pane to call a method on us reentrantly,
21578 // which panics if we're on the stack.
21579 window.defer(cx, move |window, cx| {
21580 workspace.update(cx, |workspace, cx| {
21581 let pane = if split {
21582 workspace.adjacent_pane(window, cx)
21583 } else {
21584 workspace.active_pane().clone()
21585 };
21586
21587 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21588 let editor = buffer
21589 .read(cx)
21590 .file()
21591 .is_none()
21592 .then(|| {
21593 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21594 // so `workspace.open_project_item` will never find them, always opening a new editor.
21595 // Instead, we try to activate the existing editor in the pane first.
21596 let (editor, pane_item_index) =
21597 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21598 let editor = item.downcast::<Editor>()?;
21599 let singleton_buffer =
21600 editor.read(cx).buffer().read(cx).as_singleton()?;
21601 if singleton_buffer == buffer {
21602 Some((editor, i))
21603 } else {
21604 None
21605 }
21606 })?;
21607 pane.update(cx, |pane, cx| {
21608 pane.activate_item(pane_item_index, true, true, window, cx)
21609 });
21610 Some(editor)
21611 })
21612 .flatten()
21613 .unwrap_or_else(|| {
21614 workspace.open_project_item::<Self>(
21615 pane.clone(),
21616 buffer,
21617 true,
21618 true,
21619 window,
21620 cx,
21621 )
21622 });
21623
21624 editor.update(cx, |editor, cx| {
21625 let autoscroll = match scroll_offset {
21626 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21627 None => Autoscroll::newest(),
21628 };
21629 let nav_history = editor.nav_history.take();
21630 editor.change_selections(
21631 SelectionEffects::scroll(autoscroll),
21632 window,
21633 cx,
21634 |s| {
21635 s.select_ranges(ranges);
21636 },
21637 );
21638 editor.nav_history = nav_history;
21639 });
21640 }
21641 })
21642 });
21643 }
21644
21645 // For now, don't allow opening excerpts in buffers that aren't backed by
21646 // regular project files.
21647 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21648 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21649 }
21650
21651 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21652 let snapshot = self.buffer.read(cx).read(cx);
21653 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21654 Some(
21655 ranges
21656 .iter()
21657 .map(move |range| {
21658 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21659 })
21660 .collect(),
21661 )
21662 }
21663
21664 fn selection_replacement_ranges(
21665 &self,
21666 range: Range<OffsetUtf16>,
21667 cx: &mut App,
21668 ) -> Vec<Range<OffsetUtf16>> {
21669 let selections = self
21670 .selections
21671 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21672 let newest_selection = selections
21673 .iter()
21674 .max_by_key(|selection| selection.id)
21675 .unwrap();
21676 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21677 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21678 let snapshot = self.buffer.read(cx).read(cx);
21679 selections
21680 .into_iter()
21681 .map(|mut selection| {
21682 selection.start.0 =
21683 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21684 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21685 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21686 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21687 })
21688 .collect()
21689 }
21690
21691 fn report_editor_event(
21692 &self,
21693 reported_event: ReportEditorEvent,
21694 file_extension: Option<String>,
21695 cx: &App,
21696 ) {
21697 if cfg!(any(test, feature = "test-support")) {
21698 return;
21699 }
21700
21701 let Some(project) = &self.project else { return };
21702
21703 // If None, we are in a file without an extension
21704 let file = self
21705 .buffer
21706 .read(cx)
21707 .as_singleton()
21708 .and_then(|b| b.read(cx).file());
21709 let file_extension = file_extension.or(file
21710 .as_ref()
21711 .and_then(|file| Path::new(file.file_name(cx)).extension())
21712 .and_then(|e| e.to_str())
21713 .map(|a| a.to_string()));
21714
21715 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21716 .map(|vim_mode| vim_mode.0)
21717 .unwrap_or(false);
21718
21719 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21720 let copilot_enabled = edit_predictions_provider
21721 == language::language_settings::EditPredictionProvider::Copilot;
21722 let copilot_enabled_for_language = self
21723 .buffer
21724 .read(cx)
21725 .language_settings(cx)
21726 .show_edit_predictions;
21727
21728 let project = project.read(cx);
21729 let event_type = reported_event.event_type();
21730
21731 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21732 telemetry::event!(
21733 event_type,
21734 type = if auto_saved {"autosave"} else {"manual"},
21735 file_extension,
21736 vim_mode,
21737 copilot_enabled,
21738 copilot_enabled_for_language,
21739 edit_predictions_provider,
21740 is_via_ssh = project.is_via_remote_server(),
21741 );
21742 } else {
21743 telemetry::event!(
21744 event_type,
21745 file_extension,
21746 vim_mode,
21747 copilot_enabled,
21748 copilot_enabled_for_language,
21749 edit_predictions_provider,
21750 is_via_ssh = project.is_via_remote_server(),
21751 );
21752 };
21753 }
21754
21755 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21756 /// with each line being an array of {text, highlight} objects.
21757 fn copy_highlight_json(
21758 &mut self,
21759 _: &CopyHighlightJson,
21760 window: &mut Window,
21761 cx: &mut Context<Self>,
21762 ) {
21763 #[derive(Serialize)]
21764 struct Chunk<'a> {
21765 text: String,
21766 highlight: Option<&'a str>,
21767 }
21768
21769 let snapshot = self.buffer.read(cx).snapshot(cx);
21770 let range = self
21771 .selected_text_range(false, window, cx)
21772 .and_then(|selection| {
21773 if selection.range.is_empty() {
21774 None
21775 } else {
21776 Some(
21777 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21778 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21779 )
21780 }
21781 })
21782 .unwrap_or_else(|| 0..snapshot.len());
21783
21784 let chunks = snapshot.chunks(range, true);
21785 let mut lines = Vec::new();
21786 let mut line: VecDeque<Chunk> = VecDeque::new();
21787
21788 let Some(style) = self.style.as_ref() else {
21789 return;
21790 };
21791
21792 for chunk in chunks {
21793 let highlight = chunk
21794 .syntax_highlight_id
21795 .and_then(|id| id.name(&style.syntax));
21796 let mut chunk_lines = chunk.text.split('\n').peekable();
21797 while let Some(text) = chunk_lines.next() {
21798 let mut merged_with_last_token = false;
21799 if let Some(last_token) = line.back_mut()
21800 && last_token.highlight == highlight
21801 {
21802 last_token.text.push_str(text);
21803 merged_with_last_token = true;
21804 }
21805
21806 if !merged_with_last_token {
21807 line.push_back(Chunk {
21808 text: text.into(),
21809 highlight,
21810 });
21811 }
21812
21813 if chunk_lines.peek().is_some() {
21814 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21815 line.pop_front();
21816 }
21817 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21818 line.pop_back();
21819 }
21820
21821 lines.push(mem::take(&mut line));
21822 }
21823 }
21824 }
21825
21826 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21827 return;
21828 };
21829 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21830 }
21831
21832 pub fn open_context_menu(
21833 &mut self,
21834 _: &OpenContextMenu,
21835 window: &mut Window,
21836 cx: &mut Context<Self>,
21837 ) {
21838 self.request_autoscroll(Autoscroll::newest(), cx);
21839 let position = self
21840 .selections
21841 .newest_display(&self.display_snapshot(cx))
21842 .start;
21843 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21844 }
21845
21846 pub fn replay_insert_event(
21847 &mut self,
21848 text: &str,
21849 relative_utf16_range: Option<Range<isize>>,
21850 window: &mut Window,
21851 cx: &mut Context<Self>,
21852 ) {
21853 if !self.input_enabled {
21854 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21855 return;
21856 }
21857 if let Some(relative_utf16_range) = relative_utf16_range {
21858 let selections = self
21859 .selections
21860 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21861 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21862 let new_ranges = selections.into_iter().map(|range| {
21863 let start = OffsetUtf16(
21864 range
21865 .head()
21866 .0
21867 .saturating_add_signed(relative_utf16_range.start),
21868 );
21869 let end = OffsetUtf16(
21870 range
21871 .head()
21872 .0
21873 .saturating_add_signed(relative_utf16_range.end),
21874 );
21875 start..end
21876 });
21877 s.select_ranges(new_ranges);
21878 });
21879 }
21880
21881 self.handle_input(text, window, cx);
21882 }
21883
21884 pub fn is_focused(&self, window: &Window) -> bool {
21885 self.focus_handle.is_focused(window)
21886 }
21887
21888 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21889 cx.emit(EditorEvent::Focused);
21890
21891 if let Some(descendant) = self
21892 .last_focused_descendant
21893 .take()
21894 .and_then(|descendant| descendant.upgrade())
21895 {
21896 window.focus(&descendant);
21897 } else {
21898 if let Some(blame) = self.blame.as_ref() {
21899 blame.update(cx, GitBlame::focus)
21900 }
21901
21902 self.blink_manager.update(cx, BlinkManager::enable);
21903 self.show_cursor_names(window, cx);
21904 self.buffer.update(cx, |buffer, cx| {
21905 buffer.finalize_last_transaction(cx);
21906 if self.leader_id.is_none() {
21907 buffer.set_active_selections(
21908 &self.selections.disjoint_anchors_arc(),
21909 self.selections.line_mode(),
21910 self.cursor_shape,
21911 cx,
21912 );
21913 }
21914 });
21915 }
21916 }
21917
21918 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21919 cx.emit(EditorEvent::FocusedIn)
21920 }
21921
21922 fn handle_focus_out(
21923 &mut self,
21924 event: FocusOutEvent,
21925 _window: &mut Window,
21926 cx: &mut Context<Self>,
21927 ) {
21928 if event.blurred != self.focus_handle {
21929 self.last_focused_descendant = Some(event.blurred);
21930 }
21931 self.selection_drag_state = SelectionDragState::None;
21932 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21933 }
21934
21935 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21936 self.blink_manager.update(cx, BlinkManager::disable);
21937 self.buffer
21938 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21939
21940 if let Some(blame) = self.blame.as_ref() {
21941 blame.update(cx, GitBlame::blur)
21942 }
21943 if !self.hover_state.focused(window, cx) {
21944 hide_hover(self, cx);
21945 }
21946 if !self
21947 .context_menu
21948 .borrow()
21949 .as_ref()
21950 .is_some_and(|context_menu| context_menu.focused(window, cx))
21951 {
21952 self.hide_context_menu(window, cx);
21953 }
21954 self.take_active_edit_prediction(cx);
21955 cx.emit(EditorEvent::Blurred);
21956 cx.notify();
21957 }
21958
21959 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21960 let mut pending: String = window
21961 .pending_input_keystrokes()
21962 .into_iter()
21963 .flatten()
21964 .filter_map(|keystroke| {
21965 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21966 keystroke.key_char.clone()
21967 } else {
21968 None
21969 }
21970 })
21971 .collect();
21972
21973 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21974 pending = "".to_string();
21975 }
21976
21977 let existing_pending = self
21978 .text_highlights::<PendingInput>(cx)
21979 .map(|(_, ranges)| ranges.to_vec());
21980 if existing_pending.is_none() && pending.is_empty() {
21981 return;
21982 }
21983 let transaction =
21984 self.transact(window, cx, |this, window, cx| {
21985 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21986 let edits = selections
21987 .iter()
21988 .map(|selection| (selection.end..selection.end, pending.clone()));
21989 this.edit(edits, cx);
21990 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21991 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21992 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21993 }));
21994 });
21995 if let Some(existing_ranges) = existing_pending {
21996 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21997 this.edit(edits, cx);
21998 }
21999 });
22000
22001 let snapshot = self.snapshot(window, cx);
22002 let ranges = self
22003 .selections
22004 .all::<usize>(&snapshot.display_snapshot)
22005 .into_iter()
22006 .map(|selection| {
22007 snapshot.buffer_snapshot().anchor_after(selection.end)
22008 ..snapshot
22009 .buffer_snapshot()
22010 .anchor_before(selection.end + pending.len())
22011 })
22012 .collect();
22013
22014 if pending.is_empty() {
22015 self.clear_highlights::<PendingInput>(cx);
22016 } else {
22017 self.highlight_text::<PendingInput>(
22018 ranges,
22019 HighlightStyle {
22020 underline: Some(UnderlineStyle {
22021 thickness: px(1.),
22022 color: None,
22023 wavy: false,
22024 }),
22025 ..Default::default()
22026 },
22027 cx,
22028 );
22029 }
22030
22031 self.ime_transaction = self.ime_transaction.or(transaction);
22032 if let Some(transaction) = self.ime_transaction {
22033 self.buffer.update(cx, |buffer, cx| {
22034 buffer.group_until_transaction(transaction, cx);
22035 });
22036 }
22037
22038 if self.text_highlights::<PendingInput>(cx).is_none() {
22039 self.ime_transaction.take();
22040 }
22041 }
22042
22043 pub fn register_action_renderer(
22044 &mut self,
22045 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22046 ) -> Subscription {
22047 let id = self.next_editor_action_id.post_inc();
22048 self.editor_actions
22049 .borrow_mut()
22050 .insert(id, Box::new(listener));
22051
22052 let editor_actions = self.editor_actions.clone();
22053 Subscription::new(move || {
22054 editor_actions.borrow_mut().remove(&id);
22055 })
22056 }
22057
22058 pub fn register_action<A: Action>(
22059 &mut self,
22060 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22061 ) -> Subscription {
22062 let id = self.next_editor_action_id.post_inc();
22063 let listener = Arc::new(listener);
22064 self.editor_actions.borrow_mut().insert(
22065 id,
22066 Box::new(move |_, window, _| {
22067 let listener = listener.clone();
22068 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22069 let action = action.downcast_ref().unwrap();
22070 if phase == DispatchPhase::Bubble {
22071 listener(action, window, cx)
22072 }
22073 })
22074 }),
22075 );
22076
22077 let editor_actions = self.editor_actions.clone();
22078 Subscription::new(move || {
22079 editor_actions.borrow_mut().remove(&id);
22080 })
22081 }
22082
22083 pub fn file_header_size(&self) -> u32 {
22084 FILE_HEADER_HEIGHT
22085 }
22086
22087 pub fn restore(
22088 &mut self,
22089 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22090 window: &mut Window,
22091 cx: &mut Context<Self>,
22092 ) {
22093 let workspace = self.workspace();
22094 let project = self.project();
22095 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22096 let mut tasks = Vec::new();
22097 for (buffer_id, changes) in revert_changes {
22098 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22099 buffer.update(cx, |buffer, cx| {
22100 buffer.edit(
22101 changes
22102 .into_iter()
22103 .map(|(range, text)| (range, text.to_string())),
22104 None,
22105 cx,
22106 );
22107 });
22108
22109 if let Some(project) =
22110 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22111 {
22112 project.update(cx, |project, cx| {
22113 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22114 })
22115 }
22116 }
22117 }
22118 tasks
22119 });
22120 cx.spawn_in(window, async move |_, cx| {
22121 for (buffer, task) in save_tasks {
22122 let result = task.await;
22123 if result.is_err() {
22124 let Some(path) = buffer
22125 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22126 .ok()
22127 else {
22128 continue;
22129 };
22130 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22131 let Some(task) = cx
22132 .update_window_entity(workspace, |workspace, window, cx| {
22133 workspace
22134 .open_path_preview(path, None, false, false, false, window, cx)
22135 })
22136 .ok()
22137 else {
22138 continue;
22139 };
22140 task.await.log_err();
22141 }
22142 }
22143 }
22144 })
22145 .detach();
22146 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22147 selections.refresh()
22148 });
22149 }
22150
22151 pub fn to_pixel_point(
22152 &self,
22153 source: multi_buffer::Anchor,
22154 editor_snapshot: &EditorSnapshot,
22155 window: &mut Window,
22156 ) -> Option<gpui::Point<Pixels>> {
22157 let source_point = source.to_display_point(editor_snapshot);
22158 self.display_to_pixel_point(source_point, editor_snapshot, window)
22159 }
22160
22161 pub fn display_to_pixel_point(
22162 &self,
22163 source: DisplayPoint,
22164 editor_snapshot: &EditorSnapshot,
22165 window: &mut Window,
22166 ) -> Option<gpui::Point<Pixels>> {
22167 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22168 let text_layout_details = self.text_layout_details(window);
22169 let scroll_top = text_layout_details
22170 .scroll_anchor
22171 .scroll_position(editor_snapshot)
22172 .y;
22173
22174 if source.row().as_f64() < scroll_top.floor() {
22175 return None;
22176 }
22177 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22178 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22179 Some(gpui::Point::new(source_x, source_y))
22180 }
22181
22182 pub fn has_visible_completions_menu(&self) -> bool {
22183 !self.edit_prediction_preview_is_active()
22184 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22185 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22186 })
22187 }
22188
22189 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22190 if self.mode.is_minimap() {
22191 return;
22192 }
22193 self.addons
22194 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22195 }
22196
22197 pub fn unregister_addon<T: Addon>(&mut self) {
22198 self.addons.remove(&std::any::TypeId::of::<T>());
22199 }
22200
22201 pub fn addon<T: Addon>(&self) -> Option<&T> {
22202 let type_id = std::any::TypeId::of::<T>();
22203 self.addons
22204 .get(&type_id)
22205 .and_then(|item| item.to_any().downcast_ref::<T>())
22206 }
22207
22208 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22209 let type_id = std::any::TypeId::of::<T>();
22210 self.addons
22211 .get_mut(&type_id)
22212 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22213 }
22214
22215 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22216 let text_layout_details = self.text_layout_details(window);
22217 let style = &text_layout_details.editor_style;
22218 let font_id = window.text_system().resolve_font(&style.text.font());
22219 let font_size = style.text.font_size.to_pixels(window.rem_size());
22220 let line_height = style.text.line_height_in_pixels(window.rem_size());
22221 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22222 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22223
22224 CharacterDimensions {
22225 em_width,
22226 em_advance,
22227 line_height,
22228 }
22229 }
22230
22231 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22232 self.load_diff_task.clone()
22233 }
22234
22235 fn read_metadata_from_db(
22236 &mut self,
22237 item_id: u64,
22238 workspace_id: WorkspaceId,
22239 window: &mut Window,
22240 cx: &mut Context<Editor>,
22241 ) {
22242 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22243 && !self.mode.is_minimap()
22244 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22245 {
22246 let buffer_snapshot = OnceCell::new();
22247
22248 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22249 && !folds.is_empty()
22250 {
22251 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22252 self.fold_ranges(
22253 folds
22254 .into_iter()
22255 .map(|(start, end)| {
22256 snapshot.clip_offset(start, Bias::Left)
22257 ..snapshot.clip_offset(end, Bias::Right)
22258 })
22259 .collect(),
22260 false,
22261 window,
22262 cx,
22263 );
22264 }
22265
22266 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22267 && !selections.is_empty()
22268 {
22269 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22270 // skip adding the initial selection to selection history
22271 self.selection_history.mode = SelectionHistoryMode::Skipping;
22272 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22273 s.select_ranges(selections.into_iter().map(|(start, end)| {
22274 snapshot.clip_offset(start, Bias::Left)
22275 ..snapshot.clip_offset(end, Bias::Right)
22276 }));
22277 });
22278 self.selection_history.mode = SelectionHistoryMode::Normal;
22279 };
22280 }
22281
22282 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22283 }
22284
22285 fn update_lsp_data(
22286 &mut self,
22287 for_buffer: Option<BufferId>,
22288 window: &mut Window,
22289 cx: &mut Context<'_, Self>,
22290 ) {
22291 self.pull_diagnostics(for_buffer, window, cx);
22292 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22293 }
22294
22295 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22296 if self.ignore_lsp_data() {
22297 return;
22298 }
22299 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22300 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22301 }
22302 }
22303
22304 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22305 if self.ignore_lsp_data() {
22306 return;
22307 }
22308
22309 if !self.registered_buffers.contains_key(&buffer_id)
22310 && let Some(project) = self.project.as_ref()
22311 {
22312 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22313 project.update(cx, |project, cx| {
22314 self.registered_buffers.insert(
22315 buffer_id,
22316 project.register_buffer_with_language_servers(&buffer, cx),
22317 );
22318 });
22319 } else {
22320 self.registered_buffers.remove(&buffer_id);
22321 }
22322 }
22323 }
22324
22325 fn ignore_lsp_data(&self) -> bool {
22326 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22327 // skip any LSP updates for it.
22328 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22329 }
22330}
22331
22332fn edit_for_markdown_paste<'a>(
22333 buffer: &MultiBufferSnapshot,
22334 range: Range<usize>,
22335 to_insert: &'a str,
22336 url: Option<url::Url>,
22337) -> (Range<usize>, Cow<'a, str>) {
22338 if url.is_none() {
22339 return (range, Cow::Borrowed(to_insert));
22340 };
22341
22342 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22343
22344 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22345 Cow::Borrowed(to_insert)
22346 } else {
22347 Cow::Owned(format!("[{old_text}]({to_insert})"))
22348 };
22349 (range, new_text)
22350}
22351
22352fn process_completion_for_edit(
22353 completion: &Completion,
22354 intent: CompletionIntent,
22355 buffer: &Entity<Buffer>,
22356 cursor_position: &text::Anchor,
22357 cx: &mut Context<Editor>,
22358) -> CompletionEdit {
22359 let buffer = buffer.read(cx);
22360 let buffer_snapshot = buffer.snapshot();
22361 let (snippet, new_text) = if completion.is_snippet() {
22362 let mut snippet_source = completion.new_text.clone();
22363 // Workaround for typescript language server issues so that methods don't expand within
22364 // strings and functions with type expressions. The previous point is used because the query
22365 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22366 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22367 let previous_point = if previous_point.column > 0 {
22368 cursor_position.to_previous_offset(&buffer_snapshot)
22369 } else {
22370 cursor_position.to_offset(&buffer_snapshot)
22371 };
22372 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22373 && scope.prefers_label_for_snippet_in_completion()
22374 && let Some(label) = completion.label()
22375 && matches!(
22376 completion.kind(),
22377 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22378 )
22379 {
22380 snippet_source = label;
22381 }
22382 match Snippet::parse(&snippet_source).log_err() {
22383 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22384 None => (None, completion.new_text.clone()),
22385 }
22386 } else {
22387 (None, completion.new_text.clone())
22388 };
22389
22390 let mut range_to_replace = {
22391 let replace_range = &completion.replace_range;
22392 if let CompletionSource::Lsp {
22393 insert_range: Some(insert_range),
22394 ..
22395 } = &completion.source
22396 {
22397 debug_assert_eq!(
22398 insert_range.start, replace_range.start,
22399 "insert_range and replace_range should start at the same position"
22400 );
22401 debug_assert!(
22402 insert_range
22403 .start
22404 .cmp(cursor_position, &buffer_snapshot)
22405 .is_le(),
22406 "insert_range should start before or at cursor position"
22407 );
22408 debug_assert!(
22409 replace_range
22410 .start
22411 .cmp(cursor_position, &buffer_snapshot)
22412 .is_le(),
22413 "replace_range should start before or at cursor position"
22414 );
22415
22416 let should_replace = match intent {
22417 CompletionIntent::CompleteWithInsert => false,
22418 CompletionIntent::CompleteWithReplace => true,
22419 CompletionIntent::Complete | CompletionIntent::Compose => {
22420 let insert_mode =
22421 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22422 .completions
22423 .lsp_insert_mode;
22424 match insert_mode {
22425 LspInsertMode::Insert => false,
22426 LspInsertMode::Replace => true,
22427 LspInsertMode::ReplaceSubsequence => {
22428 let mut text_to_replace = buffer.chars_for_range(
22429 buffer.anchor_before(replace_range.start)
22430 ..buffer.anchor_after(replace_range.end),
22431 );
22432 let mut current_needle = text_to_replace.next();
22433 for haystack_ch in completion.label.text.chars() {
22434 if let Some(needle_ch) = current_needle
22435 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22436 {
22437 current_needle = text_to_replace.next();
22438 }
22439 }
22440 current_needle.is_none()
22441 }
22442 LspInsertMode::ReplaceSuffix => {
22443 if replace_range
22444 .end
22445 .cmp(cursor_position, &buffer_snapshot)
22446 .is_gt()
22447 {
22448 let range_after_cursor = *cursor_position..replace_range.end;
22449 let text_after_cursor = buffer
22450 .text_for_range(
22451 buffer.anchor_before(range_after_cursor.start)
22452 ..buffer.anchor_after(range_after_cursor.end),
22453 )
22454 .collect::<String>()
22455 .to_ascii_lowercase();
22456 completion
22457 .label
22458 .text
22459 .to_ascii_lowercase()
22460 .ends_with(&text_after_cursor)
22461 } else {
22462 true
22463 }
22464 }
22465 }
22466 }
22467 };
22468
22469 if should_replace {
22470 replace_range.clone()
22471 } else {
22472 insert_range.clone()
22473 }
22474 } else {
22475 replace_range.clone()
22476 }
22477 };
22478
22479 if range_to_replace
22480 .end
22481 .cmp(cursor_position, &buffer_snapshot)
22482 .is_lt()
22483 {
22484 range_to_replace.end = *cursor_position;
22485 }
22486
22487 CompletionEdit {
22488 new_text,
22489 replace_range: range_to_replace.to_offset(buffer),
22490 snippet,
22491 }
22492}
22493
22494struct CompletionEdit {
22495 new_text: String,
22496 replace_range: Range<usize>,
22497 snippet: Option<Snippet>,
22498}
22499
22500fn insert_extra_newline_brackets(
22501 buffer: &MultiBufferSnapshot,
22502 range: Range<usize>,
22503 language: &language::LanguageScope,
22504) -> bool {
22505 let leading_whitespace_len = buffer
22506 .reversed_chars_at(range.start)
22507 .take_while(|c| c.is_whitespace() && *c != '\n')
22508 .map(|c| c.len_utf8())
22509 .sum::<usize>();
22510 let trailing_whitespace_len = buffer
22511 .chars_at(range.end)
22512 .take_while(|c| c.is_whitespace() && *c != '\n')
22513 .map(|c| c.len_utf8())
22514 .sum::<usize>();
22515 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22516
22517 language.brackets().any(|(pair, enabled)| {
22518 let pair_start = pair.start.trim_end();
22519 let pair_end = pair.end.trim_start();
22520
22521 enabled
22522 && pair.newline
22523 && buffer.contains_str_at(range.end, pair_end)
22524 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22525 })
22526}
22527
22528fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22529 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22530 [(buffer, range, _)] => (*buffer, range.clone()),
22531 _ => return false,
22532 };
22533 let pair = {
22534 let mut result: Option<BracketMatch> = None;
22535
22536 for pair in buffer
22537 .all_bracket_ranges(range.clone())
22538 .filter(move |pair| {
22539 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22540 })
22541 {
22542 let len = pair.close_range.end - pair.open_range.start;
22543
22544 if let Some(existing) = &result {
22545 let existing_len = existing.close_range.end - existing.open_range.start;
22546 if len > existing_len {
22547 continue;
22548 }
22549 }
22550
22551 result = Some(pair);
22552 }
22553
22554 result
22555 };
22556 let Some(pair) = pair else {
22557 return false;
22558 };
22559 pair.newline_only
22560 && buffer
22561 .chars_for_range(pair.open_range.end..range.start)
22562 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22563 .all(|c| c.is_whitespace() && c != '\n')
22564}
22565
22566fn update_uncommitted_diff_for_buffer(
22567 editor: Entity<Editor>,
22568 project: &Entity<Project>,
22569 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22570 buffer: Entity<MultiBuffer>,
22571 cx: &mut App,
22572) -> Task<()> {
22573 let mut tasks = Vec::new();
22574 project.update(cx, |project, cx| {
22575 for buffer in buffers {
22576 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22577 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22578 }
22579 }
22580 });
22581 cx.spawn(async move |cx| {
22582 let diffs = future::join_all(tasks).await;
22583 if editor
22584 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22585 .unwrap_or(false)
22586 {
22587 return;
22588 }
22589
22590 buffer
22591 .update(cx, |buffer, cx| {
22592 for diff in diffs.into_iter().flatten() {
22593 buffer.add_diff(diff, cx);
22594 }
22595 })
22596 .ok();
22597 })
22598}
22599
22600fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22601 let tab_size = tab_size.get() as usize;
22602 let mut width = offset;
22603
22604 for ch in text.chars() {
22605 width += if ch == '\t' {
22606 tab_size - (width % tab_size)
22607 } else {
22608 1
22609 };
22610 }
22611
22612 width - offset
22613}
22614
22615#[cfg(test)]
22616mod tests {
22617 use super::*;
22618
22619 #[test]
22620 fn test_string_size_with_expanded_tabs() {
22621 let nz = |val| NonZeroU32::new(val).unwrap();
22622 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22623 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22624 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22625 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22626 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22627 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22628 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22629 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22630 }
22631}
22632
22633/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22634struct WordBreakingTokenizer<'a> {
22635 input: &'a str,
22636}
22637
22638impl<'a> WordBreakingTokenizer<'a> {
22639 fn new(input: &'a str) -> Self {
22640 Self { input }
22641 }
22642}
22643
22644fn is_char_ideographic(ch: char) -> bool {
22645 use unicode_script::Script::*;
22646 use unicode_script::UnicodeScript;
22647 matches!(ch.script(), Han | Tangut | Yi)
22648}
22649
22650fn is_grapheme_ideographic(text: &str) -> bool {
22651 text.chars().any(is_char_ideographic)
22652}
22653
22654fn is_grapheme_whitespace(text: &str) -> bool {
22655 text.chars().any(|x| x.is_whitespace())
22656}
22657
22658fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22659 text.chars()
22660 .next()
22661 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22662}
22663
22664#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22665enum WordBreakToken<'a> {
22666 Word { token: &'a str, grapheme_len: usize },
22667 InlineWhitespace { token: &'a str, grapheme_len: usize },
22668 Newline,
22669}
22670
22671impl<'a> Iterator for WordBreakingTokenizer<'a> {
22672 /// Yields a span, the count of graphemes in the token, and whether it was
22673 /// whitespace. Note that it also breaks at word boundaries.
22674 type Item = WordBreakToken<'a>;
22675
22676 fn next(&mut self) -> Option<Self::Item> {
22677 use unicode_segmentation::UnicodeSegmentation;
22678 if self.input.is_empty() {
22679 return None;
22680 }
22681
22682 let mut iter = self.input.graphemes(true).peekable();
22683 let mut offset = 0;
22684 let mut grapheme_len = 0;
22685 if let Some(first_grapheme) = iter.next() {
22686 let is_newline = first_grapheme == "\n";
22687 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22688 offset += first_grapheme.len();
22689 grapheme_len += 1;
22690 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22691 if let Some(grapheme) = iter.peek().copied()
22692 && should_stay_with_preceding_ideograph(grapheme)
22693 {
22694 offset += grapheme.len();
22695 grapheme_len += 1;
22696 }
22697 } else {
22698 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22699 let mut next_word_bound = words.peek().copied();
22700 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22701 next_word_bound = words.next();
22702 }
22703 while let Some(grapheme) = iter.peek().copied() {
22704 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22705 break;
22706 };
22707 if is_grapheme_whitespace(grapheme) != is_whitespace
22708 || (grapheme == "\n") != is_newline
22709 {
22710 break;
22711 };
22712 offset += grapheme.len();
22713 grapheme_len += 1;
22714 iter.next();
22715 }
22716 }
22717 let token = &self.input[..offset];
22718 self.input = &self.input[offset..];
22719 if token == "\n" {
22720 Some(WordBreakToken::Newline)
22721 } else if is_whitespace {
22722 Some(WordBreakToken::InlineWhitespace {
22723 token,
22724 grapheme_len,
22725 })
22726 } else {
22727 Some(WordBreakToken::Word {
22728 token,
22729 grapheme_len,
22730 })
22731 }
22732 } else {
22733 None
22734 }
22735 }
22736}
22737
22738#[test]
22739fn test_word_breaking_tokenizer() {
22740 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22741 ("", &[]),
22742 (" ", &[whitespace(" ", 2)]),
22743 ("Ʒ", &[word("Ʒ", 1)]),
22744 ("Ǽ", &[word("Ǽ", 1)]),
22745 ("⋑", &[word("⋑", 1)]),
22746 ("⋑⋑", &[word("⋑⋑", 2)]),
22747 (
22748 "原理,进而",
22749 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22750 ),
22751 (
22752 "hello world",
22753 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22754 ),
22755 (
22756 "hello, world",
22757 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22758 ),
22759 (
22760 " hello world",
22761 &[
22762 whitespace(" ", 2),
22763 word("hello", 5),
22764 whitespace(" ", 1),
22765 word("world", 5),
22766 ],
22767 ),
22768 (
22769 "这是什么 \n 钢笔",
22770 &[
22771 word("这", 1),
22772 word("是", 1),
22773 word("什", 1),
22774 word("么", 1),
22775 whitespace(" ", 1),
22776 newline(),
22777 whitespace(" ", 1),
22778 word("钢", 1),
22779 word("笔", 1),
22780 ],
22781 ),
22782 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22783 ];
22784
22785 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22786 WordBreakToken::Word {
22787 token,
22788 grapheme_len,
22789 }
22790 }
22791
22792 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22793 WordBreakToken::InlineWhitespace {
22794 token,
22795 grapheme_len,
22796 }
22797 }
22798
22799 fn newline() -> WordBreakToken<'static> {
22800 WordBreakToken::Newline
22801 }
22802
22803 for (input, result) in tests {
22804 assert_eq!(
22805 WordBreakingTokenizer::new(input)
22806 .collect::<Vec<_>>()
22807 .as_slice(),
22808 *result,
22809 );
22810 }
22811}
22812
22813fn wrap_with_prefix(
22814 first_line_prefix: String,
22815 subsequent_lines_prefix: String,
22816 unwrapped_text: String,
22817 wrap_column: usize,
22818 tab_size: NonZeroU32,
22819 preserve_existing_whitespace: bool,
22820) -> String {
22821 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22822 let subsequent_lines_prefix_len =
22823 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22824 let mut wrapped_text = String::new();
22825 let mut current_line = first_line_prefix;
22826 let mut is_first_line = true;
22827
22828 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22829 let mut current_line_len = first_line_prefix_len;
22830 let mut in_whitespace = false;
22831 for token in tokenizer {
22832 let have_preceding_whitespace = in_whitespace;
22833 match token {
22834 WordBreakToken::Word {
22835 token,
22836 grapheme_len,
22837 } => {
22838 in_whitespace = false;
22839 let current_prefix_len = if is_first_line {
22840 first_line_prefix_len
22841 } else {
22842 subsequent_lines_prefix_len
22843 };
22844 if current_line_len + grapheme_len > wrap_column
22845 && current_line_len != current_prefix_len
22846 {
22847 wrapped_text.push_str(current_line.trim_end());
22848 wrapped_text.push('\n');
22849 is_first_line = false;
22850 current_line = subsequent_lines_prefix.clone();
22851 current_line_len = subsequent_lines_prefix_len;
22852 }
22853 current_line.push_str(token);
22854 current_line_len += grapheme_len;
22855 }
22856 WordBreakToken::InlineWhitespace {
22857 mut token,
22858 mut grapheme_len,
22859 } => {
22860 in_whitespace = true;
22861 if have_preceding_whitespace && !preserve_existing_whitespace {
22862 continue;
22863 }
22864 if !preserve_existing_whitespace {
22865 // Keep a single whitespace grapheme as-is
22866 if let Some(first) =
22867 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22868 {
22869 token = first;
22870 } else {
22871 token = " ";
22872 }
22873 grapheme_len = 1;
22874 }
22875 let current_prefix_len = if is_first_line {
22876 first_line_prefix_len
22877 } else {
22878 subsequent_lines_prefix_len
22879 };
22880 if current_line_len + grapheme_len > wrap_column {
22881 wrapped_text.push_str(current_line.trim_end());
22882 wrapped_text.push('\n');
22883 is_first_line = false;
22884 current_line = subsequent_lines_prefix.clone();
22885 current_line_len = subsequent_lines_prefix_len;
22886 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22887 current_line.push_str(token);
22888 current_line_len += grapheme_len;
22889 }
22890 }
22891 WordBreakToken::Newline => {
22892 in_whitespace = true;
22893 let current_prefix_len = if is_first_line {
22894 first_line_prefix_len
22895 } else {
22896 subsequent_lines_prefix_len
22897 };
22898 if preserve_existing_whitespace {
22899 wrapped_text.push_str(current_line.trim_end());
22900 wrapped_text.push('\n');
22901 is_first_line = false;
22902 current_line = subsequent_lines_prefix.clone();
22903 current_line_len = subsequent_lines_prefix_len;
22904 } else if have_preceding_whitespace {
22905 continue;
22906 } else if current_line_len + 1 > wrap_column
22907 && current_line_len != current_prefix_len
22908 {
22909 wrapped_text.push_str(current_line.trim_end());
22910 wrapped_text.push('\n');
22911 is_first_line = false;
22912 current_line = subsequent_lines_prefix.clone();
22913 current_line_len = subsequent_lines_prefix_len;
22914 } else if current_line_len != current_prefix_len {
22915 current_line.push(' ');
22916 current_line_len += 1;
22917 }
22918 }
22919 }
22920 }
22921
22922 if !current_line.is_empty() {
22923 wrapped_text.push_str(¤t_line);
22924 }
22925 wrapped_text
22926}
22927
22928#[test]
22929fn test_wrap_with_prefix() {
22930 assert_eq!(
22931 wrap_with_prefix(
22932 "# ".to_string(),
22933 "# ".to_string(),
22934 "abcdefg".to_string(),
22935 4,
22936 NonZeroU32::new(4).unwrap(),
22937 false,
22938 ),
22939 "# abcdefg"
22940 );
22941 assert_eq!(
22942 wrap_with_prefix(
22943 "".to_string(),
22944 "".to_string(),
22945 "\thello world".to_string(),
22946 8,
22947 NonZeroU32::new(4).unwrap(),
22948 false,
22949 ),
22950 "hello\nworld"
22951 );
22952 assert_eq!(
22953 wrap_with_prefix(
22954 "// ".to_string(),
22955 "// ".to_string(),
22956 "xx \nyy zz aa bb cc".to_string(),
22957 12,
22958 NonZeroU32::new(4).unwrap(),
22959 false,
22960 ),
22961 "// xx yy zz\n// aa bb cc"
22962 );
22963 assert_eq!(
22964 wrap_with_prefix(
22965 String::new(),
22966 String::new(),
22967 "这是什么 \n 钢笔".to_string(),
22968 3,
22969 NonZeroU32::new(4).unwrap(),
22970 false,
22971 ),
22972 "这是什\n么 钢\n笔"
22973 );
22974 assert_eq!(
22975 wrap_with_prefix(
22976 String::new(),
22977 String::new(),
22978 format!("foo{}bar", '\u{2009}'), // thin space
22979 80,
22980 NonZeroU32::new(4).unwrap(),
22981 false,
22982 ),
22983 format!("foo{}bar", '\u{2009}')
22984 );
22985}
22986
22987pub trait CollaborationHub {
22988 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22989 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22990 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22991}
22992
22993impl CollaborationHub for Entity<Project> {
22994 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22995 self.read(cx).collaborators()
22996 }
22997
22998 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22999 self.read(cx).user_store().read(cx).participant_indices()
23000 }
23001
23002 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23003 let this = self.read(cx);
23004 let user_ids = this.collaborators().values().map(|c| c.user_id);
23005 this.user_store().read(cx).participant_names(user_ids, cx)
23006 }
23007}
23008
23009pub trait SemanticsProvider {
23010 fn hover(
23011 &self,
23012 buffer: &Entity<Buffer>,
23013 position: text::Anchor,
23014 cx: &mut App,
23015 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23016
23017 fn inline_values(
23018 &self,
23019 buffer_handle: Entity<Buffer>,
23020 range: Range<text::Anchor>,
23021 cx: &mut App,
23022 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23023
23024 fn applicable_inlay_chunks(
23025 &self,
23026 buffer: &Entity<Buffer>,
23027 ranges: &[Range<text::Anchor>],
23028 cx: &mut App,
23029 ) -> Vec<Range<BufferRow>>;
23030
23031 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23032
23033 fn inlay_hints(
23034 &self,
23035 invalidate: InvalidationStrategy,
23036 buffer: Entity<Buffer>,
23037 ranges: Vec<Range<text::Anchor>>,
23038 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23039 cx: &mut App,
23040 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23041
23042 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23043
23044 fn document_highlights(
23045 &self,
23046 buffer: &Entity<Buffer>,
23047 position: text::Anchor,
23048 cx: &mut App,
23049 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23050
23051 fn definitions(
23052 &self,
23053 buffer: &Entity<Buffer>,
23054 position: text::Anchor,
23055 kind: GotoDefinitionKind,
23056 cx: &mut App,
23057 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23058
23059 fn range_for_rename(
23060 &self,
23061 buffer: &Entity<Buffer>,
23062 position: text::Anchor,
23063 cx: &mut App,
23064 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23065
23066 fn perform_rename(
23067 &self,
23068 buffer: &Entity<Buffer>,
23069 position: text::Anchor,
23070 new_name: String,
23071 cx: &mut App,
23072 ) -> Option<Task<Result<ProjectTransaction>>>;
23073}
23074
23075pub trait CompletionProvider {
23076 fn completions(
23077 &self,
23078 excerpt_id: ExcerptId,
23079 buffer: &Entity<Buffer>,
23080 buffer_position: text::Anchor,
23081 trigger: CompletionContext,
23082 window: &mut Window,
23083 cx: &mut Context<Editor>,
23084 ) -> Task<Result<Vec<CompletionResponse>>>;
23085
23086 fn resolve_completions(
23087 &self,
23088 _buffer: Entity<Buffer>,
23089 _completion_indices: Vec<usize>,
23090 _completions: Rc<RefCell<Box<[Completion]>>>,
23091 _cx: &mut Context<Editor>,
23092 ) -> Task<Result<bool>> {
23093 Task::ready(Ok(false))
23094 }
23095
23096 fn apply_additional_edits_for_completion(
23097 &self,
23098 _buffer: Entity<Buffer>,
23099 _completions: Rc<RefCell<Box<[Completion]>>>,
23100 _completion_index: usize,
23101 _push_to_history: bool,
23102 _cx: &mut Context<Editor>,
23103 ) -> Task<Result<Option<language::Transaction>>> {
23104 Task::ready(Ok(None))
23105 }
23106
23107 fn is_completion_trigger(
23108 &self,
23109 buffer: &Entity<Buffer>,
23110 position: language::Anchor,
23111 text: &str,
23112 trigger_in_words: bool,
23113 menu_is_open: bool,
23114 cx: &mut Context<Editor>,
23115 ) -> bool;
23116
23117 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23118
23119 fn sort_completions(&self) -> bool {
23120 true
23121 }
23122
23123 fn filter_completions(&self) -> bool {
23124 true
23125 }
23126
23127 fn show_snippets(&self) -> bool {
23128 false
23129 }
23130}
23131
23132pub trait CodeActionProvider {
23133 fn id(&self) -> Arc<str>;
23134
23135 fn code_actions(
23136 &self,
23137 buffer: &Entity<Buffer>,
23138 range: Range<text::Anchor>,
23139 window: &mut Window,
23140 cx: &mut App,
23141 ) -> Task<Result<Vec<CodeAction>>>;
23142
23143 fn apply_code_action(
23144 &self,
23145 buffer_handle: Entity<Buffer>,
23146 action: CodeAction,
23147 excerpt_id: ExcerptId,
23148 push_to_history: bool,
23149 window: &mut Window,
23150 cx: &mut App,
23151 ) -> Task<Result<ProjectTransaction>>;
23152}
23153
23154impl CodeActionProvider for Entity<Project> {
23155 fn id(&self) -> Arc<str> {
23156 "project".into()
23157 }
23158
23159 fn code_actions(
23160 &self,
23161 buffer: &Entity<Buffer>,
23162 range: Range<text::Anchor>,
23163 _window: &mut Window,
23164 cx: &mut App,
23165 ) -> Task<Result<Vec<CodeAction>>> {
23166 self.update(cx, |project, cx| {
23167 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23168 let code_actions = project.code_actions(buffer, range, None, cx);
23169 cx.background_spawn(async move {
23170 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23171 Ok(code_lens_actions
23172 .context("code lens fetch")?
23173 .into_iter()
23174 .flatten()
23175 .chain(
23176 code_actions
23177 .context("code action fetch")?
23178 .into_iter()
23179 .flatten(),
23180 )
23181 .collect())
23182 })
23183 })
23184 }
23185
23186 fn apply_code_action(
23187 &self,
23188 buffer_handle: Entity<Buffer>,
23189 action: CodeAction,
23190 _excerpt_id: ExcerptId,
23191 push_to_history: bool,
23192 _window: &mut Window,
23193 cx: &mut App,
23194 ) -> Task<Result<ProjectTransaction>> {
23195 self.update(cx, |project, cx| {
23196 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23197 })
23198 }
23199}
23200
23201fn snippet_completions(
23202 project: &Project,
23203 buffer: &Entity<Buffer>,
23204 buffer_anchor: text::Anchor,
23205 classifier: CharClassifier,
23206 cx: &mut App,
23207) -> Task<Result<CompletionResponse>> {
23208 let languages = buffer.read(cx).languages_at(buffer_anchor);
23209 let snippet_store = project.snippets().read(cx);
23210
23211 let scopes: Vec<_> = languages
23212 .iter()
23213 .filter_map(|language| {
23214 let language_name = language.lsp_id();
23215 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23216
23217 if snippets.is_empty() {
23218 None
23219 } else {
23220 Some((language.default_scope(), snippets))
23221 }
23222 })
23223 .collect();
23224
23225 if scopes.is_empty() {
23226 return Task::ready(Ok(CompletionResponse {
23227 completions: vec![],
23228 display_options: CompletionDisplayOptions::default(),
23229 is_incomplete: false,
23230 }));
23231 }
23232
23233 let snapshot = buffer.read(cx).text_snapshot();
23234 let executor = cx.background_executor().clone();
23235
23236 cx.background_spawn(async move {
23237 let is_word_char = |c| classifier.is_word(c);
23238
23239 let mut is_incomplete = false;
23240 let mut completions: Vec<Completion> = Vec::new();
23241
23242 const MAX_PREFIX_LEN: usize = 128;
23243 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23244 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23245 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23246
23247 let max_buffer_window: String = snapshot
23248 .text_for_range(window_start..buffer_offset)
23249 .collect();
23250
23251 if max_buffer_window.is_empty() {
23252 return Ok(CompletionResponse {
23253 completions: vec![],
23254 display_options: CompletionDisplayOptions::default(),
23255 is_incomplete: true,
23256 });
23257 }
23258
23259 for (_scope, snippets) in scopes.into_iter() {
23260 // Sort snippets by word count to match longer snippet prefixes first.
23261 let mut sorted_snippet_candidates = snippets
23262 .iter()
23263 .enumerate()
23264 .flat_map(|(snippet_ix, snippet)| {
23265 snippet
23266 .prefix
23267 .iter()
23268 .enumerate()
23269 .map(move |(prefix_ix, prefix)| {
23270 let word_count =
23271 snippet_candidate_suffixes(prefix, is_word_char).count();
23272 ((snippet_ix, prefix_ix), prefix, word_count)
23273 })
23274 })
23275 .collect_vec();
23276 sorted_snippet_candidates
23277 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23278
23279 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23280
23281 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23282 .take(
23283 sorted_snippet_candidates
23284 .first()
23285 .map(|(_, _, word_count)| *word_count)
23286 .unwrap_or_default(),
23287 )
23288 .collect_vec();
23289
23290 const MAX_RESULTS: usize = 100;
23291 // Each match also remembers how many characters from the buffer it consumed
23292 let mut matches: Vec<(StringMatch, usize)> = vec![];
23293
23294 let mut snippet_list_cutoff_index = 0;
23295 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23296 let word_count = buffer_index + 1;
23297 // Increase `snippet_list_cutoff_index` until we have all of the
23298 // snippets with sufficiently many words.
23299 while sorted_snippet_candidates
23300 .get(snippet_list_cutoff_index)
23301 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23302 *snippet_word_count >= word_count
23303 })
23304 {
23305 snippet_list_cutoff_index += 1;
23306 }
23307
23308 // Take only the candidates with at least `word_count` many words
23309 let snippet_candidates_at_word_len =
23310 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23311
23312 let candidates = snippet_candidates_at_word_len
23313 .iter()
23314 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23315 .enumerate() // index in `sorted_snippet_candidates`
23316 // First char must match
23317 .filter(|(_ix, prefix)| {
23318 itertools::equal(
23319 prefix
23320 .chars()
23321 .next()
23322 .into_iter()
23323 .flat_map(|c| c.to_lowercase()),
23324 buffer_window
23325 .chars()
23326 .next()
23327 .into_iter()
23328 .flat_map(|c| c.to_lowercase()),
23329 )
23330 })
23331 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23332 .collect::<Vec<StringMatchCandidate>>();
23333
23334 matches.extend(
23335 fuzzy::match_strings(
23336 &candidates,
23337 &buffer_window,
23338 buffer_window.chars().any(|c| c.is_uppercase()),
23339 true,
23340 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23341 &Default::default(),
23342 executor.clone(),
23343 )
23344 .await
23345 .into_iter()
23346 .map(|string_match| (string_match, buffer_window.len())),
23347 );
23348
23349 if matches.len() >= MAX_RESULTS {
23350 break;
23351 }
23352 }
23353
23354 let to_lsp = |point: &text::Anchor| {
23355 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23356 point_to_lsp(end)
23357 };
23358 let lsp_end = to_lsp(&buffer_anchor);
23359
23360 if matches.len() >= MAX_RESULTS {
23361 is_incomplete = true;
23362 }
23363
23364 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23365 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23366 sorted_snippet_candidates[string_match.candidate_id];
23367 let snippet = &snippets[snippet_index];
23368 let start = buffer_offset - buffer_window_len;
23369 let start = snapshot.anchor_before(start);
23370 let range = start..buffer_anchor;
23371 let lsp_start = to_lsp(&start);
23372 let lsp_range = lsp::Range {
23373 start: lsp_start,
23374 end: lsp_end,
23375 };
23376 Completion {
23377 replace_range: range,
23378 new_text: snippet.body.clone(),
23379 source: CompletionSource::Lsp {
23380 insert_range: None,
23381 server_id: LanguageServerId(usize::MAX),
23382 resolved: true,
23383 lsp_completion: Box::new(lsp::CompletionItem {
23384 label: snippet.prefix.first().unwrap().clone(),
23385 kind: Some(CompletionItemKind::SNIPPET),
23386 label_details: snippet.description.as_ref().map(|description| {
23387 lsp::CompletionItemLabelDetails {
23388 detail: Some(description.clone()),
23389 description: None,
23390 }
23391 }),
23392 insert_text_format: Some(InsertTextFormat::SNIPPET),
23393 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23394 lsp::InsertReplaceEdit {
23395 new_text: snippet.body.clone(),
23396 insert: lsp_range,
23397 replace: lsp_range,
23398 },
23399 )),
23400 filter_text: Some(snippet.body.clone()),
23401 sort_text: Some(char::MAX.to_string()),
23402 ..lsp::CompletionItem::default()
23403 }),
23404 lsp_defaults: None,
23405 },
23406 label: CodeLabel {
23407 text: matching_prefix.clone(),
23408 runs: Vec::new(),
23409 filter_range: 0..matching_prefix.len(),
23410 },
23411 icon_path: None,
23412 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23413 single_line: snippet.name.clone().into(),
23414 plain_text: snippet
23415 .description
23416 .clone()
23417 .map(|description| description.into()),
23418 }),
23419 insert_text_mode: None,
23420 confirm: None,
23421 match_start: Some(start),
23422 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23423 }
23424 }));
23425 }
23426
23427 Ok(CompletionResponse {
23428 completions,
23429 display_options: CompletionDisplayOptions::default(),
23430 is_incomplete,
23431 })
23432 })
23433}
23434
23435impl CompletionProvider for Entity<Project> {
23436 fn completions(
23437 &self,
23438 _excerpt_id: ExcerptId,
23439 buffer: &Entity<Buffer>,
23440 buffer_position: text::Anchor,
23441 options: CompletionContext,
23442 _window: &mut Window,
23443 cx: &mut Context<Editor>,
23444 ) -> Task<Result<Vec<CompletionResponse>>> {
23445 self.update(cx, |project, cx| {
23446 let task = project.completions(buffer, buffer_position, options, cx);
23447 cx.background_spawn(task)
23448 })
23449 }
23450
23451 fn resolve_completions(
23452 &self,
23453 buffer: Entity<Buffer>,
23454 completion_indices: Vec<usize>,
23455 completions: Rc<RefCell<Box<[Completion]>>>,
23456 cx: &mut Context<Editor>,
23457 ) -> Task<Result<bool>> {
23458 self.update(cx, |project, cx| {
23459 project.lsp_store().update(cx, |lsp_store, cx| {
23460 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23461 })
23462 })
23463 }
23464
23465 fn apply_additional_edits_for_completion(
23466 &self,
23467 buffer: Entity<Buffer>,
23468 completions: Rc<RefCell<Box<[Completion]>>>,
23469 completion_index: usize,
23470 push_to_history: bool,
23471 cx: &mut Context<Editor>,
23472 ) -> Task<Result<Option<language::Transaction>>> {
23473 self.update(cx, |project, cx| {
23474 project.lsp_store().update(cx, |lsp_store, cx| {
23475 lsp_store.apply_additional_edits_for_completion(
23476 buffer,
23477 completions,
23478 completion_index,
23479 push_to_history,
23480 cx,
23481 )
23482 })
23483 })
23484 }
23485
23486 fn is_completion_trigger(
23487 &self,
23488 buffer: &Entity<Buffer>,
23489 position: language::Anchor,
23490 text: &str,
23491 trigger_in_words: bool,
23492 menu_is_open: bool,
23493 cx: &mut Context<Editor>,
23494 ) -> bool {
23495 let mut chars = text.chars();
23496 let char = if let Some(char) = chars.next() {
23497 char
23498 } else {
23499 return false;
23500 };
23501 if chars.next().is_some() {
23502 return false;
23503 }
23504
23505 let buffer = buffer.read(cx);
23506 let snapshot = buffer.snapshot();
23507 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23508 return false;
23509 }
23510 let classifier = snapshot
23511 .char_classifier_at(position)
23512 .scope_context(Some(CharScopeContext::Completion));
23513 if trigger_in_words && classifier.is_word(char) {
23514 return true;
23515 }
23516
23517 buffer.completion_triggers().contains(text)
23518 }
23519
23520 fn show_snippets(&self) -> bool {
23521 true
23522 }
23523}
23524
23525impl SemanticsProvider for Entity<Project> {
23526 fn hover(
23527 &self,
23528 buffer: &Entity<Buffer>,
23529 position: text::Anchor,
23530 cx: &mut App,
23531 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23532 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23533 }
23534
23535 fn document_highlights(
23536 &self,
23537 buffer: &Entity<Buffer>,
23538 position: text::Anchor,
23539 cx: &mut App,
23540 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23541 Some(self.update(cx, |project, cx| {
23542 project.document_highlights(buffer, position, cx)
23543 }))
23544 }
23545
23546 fn definitions(
23547 &self,
23548 buffer: &Entity<Buffer>,
23549 position: text::Anchor,
23550 kind: GotoDefinitionKind,
23551 cx: &mut App,
23552 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23553 Some(self.update(cx, |project, cx| match kind {
23554 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23555 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23556 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23557 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23558 }))
23559 }
23560
23561 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23562 self.update(cx, |project, cx| {
23563 if project
23564 .active_debug_session(cx)
23565 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23566 {
23567 return true;
23568 }
23569
23570 buffer.update(cx, |buffer, cx| {
23571 project.any_language_server_supports_inlay_hints(buffer, cx)
23572 })
23573 })
23574 }
23575
23576 fn inline_values(
23577 &self,
23578 buffer_handle: Entity<Buffer>,
23579 range: Range<text::Anchor>,
23580 cx: &mut App,
23581 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23582 self.update(cx, |project, cx| {
23583 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23584
23585 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23586 })
23587 }
23588
23589 fn applicable_inlay_chunks(
23590 &self,
23591 buffer: &Entity<Buffer>,
23592 ranges: &[Range<text::Anchor>],
23593 cx: &mut App,
23594 ) -> Vec<Range<BufferRow>> {
23595 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23596 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23597 })
23598 }
23599
23600 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23601 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23602 lsp_store.invalidate_inlay_hints(for_buffers)
23603 });
23604 }
23605
23606 fn inlay_hints(
23607 &self,
23608 invalidate: InvalidationStrategy,
23609 buffer: Entity<Buffer>,
23610 ranges: Vec<Range<text::Anchor>>,
23611 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23612 cx: &mut App,
23613 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23614 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23615 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23616 }))
23617 }
23618
23619 fn range_for_rename(
23620 &self,
23621 buffer: &Entity<Buffer>,
23622 position: text::Anchor,
23623 cx: &mut App,
23624 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23625 Some(self.update(cx, |project, cx| {
23626 let buffer = buffer.clone();
23627 let task = project.prepare_rename(buffer.clone(), position, cx);
23628 cx.spawn(async move |_, cx| {
23629 Ok(match task.await? {
23630 PrepareRenameResponse::Success(range) => Some(range),
23631 PrepareRenameResponse::InvalidPosition => None,
23632 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23633 // Fallback on using TreeSitter info to determine identifier range
23634 buffer.read_with(cx, |buffer, _| {
23635 let snapshot = buffer.snapshot();
23636 let (range, kind) = snapshot.surrounding_word(position, None);
23637 if kind != Some(CharKind::Word) {
23638 return None;
23639 }
23640 Some(
23641 snapshot.anchor_before(range.start)
23642 ..snapshot.anchor_after(range.end),
23643 )
23644 })?
23645 }
23646 })
23647 })
23648 }))
23649 }
23650
23651 fn perform_rename(
23652 &self,
23653 buffer: &Entity<Buffer>,
23654 position: text::Anchor,
23655 new_name: String,
23656 cx: &mut App,
23657 ) -> Option<Task<Result<ProjectTransaction>>> {
23658 Some(self.update(cx, |project, cx| {
23659 project.perform_rename(buffer.clone(), position, new_name, cx)
23660 }))
23661 }
23662}
23663
23664fn consume_contiguous_rows(
23665 contiguous_row_selections: &mut Vec<Selection<Point>>,
23666 selection: &Selection<Point>,
23667 display_map: &DisplaySnapshot,
23668 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23669) -> (MultiBufferRow, MultiBufferRow) {
23670 contiguous_row_selections.push(selection.clone());
23671 let start_row = starting_row(selection, display_map);
23672 let mut end_row = ending_row(selection, display_map);
23673
23674 while let Some(next_selection) = selections.peek() {
23675 if next_selection.start.row <= end_row.0 {
23676 end_row = ending_row(next_selection, display_map);
23677 contiguous_row_selections.push(selections.next().unwrap().clone());
23678 } else {
23679 break;
23680 }
23681 }
23682 (start_row, end_row)
23683}
23684
23685fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23686 if selection.start.column > 0 {
23687 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23688 } else {
23689 MultiBufferRow(selection.start.row)
23690 }
23691}
23692
23693fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23694 if next_selection.end.column > 0 || next_selection.is_empty() {
23695 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23696 } else {
23697 MultiBufferRow(next_selection.end.row)
23698 }
23699}
23700
23701impl EditorSnapshot {
23702 pub fn remote_selections_in_range<'a>(
23703 &'a self,
23704 range: &'a Range<Anchor>,
23705 collaboration_hub: &dyn CollaborationHub,
23706 cx: &'a App,
23707 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23708 let participant_names = collaboration_hub.user_names(cx);
23709 let participant_indices = collaboration_hub.user_participant_indices(cx);
23710 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23711 let collaborators_by_replica_id = collaborators_by_peer_id
23712 .values()
23713 .map(|collaborator| (collaborator.replica_id, collaborator))
23714 .collect::<HashMap<_, _>>();
23715 self.buffer_snapshot()
23716 .selections_in_range(range, false)
23717 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23718 if replica_id == ReplicaId::AGENT {
23719 Some(RemoteSelection {
23720 replica_id,
23721 selection,
23722 cursor_shape,
23723 line_mode,
23724 collaborator_id: CollaboratorId::Agent,
23725 user_name: Some("Agent".into()),
23726 color: cx.theme().players().agent(),
23727 })
23728 } else {
23729 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23730 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23731 let user_name = participant_names.get(&collaborator.user_id).cloned();
23732 Some(RemoteSelection {
23733 replica_id,
23734 selection,
23735 cursor_shape,
23736 line_mode,
23737 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23738 user_name,
23739 color: if let Some(index) = participant_index {
23740 cx.theme().players().color_for_participant(index.0)
23741 } else {
23742 cx.theme().players().absent()
23743 },
23744 })
23745 }
23746 })
23747 }
23748
23749 pub fn hunks_for_ranges(
23750 &self,
23751 ranges: impl IntoIterator<Item = Range<Point>>,
23752 ) -> Vec<MultiBufferDiffHunk> {
23753 let mut hunks = Vec::new();
23754 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23755 HashMap::default();
23756 for query_range in ranges {
23757 let query_rows =
23758 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23759 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23760 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23761 ) {
23762 // Include deleted hunks that are adjacent to the query range, because
23763 // otherwise they would be missed.
23764 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23765 if hunk.status().is_deleted() {
23766 intersects_range |= hunk.row_range.start == query_rows.end;
23767 intersects_range |= hunk.row_range.end == query_rows.start;
23768 }
23769 if intersects_range {
23770 if !processed_buffer_rows
23771 .entry(hunk.buffer_id)
23772 .or_default()
23773 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23774 {
23775 continue;
23776 }
23777 hunks.push(hunk);
23778 }
23779 }
23780 }
23781
23782 hunks
23783 }
23784
23785 fn display_diff_hunks_for_rows<'a>(
23786 &'a self,
23787 display_rows: Range<DisplayRow>,
23788 folded_buffers: &'a HashSet<BufferId>,
23789 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23790 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23791 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23792
23793 self.buffer_snapshot()
23794 .diff_hunks_in_range(buffer_start..buffer_end)
23795 .filter_map(|hunk| {
23796 if folded_buffers.contains(&hunk.buffer_id) {
23797 return None;
23798 }
23799
23800 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23801 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23802
23803 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23804 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23805
23806 let display_hunk = if hunk_display_start.column() != 0 {
23807 DisplayDiffHunk::Folded {
23808 display_row: hunk_display_start.row(),
23809 }
23810 } else {
23811 let mut end_row = hunk_display_end.row();
23812 if hunk_display_end.column() > 0 {
23813 end_row.0 += 1;
23814 }
23815 let is_created_file = hunk.is_created_file();
23816 DisplayDiffHunk::Unfolded {
23817 status: hunk.status(),
23818 diff_base_byte_range: hunk.diff_base_byte_range,
23819 display_row_range: hunk_display_start.row()..end_row,
23820 multi_buffer_range: Anchor::range_in_buffer(
23821 hunk.excerpt_id,
23822 hunk.buffer_id,
23823 hunk.buffer_range,
23824 ),
23825 is_created_file,
23826 }
23827 };
23828
23829 Some(display_hunk)
23830 })
23831 }
23832
23833 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23834 self.display_snapshot
23835 .buffer_snapshot()
23836 .language_at(position)
23837 }
23838
23839 pub fn is_focused(&self) -> bool {
23840 self.is_focused
23841 }
23842
23843 pub fn placeholder_text(&self) -> Option<String> {
23844 self.placeholder_display_snapshot
23845 .as_ref()
23846 .map(|display_map| display_map.text())
23847 }
23848
23849 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23850 self.scroll_anchor.scroll_position(&self.display_snapshot)
23851 }
23852
23853 fn gutter_dimensions(
23854 &self,
23855 font_id: FontId,
23856 font_size: Pixels,
23857 max_line_number_width: Pixels,
23858 cx: &App,
23859 ) -> Option<GutterDimensions> {
23860 if !self.show_gutter {
23861 return None;
23862 }
23863
23864 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23865 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23866
23867 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23868 matches!(
23869 ProjectSettings::get_global(cx).git.git_gutter,
23870 GitGutterSetting::TrackedFiles
23871 )
23872 });
23873 let gutter_settings = EditorSettings::get_global(cx).gutter;
23874 let show_line_numbers = self
23875 .show_line_numbers
23876 .unwrap_or(gutter_settings.line_numbers);
23877 let line_gutter_width = if show_line_numbers {
23878 // Avoid flicker-like gutter resizes when the line number gains another digit by
23879 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23880 let min_width_for_number_on_gutter =
23881 ch_advance * gutter_settings.min_line_number_digits as f32;
23882 max_line_number_width.max(min_width_for_number_on_gutter)
23883 } else {
23884 0.0.into()
23885 };
23886
23887 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23888 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23889
23890 let git_blame_entries_width =
23891 self.git_blame_gutter_max_author_length
23892 .map(|max_author_length| {
23893 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23894 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23895
23896 /// The number of characters to dedicate to gaps and margins.
23897 const SPACING_WIDTH: usize = 4;
23898
23899 let max_char_count = max_author_length.min(renderer.max_author_length())
23900 + ::git::SHORT_SHA_LENGTH
23901 + MAX_RELATIVE_TIMESTAMP.len()
23902 + SPACING_WIDTH;
23903
23904 ch_advance * max_char_count
23905 });
23906
23907 let is_singleton = self.buffer_snapshot().is_singleton();
23908
23909 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23910 left_padding += if !is_singleton {
23911 ch_width * 4.0
23912 } else if show_runnables || show_breakpoints {
23913 ch_width * 3.0
23914 } else if show_git_gutter && show_line_numbers {
23915 ch_width * 2.0
23916 } else if show_git_gutter || show_line_numbers {
23917 ch_width
23918 } else {
23919 px(0.)
23920 };
23921
23922 let shows_folds = is_singleton && gutter_settings.folds;
23923
23924 let right_padding = if shows_folds && show_line_numbers {
23925 ch_width * 4.0
23926 } else if shows_folds || (!is_singleton && show_line_numbers) {
23927 ch_width * 3.0
23928 } else if show_line_numbers {
23929 ch_width
23930 } else {
23931 px(0.)
23932 };
23933
23934 Some(GutterDimensions {
23935 left_padding,
23936 right_padding,
23937 width: line_gutter_width + left_padding + right_padding,
23938 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23939 git_blame_entries_width,
23940 })
23941 }
23942
23943 pub fn render_crease_toggle(
23944 &self,
23945 buffer_row: MultiBufferRow,
23946 row_contains_cursor: bool,
23947 editor: Entity<Editor>,
23948 window: &mut Window,
23949 cx: &mut App,
23950 ) -> Option<AnyElement> {
23951 let folded = self.is_line_folded(buffer_row);
23952 let mut is_foldable = false;
23953
23954 if let Some(crease) = self
23955 .crease_snapshot
23956 .query_row(buffer_row, self.buffer_snapshot())
23957 {
23958 is_foldable = true;
23959 match crease {
23960 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23961 if let Some(render_toggle) = render_toggle {
23962 let toggle_callback =
23963 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23964 if folded {
23965 editor.update(cx, |editor, cx| {
23966 editor.fold_at(buffer_row, window, cx)
23967 });
23968 } else {
23969 editor.update(cx, |editor, cx| {
23970 editor.unfold_at(buffer_row, window, cx)
23971 });
23972 }
23973 });
23974 return Some((render_toggle)(
23975 buffer_row,
23976 folded,
23977 toggle_callback,
23978 window,
23979 cx,
23980 ));
23981 }
23982 }
23983 }
23984 }
23985
23986 is_foldable |= self.starts_indent(buffer_row);
23987
23988 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23989 Some(
23990 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23991 .toggle_state(folded)
23992 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23993 if folded {
23994 this.unfold_at(buffer_row, window, cx);
23995 } else {
23996 this.fold_at(buffer_row, window, cx);
23997 }
23998 }))
23999 .into_any_element(),
24000 )
24001 } else {
24002 None
24003 }
24004 }
24005
24006 pub fn render_crease_trailer(
24007 &self,
24008 buffer_row: MultiBufferRow,
24009 window: &mut Window,
24010 cx: &mut App,
24011 ) -> Option<AnyElement> {
24012 let folded = self.is_line_folded(buffer_row);
24013 if let Crease::Inline { render_trailer, .. } = self
24014 .crease_snapshot
24015 .query_row(buffer_row, self.buffer_snapshot())?
24016 {
24017 let render_trailer = render_trailer.as_ref()?;
24018 Some(render_trailer(buffer_row, folded, window, cx))
24019 } else {
24020 None
24021 }
24022 }
24023}
24024
24025impl Deref for EditorSnapshot {
24026 type Target = DisplaySnapshot;
24027
24028 fn deref(&self) -> &Self::Target {
24029 &self.display_snapshot
24030 }
24031}
24032
24033#[derive(Clone, Debug, PartialEq, Eq)]
24034pub enum EditorEvent {
24035 InputIgnored {
24036 text: Arc<str>,
24037 },
24038 InputHandled {
24039 utf16_range_to_replace: Option<Range<isize>>,
24040 text: Arc<str>,
24041 },
24042 ExcerptsAdded {
24043 buffer: Entity<Buffer>,
24044 predecessor: ExcerptId,
24045 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24046 },
24047 ExcerptsRemoved {
24048 ids: Vec<ExcerptId>,
24049 removed_buffer_ids: Vec<BufferId>,
24050 },
24051 BufferFoldToggled {
24052 ids: Vec<ExcerptId>,
24053 folded: bool,
24054 },
24055 ExcerptsEdited {
24056 ids: Vec<ExcerptId>,
24057 },
24058 ExcerptsExpanded {
24059 ids: Vec<ExcerptId>,
24060 },
24061 BufferEdited,
24062 Edited {
24063 transaction_id: clock::Lamport,
24064 },
24065 Reparsed(BufferId),
24066 Focused,
24067 FocusedIn,
24068 Blurred,
24069 DirtyChanged,
24070 Saved,
24071 TitleChanged,
24072 SelectionsChanged {
24073 local: bool,
24074 },
24075 ScrollPositionChanged {
24076 local: bool,
24077 autoscroll: bool,
24078 },
24079 TransactionUndone {
24080 transaction_id: clock::Lamport,
24081 },
24082 TransactionBegun {
24083 transaction_id: clock::Lamport,
24084 },
24085 CursorShapeChanged,
24086 BreadcrumbsChanged,
24087 PushedToNavHistory {
24088 anchor: Anchor,
24089 is_deactivate: bool,
24090 },
24091}
24092
24093impl EventEmitter<EditorEvent> for Editor {}
24094
24095impl Focusable for Editor {
24096 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24097 self.focus_handle.clone()
24098 }
24099}
24100
24101impl Render for Editor {
24102 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24103 let settings = ThemeSettings::get_global(cx);
24104
24105 let mut text_style = match self.mode {
24106 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24107 color: cx.theme().colors().editor_foreground,
24108 font_family: settings.ui_font.family.clone(),
24109 font_features: settings.ui_font.features.clone(),
24110 font_fallbacks: settings.ui_font.fallbacks.clone(),
24111 font_size: rems(0.875).into(),
24112 font_weight: settings.ui_font.weight,
24113 line_height: relative(settings.buffer_line_height.value()),
24114 ..Default::default()
24115 },
24116 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24117 color: cx.theme().colors().editor_foreground,
24118 font_family: settings.buffer_font.family.clone(),
24119 font_features: settings.buffer_font.features.clone(),
24120 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24121 font_size: settings.buffer_font_size(cx).into(),
24122 font_weight: settings.buffer_font.weight,
24123 line_height: relative(settings.buffer_line_height.value()),
24124 ..Default::default()
24125 },
24126 };
24127 if let Some(text_style_refinement) = &self.text_style_refinement {
24128 text_style.refine(text_style_refinement)
24129 }
24130
24131 let background = match self.mode {
24132 EditorMode::SingleLine => cx.theme().system().transparent,
24133 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24134 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24135 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24136 };
24137
24138 EditorElement::new(
24139 &cx.entity(),
24140 EditorStyle {
24141 background,
24142 border: cx.theme().colors().border,
24143 local_player: cx.theme().players().local(),
24144 text: text_style,
24145 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24146 syntax: cx.theme().syntax().clone(),
24147 status: cx.theme().status().clone(),
24148 inlay_hints_style: make_inlay_hints_style(cx),
24149 edit_prediction_styles: make_suggestion_styles(cx),
24150 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24151 show_underlines: self.diagnostics_enabled(),
24152 },
24153 )
24154 }
24155}
24156
24157impl EntityInputHandler for Editor {
24158 fn text_for_range(
24159 &mut self,
24160 range_utf16: Range<usize>,
24161 adjusted_range: &mut Option<Range<usize>>,
24162 _: &mut Window,
24163 cx: &mut Context<Self>,
24164 ) -> Option<String> {
24165 let snapshot = self.buffer.read(cx).read(cx);
24166 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24167 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24168 if (start.0..end.0) != range_utf16 {
24169 adjusted_range.replace(start.0..end.0);
24170 }
24171 Some(snapshot.text_for_range(start..end).collect())
24172 }
24173
24174 fn selected_text_range(
24175 &mut self,
24176 ignore_disabled_input: bool,
24177 _: &mut Window,
24178 cx: &mut Context<Self>,
24179 ) -> Option<UTF16Selection> {
24180 // Prevent the IME menu from appearing when holding down an alphabetic key
24181 // while input is disabled.
24182 if !ignore_disabled_input && !self.input_enabled {
24183 return None;
24184 }
24185
24186 let selection = self
24187 .selections
24188 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24189 let range = selection.range();
24190
24191 Some(UTF16Selection {
24192 range: range.start.0..range.end.0,
24193 reversed: selection.reversed,
24194 })
24195 }
24196
24197 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24198 let snapshot = self.buffer.read(cx).read(cx);
24199 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24200 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24201 }
24202
24203 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24204 self.clear_highlights::<InputComposition>(cx);
24205 self.ime_transaction.take();
24206 }
24207
24208 fn replace_text_in_range(
24209 &mut self,
24210 range_utf16: Option<Range<usize>>,
24211 text: &str,
24212 window: &mut Window,
24213 cx: &mut Context<Self>,
24214 ) {
24215 if !self.input_enabled {
24216 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24217 return;
24218 }
24219
24220 self.transact(window, cx, |this, window, cx| {
24221 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24222 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24223 Some(this.selection_replacement_ranges(range_utf16, cx))
24224 } else {
24225 this.marked_text_ranges(cx)
24226 };
24227
24228 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24229 let newest_selection_id = this.selections.newest_anchor().id;
24230 this.selections
24231 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24232 .iter()
24233 .zip(ranges_to_replace.iter())
24234 .find_map(|(selection, range)| {
24235 if selection.id == newest_selection_id {
24236 Some(
24237 (range.start.0 as isize - selection.head().0 as isize)
24238 ..(range.end.0 as isize - selection.head().0 as isize),
24239 )
24240 } else {
24241 None
24242 }
24243 })
24244 });
24245
24246 cx.emit(EditorEvent::InputHandled {
24247 utf16_range_to_replace: range_to_replace,
24248 text: text.into(),
24249 });
24250
24251 if let Some(new_selected_ranges) = new_selected_ranges {
24252 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24253 selections.select_ranges(new_selected_ranges)
24254 });
24255 this.backspace(&Default::default(), window, cx);
24256 }
24257
24258 this.handle_input(text, window, cx);
24259 });
24260
24261 if let Some(transaction) = self.ime_transaction {
24262 self.buffer.update(cx, |buffer, cx| {
24263 buffer.group_until_transaction(transaction, cx);
24264 });
24265 }
24266
24267 self.unmark_text(window, cx);
24268 }
24269
24270 fn replace_and_mark_text_in_range(
24271 &mut self,
24272 range_utf16: Option<Range<usize>>,
24273 text: &str,
24274 new_selected_range_utf16: Option<Range<usize>>,
24275 window: &mut Window,
24276 cx: &mut Context<Self>,
24277 ) {
24278 if !self.input_enabled {
24279 return;
24280 }
24281
24282 let transaction = self.transact(window, cx, |this, window, cx| {
24283 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24284 let snapshot = this.buffer.read(cx).read(cx);
24285 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24286 for marked_range in &mut marked_ranges {
24287 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24288 marked_range.start.0 += relative_range_utf16.start;
24289 marked_range.start =
24290 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24291 marked_range.end =
24292 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24293 }
24294 }
24295 Some(marked_ranges)
24296 } else if let Some(range_utf16) = range_utf16 {
24297 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24298 Some(this.selection_replacement_ranges(range_utf16, cx))
24299 } else {
24300 None
24301 };
24302
24303 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24304 let newest_selection_id = this.selections.newest_anchor().id;
24305 this.selections
24306 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24307 .iter()
24308 .zip(ranges_to_replace.iter())
24309 .find_map(|(selection, range)| {
24310 if selection.id == newest_selection_id {
24311 Some(
24312 (range.start.0 as isize - selection.head().0 as isize)
24313 ..(range.end.0 as isize - selection.head().0 as isize),
24314 )
24315 } else {
24316 None
24317 }
24318 })
24319 });
24320
24321 cx.emit(EditorEvent::InputHandled {
24322 utf16_range_to_replace: range_to_replace,
24323 text: text.into(),
24324 });
24325
24326 if let Some(ranges) = ranges_to_replace {
24327 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24328 s.select_ranges(ranges)
24329 });
24330 }
24331
24332 let marked_ranges = {
24333 let snapshot = this.buffer.read(cx).read(cx);
24334 this.selections
24335 .disjoint_anchors_arc()
24336 .iter()
24337 .map(|selection| {
24338 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24339 })
24340 .collect::<Vec<_>>()
24341 };
24342
24343 if text.is_empty() {
24344 this.unmark_text(window, cx);
24345 } else {
24346 this.highlight_text::<InputComposition>(
24347 marked_ranges.clone(),
24348 HighlightStyle {
24349 underline: Some(UnderlineStyle {
24350 thickness: px(1.),
24351 color: None,
24352 wavy: false,
24353 }),
24354 ..Default::default()
24355 },
24356 cx,
24357 );
24358 }
24359
24360 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24361 let use_autoclose = this.use_autoclose;
24362 let use_auto_surround = this.use_auto_surround;
24363 this.set_use_autoclose(false);
24364 this.set_use_auto_surround(false);
24365 this.handle_input(text, window, cx);
24366 this.set_use_autoclose(use_autoclose);
24367 this.set_use_auto_surround(use_auto_surround);
24368
24369 if let Some(new_selected_range) = new_selected_range_utf16 {
24370 let snapshot = this.buffer.read(cx).read(cx);
24371 let new_selected_ranges = marked_ranges
24372 .into_iter()
24373 .map(|marked_range| {
24374 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24375 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24376 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24377 snapshot.clip_offset_utf16(new_start, Bias::Left)
24378 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24379 })
24380 .collect::<Vec<_>>();
24381
24382 drop(snapshot);
24383 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24384 selections.select_ranges(new_selected_ranges)
24385 });
24386 }
24387 });
24388
24389 self.ime_transaction = self.ime_transaction.or(transaction);
24390 if let Some(transaction) = self.ime_transaction {
24391 self.buffer.update(cx, |buffer, cx| {
24392 buffer.group_until_transaction(transaction, cx);
24393 });
24394 }
24395
24396 if self.text_highlights::<InputComposition>(cx).is_none() {
24397 self.ime_transaction.take();
24398 }
24399 }
24400
24401 fn bounds_for_range(
24402 &mut self,
24403 range_utf16: Range<usize>,
24404 element_bounds: gpui::Bounds<Pixels>,
24405 window: &mut Window,
24406 cx: &mut Context<Self>,
24407 ) -> Option<gpui::Bounds<Pixels>> {
24408 let text_layout_details = self.text_layout_details(window);
24409 let CharacterDimensions {
24410 em_width,
24411 em_advance,
24412 line_height,
24413 } = self.character_dimensions(window);
24414
24415 let snapshot = self.snapshot(window, cx);
24416 let scroll_position = snapshot.scroll_position();
24417 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24418
24419 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24420 let x = Pixels::from(
24421 ScrollOffset::from(
24422 snapshot.x_for_display_point(start, &text_layout_details)
24423 + self.gutter_dimensions.full_width(),
24424 ) - scroll_left,
24425 );
24426 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24427
24428 Some(Bounds {
24429 origin: element_bounds.origin + point(x, y),
24430 size: size(em_width, line_height),
24431 })
24432 }
24433
24434 fn character_index_for_point(
24435 &mut self,
24436 point: gpui::Point<Pixels>,
24437 _window: &mut Window,
24438 _cx: &mut Context<Self>,
24439 ) -> Option<usize> {
24440 let position_map = self.last_position_map.as_ref()?;
24441 if !position_map.text_hitbox.contains(&point) {
24442 return None;
24443 }
24444 let display_point = position_map.point_for_position(point).previous_valid;
24445 let anchor = position_map
24446 .snapshot
24447 .display_point_to_anchor(display_point, Bias::Left);
24448 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24449 Some(utf16_offset.0)
24450 }
24451
24452 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24453 self.input_enabled
24454 }
24455}
24456
24457trait SelectionExt {
24458 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24459 fn spanned_rows(
24460 &self,
24461 include_end_if_at_line_start: bool,
24462 map: &DisplaySnapshot,
24463 ) -> Range<MultiBufferRow>;
24464}
24465
24466impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24467 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24468 let start = self
24469 .start
24470 .to_point(map.buffer_snapshot())
24471 .to_display_point(map);
24472 let end = self
24473 .end
24474 .to_point(map.buffer_snapshot())
24475 .to_display_point(map);
24476 if self.reversed {
24477 end..start
24478 } else {
24479 start..end
24480 }
24481 }
24482
24483 fn spanned_rows(
24484 &self,
24485 include_end_if_at_line_start: bool,
24486 map: &DisplaySnapshot,
24487 ) -> Range<MultiBufferRow> {
24488 let start = self.start.to_point(map.buffer_snapshot());
24489 let mut end = self.end.to_point(map.buffer_snapshot());
24490 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24491 end.row -= 1;
24492 }
24493
24494 let buffer_start = map.prev_line_boundary(start).0;
24495 let buffer_end = map.next_line_boundary(end).0;
24496 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24497 }
24498}
24499
24500impl<T: InvalidationRegion> InvalidationStack<T> {
24501 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24502 where
24503 S: Clone + ToOffset,
24504 {
24505 while let Some(region) = self.last() {
24506 let all_selections_inside_invalidation_ranges =
24507 if selections.len() == region.ranges().len() {
24508 selections
24509 .iter()
24510 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24511 .all(|(selection, invalidation_range)| {
24512 let head = selection.head().to_offset(buffer);
24513 invalidation_range.start <= head && invalidation_range.end >= head
24514 })
24515 } else {
24516 false
24517 };
24518
24519 if all_selections_inside_invalidation_ranges {
24520 break;
24521 } else {
24522 self.pop();
24523 }
24524 }
24525 }
24526}
24527
24528impl<T> Default for InvalidationStack<T> {
24529 fn default() -> Self {
24530 Self(Default::default())
24531 }
24532}
24533
24534impl<T> Deref for InvalidationStack<T> {
24535 type Target = Vec<T>;
24536
24537 fn deref(&self) -> &Self::Target {
24538 &self.0
24539 }
24540}
24541
24542impl<T> DerefMut for InvalidationStack<T> {
24543 fn deref_mut(&mut self) -> &mut Self::Target {
24544 &mut self.0
24545 }
24546}
24547
24548impl InvalidationRegion for SnippetState {
24549 fn ranges(&self) -> &[Range<Anchor>] {
24550 &self.ranges[self.active_index]
24551 }
24552}
24553
24554fn edit_prediction_edit_text(
24555 current_snapshot: &BufferSnapshot,
24556 edits: &[(Range<Anchor>, impl AsRef<str>)],
24557 edit_preview: &EditPreview,
24558 include_deletions: bool,
24559 cx: &App,
24560) -> HighlightedText {
24561 let edits = edits
24562 .iter()
24563 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24564 .collect::<Vec<_>>();
24565
24566 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24567}
24568
24569fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24570 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24571 // Just show the raw edit text with basic styling
24572 let mut text = String::new();
24573 let mut highlights = Vec::new();
24574
24575 let insertion_highlight_style = HighlightStyle {
24576 color: Some(cx.theme().colors().text),
24577 ..Default::default()
24578 };
24579
24580 for (_, edit_text) in edits {
24581 let start_offset = text.len();
24582 text.push_str(edit_text);
24583 let end_offset = text.len();
24584
24585 if start_offset < end_offset {
24586 highlights.push((start_offset..end_offset, insertion_highlight_style));
24587 }
24588 }
24589
24590 HighlightedText {
24591 text: text.into(),
24592 highlights,
24593 }
24594}
24595
24596pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24597 match severity {
24598 lsp::DiagnosticSeverity::ERROR => colors.error,
24599 lsp::DiagnosticSeverity::WARNING => colors.warning,
24600 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24601 lsp::DiagnosticSeverity::HINT => colors.info,
24602 _ => colors.ignored,
24603 }
24604}
24605
24606pub fn styled_runs_for_code_label<'a>(
24607 label: &'a CodeLabel,
24608 syntax_theme: &'a theme::SyntaxTheme,
24609) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24610 let fade_out = HighlightStyle {
24611 fade_out: Some(0.35),
24612 ..Default::default()
24613 };
24614
24615 let mut prev_end = label.filter_range.end;
24616 label
24617 .runs
24618 .iter()
24619 .enumerate()
24620 .flat_map(move |(ix, (range, highlight_id))| {
24621 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24622 style
24623 } else {
24624 return Default::default();
24625 };
24626 let muted_style = style.highlight(fade_out);
24627
24628 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24629 if range.start >= label.filter_range.end {
24630 if range.start > prev_end {
24631 runs.push((prev_end..range.start, fade_out));
24632 }
24633 runs.push((range.clone(), muted_style));
24634 } else if range.end <= label.filter_range.end {
24635 runs.push((range.clone(), style));
24636 } else {
24637 runs.push((range.start..label.filter_range.end, style));
24638 runs.push((label.filter_range.end..range.end, muted_style));
24639 }
24640 prev_end = cmp::max(prev_end, range.end);
24641
24642 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24643 runs.push((prev_end..label.text.len(), fade_out));
24644 }
24645
24646 runs
24647 })
24648}
24649
24650pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24651 let mut prev_index = 0;
24652 let mut prev_codepoint: Option<char> = None;
24653 text.char_indices()
24654 .chain([(text.len(), '\0')])
24655 .filter_map(move |(index, codepoint)| {
24656 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24657 let is_boundary = index == text.len()
24658 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24659 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24660 if is_boundary {
24661 let chunk = &text[prev_index..index];
24662 prev_index = index;
24663 Some(chunk)
24664 } else {
24665 None
24666 }
24667 })
24668}
24669
24670/// Given a string of text immediately before the cursor, iterates over possible
24671/// strings a snippet could match to. More precisely: returns an iterator over
24672/// suffixes of `text` created by splitting at word boundaries (before & after
24673/// every non-word character).
24674///
24675/// Shorter suffixes are returned first.
24676pub(crate) fn snippet_candidate_suffixes(
24677 text: &str,
24678 is_word_char: impl Fn(char) -> bool,
24679) -> impl std::iter::Iterator<Item = &str> {
24680 let mut prev_index = text.len();
24681 let mut prev_codepoint = None;
24682 text.char_indices()
24683 .rev()
24684 .chain([(0, '\0')])
24685 .filter_map(move |(index, codepoint)| {
24686 let prev_index = std::mem::replace(&mut prev_index, index);
24687 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24688 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24689 None
24690 } else {
24691 let chunk = &text[prev_index..]; // go to end of string
24692 Some(chunk)
24693 }
24694 })
24695}
24696
24697pub trait RangeToAnchorExt: Sized {
24698 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24699
24700 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24701 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24702 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24703 }
24704}
24705
24706impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24707 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24708 let start_offset = self.start.to_offset(snapshot);
24709 let end_offset = self.end.to_offset(snapshot);
24710 if start_offset == end_offset {
24711 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24712 } else {
24713 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24714 }
24715 }
24716}
24717
24718pub trait RowExt {
24719 fn as_f64(&self) -> f64;
24720
24721 fn next_row(&self) -> Self;
24722
24723 fn previous_row(&self) -> Self;
24724
24725 fn minus(&self, other: Self) -> u32;
24726}
24727
24728impl RowExt for DisplayRow {
24729 fn as_f64(&self) -> f64 {
24730 self.0 as _
24731 }
24732
24733 fn next_row(&self) -> Self {
24734 Self(self.0 + 1)
24735 }
24736
24737 fn previous_row(&self) -> Self {
24738 Self(self.0.saturating_sub(1))
24739 }
24740
24741 fn minus(&self, other: Self) -> u32 {
24742 self.0 - other.0
24743 }
24744}
24745
24746impl RowExt for MultiBufferRow {
24747 fn as_f64(&self) -> f64 {
24748 self.0 as _
24749 }
24750
24751 fn next_row(&self) -> Self {
24752 Self(self.0 + 1)
24753 }
24754
24755 fn previous_row(&self) -> Self {
24756 Self(self.0.saturating_sub(1))
24757 }
24758
24759 fn minus(&self, other: Self) -> u32 {
24760 self.0 - other.0
24761 }
24762}
24763
24764trait RowRangeExt {
24765 type Row;
24766
24767 fn len(&self) -> usize;
24768
24769 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24770}
24771
24772impl RowRangeExt for Range<MultiBufferRow> {
24773 type Row = MultiBufferRow;
24774
24775 fn len(&self) -> usize {
24776 (self.end.0 - self.start.0) as usize
24777 }
24778
24779 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24780 (self.start.0..self.end.0).map(MultiBufferRow)
24781 }
24782}
24783
24784impl RowRangeExt for Range<DisplayRow> {
24785 type Row = DisplayRow;
24786
24787 fn len(&self) -> usize {
24788 (self.end.0 - self.start.0) as usize
24789 }
24790
24791 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24792 (self.start.0..self.end.0).map(DisplayRow)
24793 }
24794}
24795
24796/// If select range has more than one line, we
24797/// just point the cursor to range.start.
24798fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24799 if range.start.row == range.end.row {
24800 range
24801 } else {
24802 range.start..range.start
24803 }
24804}
24805pub struct KillRing(ClipboardItem);
24806impl Global for KillRing {}
24807
24808const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24809
24810enum BreakpointPromptEditAction {
24811 Log,
24812 Condition,
24813 HitCondition,
24814}
24815
24816struct BreakpointPromptEditor {
24817 pub(crate) prompt: Entity<Editor>,
24818 editor: WeakEntity<Editor>,
24819 breakpoint_anchor: Anchor,
24820 breakpoint: Breakpoint,
24821 edit_action: BreakpointPromptEditAction,
24822 block_ids: HashSet<CustomBlockId>,
24823 editor_margins: Arc<Mutex<EditorMargins>>,
24824 _subscriptions: Vec<Subscription>,
24825}
24826
24827impl BreakpointPromptEditor {
24828 const MAX_LINES: u8 = 4;
24829
24830 fn new(
24831 editor: WeakEntity<Editor>,
24832 breakpoint_anchor: Anchor,
24833 breakpoint: Breakpoint,
24834 edit_action: BreakpointPromptEditAction,
24835 window: &mut Window,
24836 cx: &mut Context<Self>,
24837 ) -> Self {
24838 let base_text = match edit_action {
24839 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24840 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24841 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24842 }
24843 .map(|msg| msg.to_string())
24844 .unwrap_or_default();
24845
24846 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24847 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24848
24849 let prompt = cx.new(|cx| {
24850 let mut prompt = Editor::new(
24851 EditorMode::AutoHeight {
24852 min_lines: 1,
24853 max_lines: Some(Self::MAX_LINES as usize),
24854 },
24855 buffer,
24856 None,
24857 window,
24858 cx,
24859 );
24860 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24861 prompt.set_show_cursor_when_unfocused(false, cx);
24862 prompt.set_placeholder_text(
24863 match edit_action {
24864 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24865 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24866 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24867 },
24868 window,
24869 cx,
24870 );
24871
24872 prompt
24873 });
24874
24875 Self {
24876 prompt,
24877 editor,
24878 breakpoint_anchor,
24879 breakpoint,
24880 edit_action,
24881 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24882 block_ids: Default::default(),
24883 _subscriptions: vec![],
24884 }
24885 }
24886
24887 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24888 self.block_ids.extend(block_ids)
24889 }
24890
24891 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24892 if let Some(editor) = self.editor.upgrade() {
24893 let message = self
24894 .prompt
24895 .read(cx)
24896 .buffer
24897 .read(cx)
24898 .as_singleton()
24899 .expect("A multi buffer in breakpoint prompt isn't possible")
24900 .read(cx)
24901 .as_rope()
24902 .to_string();
24903
24904 editor.update(cx, |editor, cx| {
24905 editor.edit_breakpoint_at_anchor(
24906 self.breakpoint_anchor,
24907 self.breakpoint.clone(),
24908 match self.edit_action {
24909 BreakpointPromptEditAction::Log => {
24910 BreakpointEditAction::EditLogMessage(message.into())
24911 }
24912 BreakpointPromptEditAction::Condition => {
24913 BreakpointEditAction::EditCondition(message.into())
24914 }
24915 BreakpointPromptEditAction::HitCondition => {
24916 BreakpointEditAction::EditHitCondition(message.into())
24917 }
24918 },
24919 cx,
24920 );
24921
24922 editor.remove_blocks(self.block_ids.clone(), None, cx);
24923 cx.focus_self(window);
24924 });
24925 }
24926 }
24927
24928 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24929 self.editor
24930 .update(cx, |editor, cx| {
24931 editor.remove_blocks(self.block_ids.clone(), None, cx);
24932 window.focus(&editor.focus_handle);
24933 })
24934 .log_err();
24935 }
24936
24937 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24938 let settings = ThemeSettings::get_global(cx);
24939 let text_style = TextStyle {
24940 color: if self.prompt.read(cx).read_only(cx) {
24941 cx.theme().colors().text_disabled
24942 } else {
24943 cx.theme().colors().text
24944 },
24945 font_family: settings.buffer_font.family.clone(),
24946 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24947 font_size: settings.buffer_font_size(cx).into(),
24948 font_weight: settings.buffer_font.weight,
24949 line_height: relative(settings.buffer_line_height.value()),
24950 ..Default::default()
24951 };
24952 EditorElement::new(
24953 &self.prompt,
24954 EditorStyle {
24955 background: cx.theme().colors().editor_background,
24956 local_player: cx.theme().players().local(),
24957 text: text_style,
24958 ..Default::default()
24959 },
24960 )
24961 }
24962}
24963
24964impl Render for BreakpointPromptEditor {
24965 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24966 let editor_margins = *self.editor_margins.lock();
24967 let gutter_dimensions = editor_margins.gutter;
24968 h_flex()
24969 .key_context("Editor")
24970 .bg(cx.theme().colors().editor_background)
24971 .border_y_1()
24972 .border_color(cx.theme().status().info_border)
24973 .size_full()
24974 .py(window.line_height() / 2.5)
24975 .on_action(cx.listener(Self::confirm))
24976 .on_action(cx.listener(Self::cancel))
24977 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24978 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24979 }
24980}
24981
24982impl Focusable for BreakpointPromptEditor {
24983 fn focus_handle(&self, cx: &App) -> FocusHandle {
24984 self.prompt.focus_handle(cx)
24985 }
24986}
24987
24988fn all_edits_insertions_or_deletions(
24989 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24990 snapshot: &MultiBufferSnapshot,
24991) -> bool {
24992 let mut all_insertions = true;
24993 let mut all_deletions = true;
24994
24995 for (range, new_text) in edits.iter() {
24996 let range_is_empty = range.to_offset(snapshot).is_empty();
24997 let text_is_empty = new_text.is_empty();
24998
24999 if range_is_empty != text_is_empty {
25000 if range_is_empty {
25001 all_deletions = false;
25002 } else {
25003 all_insertions = false;
25004 }
25005 } else {
25006 return false;
25007 }
25008
25009 if !all_insertions && !all_deletions {
25010 return false;
25011 }
25012 }
25013 all_insertions || all_deletions
25014}
25015
25016struct MissingEditPredictionKeybindingTooltip;
25017
25018impl Render for MissingEditPredictionKeybindingTooltip {
25019 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25020 ui::tooltip_container(cx, |container, cx| {
25021 container
25022 .flex_shrink_0()
25023 .max_w_80()
25024 .min_h(rems_from_px(124.))
25025 .justify_between()
25026 .child(
25027 v_flex()
25028 .flex_1()
25029 .text_ui_sm(cx)
25030 .child(Label::new("Conflict with Accept Keybinding"))
25031 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25032 )
25033 .child(
25034 h_flex()
25035 .pb_1()
25036 .gap_1()
25037 .items_end()
25038 .w_full()
25039 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25040 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25041 }))
25042 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25043 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25044 })),
25045 )
25046 })
25047 }
25048}
25049
25050#[derive(Debug, Clone, Copy, PartialEq)]
25051pub struct LineHighlight {
25052 pub background: Background,
25053 pub border: Option<gpui::Hsla>,
25054 pub include_gutter: bool,
25055 pub type_id: Option<TypeId>,
25056}
25057
25058struct LineManipulationResult {
25059 pub new_text: String,
25060 pub line_count_before: usize,
25061 pub line_count_after: usize,
25062}
25063
25064fn render_diff_hunk_controls(
25065 row: u32,
25066 status: &DiffHunkStatus,
25067 hunk_range: Range<Anchor>,
25068 is_created_file: bool,
25069 line_height: Pixels,
25070 editor: &Entity<Editor>,
25071 _window: &mut Window,
25072 cx: &mut App,
25073) -> AnyElement {
25074 h_flex()
25075 .h(line_height)
25076 .mr_1()
25077 .gap_1()
25078 .px_0p5()
25079 .pb_1()
25080 .border_x_1()
25081 .border_b_1()
25082 .border_color(cx.theme().colors().border_variant)
25083 .rounded_b_lg()
25084 .bg(cx.theme().colors().editor_background)
25085 .gap_1()
25086 .block_mouse_except_scroll()
25087 .shadow_md()
25088 .child(if status.has_secondary_hunk() {
25089 Button::new(("stage", row as u64), "Stage")
25090 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25091 .tooltip({
25092 let focus_handle = editor.focus_handle(cx);
25093 move |_window, cx| {
25094 Tooltip::for_action_in(
25095 "Stage Hunk",
25096 &::git::ToggleStaged,
25097 &focus_handle,
25098 cx,
25099 )
25100 }
25101 })
25102 .on_click({
25103 let editor = editor.clone();
25104 move |_event, _window, cx| {
25105 editor.update(cx, |editor, cx| {
25106 editor.stage_or_unstage_diff_hunks(
25107 true,
25108 vec![hunk_range.start..hunk_range.start],
25109 cx,
25110 );
25111 });
25112 }
25113 })
25114 } else {
25115 Button::new(("unstage", row as u64), "Unstage")
25116 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25117 .tooltip({
25118 let focus_handle = editor.focus_handle(cx);
25119 move |_window, cx| {
25120 Tooltip::for_action_in(
25121 "Unstage Hunk",
25122 &::git::ToggleStaged,
25123 &focus_handle,
25124 cx,
25125 )
25126 }
25127 })
25128 .on_click({
25129 let editor = editor.clone();
25130 move |_event, _window, cx| {
25131 editor.update(cx, |editor, cx| {
25132 editor.stage_or_unstage_diff_hunks(
25133 false,
25134 vec![hunk_range.start..hunk_range.start],
25135 cx,
25136 );
25137 });
25138 }
25139 })
25140 })
25141 .child(
25142 Button::new(("restore", row as u64), "Restore")
25143 .tooltip({
25144 let focus_handle = editor.focus_handle(cx);
25145 move |_window, cx| {
25146 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25147 }
25148 })
25149 .on_click({
25150 let editor = editor.clone();
25151 move |_event, window, cx| {
25152 editor.update(cx, |editor, cx| {
25153 let snapshot = editor.snapshot(window, cx);
25154 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25155 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25156 });
25157 }
25158 })
25159 .disabled(is_created_file),
25160 )
25161 .when(
25162 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25163 |el| {
25164 el.child(
25165 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25166 .shape(IconButtonShape::Square)
25167 .icon_size(IconSize::Small)
25168 // .disabled(!has_multiple_hunks)
25169 .tooltip({
25170 let focus_handle = editor.focus_handle(cx);
25171 move |_window, cx| {
25172 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25173 }
25174 })
25175 .on_click({
25176 let editor = editor.clone();
25177 move |_event, window, cx| {
25178 editor.update(cx, |editor, cx| {
25179 let snapshot = editor.snapshot(window, cx);
25180 let position =
25181 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25182 editor.go_to_hunk_before_or_after_position(
25183 &snapshot,
25184 position,
25185 Direction::Next,
25186 window,
25187 cx,
25188 );
25189 editor.expand_selected_diff_hunks(cx);
25190 });
25191 }
25192 }),
25193 )
25194 .child(
25195 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25196 .shape(IconButtonShape::Square)
25197 .icon_size(IconSize::Small)
25198 // .disabled(!has_multiple_hunks)
25199 .tooltip({
25200 let focus_handle = editor.focus_handle(cx);
25201 move |_window, cx| {
25202 Tooltip::for_action_in(
25203 "Previous Hunk",
25204 &GoToPreviousHunk,
25205 &focus_handle,
25206 cx,
25207 )
25208 }
25209 })
25210 .on_click({
25211 let editor = editor.clone();
25212 move |_event, window, cx| {
25213 editor.update(cx, |editor, cx| {
25214 let snapshot = editor.snapshot(window, cx);
25215 let point =
25216 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25217 editor.go_to_hunk_before_or_after_position(
25218 &snapshot,
25219 point,
25220 Direction::Prev,
25221 window,
25222 cx,
25223 );
25224 editor.expand_selected_diff_hunks(cx);
25225 });
25226 }
25227 }),
25228 )
25229 },
25230 )
25231 .into_any_element()
25232}
25233
25234pub fn multibuffer_context_lines(cx: &App) -> u32 {
25235 EditorSettings::try_get(cx)
25236 .map(|settings| settings.excerpt_context_lines)
25237 .unwrap_or(2)
25238 .min(32)
25239}