1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init(cx: &mut App) {
314 cx.set_global(GlobalBlameRenderer(Arc::new(())));
315
316 workspace::register_project_item::<Editor>(cx);
317 workspace::FollowableViewRegistry::register::<Editor>(cx);
318 workspace::register_serializable_item::<Editor>(cx);
319
320 cx.observe_new(
321 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
322 workspace.register_action(Editor::new_file);
323 workspace.register_action(Editor::new_file_split);
324 workspace.register_action(Editor::new_file_vertical);
325 workspace.register_action(Editor::new_file_horizontal);
326 workspace.register_action(Editor::cancel_language_server_work);
327 workspace.register_action(Editor::toggle_focus);
328 },
329 )
330 .detach();
331
332 cx.on_action(move |_: &workspace::NewFile, cx| {
333 let app_state = workspace::AppState::global(cx);
334 if let Some(app_state) = app_state.upgrade() {
335 workspace::open_new(
336 Default::default(),
337 app_state,
338 cx,
339 |workspace, window, cx| {
340 Editor::new_file(workspace, &Default::default(), window, cx)
341 },
342 )
343 .detach();
344 }
345 });
346 cx.on_action(move |_: &workspace::NewWindow, cx| {
347 let app_state = workspace::AppState::global(cx);
348 if let Some(app_state) = app_state.upgrade() {
349 workspace::open_new(
350 Default::default(),
351 app_state,
352 cx,
353 |workspace, window, cx| {
354 cx.activate(true);
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 });
361}
362
363pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
364 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
365}
366
367pub trait DiagnosticRenderer {
368 fn render_group(
369 &self,
370 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
371 buffer_id: BufferId,
372 snapshot: EditorSnapshot,
373 editor: WeakEntity<Editor>,
374 cx: &mut App,
375 ) -> Vec<BlockProperties<Anchor>>;
376
377 fn render_hover(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 range: Range<Point>,
381 buffer_id: BufferId,
382 cx: &mut App,
383 ) -> Option<Entity<markdown::Markdown>>;
384
385 fn open_link(
386 &self,
387 editor: &mut Editor,
388 link: SharedString,
389 window: &mut Window,
390 cx: &mut Context<Editor>,
391 );
392}
393
394pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
395
396impl GlobalDiagnosticRenderer {
397 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
398 cx.try_global::<Self>().map(|g| g.0.clone())
399 }
400}
401
402impl gpui::Global for GlobalDiagnosticRenderer {}
403pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
404 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
405}
406
407pub struct SearchWithinRange;
408
409trait InvalidationRegion {
410 fn ranges(&self) -> &[Range<Anchor>];
411}
412
413#[derive(Clone, Debug, PartialEq)]
414pub enum SelectPhase {
415 Begin {
416 position: DisplayPoint,
417 add: bool,
418 click_count: usize,
419 },
420 BeginColumnar {
421 position: DisplayPoint,
422 reset: bool,
423 mode: ColumnarMode,
424 goal_column: u32,
425 },
426 Extend {
427 position: DisplayPoint,
428 click_count: usize,
429 },
430 Update {
431 position: DisplayPoint,
432 goal_column: u32,
433 scroll_delta: gpui::Point<f32>,
434 },
435 End,
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum ColumnarMode {
440 FromMouse,
441 FromSelection,
442}
443
444#[derive(Clone, Debug)]
445pub enum SelectMode {
446 Character,
447 Word(Range<Anchor>),
448 Line(Range<Anchor>),
449 All,
450}
451
452#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
453pub enum SizingBehavior {
454 /// The editor will layout itself using `size_full` and will include the vertical
455 /// scroll margin as requested by user settings.
456 #[default]
457 Default,
458 /// The editor will layout itself using `size_full`, but will not have any
459 /// vertical overscroll.
460 ExcludeOverscrollMargin,
461 /// The editor will request a vertical size according to its content and will be
462 /// layouted without a vertical scroll margin.
463 SizeByContent,
464}
465
466#[derive(Clone, PartialEq, Eq, Debug)]
467pub enum EditorMode {
468 SingleLine,
469 AutoHeight {
470 min_lines: usize,
471 max_lines: Option<usize>,
472 },
473 Full {
474 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
475 scale_ui_elements_with_buffer_font_size: bool,
476 /// When set to `true`, the editor will render a background for the active line.
477 show_active_line_background: bool,
478 /// Determines the sizing behavior for this editor
479 sizing_behavior: SizingBehavior,
480 },
481 Minimap {
482 parent: WeakEntity<Editor>,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sizing_behavior: SizingBehavior::Default,
492 }
493 }
494
495 #[inline]
496 pub fn is_full(&self) -> bool {
497 matches!(self, Self::Full { .. })
498 }
499
500 #[inline]
501 pub fn is_single_line(&self) -> bool {
502 matches!(self, Self::SingleLine { .. })
503 }
504
505 #[inline]
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub border: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub edit_prediction_styles: EditPredictionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 border: Hsla::default(),
548 local_player: PlayerColor::default(),
549 text: TextStyle::default(),
550 scrollbar_width: Pixels::default(),
551 syntax: Default::default(),
552 // HACK: Status colors don't have a real default.
553 // We should look into removing the status colors from the editor
554 // style and retrieve them directly from the theme.
555 status: StatusColors::dark(),
556 inlay_hints_style: HighlightStyle::default(),
557 edit_prediction_styles: EditPredictionStyles {
558 insertion: HighlightStyle::default(),
559 whitespace: HighlightStyle::default(),
560 },
561 unnecessary_code_fade: Default::default(),
562 show_underlines: true,
563 }
564 }
565}
566
567pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
568 let show_background = language_settings::language_settings(None, None, cx)
569 .inlay_hints
570 .show_background;
571
572 let mut style = cx.theme().syntax().get("hint");
573
574 if style.color.is_none() {
575 style.color = Some(cx.theme().status().hint);
576 }
577
578 if !show_background {
579 style.background_color = None;
580 return style;
581 }
582
583 if style.background_color.is_none() {
584 style.background_color = Some(cx.theme().status().hint_background);
585 }
586
587 style
588}
589
590pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
591 EditPredictionStyles {
592 insertion: HighlightStyle {
593 color: Some(cx.theme().status().predictive),
594 ..HighlightStyle::default()
595 },
596 whitespace: HighlightStyle {
597 background_color: Some(cx.theme().status().created_background),
598 ..HighlightStyle::default()
599 },
600 }
601}
602
603type CompletionId = usize;
604
605pub(crate) enum EditDisplayMode {
606 TabAccept,
607 DiffPopover,
608 Inline,
609}
610
611enum EditPrediction {
612 Edit {
613 edits: Vec<(Range<Anchor>, Arc<str>)>,
614 edit_preview: Option<EditPreview>,
615 display_mode: EditDisplayMode,
616 snapshot: BufferSnapshot,
617 },
618 /// Move to a specific location in the active editor
619 MoveWithin {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623 /// Move to a specific location in a different editor (not the active one)
624 MoveOutside {
625 target: language::Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct EditPredictionState {
631 inlay_ids: Vec<InlayId>,
632 completion: EditPrediction,
633 completion_id: Option<SharedString>,
634 invalidation_range: Option<Range<Anchor>>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum EditPredictionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuEditPredictionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 matches!(*self, Self::Disabled)
773 }
774
775 fn settings_visibility(&self) -> bool {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => setting_configuration,
781 _ => false,
782 }
783 }
784
785 fn visible(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 toggle_override,
790 } => setting_configuration ^ toggle_override,
791 _ => false,
792 }
793 }
794
795 fn toggle_visibility(&self) -> Self {
796 match *self {
797 Self::Enabled {
798 toggle_override,
799 setting_configuration,
800 } => Self::Enabled {
801 setting_configuration,
802 toggle_override: !toggle_override,
803 },
804 Self::Disabled => Self::Disabled,
805 }
806 }
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq)]
810pub enum BufferSerialization {
811 All,
812 NonDirtyBuffers,
813}
814
815impl BufferSerialization {
816 fn new(restore_unsaved_buffers: bool) -> Self {
817 if restore_unsaved_buffers {
818 Self::All
819 } else {
820 Self::NonDirtyBuffers
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859/// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
873 None
874 }
875
876 fn to_any(&self) -> &dyn std::any::Any;
877
878 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
879 None
880 }
881}
882
883struct ChangeLocation {
884 current: Option<Vec<Anchor>>,
885 original: Vec<Anchor>,
886}
887impl ChangeLocation {
888 fn locations(&self) -> &[Anchor] {
889 self.current.as_ref().unwrap_or(&self.original)
890 }
891}
892
893/// A set of caret positions, registered when the editor was edited.
894pub struct ChangeList {
895 changes: Vec<ChangeLocation>,
896 /// Currently "selected" change.
897 position: Option<usize>,
898}
899
900impl ChangeList {
901 pub fn new() -> Self {
902 Self {
903 changes: Vec::new(),
904 position: None,
905 }
906 }
907
908 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
909 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
910 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
911 if self.changes.is_empty() {
912 return None;
913 }
914
915 let prev = self.position.unwrap_or(self.changes.len());
916 let next = if direction == Direction::Prev {
917 prev.saturating_sub(count)
918 } else {
919 (prev + count).min(self.changes.len() - 1)
920 };
921 self.position = Some(next);
922 self.changes.get(next).map(|change| change.locations())
923 }
924
925 /// Adds a new change to the list, resetting the change list position.
926 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
927 self.position.take();
928 if let Some(last) = self.changes.last_mut()
929 && group
930 {
931 last.current = Some(new_positions)
932 } else {
933 self.changes.push(ChangeLocation {
934 original: new_positions,
935 current: None,
936 });
937 }
938 }
939
940 pub fn last(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.locations())
942 }
943
944 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.original.as_slice())
946 }
947
948 pub fn invert_last_group(&mut self) {
949 if let Some(last) = self.changes.last_mut()
950 && let Some(current) = last.current.as_mut()
951 {
952 mem::swap(&mut last.original, current);
953 }
954 }
955}
956
957#[derive(Clone)]
958struct InlineBlamePopoverState {
959 scroll_handle: ScrollHandle,
960 commit_message: Option<ParsedCommitMessage>,
961 markdown: Entity<Markdown>,
962}
963
964struct InlineBlamePopover {
965 position: gpui::Point<Pixels>,
966 hide_task: Option<Task<()>>,
967 popover_bounds: Option<Bounds<Pixels>>,
968 popover_state: InlineBlamePopoverState,
969 keyboard_grace: bool,
970}
971
972enum SelectionDragState {
973 /// State when no drag related activity is detected.
974 None,
975 /// State when the mouse is down on a selection that is about to be dragged.
976 ReadyToDrag {
977 selection: Selection<Anchor>,
978 click_position: gpui::Point<Pixels>,
979 mouse_down_time: Instant,
980 },
981 /// State when the mouse is dragging the selection in the editor.
982 Dragging {
983 selection: Selection<Anchor>,
984 drop_cursor: Selection<Anchor>,
985 hide_drop_cursor: bool,
986 },
987}
988
989enum ColumnarSelectionState {
990 FromMouse {
991 selection_tail: Anchor,
992 display_point: Option<DisplayPoint>,
993 },
994 FromSelection {
995 selection_tail: Anchor,
996 },
997}
998
999/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1000/// a breakpoint on them.
1001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1002struct PhantomBreakpointIndicator {
1003 display_row: DisplayRow,
1004 /// There's a small debounce between hovering over the line and showing the indicator.
1005 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1006 is_active: bool,
1007 collides_with_existing_breakpoint: bool,
1008}
1009
1010/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1011///
1012/// See the [module level documentation](self) for more information.
1013pub struct Editor {
1014 focus_handle: FocusHandle,
1015 last_focused_descendant: Option<WeakFocusHandle>,
1016 /// The text buffer being edited
1017 buffer: Entity<MultiBuffer>,
1018 /// Map of how text in the buffer should be displayed.
1019 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1020 pub display_map: Entity<DisplayMap>,
1021 placeholder_display_map: Option<Entity<DisplayMap>>,
1022 pub selections: SelectionsCollection,
1023 pub scroll_manager: ScrollManager,
1024 /// When inline assist editors are linked, they all render cursors because
1025 /// typing enters text into each of them, even the ones that aren't focused.
1026 pub(crate) show_cursor_when_unfocused: bool,
1027 columnar_selection_state: Option<ColumnarSelectionState>,
1028 add_selections_state: Option<AddSelectionsState>,
1029 select_next_state: Option<SelectNextState>,
1030 select_prev_state: Option<SelectNextState>,
1031 selection_history: SelectionHistory,
1032 defer_selection_effects: bool,
1033 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1034 autoclose_regions: Vec<AutocloseRegion>,
1035 snippet_stack: InvalidationStack<SnippetState>,
1036 select_syntax_node_history: SelectSyntaxNodeHistory,
1037 ime_transaction: Option<TransactionId>,
1038 pub diagnostics_max_severity: DiagnosticSeverity,
1039 active_diagnostics: ActiveDiagnostic,
1040 show_inline_diagnostics: bool,
1041 inline_diagnostics_update: Task<()>,
1042 inline_diagnostics_enabled: bool,
1043 diagnostics_enabled: bool,
1044 word_completions_enabled: bool,
1045 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1046 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1047 hard_wrap: Option<usize>,
1048 project: Option<Entity<Project>>,
1049 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1050 completion_provider: Option<Rc<dyn CompletionProvider>>,
1051 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1052 blink_manager: Entity<BlinkManager>,
1053 show_cursor_names: bool,
1054 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1055 pub show_local_selections: bool,
1056 mode: EditorMode,
1057 show_breadcrumbs: bool,
1058 show_gutter: bool,
1059 show_scrollbars: ScrollbarAxes,
1060 minimap_visibility: MinimapVisibility,
1061 offset_content: bool,
1062 disable_expand_excerpt_buttons: bool,
1063 show_line_numbers: Option<bool>,
1064 use_relative_line_numbers: Option<bool>,
1065 show_git_diff_gutter: Option<bool>,
1066 show_code_actions: Option<bool>,
1067 show_runnables: Option<bool>,
1068 show_breakpoints: Option<bool>,
1069 show_wrap_guides: Option<bool>,
1070 show_indent_guides: Option<bool>,
1071 highlight_order: usize,
1072 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1073 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1074 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1075 scrollbar_marker_state: ScrollbarMarkerState,
1076 active_indent_guides_state: ActiveIndentGuidesState,
1077 nav_history: Option<ItemNavHistory>,
1078 context_menu: RefCell<Option<CodeContextMenu>>,
1079 context_menu_options: Option<ContextMenuOptions>,
1080 mouse_context_menu: Option<MouseContextMenu>,
1081 completion_tasks: Vec<(CompletionId, Task<()>)>,
1082 inline_blame_popover: Option<InlineBlamePopover>,
1083 inline_blame_popover_show_task: Option<Task<()>>,
1084 signature_help_state: SignatureHelpState,
1085 auto_signature_help: Option<bool>,
1086 find_all_references_task_sources: Vec<Anchor>,
1087 next_completion_id: CompletionId,
1088 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1089 code_actions_task: Option<Task<Result<()>>>,
1090 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1091 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1092 document_highlights_task: Option<Task<()>>,
1093 linked_editing_range_task: Option<Task<Option<()>>>,
1094 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1095 pending_rename: Option<RenameState>,
1096 searchable: bool,
1097 cursor_shape: CursorShape,
1098 current_line_highlight: Option<CurrentLineHighlight>,
1099 autoindent_mode: Option<AutoindentMode>,
1100 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1101 input_enabled: bool,
1102 use_modal_editing: bool,
1103 read_only: bool,
1104 leader_id: Option<CollaboratorId>,
1105 remote_id: Option<ViewId>,
1106 pub hover_state: HoverState,
1107 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1108 gutter_hovered: bool,
1109 hovered_link_state: Option<HoveredLinkState>,
1110 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1111 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1112 active_edit_prediction: Option<EditPredictionState>,
1113 /// Used to prevent flickering as the user types while the menu is open
1114 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1115 edit_prediction_settings: EditPredictionSettings,
1116 edit_predictions_hidden_for_vim_mode: bool,
1117 show_edit_predictions_override: Option<bool>,
1118 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1119 edit_prediction_preview: EditPredictionPreview,
1120 edit_prediction_indent_conflict: bool,
1121 edit_prediction_requires_modifier_in_indent_conflict: bool,
1122 next_inlay_id: usize,
1123 next_color_inlay_id: usize,
1124 _subscriptions: Vec<Subscription>,
1125 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1126 gutter_dimensions: GutterDimensions,
1127 style: Option<EditorStyle>,
1128 text_style_refinement: Option<TextStyleRefinement>,
1129 next_editor_action_id: EditorActionId,
1130 editor_actions: Rc<
1131 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1132 >,
1133 use_autoclose: bool,
1134 use_auto_surround: bool,
1135 auto_replace_emoji_shortcode: bool,
1136 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1137 show_git_blame_gutter: bool,
1138 show_git_blame_inline: bool,
1139 show_git_blame_inline_delay_task: Option<Task<()>>,
1140 git_blame_inline_enabled: bool,
1141 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1142 buffer_serialization: Option<BufferSerialization>,
1143 show_selection_menu: Option<bool>,
1144 blame: Option<Entity<GitBlame>>,
1145 blame_subscription: Option<Subscription>,
1146 custom_context_menu: Option<
1147 Box<
1148 dyn 'static
1149 + Fn(
1150 &mut Self,
1151 DisplayPoint,
1152 &mut Window,
1153 &mut Context<Self>,
1154 ) -> Option<Entity<ui::ContextMenu>>,
1155 >,
1156 >,
1157 last_bounds: Option<Bounds<Pixels>>,
1158 last_position_map: Option<Rc<PositionMap>>,
1159 expect_bounds_change: Option<Bounds<Pixels>>,
1160 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1161 tasks_update_task: Option<Task<()>>,
1162 breakpoint_store: Option<Entity<BreakpointStore>>,
1163 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1164 hovered_diff_hunk_row: Option<DisplayRow>,
1165 pull_diagnostics_task: Task<()>,
1166 in_project_search: bool,
1167 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1168 breadcrumb_header: Option<String>,
1169 focused_block: Option<FocusedBlock>,
1170 next_scroll_position: NextScrollCursorCenterTopBottom,
1171 addons: HashMap<TypeId, Box<dyn Addon>>,
1172 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1173 load_diff_task: Option<Shared<Task<()>>>,
1174 /// Whether we are temporarily displaying a diff other than git's
1175 temporary_diff_override: bool,
1176 selection_mark_mode: bool,
1177 toggle_fold_multiple_buffers: Task<()>,
1178 _scroll_cursor_center_top_bottom_task: Task<()>,
1179 serialize_selections: Task<()>,
1180 serialize_folds: Task<()>,
1181 mouse_cursor_hidden: bool,
1182 minimap: Option<Entity<Self>>,
1183 hide_mouse_mode: HideMouseMode,
1184 pub change_list: ChangeList,
1185 inline_value_cache: InlineValueCache,
1186 selection_drag_state: SelectionDragState,
1187 colors: Option<LspColorData>,
1188 post_scroll_update: Task<()>,
1189 refresh_colors_task: Task<()>,
1190 inlay_hints: Option<LspInlayHintData>,
1191 folding_newlines: Task<()>,
1192 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1193}
1194
1195fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1196 if debounce_ms > 0 {
1197 Some(Duration::from_millis(debounce_ms))
1198 } else {
1199 None
1200 }
1201}
1202
1203#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1204enum NextScrollCursorCenterTopBottom {
1205 #[default]
1206 Center,
1207 Top,
1208 Bottom,
1209}
1210
1211impl NextScrollCursorCenterTopBottom {
1212 fn next(&self) -> Self {
1213 match self {
1214 Self::Center => Self::Top,
1215 Self::Top => Self::Bottom,
1216 Self::Bottom => Self::Center,
1217 }
1218 }
1219}
1220
1221#[derive(Clone)]
1222pub struct EditorSnapshot {
1223 pub mode: EditorMode,
1224 show_gutter: bool,
1225 show_line_numbers: Option<bool>,
1226 show_git_diff_gutter: Option<bool>,
1227 show_code_actions: Option<bool>,
1228 show_runnables: Option<bool>,
1229 show_breakpoints: Option<bool>,
1230 git_blame_gutter_max_author_length: Option<usize>,
1231 pub display_snapshot: DisplaySnapshot,
1232 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1233 is_focused: bool,
1234 scroll_anchor: ScrollAnchor,
1235 ongoing_scroll: OngoingScroll,
1236 current_line_highlight: CurrentLineHighlight,
1237 gutter_hovered: bool,
1238}
1239
1240#[derive(Default, Debug, Clone, Copy)]
1241pub struct GutterDimensions {
1242 pub left_padding: Pixels,
1243 pub right_padding: Pixels,
1244 pub width: Pixels,
1245 pub margin: Pixels,
1246 pub git_blame_entries_width: Option<Pixels>,
1247}
1248
1249impl GutterDimensions {
1250 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1251 Self {
1252 margin: Self::default_gutter_margin(font_id, font_size, cx),
1253 ..Default::default()
1254 }
1255 }
1256
1257 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1258 -cx.text_system().descent(font_id, font_size)
1259 }
1260 /// The full width of the space taken up by the gutter.
1261 pub fn full_width(&self) -> Pixels {
1262 self.margin + self.width
1263 }
1264
1265 /// The width of the space reserved for the fold indicators,
1266 /// use alongside 'justify_end' and `gutter_width` to
1267 /// right align content with the line numbers
1268 pub fn fold_area_width(&self) -> Pixels {
1269 self.margin + self.right_padding
1270 }
1271}
1272
1273struct CharacterDimensions {
1274 em_width: Pixels,
1275 em_advance: Pixels,
1276 line_height: Pixels,
1277}
1278
1279#[derive(Debug)]
1280pub struct RemoteSelection {
1281 pub replica_id: ReplicaId,
1282 pub selection: Selection<Anchor>,
1283 pub cursor_shape: CursorShape,
1284 pub collaborator_id: CollaboratorId,
1285 pub line_mode: bool,
1286 pub user_name: Option<SharedString>,
1287 pub color: PlayerColor,
1288}
1289
1290#[derive(Clone, Debug)]
1291struct SelectionHistoryEntry {
1292 selections: Arc<[Selection<Anchor>]>,
1293 select_next_state: Option<SelectNextState>,
1294 select_prev_state: Option<SelectNextState>,
1295 add_selections_state: Option<AddSelectionsState>,
1296}
1297
1298#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1299enum SelectionHistoryMode {
1300 Normal,
1301 Undoing,
1302 Redoing,
1303 Skipping,
1304}
1305
1306#[derive(Clone, PartialEq, Eq, Hash)]
1307struct HoveredCursor {
1308 replica_id: ReplicaId,
1309 selection_id: usize,
1310}
1311
1312impl Default for SelectionHistoryMode {
1313 fn default() -> Self {
1314 Self::Normal
1315 }
1316}
1317
1318#[derive(Debug)]
1319/// SelectionEffects controls the side-effects of updating the selection.
1320///
1321/// The default behaviour does "what you mostly want":
1322/// - it pushes to the nav history if the cursor moved by >10 lines
1323/// - it re-triggers completion requests
1324/// - it scrolls to fit
1325///
1326/// You might want to modify these behaviours. For example when doing a "jump"
1327/// like go to definition, we always want to add to nav history; but when scrolling
1328/// in vim mode we never do.
1329///
1330/// Similarly, you might want to disable scrolling if you don't want the viewport to
1331/// move.
1332#[derive(Clone)]
1333pub struct SelectionEffects {
1334 nav_history: Option<bool>,
1335 completions: bool,
1336 scroll: Option<Autoscroll>,
1337}
1338
1339impl Default for SelectionEffects {
1340 fn default() -> Self {
1341 Self {
1342 nav_history: None,
1343 completions: true,
1344 scroll: Some(Autoscroll::fit()),
1345 }
1346 }
1347}
1348impl SelectionEffects {
1349 pub fn scroll(scroll: Autoscroll) -> Self {
1350 Self {
1351 scroll: Some(scroll),
1352 ..Default::default()
1353 }
1354 }
1355
1356 pub fn no_scroll() -> Self {
1357 Self {
1358 scroll: None,
1359 ..Default::default()
1360 }
1361 }
1362
1363 pub fn completions(self, completions: bool) -> Self {
1364 Self {
1365 completions,
1366 ..self
1367 }
1368 }
1369
1370 pub fn nav_history(self, nav_history: bool) -> Self {
1371 Self {
1372 nav_history: Some(nav_history),
1373 ..self
1374 }
1375 }
1376}
1377
1378struct DeferredSelectionEffectsState {
1379 changed: bool,
1380 effects: SelectionEffects,
1381 old_cursor_position: Anchor,
1382 history_entry: SelectionHistoryEntry,
1383}
1384
1385#[derive(Default)]
1386struct SelectionHistory {
1387 #[allow(clippy::type_complexity)]
1388 selections_by_transaction:
1389 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1390 mode: SelectionHistoryMode,
1391 undo_stack: VecDeque<SelectionHistoryEntry>,
1392 redo_stack: VecDeque<SelectionHistoryEntry>,
1393}
1394
1395impl SelectionHistory {
1396 #[track_caller]
1397 fn insert_transaction(
1398 &mut self,
1399 transaction_id: TransactionId,
1400 selections: Arc<[Selection<Anchor>]>,
1401 ) {
1402 if selections.is_empty() {
1403 log::error!(
1404 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1405 std::panic::Location::caller()
1406 );
1407 return;
1408 }
1409 self.selections_by_transaction
1410 .insert(transaction_id, (selections, None));
1411 }
1412
1413 #[allow(clippy::type_complexity)]
1414 fn transaction(
1415 &self,
1416 transaction_id: TransactionId,
1417 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1418 self.selections_by_transaction.get(&transaction_id)
1419 }
1420
1421 #[allow(clippy::type_complexity)]
1422 fn transaction_mut(
1423 &mut self,
1424 transaction_id: TransactionId,
1425 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1426 self.selections_by_transaction.get_mut(&transaction_id)
1427 }
1428
1429 fn push(&mut self, entry: SelectionHistoryEntry) {
1430 if !entry.selections.is_empty() {
1431 match self.mode {
1432 SelectionHistoryMode::Normal => {
1433 self.push_undo(entry);
1434 self.redo_stack.clear();
1435 }
1436 SelectionHistoryMode::Undoing => self.push_redo(entry),
1437 SelectionHistoryMode::Redoing => self.push_undo(entry),
1438 SelectionHistoryMode::Skipping => {}
1439 }
1440 }
1441 }
1442
1443 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1444 if self
1445 .undo_stack
1446 .back()
1447 .is_none_or(|e| e.selections != entry.selections)
1448 {
1449 self.undo_stack.push_back(entry);
1450 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1451 self.undo_stack.pop_front();
1452 }
1453 }
1454 }
1455
1456 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1457 if self
1458 .redo_stack
1459 .back()
1460 .is_none_or(|e| e.selections != entry.selections)
1461 {
1462 self.redo_stack.push_back(entry);
1463 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1464 self.redo_stack.pop_front();
1465 }
1466 }
1467 }
1468}
1469
1470#[derive(Clone, Copy)]
1471pub struct RowHighlightOptions {
1472 pub autoscroll: bool,
1473 pub include_gutter: bool,
1474}
1475
1476impl Default for RowHighlightOptions {
1477 fn default() -> Self {
1478 Self {
1479 autoscroll: Default::default(),
1480 include_gutter: true,
1481 }
1482 }
1483}
1484
1485struct RowHighlight {
1486 index: usize,
1487 range: Range<Anchor>,
1488 color: Hsla,
1489 options: RowHighlightOptions,
1490 type_id: TypeId,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsState {
1495 groups: Vec<AddSelectionsGroup>,
1496}
1497
1498#[derive(Clone, Debug)]
1499struct AddSelectionsGroup {
1500 above: bool,
1501 stack: Vec<usize>,
1502}
1503
1504#[derive(Clone)]
1505struct SelectNextState {
1506 query: AhoCorasick,
1507 wordwise: bool,
1508 done: bool,
1509}
1510
1511impl std::fmt::Debug for SelectNextState {
1512 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1513 f.debug_struct(std::any::type_name::<Self>())
1514 .field("wordwise", &self.wordwise)
1515 .field("done", &self.done)
1516 .finish()
1517 }
1518}
1519
1520#[derive(Debug)]
1521struct AutocloseRegion {
1522 selection_id: usize,
1523 range: Range<Anchor>,
1524 pair: BracketPair,
1525}
1526
1527#[derive(Debug)]
1528struct SnippetState {
1529 ranges: Vec<Vec<Range<Anchor>>>,
1530 active_index: usize,
1531 choices: Vec<Option<Vec<String>>>,
1532}
1533
1534#[doc(hidden)]
1535pub struct RenameState {
1536 pub range: Range<Anchor>,
1537 pub old_name: Arc<str>,
1538 pub editor: Entity<Editor>,
1539 block_id: CustomBlockId,
1540}
1541
1542struct InvalidationStack<T>(Vec<T>);
1543
1544struct RegisteredEditPredictionProvider {
1545 provider: Arc<dyn EditPredictionProviderHandle>,
1546 _subscription: Subscription,
1547}
1548
1549#[derive(Debug, PartialEq, Eq)]
1550pub struct ActiveDiagnosticGroup {
1551 pub active_range: Range<Anchor>,
1552 pub active_message: String,
1553 pub group_id: usize,
1554 pub blocks: HashSet<CustomBlockId>,
1555}
1556
1557#[derive(Debug, PartialEq, Eq)]
1558
1559pub(crate) enum ActiveDiagnostic {
1560 None,
1561 All,
1562 Group(ActiveDiagnosticGroup),
1563}
1564
1565#[derive(Serialize, Deserialize, Clone, Debug)]
1566pub struct ClipboardSelection {
1567 /// The number of bytes in this selection.
1568 pub len: usize,
1569 /// Whether this was a full-line selection.
1570 pub is_entire_line: bool,
1571 /// The indentation of the first line when this content was originally copied.
1572 pub first_line_indent: u32,
1573}
1574
1575// selections, scroll behavior, was newest selection reversed
1576type SelectSyntaxNodeHistoryState = (
1577 Box<[Selection<usize>]>,
1578 SelectSyntaxNodeScrollBehavior,
1579 bool,
1580);
1581
1582#[derive(Default)]
1583struct SelectSyntaxNodeHistory {
1584 stack: Vec<SelectSyntaxNodeHistoryState>,
1585 // disable temporarily to allow changing selections without losing the stack
1586 pub disable_clearing: bool,
1587}
1588
1589impl SelectSyntaxNodeHistory {
1590 pub fn try_clear(&mut self) {
1591 if !self.disable_clearing {
1592 self.stack.clear();
1593 }
1594 }
1595
1596 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1597 self.stack.push(selection);
1598 }
1599
1600 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1601 self.stack.pop()
1602 }
1603}
1604
1605enum SelectSyntaxNodeScrollBehavior {
1606 CursorTop,
1607 FitSelection,
1608 CursorBottom,
1609}
1610
1611#[derive(Debug)]
1612pub(crate) struct NavigationData {
1613 cursor_anchor: Anchor,
1614 cursor_position: Point,
1615 scroll_anchor: ScrollAnchor,
1616 scroll_top_row: u32,
1617}
1618
1619#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1620pub enum GotoDefinitionKind {
1621 Symbol,
1622 Declaration,
1623 Type,
1624 Implementation,
1625}
1626
1627pub enum FormatTarget {
1628 Buffers(HashSet<Entity<Buffer>>),
1629 Ranges(Vec<Range<MultiBufferPoint>>),
1630}
1631
1632pub(crate) struct FocusedBlock {
1633 id: BlockId,
1634 focus_handle: WeakFocusHandle,
1635}
1636
1637#[derive(Clone)]
1638enum JumpData {
1639 MultiBufferRow {
1640 row: MultiBufferRow,
1641 line_offset_from_top: u32,
1642 },
1643 MultiBufferPoint {
1644 excerpt_id: ExcerptId,
1645 position: Point,
1646 anchor: text::Anchor,
1647 line_offset_from_top: u32,
1648 },
1649}
1650
1651pub enum MultibufferSelectionMode {
1652 First,
1653 All,
1654}
1655
1656#[derive(Clone, Copy, Debug, Default)]
1657pub struct RewrapOptions {
1658 pub override_language_settings: bool,
1659 pub preserve_existing_whitespace: bool,
1660}
1661
1662impl Editor {
1663 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1664 let buffer = cx.new(|cx| Buffer::local("", cx));
1665 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1666 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1667 }
1668
1669 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1670 let buffer = cx.new(|cx| Buffer::local("", cx));
1671 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1672 Self::new(EditorMode::full(), buffer, None, window, cx)
1673 }
1674
1675 pub fn auto_height(
1676 min_lines: usize,
1677 max_lines: usize,
1678 window: &mut Window,
1679 cx: &mut Context<Self>,
1680 ) -> Self {
1681 let buffer = cx.new(|cx| Buffer::local("", cx));
1682 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1683 Self::new(
1684 EditorMode::AutoHeight {
1685 min_lines,
1686 max_lines: Some(max_lines),
1687 },
1688 buffer,
1689 None,
1690 window,
1691 cx,
1692 )
1693 }
1694
1695 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1696 /// The editor grows as tall as needed to fit its content.
1697 pub fn auto_height_unbounded(
1698 min_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: None,
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 pub fn for_buffer(
1717 buffer: Entity<Buffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1723 Self::new(EditorMode::full(), buffer, project, window, cx)
1724 }
1725
1726 pub fn for_multibuffer(
1727 buffer: Entity<MultiBuffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 Self::new(EditorMode::full(), buffer, project, window, cx)
1733 }
1734
1735 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1736 let mut clone = Self::new(
1737 self.mode.clone(),
1738 self.buffer.clone(),
1739 self.project.clone(),
1740 window,
1741 cx,
1742 );
1743 self.display_map.update(cx, |display_map, cx| {
1744 let snapshot = display_map.snapshot(cx);
1745 clone.display_map.update(cx, |display_map, cx| {
1746 display_map.set_state(&snapshot, cx);
1747 });
1748 });
1749 clone.folds_did_change(cx);
1750 clone.selections.clone_state(&self.selections);
1751 clone.scroll_manager.clone_state(&self.scroll_manager);
1752 clone.searchable = self.searchable;
1753 clone.read_only = self.read_only;
1754 clone
1755 }
1756
1757 pub fn new(
1758 mode: EditorMode,
1759 buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 Editor::new_internal(mode, buffer, project, None, window, cx)
1765 }
1766
1767 fn new_internal(
1768 mode: EditorMode,
1769 multi_buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 display_map: Option<Entity<DisplayMap>>,
1772 window: &mut Window,
1773 cx: &mut Context<Self>,
1774 ) -> Self {
1775 debug_assert!(
1776 display_map.is_none() || mode.is_minimap(),
1777 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1778 );
1779
1780 let full_mode = mode.is_full();
1781 let is_minimap = mode.is_minimap();
1782 let diagnostics_max_severity = if full_mode {
1783 EditorSettings::get_global(cx)
1784 .diagnostics_max_severity
1785 .unwrap_or(DiagnosticSeverity::Hint)
1786 } else {
1787 DiagnosticSeverity::Off
1788 };
1789 let style = window.text_style();
1790 let font_size = style.font_size.to_pixels(window.rem_size());
1791 let editor = cx.entity().downgrade();
1792 let fold_placeholder = FoldPlaceholder {
1793 constrain_width: false,
1794 render: Arc::new(move |fold_id, fold_range, cx| {
1795 let editor = editor.clone();
1796 div()
1797 .id(fold_id)
1798 .bg(cx.theme().colors().ghost_element_background)
1799 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1800 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1801 .rounded_xs()
1802 .size_full()
1803 .cursor_pointer()
1804 .child("⋯")
1805 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1806 .on_click(move |_, _window, cx| {
1807 editor
1808 .update(cx, |editor, cx| {
1809 editor.unfold_ranges(
1810 &[fold_range.start..fold_range.end],
1811 true,
1812 false,
1813 cx,
1814 );
1815 cx.stop_propagation();
1816 })
1817 .ok();
1818 })
1819 .into_any()
1820 }),
1821 merge_adjacent: true,
1822 ..FoldPlaceholder::default()
1823 };
1824 let display_map = display_map.unwrap_or_else(|| {
1825 cx.new(|cx| {
1826 DisplayMap::new(
1827 multi_buffer.clone(),
1828 style.font(),
1829 font_size,
1830 None,
1831 FILE_HEADER_HEIGHT,
1832 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1833 fold_placeholder,
1834 diagnostics_max_severity,
1835 cx,
1836 )
1837 })
1838 });
1839
1840 let selections = SelectionsCollection::new();
1841
1842 let blink_manager = cx.new(|cx| {
1843 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1844 if is_minimap {
1845 blink_manager.disable(cx);
1846 }
1847 blink_manager
1848 });
1849
1850 let soft_wrap_mode_override =
1851 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1852
1853 let mut project_subscriptions = Vec::new();
1854 if full_mode && let Some(project) = project.as_ref() {
1855 project_subscriptions.push(cx.subscribe_in(
1856 project,
1857 window,
1858 |editor, _, event, window, cx| match event {
1859 project::Event::RefreshCodeLens => {
1860 // we always query lens with actions, without storing them, always refreshing them
1861 }
1862 project::Event::RefreshInlayHints {
1863 server_id,
1864 request_id,
1865 } => {
1866 editor.refresh_inlay_hints(
1867 InlayHintRefreshReason::RefreshRequested {
1868 server_id: *server_id,
1869 request_id: *request_id,
1870 },
1871 cx,
1872 );
1873 }
1874 project::Event::LanguageServerRemoved(..) => {
1875 if editor.tasks_update_task.is_none() {
1876 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1877 }
1878 editor.registered_buffers.clear();
1879 editor.register_visible_buffers(cx);
1880 }
1881 project::Event::LanguageServerAdded(..) => {
1882 if editor.tasks_update_task.is_none() {
1883 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1884 }
1885 }
1886 project::Event::SnippetEdit(id, snippet_edits) => {
1887 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1888 let focus_handle = editor.focus_handle(cx);
1889 if focus_handle.is_focused(window) {
1890 let snapshot = buffer.read(cx).snapshot();
1891 for (range, snippet) in snippet_edits {
1892 let editor_range =
1893 language::range_from_lsp(*range).to_offset(&snapshot);
1894 editor
1895 .insert_snippet(
1896 &[editor_range],
1897 snippet.clone(),
1898 window,
1899 cx,
1900 )
1901 .ok();
1902 }
1903 }
1904 }
1905 }
1906 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1907 let buffer_id = *buffer_id;
1908 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1909 editor.register_buffer(buffer_id, cx);
1910 editor.update_lsp_data(Some(buffer_id), window, cx);
1911 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1912 refresh_linked_ranges(editor, window, cx);
1913 editor.refresh_code_actions(window, cx);
1914 editor.refresh_document_highlights(cx);
1915 }
1916 }
1917
1918 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1919 let Some(workspace) = editor.workspace() else {
1920 return;
1921 };
1922 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1923 else {
1924 return;
1925 };
1926
1927 if active_editor.entity_id() == cx.entity_id() {
1928 let entity_id = cx.entity_id();
1929 workspace.update(cx, |this, cx| {
1930 this.panes_mut()
1931 .iter_mut()
1932 .filter(|pane| pane.entity_id() != entity_id)
1933 .for_each(|p| {
1934 p.update(cx, |pane, _| {
1935 pane.nav_history_mut().rename_item(
1936 entity_id,
1937 project_path.clone(),
1938 abs_path.clone().into(),
1939 );
1940 })
1941 });
1942 });
1943 let edited_buffers_already_open = {
1944 let other_editors: Vec<Entity<Editor>> = workspace
1945 .read(cx)
1946 .panes()
1947 .iter()
1948 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1949 .filter(|editor| editor.entity_id() != cx.entity_id())
1950 .collect();
1951
1952 transaction.0.keys().all(|buffer| {
1953 other_editors.iter().any(|editor| {
1954 let multi_buffer = editor.read(cx).buffer();
1955 multi_buffer.read(cx).is_singleton()
1956 && multi_buffer.read(cx).as_singleton().map_or(
1957 false,
1958 |singleton| {
1959 singleton.entity_id() == buffer.entity_id()
1960 },
1961 )
1962 })
1963 })
1964 };
1965 if !edited_buffers_already_open {
1966 let workspace = workspace.downgrade();
1967 let transaction = transaction.clone();
1968 cx.defer_in(window, move |_, window, cx| {
1969 cx.spawn_in(window, async move |editor, cx| {
1970 Self::open_project_transaction(
1971 &editor,
1972 workspace,
1973 transaction,
1974 "Rename".to_string(),
1975 cx,
1976 )
1977 .await
1978 .ok()
1979 })
1980 .detach();
1981 });
1982 }
1983 }
1984 }
1985
1986 _ => {}
1987 },
1988 ));
1989 if let Some(task_inventory) = project
1990 .read(cx)
1991 .task_store()
1992 .read(cx)
1993 .task_inventory()
1994 .cloned()
1995 {
1996 project_subscriptions.push(cx.observe_in(
1997 &task_inventory,
1998 window,
1999 |editor, _, window, cx| {
2000 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2001 },
2002 ));
2003 };
2004
2005 project_subscriptions.push(cx.subscribe_in(
2006 &project.read(cx).breakpoint_store(),
2007 window,
2008 |editor, _, event, window, cx| match event {
2009 BreakpointStoreEvent::ClearDebugLines => {
2010 editor.clear_row_highlights::<ActiveDebugLine>();
2011 editor.refresh_inline_values(cx);
2012 }
2013 BreakpointStoreEvent::SetDebugLine => {
2014 if editor.go_to_active_debug_line(window, cx) {
2015 cx.stop_propagation();
2016 }
2017
2018 editor.refresh_inline_values(cx);
2019 }
2020 _ => {}
2021 },
2022 ));
2023 let git_store = project.read(cx).git_store().clone();
2024 let project = project.clone();
2025 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2026 if let GitStoreEvent::RepositoryAdded = event {
2027 this.load_diff_task = Some(
2028 update_uncommitted_diff_for_buffer(
2029 cx.entity(),
2030 &project,
2031 this.buffer.read(cx).all_buffers(),
2032 this.buffer.clone(),
2033 cx,
2034 )
2035 .shared(),
2036 );
2037 }
2038 }));
2039 }
2040
2041 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2042
2043 let inlay_hint_settings =
2044 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2045 let focus_handle = cx.focus_handle();
2046 if !is_minimap {
2047 cx.on_focus(&focus_handle, window, Self::handle_focus)
2048 .detach();
2049 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2050 .detach();
2051 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2052 .detach();
2053 cx.on_blur(&focus_handle, window, Self::handle_blur)
2054 .detach();
2055 cx.observe_pending_input(window, Self::observe_pending_input)
2056 .detach();
2057 }
2058
2059 let show_indent_guides =
2060 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2061 Some(false)
2062 } else {
2063 None
2064 };
2065
2066 let breakpoint_store = match (&mode, project.as_ref()) {
2067 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2068 _ => None,
2069 };
2070
2071 let mut code_action_providers = Vec::new();
2072 let mut load_uncommitted_diff = None;
2073 if let Some(project) = project.clone() {
2074 load_uncommitted_diff = Some(
2075 update_uncommitted_diff_for_buffer(
2076 cx.entity(),
2077 &project,
2078 multi_buffer.read(cx).all_buffers(),
2079 multi_buffer.clone(),
2080 cx,
2081 )
2082 .shared(),
2083 );
2084 code_action_providers.push(Rc::new(project) as Rc<_>);
2085 }
2086
2087 let mut editor = Self {
2088 focus_handle,
2089 show_cursor_when_unfocused: false,
2090 last_focused_descendant: None,
2091 buffer: multi_buffer.clone(),
2092 display_map: display_map.clone(),
2093 placeholder_display_map: None,
2094 selections,
2095 scroll_manager: ScrollManager::new(cx),
2096 columnar_selection_state: None,
2097 add_selections_state: None,
2098 select_next_state: None,
2099 select_prev_state: None,
2100 selection_history: SelectionHistory::default(),
2101 defer_selection_effects: false,
2102 deferred_selection_effects_state: None,
2103 autoclose_regions: Vec::new(),
2104 snippet_stack: InvalidationStack::default(),
2105 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2106 ime_transaction: None,
2107 active_diagnostics: ActiveDiagnostic::None,
2108 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2109 inline_diagnostics_update: Task::ready(()),
2110 inline_diagnostics: Vec::new(),
2111 soft_wrap_mode_override,
2112 diagnostics_max_severity,
2113 hard_wrap: None,
2114 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2115 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2116 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2117 project,
2118 blink_manager: blink_manager.clone(),
2119 show_local_selections: true,
2120 show_scrollbars: ScrollbarAxes {
2121 horizontal: full_mode,
2122 vertical: full_mode,
2123 },
2124 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2125 offset_content: !matches!(mode, EditorMode::SingleLine),
2126 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2127 show_gutter: full_mode,
2128 show_line_numbers: (!full_mode).then_some(false),
2129 use_relative_line_numbers: None,
2130 disable_expand_excerpt_buttons: !full_mode,
2131 show_git_diff_gutter: None,
2132 show_code_actions: None,
2133 show_runnables: None,
2134 show_breakpoints: None,
2135 show_wrap_guides: None,
2136 show_indent_guides,
2137 highlight_order: 0,
2138 highlighted_rows: HashMap::default(),
2139 background_highlights: HashMap::default(),
2140 gutter_highlights: HashMap::default(),
2141 scrollbar_marker_state: ScrollbarMarkerState::default(),
2142 active_indent_guides_state: ActiveIndentGuidesState::default(),
2143 nav_history: None,
2144 context_menu: RefCell::new(None),
2145 context_menu_options: None,
2146 mouse_context_menu: None,
2147 completion_tasks: Vec::new(),
2148 inline_blame_popover: None,
2149 inline_blame_popover_show_task: None,
2150 signature_help_state: SignatureHelpState::default(),
2151 auto_signature_help: None,
2152 find_all_references_task_sources: Vec::new(),
2153 next_completion_id: 0,
2154 next_inlay_id: 0,
2155 code_action_providers,
2156 available_code_actions: None,
2157 code_actions_task: None,
2158 quick_selection_highlight_task: None,
2159 debounced_selection_highlight_task: None,
2160 document_highlights_task: None,
2161 linked_editing_range_task: None,
2162 pending_rename: None,
2163 searchable: !is_minimap,
2164 cursor_shape: EditorSettings::get_global(cx)
2165 .cursor_shape
2166 .unwrap_or_default(),
2167 current_line_highlight: None,
2168 autoindent_mode: Some(AutoindentMode::EachLine),
2169
2170 workspace: None,
2171 input_enabled: !is_minimap,
2172 use_modal_editing: full_mode,
2173 read_only: is_minimap,
2174 use_autoclose: true,
2175 use_auto_surround: true,
2176 auto_replace_emoji_shortcode: false,
2177 jsx_tag_auto_close_enabled_in_any_buffer: false,
2178 leader_id: None,
2179 remote_id: None,
2180 hover_state: HoverState::default(),
2181 pending_mouse_down: None,
2182 hovered_link_state: None,
2183 edit_prediction_provider: None,
2184 active_edit_prediction: None,
2185 stale_edit_prediction_in_menu: None,
2186 edit_prediction_preview: EditPredictionPreview::Inactive {
2187 released_too_fast: false,
2188 },
2189 inline_diagnostics_enabled: full_mode,
2190 diagnostics_enabled: full_mode,
2191 word_completions_enabled: full_mode,
2192 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2193 gutter_hovered: false,
2194 pixel_position_of_newest_cursor: None,
2195 last_bounds: None,
2196 last_position_map: None,
2197 expect_bounds_change: None,
2198 gutter_dimensions: GutterDimensions::default(),
2199 style: None,
2200 show_cursor_names: false,
2201 hovered_cursors: HashMap::default(),
2202 next_editor_action_id: EditorActionId::default(),
2203 editor_actions: Rc::default(),
2204 edit_predictions_hidden_for_vim_mode: false,
2205 show_edit_predictions_override: None,
2206 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2207 edit_prediction_settings: EditPredictionSettings::Disabled,
2208 edit_prediction_indent_conflict: false,
2209 edit_prediction_requires_modifier_in_indent_conflict: true,
2210 custom_context_menu: None,
2211 show_git_blame_gutter: false,
2212 show_git_blame_inline: false,
2213 show_selection_menu: None,
2214 show_git_blame_inline_delay_task: None,
2215 git_blame_inline_enabled: full_mode
2216 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2217 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2218 buffer_serialization: is_minimap.not().then(|| {
2219 BufferSerialization::new(
2220 ProjectSettings::get_global(cx)
2221 .session
2222 .restore_unsaved_buffers,
2223 )
2224 }),
2225 blame: None,
2226 blame_subscription: None,
2227 tasks: BTreeMap::default(),
2228
2229 breakpoint_store,
2230 gutter_breakpoint_indicator: (None, None),
2231 hovered_diff_hunk_row: None,
2232 _subscriptions: (!is_minimap)
2233 .then(|| {
2234 vec![
2235 cx.observe(&multi_buffer, Self::on_buffer_changed),
2236 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2237 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2238 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2239 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2240 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2241 cx.observe_window_activation(window, |editor, window, cx| {
2242 let active = window.is_window_active();
2243 editor.blink_manager.update(cx, |blink_manager, cx| {
2244 if active {
2245 blink_manager.enable(cx);
2246 } else {
2247 blink_manager.disable(cx);
2248 }
2249 });
2250 if active {
2251 editor.show_mouse_cursor(cx);
2252 }
2253 }),
2254 ]
2255 })
2256 .unwrap_or_default(),
2257 tasks_update_task: None,
2258 pull_diagnostics_task: Task::ready(()),
2259 colors: None,
2260 refresh_colors_task: Task::ready(()),
2261 inlay_hints: None,
2262 next_color_inlay_id: 0,
2263 post_scroll_update: Task::ready(()),
2264 linked_edit_ranges: Default::default(),
2265 in_project_search: false,
2266 previous_search_ranges: None,
2267 breadcrumb_header: None,
2268 focused_block: None,
2269 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2270 addons: HashMap::default(),
2271 registered_buffers: HashMap::default(),
2272 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2273 selection_mark_mode: false,
2274 toggle_fold_multiple_buffers: Task::ready(()),
2275 serialize_selections: Task::ready(()),
2276 serialize_folds: Task::ready(()),
2277 text_style_refinement: None,
2278 load_diff_task: load_uncommitted_diff,
2279 temporary_diff_override: false,
2280 mouse_cursor_hidden: false,
2281 minimap: None,
2282 hide_mouse_mode: EditorSettings::get_global(cx)
2283 .hide_mouse
2284 .unwrap_or_default(),
2285 change_list: ChangeList::new(),
2286 mode,
2287 selection_drag_state: SelectionDragState::None,
2288 folding_newlines: Task::ready(()),
2289 lookup_key: None,
2290 };
2291
2292 if is_minimap {
2293 return editor;
2294 }
2295
2296 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2297 editor
2298 ._subscriptions
2299 .push(cx.observe(breakpoints, |_, _, cx| {
2300 cx.notify();
2301 }));
2302 }
2303 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2304 editor._subscriptions.extend(project_subscriptions);
2305
2306 editor._subscriptions.push(cx.subscribe_in(
2307 &cx.entity(),
2308 window,
2309 |editor, _, e: &EditorEvent, window, cx| match e {
2310 EditorEvent::ScrollPositionChanged { local, .. } => {
2311 if *local {
2312 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2313 editor.inline_blame_popover.take();
2314 let new_anchor = editor.scroll_manager.anchor();
2315 let snapshot = editor.snapshot(window, cx);
2316 editor.update_restoration_data(cx, move |data| {
2317 data.scroll_position = (
2318 new_anchor.top_row(snapshot.buffer_snapshot()),
2319 new_anchor.offset,
2320 );
2321 });
2322
2323 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2324 cx.background_executor()
2325 .timer(Duration::from_millis(50))
2326 .await;
2327 editor
2328 .update_in(cx, |editor, window, cx| {
2329 editor.register_visible_buffers(cx);
2330 editor.refresh_colors_for_visible_range(None, window, cx);
2331 editor.refresh_inlay_hints(
2332 InlayHintRefreshReason::NewLinesShown,
2333 cx,
2334 );
2335 })
2336 .ok();
2337 });
2338 }
2339 }
2340 EditorEvent::Edited { .. } => {
2341 if vim_flavor(cx).is_none() {
2342 let display_map = editor.display_snapshot(cx);
2343 let selections = editor.selections.all_adjusted_display(&display_map);
2344 let pop_state = editor
2345 .change_list
2346 .last()
2347 .map(|previous| {
2348 previous.len() == selections.len()
2349 && previous.iter().enumerate().all(|(ix, p)| {
2350 p.to_display_point(&display_map).row()
2351 == selections[ix].head().row()
2352 })
2353 })
2354 .unwrap_or(false);
2355 let new_positions = selections
2356 .into_iter()
2357 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2358 .collect();
2359 editor
2360 .change_list
2361 .push_to_change_list(pop_state, new_positions);
2362 }
2363 }
2364 _ => (),
2365 },
2366 ));
2367
2368 if let Some(dap_store) = editor
2369 .project
2370 .as_ref()
2371 .map(|project| project.read(cx).dap_store())
2372 {
2373 let weak_editor = cx.weak_entity();
2374
2375 editor
2376 ._subscriptions
2377 .push(
2378 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2379 let session_entity = cx.entity();
2380 weak_editor
2381 .update(cx, |editor, cx| {
2382 editor._subscriptions.push(
2383 cx.subscribe(&session_entity, Self::on_debug_session_event),
2384 );
2385 })
2386 .ok();
2387 }),
2388 );
2389
2390 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2391 editor
2392 ._subscriptions
2393 .push(cx.subscribe(&session, Self::on_debug_session_event));
2394 }
2395 }
2396
2397 // skip adding the initial selection to selection history
2398 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2399 editor.end_selection(window, cx);
2400 editor.selection_history.mode = SelectionHistoryMode::Normal;
2401
2402 editor.scroll_manager.show_scrollbars(window, cx);
2403 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2404
2405 if full_mode {
2406 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2407 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2408
2409 if editor.git_blame_inline_enabled {
2410 editor.start_git_blame_inline(false, window, cx);
2411 }
2412
2413 editor.go_to_active_debug_line(window, cx);
2414
2415 editor.minimap =
2416 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2417 editor.colors = Some(LspColorData::new(cx));
2418 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2419
2420 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2421 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2422 }
2423 editor.update_lsp_data(None, window, cx);
2424 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2425 }
2426
2427 editor
2428 }
2429
2430 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2431 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2432 }
2433
2434 pub fn deploy_mouse_context_menu(
2435 &mut self,
2436 position: gpui::Point<Pixels>,
2437 context_menu: Entity<ContextMenu>,
2438 window: &mut Window,
2439 cx: &mut Context<Self>,
2440 ) {
2441 self.mouse_context_menu = Some(MouseContextMenu::new(
2442 self,
2443 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2444 context_menu,
2445 window,
2446 cx,
2447 ));
2448 }
2449
2450 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2451 self.mouse_context_menu
2452 .as_ref()
2453 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2454 }
2455
2456 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2457 if self
2458 .selections
2459 .pending_anchor()
2460 .is_some_and(|pending_selection| {
2461 let snapshot = self.buffer().read(cx).snapshot(cx);
2462 pending_selection.range().includes(range, &snapshot)
2463 })
2464 {
2465 return true;
2466 }
2467
2468 self.selections
2469 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2470 .into_iter()
2471 .any(|selection| {
2472 // This is needed to cover a corner case, if we just check for an existing
2473 // selection in the fold range, having a cursor at the start of the fold
2474 // marks it as selected. Non-empty selections don't cause this.
2475 let length = selection.end - selection.start;
2476 length > 0
2477 })
2478 }
2479
2480 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2481 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2482 }
2483
2484 fn key_context_internal(
2485 &self,
2486 has_active_edit_prediction: bool,
2487 window: &mut Window,
2488 cx: &mut App,
2489 ) -> KeyContext {
2490 let mut key_context = KeyContext::new_with_defaults();
2491 key_context.add("Editor");
2492 let mode = match self.mode {
2493 EditorMode::SingleLine => "single_line",
2494 EditorMode::AutoHeight { .. } => "auto_height",
2495 EditorMode::Minimap { .. } => "minimap",
2496 EditorMode::Full { .. } => "full",
2497 };
2498
2499 if EditorSettings::jupyter_enabled(cx) {
2500 key_context.add("jupyter");
2501 }
2502
2503 key_context.set("mode", mode);
2504 if self.pending_rename.is_some() {
2505 key_context.add("renaming");
2506 }
2507
2508 if let Some(snippet_stack) = self.snippet_stack.last() {
2509 key_context.add("in_snippet");
2510
2511 if snippet_stack.active_index > 0 {
2512 key_context.add("has_previous_tabstop");
2513 }
2514
2515 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2516 key_context.add("has_next_tabstop");
2517 }
2518 }
2519
2520 match self.context_menu.borrow().as_ref() {
2521 Some(CodeContextMenu::Completions(menu)) => {
2522 if menu.visible() {
2523 key_context.add("menu");
2524 key_context.add("showing_completions");
2525 }
2526 }
2527 Some(CodeContextMenu::CodeActions(menu)) => {
2528 if menu.visible() {
2529 key_context.add("menu");
2530 key_context.add("showing_code_actions")
2531 }
2532 }
2533 None => {}
2534 }
2535
2536 if self.signature_help_state.has_multiple_signatures() {
2537 key_context.add("showing_signature_help");
2538 }
2539
2540 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2541 if !self.focus_handle(cx).contains_focused(window, cx)
2542 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2543 {
2544 for addon in self.addons.values() {
2545 addon.extend_key_context(&mut key_context, cx)
2546 }
2547 }
2548
2549 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2550 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2551 Some(
2552 file.full_path(cx)
2553 .extension()?
2554 .to_string_lossy()
2555 .into_owned(),
2556 )
2557 }) {
2558 key_context.set("extension", extension);
2559 }
2560 } else {
2561 key_context.add("multibuffer");
2562 }
2563
2564 if has_active_edit_prediction {
2565 if self.edit_prediction_in_conflict() {
2566 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2567 } else {
2568 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2569 key_context.add("copilot_suggestion");
2570 }
2571 }
2572
2573 if self.selection_mark_mode {
2574 key_context.add("selection_mode");
2575 }
2576
2577 let disjoint = self.selections.disjoint_anchors();
2578 let snapshot = self.snapshot(window, cx);
2579 let snapshot = snapshot.buffer_snapshot();
2580 if self.mode == EditorMode::SingleLine
2581 && let [selection] = disjoint
2582 && selection.start == selection.end
2583 && selection.end.to_offset(snapshot) == snapshot.len()
2584 {
2585 key_context.add("end_of_input");
2586 }
2587
2588 key_context
2589 }
2590
2591 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2592 self.last_bounds.as_ref()
2593 }
2594
2595 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2596 if self.mouse_cursor_hidden {
2597 self.mouse_cursor_hidden = false;
2598 cx.notify();
2599 }
2600 }
2601
2602 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2603 let hide_mouse_cursor = match origin {
2604 HideMouseCursorOrigin::TypingAction => {
2605 matches!(
2606 self.hide_mouse_mode,
2607 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2608 )
2609 }
2610 HideMouseCursorOrigin::MovementAction => {
2611 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2612 }
2613 };
2614 if self.mouse_cursor_hidden != hide_mouse_cursor {
2615 self.mouse_cursor_hidden = hide_mouse_cursor;
2616 cx.notify();
2617 }
2618 }
2619
2620 pub fn edit_prediction_in_conflict(&self) -> bool {
2621 if !self.show_edit_predictions_in_menu() {
2622 return false;
2623 }
2624
2625 let showing_completions = self
2626 .context_menu
2627 .borrow()
2628 .as_ref()
2629 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2630
2631 showing_completions
2632 || self.edit_prediction_requires_modifier()
2633 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2634 // bindings to insert tab characters.
2635 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2636 }
2637
2638 pub fn accept_edit_prediction_keybind(
2639 &self,
2640 accept_partial: bool,
2641 window: &mut Window,
2642 cx: &mut App,
2643 ) -> AcceptEditPredictionBinding {
2644 let key_context = self.key_context_internal(true, window, cx);
2645 let in_conflict = self.edit_prediction_in_conflict();
2646
2647 let bindings = if accept_partial {
2648 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2649 } else {
2650 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2651 };
2652
2653 // TODO: if the binding contains multiple keystrokes, display all of them, not
2654 // just the first one.
2655 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2656 !in_conflict
2657 || binding
2658 .keystrokes()
2659 .first()
2660 .is_some_and(|keystroke| keystroke.modifiers().modified())
2661 }))
2662 }
2663
2664 pub fn new_file(
2665 workspace: &mut Workspace,
2666 _: &workspace::NewFile,
2667 window: &mut Window,
2668 cx: &mut Context<Workspace>,
2669 ) {
2670 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2671 "Failed to create buffer",
2672 window,
2673 cx,
2674 |e, _, _| match e.error_code() {
2675 ErrorCode::RemoteUpgradeRequired => Some(format!(
2676 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2677 e.error_tag("required").unwrap_or("the latest version")
2678 )),
2679 _ => None,
2680 },
2681 );
2682 }
2683
2684 pub fn new_in_workspace(
2685 workspace: &mut Workspace,
2686 window: &mut Window,
2687 cx: &mut Context<Workspace>,
2688 ) -> Task<Result<Entity<Editor>>> {
2689 let project = workspace.project().clone();
2690 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2691
2692 cx.spawn_in(window, async move |workspace, cx| {
2693 let buffer = create.await?;
2694 workspace.update_in(cx, |workspace, window, cx| {
2695 let editor =
2696 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2697 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2698 editor
2699 })
2700 })
2701 }
2702
2703 fn new_file_vertical(
2704 workspace: &mut Workspace,
2705 _: &workspace::NewFileSplitVertical,
2706 window: &mut Window,
2707 cx: &mut Context<Workspace>,
2708 ) {
2709 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2710 }
2711
2712 fn new_file_horizontal(
2713 workspace: &mut Workspace,
2714 _: &workspace::NewFileSplitHorizontal,
2715 window: &mut Window,
2716 cx: &mut Context<Workspace>,
2717 ) {
2718 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2719 }
2720
2721 fn new_file_split(
2722 workspace: &mut Workspace,
2723 action: &workspace::NewFileSplit,
2724 window: &mut Window,
2725 cx: &mut Context<Workspace>,
2726 ) {
2727 Self::new_file_in_direction(workspace, action.0, window, cx)
2728 }
2729
2730 fn new_file_in_direction(
2731 workspace: &mut Workspace,
2732 direction: SplitDirection,
2733 window: &mut Window,
2734 cx: &mut Context<Workspace>,
2735 ) {
2736 let project = workspace.project().clone();
2737 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2738
2739 cx.spawn_in(window, async move |workspace, cx| {
2740 let buffer = create.await?;
2741 workspace.update_in(cx, move |workspace, window, cx| {
2742 workspace.split_item(
2743 direction,
2744 Box::new(
2745 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2746 ),
2747 window,
2748 cx,
2749 )
2750 })?;
2751 anyhow::Ok(())
2752 })
2753 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2754 match e.error_code() {
2755 ErrorCode::RemoteUpgradeRequired => Some(format!(
2756 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2757 e.error_tag("required").unwrap_or("the latest version")
2758 )),
2759 _ => None,
2760 }
2761 });
2762 }
2763
2764 pub fn leader_id(&self) -> Option<CollaboratorId> {
2765 self.leader_id
2766 }
2767
2768 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2769 &self.buffer
2770 }
2771
2772 pub fn project(&self) -> Option<&Entity<Project>> {
2773 self.project.as_ref()
2774 }
2775
2776 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2777 self.workspace.as_ref()?.0.upgrade()
2778 }
2779
2780 /// Returns the workspace serialization ID if this editor should be serialized.
2781 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2782 self.workspace
2783 .as_ref()
2784 .filter(|_| self.should_serialize_buffer())
2785 .and_then(|workspace| workspace.1)
2786 }
2787
2788 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2789 self.buffer().read(cx).title(cx)
2790 }
2791
2792 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2793 let git_blame_gutter_max_author_length = self
2794 .render_git_blame_gutter(cx)
2795 .then(|| {
2796 if let Some(blame) = self.blame.as_ref() {
2797 let max_author_length =
2798 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2799 Some(max_author_length)
2800 } else {
2801 None
2802 }
2803 })
2804 .flatten();
2805
2806 EditorSnapshot {
2807 mode: self.mode.clone(),
2808 show_gutter: self.show_gutter,
2809 show_line_numbers: self.show_line_numbers,
2810 show_git_diff_gutter: self.show_git_diff_gutter,
2811 show_code_actions: self.show_code_actions,
2812 show_runnables: self.show_runnables,
2813 show_breakpoints: self.show_breakpoints,
2814 git_blame_gutter_max_author_length,
2815 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2816 placeholder_display_snapshot: self
2817 .placeholder_display_map
2818 .as_ref()
2819 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2820 scroll_anchor: self.scroll_manager.anchor(),
2821 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2822 is_focused: self.focus_handle.is_focused(window),
2823 current_line_highlight: self
2824 .current_line_highlight
2825 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2826 gutter_hovered: self.gutter_hovered,
2827 }
2828 }
2829
2830 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2831 self.buffer.read(cx).language_at(point, cx)
2832 }
2833
2834 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2835 self.buffer.read(cx).read(cx).file_at(point).cloned()
2836 }
2837
2838 pub fn active_excerpt(
2839 &self,
2840 cx: &App,
2841 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2842 self.buffer
2843 .read(cx)
2844 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2845 }
2846
2847 pub fn mode(&self) -> &EditorMode {
2848 &self.mode
2849 }
2850
2851 pub fn set_mode(&mut self, mode: EditorMode) {
2852 self.mode = mode;
2853 }
2854
2855 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2856 self.collaboration_hub.as_deref()
2857 }
2858
2859 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2860 self.collaboration_hub = Some(hub);
2861 }
2862
2863 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2864 self.in_project_search = in_project_search;
2865 }
2866
2867 pub fn set_custom_context_menu(
2868 &mut self,
2869 f: impl 'static
2870 + Fn(
2871 &mut Self,
2872 DisplayPoint,
2873 &mut Window,
2874 &mut Context<Self>,
2875 ) -> Option<Entity<ui::ContextMenu>>,
2876 ) {
2877 self.custom_context_menu = Some(Box::new(f))
2878 }
2879
2880 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2881 self.completion_provider = provider;
2882 }
2883
2884 #[cfg(any(test, feature = "test-support"))]
2885 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2886 self.completion_provider.clone()
2887 }
2888
2889 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2890 self.semantics_provider.clone()
2891 }
2892
2893 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2894 self.semantics_provider = provider;
2895 }
2896
2897 pub fn set_edit_prediction_provider<T>(
2898 &mut self,
2899 provider: Option<Entity<T>>,
2900 window: &mut Window,
2901 cx: &mut Context<Self>,
2902 ) where
2903 T: EditPredictionProvider,
2904 {
2905 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2906 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2907 if this.focus_handle.is_focused(window) {
2908 this.update_visible_edit_prediction(window, cx);
2909 }
2910 }),
2911 provider: Arc::new(provider),
2912 });
2913 self.update_edit_prediction_settings(cx);
2914 self.refresh_edit_prediction(false, false, window, cx);
2915 }
2916
2917 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2918 self.placeholder_display_map
2919 .as_ref()
2920 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2921 }
2922
2923 pub fn set_placeholder_text(
2924 &mut self,
2925 placeholder_text: &str,
2926 window: &mut Window,
2927 cx: &mut Context<Self>,
2928 ) {
2929 let multibuffer = cx
2930 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2931
2932 let style = window.text_style();
2933
2934 self.placeholder_display_map = Some(cx.new(|cx| {
2935 DisplayMap::new(
2936 multibuffer,
2937 style.font(),
2938 style.font_size.to_pixels(window.rem_size()),
2939 None,
2940 FILE_HEADER_HEIGHT,
2941 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2942 Default::default(),
2943 DiagnosticSeverity::Off,
2944 cx,
2945 )
2946 }));
2947 cx.notify();
2948 }
2949
2950 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2951 self.cursor_shape = cursor_shape;
2952
2953 // Disrupt blink for immediate user feedback that the cursor shape has changed
2954 self.blink_manager.update(cx, BlinkManager::show_cursor);
2955
2956 cx.notify();
2957 }
2958
2959 pub fn set_current_line_highlight(
2960 &mut self,
2961 current_line_highlight: Option<CurrentLineHighlight>,
2962 ) {
2963 self.current_line_highlight = current_line_highlight;
2964 }
2965
2966 pub fn range_for_match<T: std::marker::Copy>(
2967 &self,
2968 range: &Range<T>,
2969 collapse: bool,
2970 ) -> Range<T> {
2971 if collapse {
2972 return range.start..range.start;
2973 }
2974 range.clone()
2975 }
2976
2977 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2978 if self.display_map.read(cx).clip_at_line_ends != clip {
2979 self.display_map
2980 .update(cx, |map, _| map.clip_at_line_ends = clip);
2981 }
2982 }
2983
2984 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2985 self.input_enabled = input_enabled;
2986 }
2987
2988 pub fn set_edit_predictions_hidden_for_vim_mode(
2989 &mut self,
2990 hidden: bool,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 ) {
2994 if hidden != self.edit_predictions_hidden_for_vim_mode {
2995 self.edit_predictions_hidden_for_vim_mode = hidden;
2996 if hidden {
2997 self.update_visible_edit_prediction(window, cx);
2998 } else {
2999 self.refresh_edit_prediction(true, false, window, cx);
3000 }
3001 }
3002 }
3003
3004 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3005 self.menu_edit_predictions_policy = value;
3006 }
3007
3008 pub fn set_autoindent(&mut self, autoindent: bool) {
3009 if autoindent {
3010 self.autoindent_mode = Some(AutoindentMode::EachLine);
3011 } else {
3012 self.autoindent_mode = None;
3013 }
3014 }
3015
3016 pub fn read_only(&self, cx: &App) -> bool {
3017 self.read_only || self.buffer.read(cx).read_only()
3018 }
3019
3020 pub fn set_read_only(&mut self, read_only: bool) {
3021 self.read_only = read_only;
3022 }
3023
3024 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3025 self.use_autoclose = autoclose;
3026 }
3027
3028 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3029 self.use_auto_surround = auto_surround;
3030 }
3031
3032 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3033 self.auto_replace_emoji_shortcode = auto_replace;
3034 }
3035
3036 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3037 self.buffer_serialization = should_serialize.then(|| {
3038 BufferSerialization::new(
3039 ProjectSettings::get_global(cx)
3040 .session
3041 .restore_unsaved_buffers,
3042 )
3043 })
3044 }
3045
3046 fn should_serialize_buffer(&self) -> bool {
3047 self.buffer_serialization.is_some()
3048 }
3049
3050 pub fn toggle_edit_predictions(
3051 &mut self,
3052 _: &ToggleEditPrediction,
3053 window: &mut Window,
3054 cx: &mut Context<Self>,
3055 ) {
3056 if self.show_edit_predictions_override.is_some() {
3057 self.set_show_edit_predictions(None, window, cx);
3058 } else {
3059 let show_edit_predictions = !self.edit_predictions_enabled();
3060 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3061 }
3062 }
3063
3064 pub fn set_show_edit_predictions(
3065 &mut self,
3066 show_edit_predictions: Option<bool>,
3067 window: &mut Window,
3068 cx: &mut Context<Self>,
3069 ) {
3070 self.show_edit_predictions_override = show_edit_predictions;
3071 self.update_edit_prediction_settings(cx);
3072
3073 if let Some(false) = show_edit_predictions {
3074 self.discard_edit_prediction(false, cx);
3075 } else {
3076 self.refresh_edit_prediction(false, true, window, cx);
3077 }
3078 }
3079
3080 fn edit_predictions_disabled_in_scope(
3081 &self,
3082 buffer: &Entity<Buffer>,
3083 buffer_position: language::Anchor,
3084 cx: &App,
3085 ) -> bool {
3086 let snapshot = buffer.read(cx).snapshot();
3087 let settings = snapshot.settings_at(buffer_position, cx);
3088
3089 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3090 return false;
3091 };
3092
3093 scope.override_name().is_some_and(|scope_name| {
3094 settings
3095 .edit_predictions_disabled_in
3096 .iter()
3097 .any(|s| s == scope_name)
3098 })
3099 }
3100
3101 pub fn set_use_modal_editing(&mut self, to: bool) {
3102 self.use_modal_editing = to;
3103 }
3104
3105 pub fn use_modal_editing(&self) -> bool {
3106 self.use_modal_editing
3107 }
3108
3109 fn selections_did_change(
3110 &mut self,
3111 local: bool,
3112 old_cursor_position: &Anchor,
3113 effects: SelectionEffects,
3114 window: &mut Window,
3115 cx: &mut Context<Self>,
3116 ) {
3117 window.invalidate_character_coordinates();
3118
3119 // Copy selections to primary selection buffer
3120 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3121 if local {
3122 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3123 let buffer_handle = self.buffer.read(cx).read(cx);
3124
3125 let mut text = String::new();
3126 for (index, selection) in selections.iter().enumerate() {
3127 let text_for_selection = buffer_handle
3128 .text_for_range(selection.start..selection.end)
3129 .collect::<String>();
3130
3131 text.push_str(&text_for_selection);
3132 if index != selections.len() - 1 {
3133 text.push('\n');
3134 }
3135 }
3136
3137 if !text.is_empty() {
3138 cx.write_to_primary(ClipboardItem::new_string(text));
3139 }
3140 }
3141
3142 let selection_anchors = self.selections.disjoint_anchors_arc();
3143
3144 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3145 self.buffer.update(cx, |buffer, cx| {
3146 buffer.set_active_selections(
3147 &selection_anchors,
3148 self.selections.line_mode(),
3149 self.cursor_shape,
3150 cx,
3151 )
3152 });
3153 }
3154 let display_map = self
3155 .display_map
3156 .update(cx, |display_map, cx| display_map.snapshot(cx));
3157 let buffer = display_map.buffer_snapshot();
3158 if self.selections.count() == 1 {
3159 self.add_selections_state = None;
3160 }
3161 self.select_next_state = None;
3162 self.select_prev_state = None;
3163 self.select_syntax_node_history.try_clear();
3164 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3165 self.snippet_stack.invalidate(&selection_anchors, buffer);
3166 self.take_rename(false, window, cx);
3167
3168 let newest_selection = self.selections.newest_anchor();
3169 let new_cursor_position = newest_selection.head();
3170 let selection_start = newest_selection.start;
3171
3172 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3173 self.push_to_nav_history(
3174 *old_cursor_position,
3175 Some(new_cursor_position.to_point(buffer)),
3176 false,
3177 effects.nav_history == Some(true),
3178 cx,
3179 );
3180 }
3181
3182 if local {
3183 if let Some(buffer_id) = new_cursor_position.buffer_id {
3184 self.register_buffer(buffer_id, cx);
3185 }
3186
3187 let mut context_menu = self.context_menu.borrow_mut();
3188 let completion_menu = match context_menu.as_ref() {
3189 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3190 Some(CodeContextMenu::CodeActions(_)) => {
3191 *context_menu = None;
3192 None
3193 }
3194 None => None,
3195 };
3196 let completion_position = completion_menu.map(|menu| menu.initial_position);
3197 drop(context_menu);
3198
3199 if effects.completions
3200 && let Some(completion_position) = completion_position
3201 {
3202 let start_offset = selection_start.to_offset(buffer);
3203 let position_matches = start_offset == completion_position.to_offset(buffer);
3204 let continue_showing = if position_matches {
3205 if self.snippet_stack.is_empty() {
3206 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3207 == Some(CharKind::Word)
3208 } else {
3209 // Snippet choices can be shown even when the cursor is in whitespace.
3210 // Dismissing the menu with actions like backspace is handled by
3211 // invalidation regions.
3212 true
3213 }
3214 } else {
3215 false
3216 };
3217
3218 if continue_showing {
3219 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3220 } else {
3221 self.hide_context_menu(window, cx);
3222 }
3223 }
3224
3225 hide_hover(self, cx);
3226
3227 if old_cursor_position.to_display_point(&display_map).row()
3228 != new_cursor_position.to_display_point(&display_map).row()
3229 {
3230 self.available_code_actions.take();
3231 }
3232 self.refresh_code_actions(window, cx);
3233 self.refresh_document_highlights(cx);
3234 refresh_linked_ranges(self, window, cx);
3235
3236 self.refresh_selected_text_highlights(false, window, cx);
3237 self.refresh_matching_bracket_highlights(window, cx);
3238 self.update_visible_edit_prediction(window, cx);
3239 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3240 self.inline_blame_popover.take();
3241 if self.git_blame_inline_enabled {
3242 self.start_inline_blame_timer(window, cx);
3243 }
3244 }
3245
3246 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3247 cx.emit(EditorEvent::SelectionsChanged { local });
3248
3249 let selections = &self.selections.disjoint_anchors_arc();
3250 if selections.len() == 1 {
3251 cx.emit(SearchEvent::ActiveMatchChanged)
3252 }
3253 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3254 let inmemory_selections = selections
3255 .iter()
3256 .map(|s| {
3257 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3258 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3259 })
3260 .collect();
3261 self.update_restoration_data(cx, |data| {
3262 data.selections = inmemory_selections;
3263 });
3264
3265 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3266 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3267 {
3268 let snapshot = self.buffer().read(cx).snapshot(cx);
3269 let selections = selections.clone();
3270 let background_executor = cx.background_executor().clone();
3271 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3272 self.serialize_selections = cx.background_spawn(async move {
3273 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3274 let db_selections = selections
3275 .iter()
3276 .map(|selection| {
3277 (
3278 selection.start.to_offset(&snapshot),
3279 selection.end.to_offset(&snapshot),
3280 )
3281 })
3282 .collect();
3283
3284 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3285 .await
3286 .with_context(|| {
3287 format!(
3288 "persisting editor selections for editor {editor_id}, \
3289 workspace {workspace_id:?}"
3290 )
3291 })
3292 .log_err();
3293 });
3294 }
3295 }
3296
3297 cx.notify();
3298 }
3299
3300 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3301 use text::ToOffset as _;
3302 use text::ToPoint as _;
3303
3304 if self.mode.is_minimap()
3305 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3306 {
3307 return;
3308 }
3309
3310 if !self.buffer().read(cx).is_singleton() {
3311 return;
3312 }
3313
3314 let display_snapshot = self
3315 .display_map
3316 .update(cx, |display_map, cx| display_map.snapshot(cx));
3317 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3318 return;
3319 };
3320 let inmemory_folds = display_snapshot
3321 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3322 .map(|fold| {
3323 fold.range.start.text_anchor.to_point(&snapshot)
3324 ..fold.range.end.text_anchor.to_point(&snapshot)
3325 })
3326 .collect();
3327 self.update_restoration_data(cx, |data| {
3328 data.folds = inmemory_folds;
3329 });
3330
3331 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3332 return;
3333 };
3334 let background_executor = cx.background_executor().clone();
3335 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3336 let db_folds = display_snapshot
3337 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3338 .map(|fold| {
3339 (
3340 fold.range.start.text_anchor.to_offset(&snapshot),
3341 fold.range.end.text_anchor.to_offset(&snapshot),
3342 )
3343 })
3344 .collect();
3345 self.serialize_folds = cx.background_spawn(async move {
3346 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3347 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3348 .await
3349 .with_context(|| {
3350 format!(
3351 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3352 )
3353 })
3354 .log_err();
3355 });
3356 }
3357
3358 pub fn sync_selections(
3359 &mut self,
3360 other: Entity<Editor>,
3361 cx: &mut Context<Self>,
3362 ) -> gpui::Subscription {
3363 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3364 if !other_selections.is_empty() {
3365 self.selections
3366 .change_with(&self.display_snapshot(cx), |selections| {
3367 selections.select_anchors(other_selections);
3368 });
3369 }
3370
3371 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3372 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3373 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3374 if other_selections.is_empty() {
3375 return;
3376 }
3377 let snapshot = this.display_snapshot(cx);
3378 this.selections.change_with(&snapshot, |selections| {
3379 selections.select_anchors(other_selections);
3380 });
3381 }
3382 });
3383
3384 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3385 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3386 let these_selections = this.selections.disjoint_anchors().to_vec();
3387 if these_selections.is_empty() {
3388 return;
3389 }
3390 other.update(cx, |other_editor, cx| {
3391 let snapshot = other_editor.display_snapshot(cx);
3392 other_editor
3393 .selections
3394 .change_with(&snapshot, |selections| {
3395 selections.select_anchors(these_selections);
3396 })
3397 });
3398 }
3399 });
3400
3401 Subscription::join(other_subscription, this_subscription)
3402 }
3403
3404 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3405 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3406 /// effects of selection change occur at the end of the transaction.
3407 pub fn change_selections<R>(
3408 &mut self,
3409 effects: SelectionEffects,
3410 window: &mut Window,
3411 cx: &mut Context<Self>,
3412 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3413 ) -> R {
3414 let snapshot = self.display_snapshot(cx);
3415 if let Some(state) = &mut self.deferred_selection_effects_state {
3416 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3417 state.effects.completions = effects.completions;
3418 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3419 let (changed, result) = self.selections.change_with(&snapshot, change);
3420 state.changed |= changed;
3421 return result;
3422 }
3423 let mut state = DeferredSelectionEffectsState {
3424 changed: false,
3425 effects,
3426 old_cursor_position: self.selections.newest_anchor().head(),
3427 history_entry: SelectionHistoryEntry {
3428 selections: self.selections.disjoint_anchors_arc(),
3429 select_next_state: self.select_next_state.clone(),
3430 select_prev_state: self.select_prev_state.clone(),
3431 add_selections_state: self.add_selections_state.clone(),
3432 },
3433 };
3434 let (changed, result) = self.selections.change_with(&snapshot, change);
3435 state.changed = state.changed || changed;
3436 if self.defer_selection_effects {
3437 self.deferred_selection_effects_state = Some(state);
3438 } else {
3439 self.apply_selection_effects(state, window, cx);
3440 }
3441 result
3442 }
3443
3444 /// Defers the effects of selection change, so that the effects of multiple calls to
3445 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3446 /// to selection history and the state of popovers based on selection position aren't
3447 /// erroneously updated.
3448 pub fn with_selection_effects_deferred<R>(
3449 &mut self,
3450 window: &mut Window,
3451 cx: &mut Context<Self>,
3452 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3453 ) -> R {
3454 let already_deferred = self.defer_selection_effects;
3455 self.defer_selection_effects = true;
3456 let result = update(self, window, cx);
3457 if !already_deferred {
3458 self.defer_selection_effects = false;
3459 if let Some(state) = self.deferred_selection_effects_state.take() {
3460 self.apply_selection_effects(state, window, cx);
3461 }
3462 }
3463 result
3464 }
3465
3466 fn apply_selection_effects(
3467 &mut self,
3468 state: DeferredSelectionEffectsState,
3469 window: &mut Window,
3470 cx: &mut Context<Self>,
3471 ) {
3472 if state.changed {
3473 self.selection_history.push(state.history_entry);
3474
3475 if let Some(autoscroll) = state.effects.scroll {
3476 self.request_autoscroll(autoscroll, cx);
3477 }
3478
3479 let old_cursor_position = &state.old_cursor_position;
3480
3481 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3482
3483 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3484 self.show_signature_help(&ShowSignatureHelp, window, cx);
3485 }
3486 }
3487 }
3488
3489 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3490 where
3491 I: IntoIterator<Item = (Range<S>, T)>,
3492 S: ToOffset,
3493 T: Into<Arc<str>>,
3494 {
3495 if self.read_only(cx) {
3496 return;
3497 }
3498
3499 self.buffer
3500 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3501 }
3502
3503 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3504 where
3505 I: IntoIterator<Item = (Range<S>, T)>,
3506 S: ToOffset,
3507 T: Into<Arc<str>>,
3508 {
3509 if self.read_only(cx) {
3510 return;
3511 }
3512
3513 self.buffer.update(cx, |buffer, cx| {
3514 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3515 });
3516 }
3517
3518 pub fn edit_with_block_indent<I, S, T>(
3519 &mut self,
3520 edits: I,
3521 original_indent_columns: Vec<Option<u32>>,
3522 cx: &mut Context<Self>,
3523 ) where
3524 I: IntoIterator<Item = (Range<S>, T)>,
3525 S: ToOffset,
3526 T: Into<Arc<str>>,
3527 {
3528 if self.read_only(cx) {
3529 return;
3530 }
3531
3532 self.buffer.update(cx, |buffer, cx| {
3533 buffer.edit(
3534 edits,
3535 Some(AutoindentMode::Block {
3536 original_indent_columns,
3537 }),
3538 cx,
3539 )
3540 });
3541 }
3542
3543 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3544 self.hide_context_menu(window, cx);
3545
3546 match phase {
3547 SelectPhase::Begin {
3548 position,
3549 add,
3550 click_count,
3551 } => self.begin_selection(position, add, click_count, window, cx),
3552 SelectPhase::BeginColumnar {
3553 position,
3554 goal_column,
3555 reset,
3556 mode,
3557 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3558 SelectPhase::Extend {
3559 position,
3560 click_count,
3561 } => self.extend_selection(position, click_count, window, cx),
3562 SelectPhase::Update {
3563 position,
3564 goal_column,
3565 scroll_delta,
3566 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3567 SelectPhase::End => self.end_selection(window, cx),
3568 }
3569 }
3570
3571 fn extend_selection(
3572 &mut self,
3573 position: DisplayPoint,
3574 click_count: usize,
3575 window: &mut Window,
3576 cx: &mut Context<Self>,
3577 ) {
3578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3579 let tail = self.selections.newest::<usize>(&display_map).tail();
3580 let click_count = click_count.max(match self.selections.select_mode() {
3581 SelectMode::Character => 1,
3582 SelectMode::Word(_) => 2,
3583 SelectMode::Line(_) => 3,
3584 SelectMode::All => 4,
3585 });
3586 self.begin_selection(position, false, click_count, window, cx);
3587
3588 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3589
3590 let current_selection = match self.selections.select_mode() {
3591 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3592 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3593 };
3594
3595 let mut pending_selection = self
3596 .selections
3597 .pending_anchor()
3598 .cloned()
3599 .expect("extend_selection not called with pending selection");
3600
3601 if pending_selection
3602 .start
3603 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3604 == Ordering::Greater
3605 {
3606 pending_selection.start = current_selection.start;
3607 }
3608 if pending_selection
3609 .end
3610 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3611 == Ordering::Less
3612 {
3613 pending_selection.end = current_selection.end;
3614 pending_selection.reversed = true;
3615 }
3616
3617 let mut pending_mode = self.selections.pending_mode().unwrap();
3618 match &mut pending_mode {
3619 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3620 _ => {}
3621 }
3622
3623 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3624 SelectionEffects::scroll(Autoscroll::fit())
3625 } else {
3626 SelectionEffects::no_scroll()
3627 };
3628
3629 self.change_selections(effects, window, cx, |s| {
3630 s.set_pending(pending_selection.clone(), pending_mode);
3631 s.set_is_extending(true);
3632 });
3633 }
3634
3635 fn begin_selection(
3636 &mut self,
3637 position: DisplayPoint,
3638 add: bool,
3639 click_count: usize,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 if !self.focus_handle.is_focused(window) {
3644 self.last_focused_descendant = None;
3645 window.focus(&self.focus_handle);
3646 }
3647
3648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3649 let buffer = display_map.buffer_snapshot();
3650 let position = display_map.clip_point(position, Bias::Left);
3651
3652 let start;
3653 let end;
3654 let mode;
3655 let mut auto_scroll;
3656 match click_count {
3657 1 => {
3658 start = buffer.anchor_before(position.to_point(&display_map));
3659 end = start;
3660 mode = SelectMode::Character;
3661 auto_scroll = true;
3662 }
3663 2 => {
3664 let position = display_map
3665 .clip_point(position, Bias::Left)
3666 .to_offset(&display_map, Bias::Left);
3667 let (range, _) = buffer.surrounding_word(position, None);
3668 start = buffer.anchor_before(range.start);
3669 end = buffer.anchor_before(range.end);
3670 mode = SelectMode::Word(start..end);
3671 auto_scroll = true;
3672 }
3673 3 => {
3674 let position = display_map
3675 .clip_point(position, Bias::Left)
3676 .to_point(&display_map);
3677 let line_start = display_map.prev_line_boundary(position).0;
3678 let next_line_start = buffer.clip_point(
3679 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3680 Bias::Left,
3681 );
3682 start = buffer.anchor_before(line_start);
3683 end = buffer.anchor_before(next_line_start);
3684 mode = SelectMode::Line(start..end);
3685 auto_scroll = true;
3686 }
3687 _ => {
3688 start = buffer.anchor_before(0);
3689 end = buffer.anchor_before(buffer.len());
3690 mode = SelectMode::All;
3691 auto_scroll = false;
3692 }
3693 }
3694 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3695
3696 let point_to_delete: Option<usize> = {
3697 let selected_points: Vec<Selection<Point>> =
3698 self.selections.disjoint_in_range(start..end, &display_map);
3699
3700 if !add || click_count > 1 {
3701 None
3702 } else if !selected_points.is_empty() {
3703 Some(selected_points[0].id)
3704 } else {
3705 let clicked_point_already_selected =
3706 self.selections.disjoint_anchors().iter().find(|selection| {
3707 selection.start.to_point(buffer) == start.to_point(buffer)
3708 || selection.end.to_point(buffer) == end.to_point(buffer)
3709 });
3710
3711 clicked_point_already_selected.map(|selection| selection.id)
3712 }
3713 };
3714
3715 let selections_count = self.selections.count();
3716 let effects = if auto_scroll {
3717 SelectionEffects::default()
3718 } else {
3719 SelectionEffects::no_scroll()
3720 };
3721
3722 self.change_selections(effects, window, cx, |s| {
3723 if let Some(point_to_delete) = point_to_delete {
3724 s.delete(point_to_delete);
3725
3726 if selections_count == 1 {
3727 s.set_pending_anchor_range(start..end, mode);
3728 }
3729 } else {
3730 if !add {
3731 s.clear_disjoint();
3732 }
3733
3734 s.set_pending_anchor_range(start..end, mode);
3735 }
3736 });
3737 }
3738
3739 fn begin_columnar_selection(
3740 &mut self,
3741 position: DisplayPoint,
3742 goal_column: u32,
3743 reset: bool,
3744 mode: ColumnarMode,
3745 window: &mut Window,
3746 cx: &mut Context<Self>,
3747 ) {
3748 if !self.focus_handle.is_focused(window) {
3749 self.last_focused_descendant = None;
3750 window.focus(&self.focus_handle);
3751 }
3752
3753 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3754
3755 if reset {
3756 let pointer_position = display_map
3757 .buffer_snapshot()
3758 .anchor_before(position.to_point(&display_map));
3759
3760 self.change_selections(
3761 SelectionEffects::scroll(Autoscroll::newest()),
3762 window,
3763 cx,
3764 |s| {
3765 s.clear_disjoint();
3766 s.set_pending_anchor_range(
3767 pointer_position..pointer_position,
3768 SelectMode::Character,
3769 );
3770 },
3771 );
3772 };
3773
3774 let tail = self.selections.newest::<Point>(&display_map).tail();
3775 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3776 self.columnar_selection_state = match mode {
3777 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3778 selection_tail: selection_anchor,
3779 display_point: if reset {
3780 if position.column() != goal_column {
3781 Some(DisplayPoint::new(position.row(), goal_column))
3782 } else {
3783 None
3784 }
3785 } else {
3786 None
3787 },
3788 }),
3789 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3790 selection_tail: selection_anchor,
3791 }),
3792 };
3793
3794 if !reset {
3795 self.select_columns(position, goal_column, &display_map, window, cx);
3796 }
3797 }
3798
3799 fn update_selection(
3800 &mut self,
3801 position: DisplayPoint,
3802 goal_column: u32,
3803 scroll_delta: gpui::Point<f32>,
3804 window: &mut Window,
3805 cx: &mut Context<Self>,
3806 ) {
3807 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3808
3809 if self.columnar_selection_state.is_some() {
3810 self.select_columns(position, goal_column, &display_map, window, cx);
3811 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3812 let buffer = display_map.buffer_snapshot();
3813 let head;
3814 let tail;
3815 let mode = self.selections.pending_mode().unwrap();
3816 match &mode {
3817 SelectMode::Character => {
3818 head = position.to_point(&display_map);
3819 tail = pending.tail().to_point(buffer);
3820 }
3821 SelectMode::Word(original_range) => {
3822 let offset = display_map
3823 .clip_point(position, Bias::Left)
3824 .to_offset(&display_map, Bias::Left);
3825 let original_range = original_range.to_offset(buffer);
3826
3827 let head_offset = if buffer.is_inside_word(offset, None)
3828 || original_range.contains(&offset)
3829 {
3830 let (word_range, _) = buffer.surrounding_word(offset, None);
3831 if word_range.start < original_range.start {
3832 word_range.start
3833 } else {
3834 word_range.end
3835 }
3836 } else {
3837 offset
3838 };
3839
3840 head = head_offset.to_point(buffer);
3841 if head_offset <= original_range.start {
3842 tail = original_range.end.to_point(buffer);
3843 } else {
3844 tail = original_range.start.to_point(buffer);
3845 }
3846 }
3847 SelectMode::Line(original_range) => {
3848 let original_range = original_range.to_point(display_map.buffer_snapshot());
3849
3850 let position = display_map
3851 .clip_point(position, Bias::Left)
3852 .to_point(&display_map);
3853 let line_start = display_map.prev_line_boundary(position).0;
3854 let next_line_start = buffer.clip_point(
3855 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3856 Bias::Left,
3857 );
3858
3859 if line_start < original_range.start {
3860 head = line_start
3861 } else {
3862 head = next_line_start
3863 }
3864
3865 if head <= original_range.start {
3866 tail = original_range.end;
3867 } else {
3868 tail = original_range.start;
3869 }
3870 }
3871 SelectMode::All => {
3872 return;
3873 }
3874 };
3875
3876 if head < tail {
3877 pending.start = buffer.anchor_before(head);
3878 pending.end = buffer.anchor_before(tail);
3879 pending.reversed = true;
3880 } else {
3881 pending.start = buffer.anchor_before(tail);
3882 pending.end = buffer.anchor_before(head);
3883 pending.reversed = false;
3884 }
3885
3886 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3887 s.set_pending(pending.clone(), mode);
3888 });
3889 } else {
3890 log::error!("update_selection dispatched with no pending selection");
3891 return;
3892 }
3893
3894 self.apply_scroll_delta(scroll_delta, window, cx);
3895 cx.notify();
3896 }
3897
3898 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3899 self.columnar_selection_state.take();
3900 if let Some(pending_mode) = self.selections.pending_mode() {
3901 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3902 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3903 s.select(selections);
3904 s.clear_pending();
3905 if s.is_extending() {
3906 s.set_is_extending(false);
3907 } else {
3908 s.set_select_mode(pending_mode);
3909 }
3910 });
3911 }
3912 }
3913
3914 fn select_columns(
3915 &mut self,
3916 head: DisplayPoint,
3917 goal_column: u32,
3918 display_map: &DisplaySnapshot,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) {
3922 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3923 return;
3924 };
3925
3926 let tail = match columnar_state {
3927 ColumnarSelectionState::FromMouse {
3928 selection_tail,
3929 display_point,
3930 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3931 ColumnarSelectionState::FromSelection { selection_tail } => {
3932 selection_tail.to_display_point(display_map)
3933 }
3934 };
3935
3936 let start_row = cmp::min(tail.row(), head.row());
3937 let end_row = cmp::max(tail.row(), head.row());
3938 let start_column = cmp::min(tail.column(), goal_column);
3939 let end_column = cmp::max(tail.column(), goal_column);
3940 let reversed = start_column < tail.column();
3941
3942 let selection_ranges = (start_row.0..=end_row.0)
3943 .map(DisplayRow)
3944 .filter_map(|row| {
3945 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3946 || start_column <= display_map.line_len(row))
3947 && !display_map.is_block_line(row)
3948 {
3949 let start = display_map
3950 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3951 .to_point(display_map);
3952 let end = display_map
3953 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3954 .to_point(display_map);
3955 if reversed {
3956 Some(end..start)
3957 } else {
3958 Some(start..end)
3959 }
3960 } else {
3961 None
3962 }
3963 })
3964 .collect::<Vec<_>>();
3965 if selection_ranges.is_empty() {
3966 return;
3967 }
3968
3969 let ranges = match columnar_state {
3970 ColumnarSelectionState::FromMouse { .. } => {
3971 let mut non_empty_ranges = selection_ranges
3972 .iter()
3973 .filter(|selection_range| selection_range.start != selection_range.end)
3974 .peekable();
3975 if non_empty_ranges.peek().is_some() {
3976 non_empty_ranges.cloned().collect()
3977 } else {
3978 selection_ranges
3979 }
3980 }
3981 _ => selection_ranges,
3982 };
3983
3984 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3985 s.select_ranges(ranges);
3986 });
3987 cx.notify();
3988 }
3989
3990 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3991 self.selections
3992 .all_adjusted(snapshot)
3993 .iter()
3994 .any(|selection| !selection.is_empty())
3995 }
3996
3997 pub fn has_pending_nonempty_selection(&self) -> bool {
3998 let pending_nonempty_selection = match self.selections.pending_anchor() {
3999 Some(Selection { start, end, .. }) => start != end,
4000 None => false,
4001 };
4002
4003 pending_nonempty_selection
4004 || (self.columnar_selection_state.is_some()
4005 && self.selections.disjoint_anchors().len() > 1)
4006 }
4007
4008 pub fn has_pending_selection(&self) -> bool {
4009 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4010 }
4011
4012 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4013 self.selection_mark_mode = false;
4014 self.selection_drag_state = SelectionDragState::None;
4015
4016 if self.clear_expanded_diff_hunks(cx) {
4017 cx.notify();
4018 return;
4019 }
4020 if self.dismiss_menus_and_popups(true, window, cx) {
4021 return;
4022 }
4023
4024 if self.mode.is_full()
4025 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4026 {
4027 return;
4028 }
4029
4030 cx.propagate();
4031 }
4032
4033 pub fn dismiss_menus_and_popups(
4034 &mut self,
4035 is_user_requested: bool,
4036 window: &mut Window,
4037 cx: &mut Context<Self>,
4038 ) -> bool {
4039 if self.take_rename(false, window, cx).is_some() {
4040 return true;
4041 }
4042
4043 if self.hide_blame_popover(true, cx) {
4044 return true;
4045 }
4046
4047 if hide_hover(self, cx) {
4048 return true;
4049 }
4050
4051 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4052 return true;
4053 }
4054
4055 if self.hide_context_menu(window, cx).is_some() {
4056 return true;
4057 }
4058
4059 if self.mouse_context_menu.take().is_some() {
4060 return true;
4061 }
4062
4063 if is_user_requested && self.discard_edit_prediction(true, cx) {
4064 return true;
4065 }
4066
4067 if self.snippet_stack.pop().is_some() {
4068 return true;
4069 }
4070
4071 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4072 self.dismiss_diagnostics(cx);
4073 return true;
4074 }
4075
4076 false
4077 }
4078
4079 fn linked_editing_ranges_for(
4080 &self,
4081 selection: Range<text::Anchor>,
4082 cx: &App,
4083 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4084 if self.linked_edit_ranges.is_empty() {
4085 return None;
4086 }
4087 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4088 selection.end.buffer_id.and_then(|end_buffer_id| {
4089 if selection.start.buffer_id != Some(end_buffer_id) {
4090 return None;
4091 }
4092 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4093 let snapshot = buffer.read(cx).snapshot();
4094 self.linked_edit_ranges
4095 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4096 .map(|ranges| (ranges, snapshot, buffer))
4097 })?;
4098 use text::ToOffset as TO;
4099 // find offset from the start of current range to current cursor position
4100 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4101
4102 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4103 let start_difference = start_offset - start_byte_offset;
4104 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4105 let end_difference = end_offset - start_byte_offset;
4106 // Current range has associated linked ranges.
4107 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4108 for range in linked_ranges.iter() {
4109 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4110 let end_offset = start_offset + end_difference;
4111 let start_offset = start_offset + start_difference;
4112 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4113 continue;
4114 }
4115 if self.selections.disjoint_anchor_ranges().any(|s| {
4116 if s.start.buffer_id != selection.start.buffer_id
4117 || s.end.buffer_id != selection.end.buffer_id
4118 {
4119 return false;
4120 }
4121 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4122 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4123 }) {
4124 continue;
4125 }
4126 let start = buffer_snapshot.anchor_after(start_offset);
4127 let end = buffer_snapshot.anchor_after(end_offset);
4128 linked_edits
4129 .entry(buffer.clone())
4130 .or_default()
4131 .push(start..end);
4132 }
4133 Some(linked_edits)
4134 }
4135
4136 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4137 let text: Arc<str> = text.into();
4138
4139 if self.read_only(cx) {
4140 return;
4141 }
4142
4143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4144
4145 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4146 let mut bracket_inserted = false;
4147 let mut edits = Vec::new();
4148 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4149 let mut new_selections = Vec::with_capacity(selections.len());
4150 let mut new_autoclose_regions = Vec::new();
4151 let snapshot = self.buffer.read(cx).read(cx);
4152 let mut clear_linked_edit_ranges = false;
4153
4154 for (selection, autoclose_region) in
4155 self.selections_with_autoclose_regions(selections, &snapshot)
4156 {
4157 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4158 // Determine if the inserted text matches the opening or closing
4159 // bracket of any of this language's bracket pairs.
4160 let mut bracket_pair = None;
4161 let mut is_bracket_pair_start = false;
4162 let mut is_bracket_pair_end = false;
4163 if !text.is_empty() {
4164 let mut bracket_pair_matching_end = None;
4165 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4166 // and they are removing the character that triggered IME popup.
4167 for (pair, enabled) in scope.brackets() {
4168 if !pair.close && !pair.surround {
4169 continue;
4170 }
4171
4172 if enabled && pair.start.ends_with(text.as_ref()) {
4173 let prefix_len = pair.start.len() - text.len();
4174 let preceding_text_matches_prefix = prefix_len == 0
4175 || (selection.start.column >= (prefix_len as u32)
4176 && snapshot.contains_str_at(
4177 Point::new(
4178 selection.start.row,
4179 selection.start.column - (prefix_len as u32),
4180 ),
4181 &pair.start[..prefix_len],
4182 ));
4183 if preceding_text_matches_prefix {
4184 bracket_pair = Some(pair.clone());
4185 is_bracket_pair_start = true;
4186 break;
4187 }
4188 }
4189 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4190 {
4191 // take first bracket pair matching end, but don't break in case a later bracket
4192 // pair matches start
4193 bracket_pair_matching_end = Some(pair.clone());
4194 }
4195 }
4196 if let Some(end) = bracket_pair_matching_end
4197 && bracket_pair.is_none()
4198 {
4199 bracket_pair = Some(end);
4200 is_bracket_pair_end = true;
4201 }
4202 }
4203
4204 if let Some(bracket_pair) = bracket_pair {
4205 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4206 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4207 let auto_surround =
4208 self.use_auto_surround && snapshot_settings.use_auto_surround;
4209 if selection.is_empty() {
4210 if is_bracket_pair_start {
4211 // If the inserted text is a suffix of an opening bracket and the
4212 // selection is preceded by the rest of the opening bracket, then
4213 // insert the closing bracket.
4214 let following_text_allows_autoclose = snapshot
4215 .chars_at(selection.start)
4216 .next()
4217 .is_none_or(|c| scope.should_autoclose_before(c));
4218
4219 let preceding_text_allows_autoclose = selection.start.column == 0
4220 || snapshot
4221 .reversed_chars_at(selection.start)
4222 .next()
4223 .is_none_or(|c| {
4224 bracket_pair.start != bracket_pair.end
4225 || !snapshot
4226 .char_classifier_at(selection.start)
4227 .is_word(c)
4228 });
4229
4230 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4231 && bracket_pair.start.len() == 1
4232 {
4233 let target = bracket_pair.start.chars().next().unwrap();
4234 let current_line_count = snapshot
4235 .reversed_chars_at(selection.start)
4236 .take_while(|&c| c != '\n')
4237 .filter(|&c| c == target)
4238 .count();
4239 current_line_count % 2 == 1
4240 } else {
4241 false
4242 };
4243
4244 if autoclose
4245 && bracket_pair.close
4246 && following_text_allows_autoclose
4247 && preceding_text_allows_autoclose
4248 && !is_closing_quote
4249 {
4250 let anchor = snapshot.anchor_before(selection.end);
4251 new_selections.push((selection.map(|_| anchor), text.len()));
4252 new_autoclose_regions.push((
4253 anchor,
4254 text.len(),
4255 selection.id,
4256 bracket_pair.clone(),
4257 ));
4258 edits.push((
4259 selection.range(),
4260 format!("{}{}", text, bracket_pair.end).into(),
4261 ));
4262 bracket_inserted = true;
4263 continue;
4264 }
4265 }
4266
4267 if let Some(region) = autoclose_region {
4268 // If the selection is followed by an auto-inserted closing bracket,
4269 // then don't insert that closing bracket again; just move the selection
4270 // past the closing bracket.
4271 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4272 && text.as_ref() == region.pair.end.as_str()
4273 && snapshot.contains_str_at(region.range.end, text.as_ref());
4274 if should_skip {
4275 let anchor = snapshot.anchor_after(selection.end);
4276 new_selections
4277 .push((selection.map(|_| anchor), region.pair.end.len()));
4278 continue;
4279 }
4280 }
4281
4282 let always_treat_brackets_as_autoclosed = snapshot
4283 .language_settings_at(selection.start, cx)
4284 .always_treat_brackets_as_autoclosed;
4285 if always_treat_brackets_as_autoclosed
4286 && is_bracket_pair_end
4287 && snapshot.contains_str_at(selection.end, text.as_ref())
4288 {
4289 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4290 // and the inserted text is a closing bracket and the selection is followed
4291 // by the closing bracket then move the selection past the closing bracket.
4292 let anchor = snapshot.anchor_after(selection.end);
4293 new_selections.push((selection.map(|_| anchor), text.len()));
4294 continue;
4295 }
4296 }
4297 // If an opening bracket is 1 character long and is typed while
4298 // text is selected, then surround that text with the bracket pair.
4299 else if auto_surround
4300 && bracket_pair.surround
4301 && is_bracket_pair_start
4302 && bracket_pair.start.chars().count() == 1
4303 {
4304 edits.push((selection.start..selection.start, text.clone()));
4305 edits.push((
4306 selection.end..selection.end,
4307 bracket_pair.end.as_str().into(),
4308 ));
4309 bracket_inserted = true;
4310 new_selections.push((
4311 Selection {
4312 id: selection.id,
4313 start: snapshot.anchor_after(selection.start),
4314 end: snapshot.anchor_before(selection.end),
4315 reversed: selection.reversed,
4316 goal: selection.goal,
4317 },
4318 0,
4319 ));
4320 continue;
4321 }
4322 }
4323 }
4324
4325 if self.auto_replace_emoji_shortcode
4326 && selection.is_empty()
4327 && text.as_ref().ends_with(':')
4328 && let Some(possible_emoji_short_code) =
4329 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4330 && !possible_emoji_short_code.is_empty()
4331 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4332 {
4333 let emoji_shortcode_start = Point::new(
4334 selection.start.row,
4335 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4336 );
4337
4338 // Remove shortcode from buffer
4339 edits.push((
4340 emoji_shortcode_start..selection.start,
4341 "".to_string().into(),
4342 ));
4343 new_selections.push((
4344 Selection {
4345 id: selection.id,
4346 start: snapshot.anchor_after(emoji_shortcode_start),
4347 end: snapshot.anchor_before(selection.start),
4348 reversed: selection.reversed,
4349 goal: selection.goal,
4350 },
4351 0,
4352 ));
4353
4354 // Insert emoji
4355 let selection_start_anchor = snapshot.anchor_after(selection.start);
4356 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4357 edits.push((selection.start..selection.end, emoji.to_string().into()));
4358
4359 continue;
4360 }
4361
4362 // If not handling any auto-close operation, then just replace the selected
4363 // text with the given input and move the selection to the end of the
4364 // newly inserted text.
4365 let anchor = snapshot.anchor_after(selection.end);
4366 if !self.linked_edit_ranges.is_empty() {
4367 let start_anchor = snapshot.anchor_before(selection.start);
4368
4369 let is_word_char = text.chars().next().is_none_or(|char| {
4370 let classifier = snapshot
4371 .char_classifier_at(start_anchor.to_offset(&snapshot))
4372 .scope_context(Some(CharScopeContext::LinkedEdit));
4373 classifier.is_word(char)
4374 });
4375
4376 if is_word_char {
4377 if let Some(ranges) = self
4378 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4379 {
4380 for (buffer, edits) in ranges {
4381 linked_edits
4382 .entry(buffer.clone())
4383 .or_default()
4384 .extend(edits.into_iter().map(|range| (range, text.clone())));
4385 }
4386 }
4387 } else {
4388 clear_linked_edit_ranges = true;
4389 }
4390 }
4391
4392 new_selections.push((selection.map(|_| anchor), 0));
4393 edits.push((selection.start..selection.end, text.clone()));
4394 }
4395
4396 drop(snapshot);
4397
4398 self.transact(window, cx, |this, window, cx| {
4399 if clear_linked_edit_ranges {
4400 this.linked_edit_ranges.clear();
4401 }
4402 let initial_buffer_versions =
4403 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4404
4405 this.buffer.update(cx, |buffer, cx| {
4406 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4407 });
4408 for (buffer, edits) in linked_edits {
4409 buffer.update(cx, |buffer, cx| {
4410 let snapshot = buffer.snapshot();
4411 let edits = edits
4412 .into_iter()
4413 .map(|(range, text)| {
4414 use text::ToPoint as TP;
4415 let end_point = TP::to_point(&range.end, &snapshot);
4416 let start_point = TP::to_point(&range.start, &snapshot);
4417 (start_point..end_point, text)
4418 })
4419 .sorted_by_key(|(range, _)| range.start);
4420 buffer.edit(edits, None, cx);
4421 })
4422 }
4423 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4424 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4425 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4426 let new_selections =
4427 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4428 .zip(new_selection_deltas)
4429 .map(|(selection, delta)| Selection {
4430 id: selection.id,
4431 start: selection.start + delta,
4432 end: selection.end + delta,
4433 reversed: selection.reversed,
4434 goal: SelectionGoal::None,
4435 })
4436 .collect::<Vec<_>>();
4437
4438 let mut i = 0;
4439 for (position, delta, selection_id, pair) in new_autoclose_regions {
4440 let position = position.to_offset(map.buffer_snapshot()) + delta;
4441 let start = map.buffer_snapshot().anchor_before(position);
4442 let end = map.buffer_snapshot().anchor_after(position);
4443 while let Some(existing_state) = this.autoclose_regions.get(i) {
4444 match existing_state
4445 .range
4446 .start
4447 .cmp(&start, map.buffer_snapshot())
4448 {
4449 Ordering::Less => i += 1,
4450 Ordering::Greater => break,
4451 Ordering::Equal => {
4452 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4453 Ordering::Less => i += 1,
4454 Ordering::Equal => break,
4455 Ordering::Greater => break,
4456 }
4457 }
4458 }
4459 }
4460 this.autoclose_regions.insert(
4461 i,
4462 AutocloseRegion {
4463 selection_id,
4464 range: start..end,
4465 pair,
4466 },
4467 );
4468 }
4469
4470 let had_active_edit_prediction = this.has_active_edit_prediction();
4471 this.change_selections(
4472 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4473 window,
4474 cx,
4475 |s| s.select(new_selections),
4476 );
4477
4478 if !bracket_inserted
4479 && let Some(on_type_format_task) =
4480 this.trigger_on_type_formatting(text.to_string(), window, cx)
4481 {
4482 on_type_format_task.detach_and_log_err(cx);
4483 }
4484
4485 let editor_settings = EditorSettings::get_global(cx);
4486 if bracket_inserted
4487 && (editor_settings.auto_signature_help
4488 || editor_settings.show_signature_help_after_edits)
4489 {
4490 this.show_signature_help(&ShowSignatureHelp, window, cx);
4491 }
4492
4493 let trigger_in_words =
4494 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4495 if this.hard_wrap.is_some() {
4496 let latest: Range<Point> = this.selections.newest(&map).range();
4497 if latest.is_empty()
4498 && this
4499 .buffer()
4500 .read(cx)
4501 .snapshot(cx)
4502 .line_len(MultiBufferRow(latest.start.row))
4503 == latest.start.column
4504 {
4505 this.rewrap_impl(
4506 RewrapOptions {
4507 override_language_settings: true,
4508 preserve_existing_whitespace: true,
4509 },
4510 cx,
4511 )
4512 }
4513 }
4514 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4515 refresh_linked_ranges(this, window, cx);
4516 this.refresh_edit_prediction(true, false, window, cx);
4517 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4518 });
4519 }
4520
4521 fn find_possible_emoji_shortcode_at_position(
4522 snapshot: &MultiBufferSnapshot,
4523 position: Point,
4524 ) -> Option<String> {
4525 let mut chars = Vec::new();
4526 let mut found_colon = false;
4527 for char in snapshot.reversed_chars_at(position).take(100) {
4528 // Found a possible emoji shortcode in the middle of the buffer
4529 if found_colon {
4530 if char.is_whitespace() {
4531 chars.reverse();
4532 return Some(chars.iter().collect());
4533 }
4534 // If the previous character is not a whitespace, we are in the middle of a word
4535 // and we only want to complete the shortcode if the word is made up of other emojis
4536 let mut containing_word = String::new();
4537 for ch in snapshot
4538 .reversed_chars_at(position)
4539 .skip(chars.len() + 1)
4540 .take(100)
4541 {
4542 if ch.is_whitespace() {
4543 break;
4544 }
4545 containing_word.push(ch);
4546 }
4547 let containing_word = containing_word.chars().rev().collect::<String>();
4548 if util::word_consists_of_emojis(containing_word.as_str()) {
4549 chars.reverse();
4550 return Some(chars.iter().collect());
4551 }
4552 }
4553
4554 if char.is_whitespace() || !char.is_ascii() {
4555 return None;
4556 }
4557 if char == ':' {
4558 found_colon = true;
4559 } else {
4560 chars.push(char);
4561 }
4562 }
4563 // Found a possible emoji shortcode at the beginning of the buffer
4564 chars.reverse();
4565 Some(chars.iter().collect())
4566 }
4567
4568 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4569 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4570 self.transact(window, cx, |this, window, cx| {
4571 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4572 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4573 let multi_buffer = this.buffer.read(cx);
4574 let buffer = multi_buffer.snapshot(cx);
4575 selections
4576 .iter()
4577 .map(|selection| {
4578 let start_point = selection.start.to_point(&buffer);
4579 let mut existing_indent =
4580 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4581 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4582 let start = selection.start;
4583 let end = selection.end;
4584 let selection_is_empty = start == end;
4585 let language_scope = buffer.language_scope_at(start);
4586 let (
4587 comment_delimiter,
4588 doc_delimiter,
4589 insert_extra_newline,
4590 indent_on_newline,
4591 indent_on_extra_newline,
4592 ) = if let Some(language) = &language_scope {
4593 let mut insert_extra_newline =
4594 insert_extra_newline_brackets(&buffer, start..end, language)
4595 || insert_extra_newline_tree_sitter(&buffer, start..end);
4596
4597 // Comment extension on newline is allowed only for cursor selections
4598 let comment_delimiter = maybe!({
4599 if !selection_is_empty {
4600 return None;
4601 }
4602
4603 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4604 return None;
4605 }
4606
4607 let delimiters = language.line_comment_prefixes();
4608 let max_len_of_delimiter =
4609 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4610 let (snapshot, range) =
4611 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4612
4613 let num_of_whitespaces = snapshot
4614 .chars_for_range(range.clone())
4615 .take_while(|c| c.is_whitespace())
4616 .count();
4617 let comment_candidate = snapshot
4618 .chars_for_range(range.clone())
4619 .skip(num_of_whitespaces)
4620 .take(max_len_of_delimiter)
4621 .collect::<String>();
4622 let (delimiter, trimmed_len) = delimiters
4623 .iter()
4624 .filter_map(|delimiter| {
4625 let prefix = delimiter.trim_end();
4626 if comment_candidate.starts_with(prefix) {
4627 Some((delimiter, prefix.len()))
4628 } else {
4629 None
4630 }
4631 })
4632 .max_by_key(|(_, len)| *len)?;
4633
4634 if let Some(BlockCommentConfig {
4635 start: block_start, ..
4636 }) = language.block_comment()
4637 {
4638 let block_start_trimmed = block_start.trim_end();
4639 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4640 let line_content = snapshot
4641 .chars_for_range(range)
4642 .skip(num_of_whitespaces)
4643 .take(block_start_trimmed.len())
4644 .collect::<String>();
4645
4646 if line_content.starts_with(block_start_trimmed) {
4647 return None;
4648 }
4649 }
4650 }
4651
4652 let cursor_is_placed_after_comment_marker =
4653 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4654 if cursor_is_placed_after_comment_marker {
4655 Some(delimiter.clone())
4656 } else {
4657 None
4658 }
4659 });
4660
4661 let mut indent_on_newline = IndentSize::spaces(0);
4662 let mut indent_on_extra_newline = IndentSize::spaces(0);
4663
4664 let doc_delimiter = maybe!({
4665 if !selection_is_empty {
4666 return None;
4667 }
4668
4669 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4670 return None;
4671 }
4672
4673 let BlockCommentConfig {
4674 start: start_tag,
4675 end: end_tag,
4676 prefix: delimiter,
4677 tab_size: len,
4678 } = language.documentation_comment()?;
4679 let is_within_block_comment = buffer
4680 .language_scope_at(start_point)
4681 .is_some_and(|scope| scope.override_name() == Some("comment"));
4682 if !is_within_block_comment {
4683 return None;
4684 }
4685
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
4694 // 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.
4695 let column = start_point.column;
4696 let cursor_is_after_start_tag = {
4697 let start_tag_len = start_tag.len();
4698 let start_tag_line = snapshot
4699 .chars_for_range(range.clone())
4700 .skip(num_of_whitespaces)
4701 .take(start_tag_len)
4702 .collect::<String>();
4703 if start_tag_line.starts_with(start_tag.as_ref()) {
4704 num_of_whitespaces + start_tag_len <= column as usize
4705 } else {
4706 false
4707 }
4708 };
4709
4710 let cursor_is_after_delimiter = {
4711 let delimiter_trim = delimiter.trim_end();
4712 let delimiter_line = snapshot
4713 .chars_for_range(range.clone())
4714 .skip(num_of_whitespaces)
4715 .take(delimiter_trim.len())
4716 .collect::<String>();
4717 if delimiter_line.starts_with(delimiter_trim) {
4718 num_of_whitespaces + delimiter_trim.len() <= column as usize
4719 } else {
4720 false
4721 }
4722 };
4723
4724 let cursor_is_before_end_tag_if_exists = {
4725 let mut char_position = 0u32;
4726 let mut end_tag_offset = None;
4727
4728 'outer: for chunk in snapshot.text_for_range(range) {
4729 if let Some(byte_pos) = chunk.find(&**end_tag) {
4730 let chars_before_match =
4731 chunk[..byte_pos].chars().count() as u32;
4732 end_tag_offset =
4733 Some(char_position + chars_before_match);
4734 break 'outer;
4735 }
4736 char_position += chunk.chars().count() as u32;
4737 }
4738
4739 if let Some(end_tag_offset) = end_tag_offset {
4740 let cursor_is_before_end_tag = column <= end_tag_offset;
4741 if cursor_is_after_start_tag {
4742 if cursor_is_before_end_tag {
4743 insert_extra_newline = true;
4744 }
4745 let cursor_is_at_start_of_end_tag =
4746 column == end_tag_offset;
4747 if cursor_is_at_start_of_end_tag {
4748 indent_on_extra_newline.len = *len;
4749 }
4750 }
4751 cursor_is_before_end_tag
4752 } else {
4753 true
4754 }
4755 };
4756
4757 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4758 && cursor_is_before_end_tag_if_exists
4759 {
4760 if cursor_is_after_start_tag {
4761 indent_on_newline.len = *len;
4762 }
4763 Some(delimiter.clone())
4764 } else {
4765 None
4766 }
4767 });
4768
4769 (
4770 comment_delimiter,
4771 doc_delimiter,
4772 insert_extra_newline,
4773 indent_on_newline,
4774 indent_on_extra_newline,
4775 )
4776 } else {
4777 (
4778 None,
4779 None,
4780 false,
4781 IndentSize::default(),
4782 IndentSize::default(),
4783 )
4784 };
4785
4786 let prevent_auto_indent = doc_delimiter.is_some();
4787 let delimiter = comment_delimiter.or(doc_delimiter);
4788
4789 let capacity_for_delimiter =
4790 delimiter.as_deref().map(str::len).unwrap_or_default();
4791 let mut new_text = String::with_capacity(
4792 1 + capacity_for_delimiter
4793 + existing_indent.len as usize
4794 + indent_on_newline.len as usize
4795 + indent_on_extra_newline.len as usize,
4796 );
4797 new_text.push('\n');
4798 new_text.extend(existing_indent.chars());
4799 new_text.extend(indent_on_newline.chars());
4800
4801 if let Some(delimiter) = &delimiter {
4802 new_text.push_str(delimiter);
4803 }
4804
4805 if insert_extra_newline {
4806 new_text.push('\n');
4807 new_text.extend(existing_indent.chars());
4808 new_text.extend(indent_on_extra_newline.chars());
4809 }
4810
4811 let anchor = buffer.anchor_after(end);
4812 let new_selection = selection.map(|_| anchor);
4813 (
4814 ((start..end, new_text), prevent_auto_indent),
4815 (insert_extra_newline, new_selection),
4816 )
4817 })
4818 .unzip()
4819 };
4820
4821 let mut auto_indent_edits = Vec::new();
4822 let mut edits = Vec::new();
4823 for (edit, prevent_auto_indent) in edits_with_flags {
4824 if prevent_auto_indent {
4825 edits.push(edit);
4826 } else {
4827 auto_indent_edits.push(edit);
4828 }
4829 }
4830 if !edits.is_empty() {
4831 this.edit(edits, cx);
4832 }
4833 if !auto_indent_edits.is_empty() {
4834 this.edit_with_autoindent(auto_indent_edits, cx);
4835 }
4836
4837 let buffer = this.buffer.read(cx).snapshot(cx);
4838 let new_selections = selection_info
4839 .into_iter()
4840 .map(|(extra_newline_inserted, new_selection)| {
4841 let mut cursor = new_selection.end.to_point(&buffer);
4842 if extra_newline_inserted {
4843 cursor.row -= 1;
4844 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4845 }
4846 new_selection.map(|_| cursor)
4847 })
4848 .collect();
4849
4850 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4851 this.refresh_edit_prediction(true, false, window, cx);
4852 });
4853 }
4854
4855 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4856 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4857
4858 let buffer = self.buffer.read(cx);
4859 let snapshot = buffer.snapshot(cx);
4860
4861 let mut edits = Vec::new();
4862 let mut rows = Vec::new();
4863
4864 for (rows_inserted, selection) in self
4865 .selections
4866 .all_adjusted(&self.display_snapshot(cx))
4867 .into_iter()
4868 .enumerate()
4869 {
4870 let cursor = selection.head();
4871 let row = cursor.row;
4872
4873 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4874
4875 let newline = "\n".to_string();
4876 edits.push((start_of_line..start_of_line, newline));
4877
4878 rows.push(row + rows_inserted as u32);
4879 }
4880
4881 self.transact(window, cx, |editor, window, cx| {
4882 editor.edit(edits, cx);
4883
4884 editor.change_selections(Default::default(), window, cx, |s| {
4885 let mut index = 0;
4886 s.move_cursors_with(|map, _, _| {
4887 let row = rows[index];
4888 index += 1;
4889
4890 let point = Point::new(row, 0);
4891 let boundary = map.next_line_boundary(point).1;
4892 let clipped = map.clip_point(boundary, Bias::Left);
4893
4894 (clipped, SelectionGoal::None)
4895 });
4896 });
4897
4898 let mut indent_edits = Vec::new();
4899 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4900 for row in rows {
4901 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4902 for (row, indent) in indents {
4903 if indent.len == 0 {
4904 continue;
4905 }
4906
4907 let text = match indent.kind {
4908 IndentKind::Space => " ".repeat(indent.len as usize),
4909 IndentKind::Tab => "\t".repeat(indent.len as usize),
4910 };
4911 let point = Point::new(row.0, 0);
4912 indent_edits.push((point..point, text));
4913 }
4914 }
4915 editor.edit(indent_edits, cx);
4916 });
4917 }
4918
4919 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4921
4922 let buffer = self.buffer.read(cx);
4923 let snapshot = buffer.snapshot(cx);
4924
4925 let mut edits = Vec::new();
4926 let mut rows = Vec::new();
4927 let mut rows_inserted = 0;
4928
4929 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4930 let cursor = selection.head();
4931 let row = cursor.row;
4932
4933 let point = Point::new(row + 1, 0);
4934 let start_of_line = snapshot.clip_point(point, Bias::Left);
4935
4936 let newline = "\n".to_string();
4937 edits.push((start_of_line..start_of_line, newline));
4938
4939 rows_inserted += 1;
4940 rows.push(row + rows_inserted);
4941 }
4942
4943 self.transact(window, cx, |editor, window, cx| {
4944 editor.edit(edits, cx);
4945
4946 editor.change_selections(Default::default(), window, cx, |s| {
4947 let mut index = 0;
4948 s.move_cursors_with(|map, _, _| {
4949 let row = rows[index];
4950 index += 1;
4951
4952 let point = Point::new(row, 0);
4953 let boundary = map.next_line_boundary(point).1;
4954 let clipped = map.clip_point(boundary, Bias::Left);
4955
4956 (clipped, SelectionGoal::None)
4957 });
4958 });
4959
4960 let mut indent_edits = Vec::new();
4961 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4962 for row in rows {
4963 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4964 for (row, indent) in indents {
4965 if indent.len == 0 {
4966 continue;
4967 }
4968
4969 let text = match indent.kind {
4970 IndentKind::Space => " ".repeat(indent.len as usize),
4971 IndentKind::Tab => "\t".repeat(indent.len as usize),
4972 };
4973 let point = Point::new(row.0, 0);
4974 indent_edits.push((point..point, text));
4975 }
4976 }
4977 editor.edit(indent_edits, cx);
4978 });
4979 }
4980
4981 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4982 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4983 original_indent_columns: Vec::new(),
4984 });
4985 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4986 }
4987
4988 fn insert_with_autoindent_mode(
4989 &mut self,
4990 text: &str,
4991 autoindent_mode: Option<AutoindentMode>,
4992 window: &mut Window,
4993 cx: &mut Context<Self>,
4994 ) {
4995 if self.read_only(cx) {
4996 return;
4997 }
4998
4999 let text: Arc<str> = text.into();
5000 self.transact(window, cx, |this, window, cx| {
5001 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5002 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5003 let anchors = {
5004 let snapshot = buffer.read(cx);
5005 old_selections
5006 .iter()
5007 .map(|s| {
5008 let anchor = snapshot.anchor_after(s.head());
5009 s.map(|_| anchor)
5010 })
5011 .collect::<Vec<_>>()
5012 };
5013 buffer.edit(
5014 old_selections
5015 .iter()
5016 .map(|s| (s.start..s.end, text.clone())),
5017 autoindent_mode,
5018 cx,
5019 );
5020 anchors
5021 });
5022
5023 this.change_selections(Default::default(), window, cx, |s| {
5024 s.select_anchors(selection_anchors);
5025 });
5026
5027 cx.notify();
5028 });
5029 }
5030
5031 fn trigger_completion_on_input(
5032 &mut self,
5033 text: &str,
5034 trigger_in_words: bool,
5035 window: &mut Window,
5036 cx: &mut Context<Self>,
5037 ) {
5038 let completions_source = self
5039 .context_menu
5040 .borrow()
5041 .as_ref()
5042 .and_then(|menu| match menu {
5043 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5044 CodeContextMenu::CodeActions(_) => None,
5045 });
5046
5047 match completions_source {
5048 Some(CompletionsMenuSource::Words { .. }) => {
5049 self.open_or_update_completions_menu(
5050 Some(CompletionsMenuSource::Words {
5051 ignore_threshold: false,
5052 }),
5053 None,
5054 window,
5055 cx,
5056 );
5057 }
5058 Some(CompletionsMenuSource::Normal)
5059 | Some(CompletionsMenuSource::SnippetChoices)
5060 | None
5061 if self.is_completion_trigger(
5062 text,
5063 trigger_in_words,
5064 completions_source.is_some(),
5065 cx,
5066 ) =>
5067 {
5068 self.show_completions(
5069 &ShowCompletions {
5070 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5071 },
5072 window,
5073 cx,
5074 )
5075 }
5076 _ => {
5077 self.hide_context_menu(window, cx);
5078 }
5079 }
5080 }
5081
5082 fn is_completion_trigger(
5083 &self,
5084 text: &str,
5085 trigger_in_words: bool,
5086 menu_is_open: bool,
5087 cx: &mut Context<Self>,
5088 ) -> bool {
5089 let position = self.selections.newest_anchor().head();
5090 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5091 return false;
5092 };
5093
5094 if let Some(completion_provider) = &self.completion_provider {
5095 completion_provider.is_completion_trigger(
5096 &buffer,
5097 position.text_anchor,
5098 text,
5099 trigger_in_words,
5100 menu_is_open,
5101 cx,
5102 )
5103 } else {
5104 false
5105 }
5106 }
5107
5108 /// If any empty selections is touching the start of its innermost containing autoclose
5109 /// region, expand it to select the brackets.
5110 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5111 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5112 let buffer = self.buffer.read(cx).read(cx);
5113 let new_selections = self
5114 .selections_with_autoclose_regions(selections, &buffer)
5115 .map(|(mut selection, region)| {
5116 if !selection.is_empty() {
5117 return selection;
5118 }
5119
5120 if let Some(region) = region {
5121 let mut range = region.range.to_offset(&buffer);
5122 if selection.start == range.start && range.start >= region.pair.start.len() {
5123 range.start -= region.pair.start.len();
5124 if buffer.contains_str_at(range.start, ®ion.pair.start)
5125 && buffer.contains_str_at(range.end, ®ion.pair.end)
5126 {
5127 range.end += region.pair.end.len();
5128 selection.start = range.start;
5129 selection.end = range.end;
5130
5131 return selection;
5132 }
5133 }
5134 }
5135
5136 let always_treat_brackets_as_autoclosed = buffer
5137 .language_settings_at(selection.start, cx)
5138 .always_treat_brackets_as_autoclosed;
5139
5140 if !always_treat_brackets_as_autoclosed {
5141 return selection;
5142 }
5143
5144 if let Some(scope) = buffer.language_scope_at(selection.start) {
5145 for (pair, enabled) in scope.brackets() {
5146 if !enabled || !pair.close {
5147 continue;
5148 }
5149
5150 if buffer.contains_str_at(selection.start, &pair.end) {
5151 let pair_start_len = pair.start.len();
5152 if buffer.contains_str_at(
5153 selection.start.saturating_sub(pair_start_len),
5154 &pair.start,
5155 ) {
5156 selection.start -= pair_start_len;
5157 selection.end += pair.end.len();
5158
5159 return selection;
5160 }
5161 }
5162 }
5163 }
5164
5165 selection
5166 })
5167 .collect();
5168
5169 drop(buffer);
5170 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5171 selections.select(new_selections)
5172 });
5173 }
5174
5175 /// Iterate the given selections, and for each one, find the smallest surrounding
5176 /// autoclose region. This uses the ordering of the selections and the autoclose
5177 /// regions to avoid repeated comparisons.
5178 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5179 &'a self,
5180 selections: impl IntoIterator<Item = Selection<D>>,
5181 buffer: &'a MultiBufferSnapshot,
5182 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5183 let mut i = 0;
5184 let mut regions = self.autoclose_regions.as_slice();
5185 selections.into_iter().map(move |selection| {
5186 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5187
5188 let mut enclosing = None;
5189 while let Some(pair_state) = regions.get(i) {
5190 if pair_state.range.end.to_offset(buffer) < range.start {
5191 regions = ®ions[i + 1..];
5192 i = 0;
5193 } else if pair_state.range.start.to_offset(buffer) > range.end {
5194 break;
5195 } else {
5196 if pair_state.selection_id == selection.id {
5197 enclosing = Some(pair_state);
5198 }
5199 i += 1;
5200 }
5201 }
5202
5203 (selection, enclosing)
5204 })
5205 }
5206
5207 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5208 fn invalidate_autoclose_regions(
5209 &mut self,
5210 mut selections: &[Selection<Anchor>],
5211 buffer: &MultiBufferSnapshot,
5212 ) {
5213 self.autoclose_regions.retain(|state| {
5214 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5215 return false;
5216 }
5217
5218 let mut i = 0;
5219 while let Some(selection) = selections.get(i) {
5220 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5221 selections = &selections[1..];
5222 continue;
5223 }
5224 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5225 break;
5226 }
5227 if selection.id == state.selection_id {
5228 return true;
5229 } else {
5230 i += 1;
5231 }
5232 }
5233 false
5234 });
5235 }
5236
5237 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5238 let offset = position.to_offset(buffer);
5239 let (word_range, kind) =
5240 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5241 if offset > word_range.start && kind == Some(CharKind::Word) {
5242 Some(
5243 buffer
5244 .text_for_range(word_range.start..offset)
5245 .collect::<String>(),
5246 )
5247 } else {
5248 None
5249 }
5250 }
5251
5252 pub fn visible_excerpts(
5253 &self,
5254 cx: &mut Context<Editor>,
5255 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5256 let Some(project) = self.project() else {
5257 return HashMap::default();
5258 };
5259 let project = project.read(cx);
5260 let multi_buffer = self.buffer().read(cx);
5261 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5262 let multi_buffer_visible_start = self
5263 .scroll_manager
5264 .anchor()
5265 .anchor
5266 .to_point(&multi_buffer_snapshot);
5267 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5268 multi_buffer_visible_start
5269 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5270 Bias::Left,
5271 );
5272 multi_buffer_snapshot
5273 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5274 .into_iter()
5275 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5276 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5277 let buffer_file = project::File::from_dyn(buffer.file())?;
5278 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5279 let worktree_entry = buffer_worktree
5280 .read(cx)
5281 .entry_for_id(buffer_file.project_entry_id()?)?;
5282 if worktree_entry.is_ignored {
5283 None
5284 } else {
5285 Some((
5286 excerpt_id,
5287 (
5288 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5289 buffer.version().clone(),
5290 excerpt_visible_range,
5291 ),
5292 ))
5293 }
5294 })
5295 .collect()
5296 }
5297
5298 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5299 TextLayoutDetails {
5300 text_system: window.text_system().clone(),
5301 editor_style: self.style.clone().unwrap(),
5302 rem_size: window.rem_size(),
5303 scroll_anchor: self.scroll_manager.anchor(),
5304 visible_rows: self.visible_line_count(),
5305 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5306 }
5307 }
5308
5309 fn trigger_on_type_formatting(
5310 &self,
5311 input: String,
5312 window: &mut Window,
5313 cx: &mut Context<Self>,
5314 ) -> Option<Task<Result<()>>> {
5315 if input.len() != 1 {
5316 return None;
5317 }
5318
5319 let project = self.project()?;
5320 let position = self.selections.newest_anchor().head();
5321 let (buffer, buffer_position) = self
5322 .buffer
5323 .read(cx)
5324 .text_anchor_for_position(position, cx)?;
5325
5326 let settings = language_settings::language_settings(
5327 buffer
5328 .read(cx)
5329 .language_at(buffer_position)
5330 .map(|l| l.name()),
5331 buffer.read(cx).file(),
5332 cx,
5333 );
5334 if !settings.use_on_type_format {
5335 return None;
5336 }
5337
5338 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5339 // hence we do LSP request & edit on host side only — add formats to host's history.
5340 let push_to_lsp_host_history = true;
5341 // If this is not the host, append its history with new edits.
5342 let push_to_client_history = project.read(cx).is_via_collab();
5343
5344 let on_type_formatting = project.update(cx, |project, cx| {
5345 project.on_type_format(
5346 buffer.clone(),
5347 buffer_position,
5348 input,
5349 push_to_lsp_host_history,
5350 cx,
5351 )
5352 });
5353 Some(cx.spawn_in(window, async move |editor, cx| {
5354 if let Some(transaction) = on_type_formatting.await? {
5355 if push_to_client_history {
5356 buffer
5357 .update(cx, |buffer, _| {
5358 buffer.push_transaction(transaction, Instant::now());
5359 buffer.finalize_last_transaction();
5360 })
5361 .ok();
5362 }
5363 editor.update(cx, |editor, cx| {
5364 editor.refresh_document_highlights(cx);
5365 })?;
5366 }
5367 Ok(())
5368 }))
5369 }
5370
5371 pub fn show_word_completions(
5372 &mut self,
5373 _: &ShowWordCompletions,
5374 window: &mut Window,
5375 cx: &mut Context<Self>,
5376 ) {
5377 self.open_or_update_completions_menu(
5378 Some(CompletionsMenuSource::Words {
5379 ignore_threshold: true,
5380 }),
5381 None,
5382 window,
5383 cx,
5384 );
5385 }
5386
5387 pub fn show_completions(
5388 &mut self,
5389 options: &ShowCompletions,
5390 window: &mut Window,
5391 cx: &mut Context<Self>,
5392 ) {
5393 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5394 }
5395
5396 fn open_or_update_completions_menu(
5397 &mut self,
5398 requested_source: Option<CompletionsMenuSource>,
5399 trigger: Option<&str>,
5400 window: &mut Window,
5401 cx: &mut Context<Self>,
5402 ) {
5403 if self.pending_rename.is_some() {
5404 return;
5405 }
5406
5407 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5408
5409 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5410 // inserted and selected. To handle that case, the start of the selection is used so that
5411 // the menu starts with all choices.
5412 let position = self
5413 .selections
5414 .newest_anchor()
5415 .start
5416 .bias_right(&multibuffer_snapshot);
5417 if position.diff_base_anchor.is_some() {
5418 return;
5419 }
5420 let buffer_position = multibuffer_snapshot.anchor_before(position);
5421 let Some(buffer) = buffer_position
5422 .buffer_id
5423 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5424 else {
5425 return;
5426 };
5427 let buffer_snapshot = buffer.read(cx).snapshot();
5428
5429 let query: Option<Arc<String>> =
5430 Self::completion_query(&multibuffer_snapshot, buffer_position)
5431 .map(|query| query.into());
5432
5433 drop(multibuffer_snapshot);
5434
5435 // Hide the current completions menu when query is empty. Without this, cached
5436 // completions from before the trigger char may be reused (#32774).
5437 if query.is_none() {
5438 let menu_is_open = matches!(
5439 self.context_menu.borrow().as_ref(),
5440 Some(CodeContextMenu::Completions(_))
5441 );
5442 if menu_is_open {
5443 self.hide_context_menu(window, cx);
5444 }
5445 }
5446
5447 let mut ignore_word_threshold = false;
5448 let provider = match requested_source {
5449 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5450 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5451 ignore_word_threshold = ignore_threshold;
5452 None
5453 }
5454 Some(CompletionsMenuSource::SnippetChoices) => {
5455 log::error!("bug: SnippetChoices requested_source is not handled");
5456 None
5457 }
5458 };
5459
5460 let sort_completions = provider
5461 .as_ref()
5462 .is_some_and(|provider| provider.sort_completions());
5463
5464 let filter_completions = provider
5465 .as_ref()
5466 .is_none_or(|provider| provider.filter_completions());
5467
5468 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5469 if filter_completions {
5470 menu.filter(query.clone(), provider.clone(), window, cx);
5471 }
5472 // When `is_incomplete` is false, no need to re-query completions when the current query
5473 // is a suffix of the initial query.
5474 if !menu.is_incomplete {
5475 // If the new query is a suffix of the old query (typing more characters) and
5476 // the previous result was complete, the existing completions can be filtered.
5477 //
5478 // Note that this is always true for snippet completions.
5479 let query_matches = match (&menu.initial_query, &query) {
5480 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5481 (None, _) => true,
5482 _ => false,
5483 };
5484 if query_matches {
5485 let position_matches = if menu.initial_position == position {
5486 true
5487 } else {
5488 let snapshot = self.buffer.read(cx).read(cx);
5489 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5490 };
5491 if position_matches {
5492 return;
5493 }
5494 }
5495 }
5496 };
5497
5498 let trigger_kind = match trigger {
5499 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5500 CompletionTriggerKind::TRIGGER_CHARACTER
5501 }
5502 _ => CompletionTriggerKind::INVOKED,
5503 };
5504 let completion_context = CompletionContext {
5505 trigger_character: trigger.and_then(|trigger| {
5506 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5507 Some(String::from(trigger))
5508 } else {
5509 None
5510 }
5511 }),
5512 trigger_kind,
5513 };
5514
5515 let Anchor {
5516 excerpt_id: buffer_excerpt_id,
5517 text_anchor: buffer_position,
5518 ..
5519 } = buffer_position;
5520
5521 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5522 buffer_snapshot.surrounding_word(buffer_position, None)
5523 {
5524 let word_to_exclude = buffer_snapshot
5525 .text_for_range(word_range.clone())
5526 .collect::<String>();
5527 (
5528 buffer_snapshot.anchor_before(word_range.start)
5529 ..buffer_snapshot.anchor_after(buffer_position),
5530 Some(word_to_exclude),
5531 )
5532 } else {
5533 (buffer_position..buffer_position, None)
5534 };
5535
5536 let language = buffer_snapshot
5537 .language_at(buffer_position)
5538 .map(|language| language.name());
5539
5540 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5541 .completions
5542 .clone();
5543
5544 let show_completion_documentation = buffer_snapshot
5545 .settings_at(buffer_position, cx)
5546 .show_completion_documentation;
5547
5548 // The document can be large, so stay in reasonable bounds when searching for words,
5549 // otherwise completion pop-up might be slow to appear.
5550 const WORD_LOOKUP_ROWS: u32 = 5_000;
5551 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5552 let min_word_search = buffer_snapshot.clip_point(
5553 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5554 Bias::Left,
5555 );
5556 let max_word_search = buffer_snapshot.clip_point(
5557 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5558 Bias::Right,
5559 );
5560 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5561 ..buffer_snapshot.point_to_offset(max_word_search);
5562
5563 let skip_digits = query
5564 .as_ref()
5565 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5566
5567 let omit_word_completions = !self.word_completions_enabled
5568 || (!ignore_word_threshold
5569 && match &query {
5570 Some(query) => query.chars().count() < completion_settings.words_min_length,
5571 None => completion_settings.words_min_length != 0,
5572 });
5573
5574 let (mut words, provider_responses) = match &provider {
5575 Some(provider) => {
5576 let provider_responses = provider.completions(
5577 buffer_excerpt_id,
5578 &buffer,
5579 buffer_position,
5580 completion_context,
5581 window,
5582 cx,
5583 );
5584
5585 let words = match (omit_word_completions, completion_settings.words) {
5586 (true, _) | (_, WordsCompletionMode::Disabled) => {
5587 Task::ready(BTreeMap::default())
5588 }
5589 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5590 .background_spawn(async move {
5591 buffer_snapshot.words_in_range(WordsQuery {
5592 fuzzy_contents: None,
5593 range: word_search_range,
5594 skip_digits,
5595 })
5596 }),
5597 };
5598
5599 (words, provider_responses)
5600 }
5601 None => {
5602 let words = if omit_word_completions {
5603 Task::ready(BTreeMap::default())
5604 } else {
5605 cx.background_spawn(async move {
5606 buffer_snapshot.words_in_range(WordsQuery {
5607 fuzzy_contents: None,
5608 range: word_search_range,
5609 skip_digits,
5610 })
5611 })
5612 };
5613 (words, Task::ready(Ok(Vec::new())))
5614 }
5615 };
5616
5617 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5618
5619 let id = post_inc(&mut self.next_completion_id);
5620 let task = cx.spawn_in(window, async move |editor, cx| {
5621 let Ok(()) = editor.update(cx, |this, _| {
5622 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5623 }) else {
5624 return;
5625 };
5626
5627 // TODO: Ideally completions from different sources would be selectively re-queried, so
5628 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5629 let mut completions = Vec::new();
5630 let mut is_incomplete = false;
5631 let mut display_options: Option<CompletionDisplayOptions> = None;
5632 if let Some(provider_responses) = provider_responses.await.log_err()
5633 && !provider_responses.is_empty()
5634 {
5635 for response in provider_responses {
5636 completions.extend(response.completions);
5637 is_incomplete = is_incomplete || response.is_incomplete;
5638 match display_options.as_mut() {
5639 None => {
5640 display_options = Some(response.display_options);
5641 }
5642 Some(options) => options.merge(&response.display_options),
5643 }
5644 }
5645 if completion_settings.words == WordsCompletionMode::Fallback {
5646 words = Task::ready(BTreeMap::default());
5647 }
5648 }
5649 let display_options = display_options.unwrap_or_default();
5650
5651 let mut words = words.await;
5652 if let Some(word_to_exclude) = &word_to_exclude {
5653 words.remove(word_to_exclude);
5654 }
5655 for lsp_completion in &completions {
5656 words.remove(&lsp_completion.new_text);
5657 }
5658 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5659 replace_range: word_replace_range.clone(),
5660 new_text: word.clone(),
5661 label: CodeLabel::plain(word, None),
5662 icon_path: None,
5663 documentation: None,
5664 source: CompletionSource::BufferWord {
5665 word_range,
5666 resolved: false,
5667 },
5668 insert_text_mode: Some(InsertTextMode::AS_IS),
5669 confirm: None,
5670 }));
5671
5672 let menu = if completions.is_empty() {
5673 None
5674 } else {
5675 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5676 let languages = editor
5677 .workspace
5678 .as_ref()
5679 .and_then(|(workspace, _)| workspace.upgrade())
5680 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5681 let menu = CompletionsMenu::new(
5682 id,
5683 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5684 sort_completions,
5685 show_completion_documentation,
5686 position,
5687 query.clone(),
5688 is_incomplete,
5689 buffer.clone(),
5690 completions.into(),
5691 display_options,
5692 snippet_sort_order,
5693 languages,
5694 language,
5695 cx,
5696 );
5697
5698 let query = if filter_completions { query } else { None };
5699 let matches_task = if let Some(query) = query {
5700 menu.do_async_filtering(query, cx)
5701 } else {
5702 Task::ready(menu.unfiltered_matches())
5703 };
5704 (menu, matches_task)
5705 }) else {
5706 return;
5707 };
5708
5709 let matches = matches_task.await;
5710
5711 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5712 // Newer menu already set, so exit.
5713 if let Some(CodeContextMenu::Completions(prev_menu)) =
5714 editor.context_menu.borrow().as_ref()
5715 && prev_menu.id > id
5716 {
5717 return;
5718 };
5719
5720 // Only valid to take prev_menu because it the new menu is immediately set
5721 // below, or the menu is hidden.
5722 if let Some(CodeContextMenu::Completions(prev_menu)) =
5723 editor.context_menu.borrow_mut().take()
5724 {
5725 let position_matches =
5726 if prev_menu.initial_position == menu.initial_position {
5727 true
5728 } else {
5729 let snapshot = editor.buffer.read(cx).read(cx);
5730 prev_menu.initial_position.to_offset(&snapshot)
5731 == menu.initial_position.to_offset(&snapshot)
5732 };
5733 if position_matches {
5734 // Preserve markdown cache before `set_filter_results` because it will
5735 // try to populate the documentation cache.
5736 menu.preserve_markdown_cache(prev_menu);
5737 }
5738 };
5739
5740 menu.set_filter_results(matches, provider, window, cx);
5741 }) else {
5742 return;
5743 };
5744
5745 menu.visible().then_some(menu)
5746 };
5747
5748 editor
5749 .update_in(cx, |editor, window, cx| {
5750 if editor.focus_handle.is_focused(window)
5751 && let Some(menu) = menu
5752 {
5753 *editor.context_menu.borrow_mut() =
5754 Some(CodeContextMenu::Completions(menu));
5755
5756 crate::hover_popover::hide_hover(editor, cx);
5757 if editor.show_edit_predictions_in_menu() {
5758 editor.update_visible_edit_prediction(window, cx);
5759 } else {
5760 editor.discard_edit_prediction(false, cx);
5761 }
5762
5763 cx.notify();
5764 return;
5765 }
5766
5767 if editor.completion_tasks.len() <= 1 {
5768 // If there are no more completion tasks and the last menu was empty, we should hide it.
5769 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5770 // If it was already hidden and we don't show edit predictions in the menu,
5771 // we should also show the edit prediction when available.
5772 if was_hidden && editor.show_edit_predictions_in_menu() {
5773 editor.update_visible_edit_prediction(window, cx);
5774 }
5775 }
5776 })
5777 .ok();
5778 });
5779
5780 self.completion_tasks.push((id, task));
5781 }
5782
5783 #[cfg(feature = "test-support")]
5784 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5785 let menu = self.context_menu.borrow();
5786 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5787 let completions = menu.completions.borrow();
5788 Some(completions.to_vec())
5789 } else {
5790 None
5791 }
5792 }
5793
5794 pub fn with_completions_menu_matching_id<R>(
5795 &self,
5796 id: CompletionId,
5797 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5798 ) -> R {
5799 let mut context_menu = self.context_menu.borrow_mut();
5800 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5801 return f(None);
5802 };
5803 if completions_menu.id != id {
5804 return f(None);
5805 }
5806 f(Some(completions_menu))
5807 }
5808
5809 pub fn confirm_completion(
5810 &mut self,
5811 action: &ConfirmCompletion,
5812 window: &mut Window,
5813 cx: &mut Context<Self>,
5814 ) -> Option<Task<Result<()>>> {
5815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5816 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5817 }
5818
5819 pub fn confirm_completion_insert(
5820 &mut self,
5821 _: &ConfirmCompletionInsert,
5822 window: &mut Window,
5823 cx: &mut Context<Self>,
5824 ) -> Option<Task<Result<()>>> {
5825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5826 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5827 }
5828
5829 pub fn confirm_completion_replace(
5830 &mut self,
5831 _: &ConfirmCompletionReplace,
5832 window: &mut Window,
5833 cx: &mut Context<Self>,
5834 ) -> Option<Task<Result<()>>> {
5835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5836 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5837 }
5838
5839 pub fn compose_completion(
5840 &mut self,
5841 action: &ComposeCompletion,
5842 window: &mut Window,
5843 cx: &mut Context<Self>,
5844 ) -> Option<Task<Result<()>>> {
5845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5846 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5847 }
5848
5849 fn do_completion(
5850 &mut self,
5851 item_ix: Option<usize>,
5852 intent: CompletionIntent,
5853 window: &mut Window,
5854 cx: &mut Context<Editor>,
5855 ) -> Option<Task<Result<()>>> {
5856 use language::ToOffset as _;
5857
5858 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5859 else {
5860 return None;
5861 };
5862
5863 let candidate_id = {
5864 let entries = completions_menu.entries.borrow();
5865 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5866 if self.show_edit_predictions_in_menu() {
5867 self.discard_edit_prediction(true, cx);
5868 }
5869 mat.candidate_id
5870 };
5871
5872 let completion = completions_menu
5873 .completions
5874 .borrow()
5875 .get(candidate_id)?
5876 .clone();
5877 cx.stop_propagation();
5878
5879 let buffer_handle = completions_menu.buffer.clone();
5880
5881 let CompletionEdit {
5882 new_text,
5883 snippet,
5884 replace_range,
5885 } = process_completion_for_edit(
5886 &completion,
5887 intent,
5888 &buffer_handle,
5889 &completions_menu.initial_position.text_anchor,
5890 cx,
5891 );
5892
5893 let buffer = buffer_handle.read(cx);
5894 let snapshot = self.buffer.read(cx).snapshot(cx);
5895 let newest_anchor = self.selections.newest_anchor();
5896 let replace_range_multibuffer = {
5897 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5898 excerpt.map_range_from_buffer(replace_range.clone())
5899 };
5900 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5901 return None;
5902 }
5903
5904 let old_text = buffer
5905 .text_for_range(replace_range.clone())
5906 .collect::<String>();
5907 let lookbehind = newest_anchor
5908 .start
5909 .text_anchor
5910 .to_offset(buffer)
5911 .saturating_sub(replace_range.start);
5912 let lookahead = replace_range
5913 .end
5914 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5915 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5916 let suffix = &old_text[lookbehind.min(old_text.len())..];
5917
5918 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5919 let mut ranges = Vec::new();
5920 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5921
5922 for selection in &selections {
5923 let range = if selection.id == newest_anchor.id {
5924 replace_range_multibuffer.clone()
5925 } else {
5926 let mut range = selection.range();
5927
5928 // if prefix is present, don't duplicate it
5929 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5930 range.start = range.start.saturating_sub(lookbehind);
5931
5932 // if suffix is also present, mimic the newest cursor and replace it
5933 if selection.id != newest_anchor.id
5934 && snapshot.contains_str_at(range.end, suffix)
5935 {
5936 range.end += lookahead;
5937 }
5938 }
5939 range
5940 };
5941
5942 ranges.push(range.clone());
5943
5944 if !self.linked_edit_ranges.is_empty() {
5945 let start_anchor = snapshot.anchor_before(range.start);
5946 let end_anchor = snapshot.anchor_after(range.end);
5947 if let Some(ranges) = self
5948 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5949 {
5950 for (buffer, edits) in ranges {
5951 linked_edits
5952 .entry(buffer.clone())
5953 .or_default()
5954 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5955 }
5956 }
5957 }
5958 }
5959
5960 let common_prefix_len = old_text
5961 .chars()
5962 .zip(new_text.chars())
5963 .take_while(|(a, b)| a == b)
5964 .map(|(a, _)| a.len_utf8())
5965 .sum::<usize>();
5966
5967 cx.emit(EditorEvent::InputHandled {
5968 utf16_range_to_replace: None,
5969 text: new_text[common_prefix_len..].into(),
5970 });
5971
5972 self.transact(window, cx, |editor, window, cx| {
5973 if let Some(mut snippet) = snippet {
5974 snippet.text = new_text.to_string();
5975 editor
5976 .insert_snippet(&ranges, snippet, window, cx)
5977 .log_err();
5978 } else {
5979 editor.buffer.update(cx, |multi_buffer, cx| {
5980 let auto_indent = match completion.insert_text_mode {
5981 Some(InsertTextMode::AS_IS) => None,
5982 _ => editor.autoindent_mode.clone(),
5983 };
5984 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5985 multi_buffer.edit(edits, auto_indent, cx);
5986 });
5987 }
5988 for (buffer, edits) in linked_edits {
5989 buffer.update(cx, |buffer, cx| {
5990 let snapshot = buffer.snapshot();
5991 let edits = edits
5992 .into_iter()
5993 .map(|(range, text)| {
5994 use text::ToPoint as TP;
5995 let end_point = TP::to_point(&range.end, &snapshot);
5996 let start_point = TP::to_point(&range.start, &snapshot);
5997 (start_point..end_point, text)
5998 })
5999 .sorted_by_key(|(range, _)| range.start);
6000 buffer.edit(edits, None, cx);
6001 })
6002 }
6003
6004 editor.refresh_edit_prediction(true, false, window, cx);
6005 });
6006 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6007
6008 let show_new_completions_on_confirm = completion
6009 .confirm
6010 .as_ref()
6011 .is_some_and(|confirm| confirm(intent, window, cx));
6012 if show_new_completions_on_confirm {
6013 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6014 }
6015
6016 let provider = self.completion_provider.as_ref()?;
6017 drop(completion);
6018 let apply_edits = provider.apply_additional_edits_for_completion(
6019 buffer_handle,
6020 completions_menu.completions.clone(),
6021 candidate_id,
6022 true,
6023 cx,
6024 );
6025
6026 let editor_settings = EditorSettings::get_global(cx);
6027 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6028 // After the code completion is finished, users often want to know what signatures are needed.
6029 // so we should automatically call signature_help
6030 self.show_signature_help(&ShowSignatureHelp, window, cx);
6031 }
6032
6033 Some(cx.foreground_executor().spawn(async move {
6034 apply_edits.await?;
6035 Ok(())
6036 }))
6037 }
6038
6039 pub fn toggle_code_actions(
6040 &mut self,
6041 action: &ToggleCodeActions,
6042 window: &mut Window,
6043 cx: &mut Context<Self>,
6044 ) {
6045 let quick_launch = action.quick_launch;
6046 let mut context_menu = self.context_menu.borrow_mut();
6047 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6048 if code_actions.deployed_from == action.deployed_from {
6049 // Toggle if we're selecting the same one
6050 *context_menu = None;
6051 cx.notify();
6052 return;
6053 } else {
6054 // Otherwise, clear it and start a new one
6055 *context_menu = None;
6056 cx.notify();
6057 }
6058 }
6059 drop(context_menu);
6060 let snapshot = self.snapshot(window, cx);
6061 let deployed_from = action.deployed_from.clone();
6062 let action = action.clone();
6063 self.completion_tasks.clear();
6064 self.discard_edit_prediction(false, cx);
6065
6066 let multibuffer_point = match &action.deployed_from {
6067 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6068 DisplayPoint::new(*row, 0).to_point(&snapshot)
6069 }
6070 _ => self
6071 .selections
6072 .newest::<Point>(&snapshot.display_snapshot)
6073 .head(),
6074 };
6075 let Some((buffer, buffer_row)) = snapshot
6076 .buffer_snapshot()
6077 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6078 .and_then(|(buffer_snapshot, range)| {
6079 self.buffer()
6080 .read(cx)
6081 .buffer(buffer_snapshot.remote_id())
6082 .map(|buffer| (buffer, range.start.row))
6083 })
6084 else {
6085 return;
6086 };
6087 let buffer_id = buffer.read(cx).remote_id();
6088 let tasks = self
6089 .tasks
6090 .get(&(buffer_id, buffer_row))
6091 .map(|t| Arc::new(t.to_owned()));
6092
6093 if !self.focus_handle.is_focused(window) {
6094 return;
6095 }
6096 let project = self.project.clone();
6097
6098 let code_actions_task = match deployed_from {
6099 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6100 _ => self.code_actions(buffer_row, window, cx),
6101 };
6102
6103 let runnable_task = match deployed_from {
6104 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6105 _ => {
6106 let mut task_context_task = Task::ready(None);
6107 if let Some(tasks) = &tasks
6108 && let Some(project) = project
6109 {
6110 task_context_task =
6111 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6112 }
6113
6114 cx.spawn_in(window, {
6115 let buffer = buffer.clone();
6116 async move |editor, cx| {
6117 let task_context = task_context_task.await;
6118
6119 let resolved_tasks =
6120 tasks
6121 .zip(task_context.clone())
6122 .map(|(tasks, task_context)| ResolvedTasks {
6123 templates: tasks.resolve(&task_context).collect(),
6124 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6125 multibuffer_point.row,
6126 tasks.column,
6127 )),
6128 });
6129 let debug_scenarios = editor
6130 .update(cx, |editor, cx| {
6131 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6132 })?
6133 .await;
6134 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6135 }
6136 })
6137 }
6138 };
6139
6140 cx.spawn_in(window, async move |editor, cx| {
6141 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6142 let code_actions = code_actions_task.await;
6143 let spawn_straight_away = quick_launch
6144 && resolved_tasks
6145 .as_ref()
6146 .is_some_and(|tasks| tasks.templates.len() == 1)
6147 && code_actions
6148 .as_ref()
6149 .is_none_or(|actions| actions.is_empty())
6150 && debug_scenarios.is_empty();
6151
6152 editor.update_in(cx, |editor, window, cx| {
6153 crate::hover_popover::hide_hover(editor, cx);
6154 let actions = CodeActionContents::new(
6155 resolved_tasks,
6156 code_actions,
6157 debug_scenarios,
6158 task_context.unwrap_or_default(),
6159 );
6160
6161 // Don't show the menu if there are no actions available
6162 if actions.is_empty() {
6163 cx.notify();
6164 return Task::ready(Ok(()));
6165 }
6166
6167 *editor.context_menu.borrow_mut() =
6168 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6169 buffer,
6170 actions,
6171 selected_item: Default::default(),
6172 scroll_handle: UniformListScrollHandle::default(),
6173 deployed_from,
6174 }));
6175 cx.notify();
6176 if spawn_straight_away
6177 && let Some(task) = editor.confirm_code_action(
6178 &ConfirmCodeAction { item_ix: Some(0) },
6179 window,
6180 cx,
6181 )
6182 {
6183 return task;
6184 }
6185
6186 Task::ready(Ok(()))
6187 })
6188 })
6189 .detach_and_log_err(cx);
6190 }
6191
6192 fn debug_scenarios(
6193 &mut self,
6194 resolved_tasks: &Option<ResolvedTasks>,
6195 buffer: &Entity<Buffer>,
6196 cx: &mut App,
6197 ) -> Task<Vec<task::DebugScenario>> {
6198 maybe!({
6199 let project = self.project()?;
6200 let dap_store = project.read(cx).dap_store();
6201 let mut scenarios = vec![];
6202 let resolved_tasks = resolved_tasks.as_ref()?;
6203 let buffer = buffer.read(cx);
6204 let language = buffer.language()?;
6205 let file = buffer.file();
6206 let debug_adapter = language_settings(language.name().into(), file, cx)
6207 .debuggers
6208 .first()
6209 .map(SharedString::from)
6210 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6211
6212 dap_store.update(cx, |dap_store, cx| {
6213 for (_, task) in &resolved_tasks.templates {
6214 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6215 task.original_task().clone(),
6216 debug_adapter.clone().into(),
6217 task.display_label().to_owned().into(),
6218 cx,
6219 );
6220 scenarios.push(maybe_scenario);
6221 }
6222 });
6223 Some(cx.background_spawn(async move {
6224 futures::future::join_all(scenarios)
6225 .await
6226 .into_iter()
6227 .flatten()
6228 .collect::<Vec<_>>()
6229 }))
6230 })
6231 .unwrap_or_else(|| Task::ready(vec![]))
6232 }
6233
6234 fn code_actions(
6235 &mut self,
6236 buffer_row: u32,
6237 window: &mut Window,
6238 cx: &mut Context<Self>,
6239 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6240 let mut task = self.code_actions_task.take();
6241 cx.spawn_in(window, async move |editor, cx| {
6242 while let Some(prev_task) = task {
6243 prev_task.await.log_err();
6244 task = editor
6245 .update(cx, |this, _| this.code_actions_task.take())
6246 .ok()?;
6247 }
6248
6249 editor
6250 .update(cx, |editor, cx| {
6251 editor
6252 .available_code_actions
6253 .clone()
6254 .and_then(|(location, code_actions)| {
6255 let snapshot = location.buffer.read(cx).snapshot();
6256 let point_range = location.range.to_point(&snapshot);
6257 let point_range = point_range.start.row..=point_range.end.row;
6258 if point_range.contains(&buffer_row) {
6259 Some(code_actions)
6260 } else {
6261 None
6262 }
6263 })
6264 })
6265 .ok()
6266 .flatten()
6267 })
6268 }
6269
6270 pub fn confirm_code_action(
6271 &mut self,
6272 action: &ConfirmCodeAction,
6273 window: &mut Window,
6274 cx: &mut Context<Self>,
6275 ) -> Option<Task<Result<()>>> {
6276 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6277
6278 let actions_menu =
6279 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6280 menu
6281 } else {
6282 return None;
6283 };
6284
6285 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6286 let action = actions_menu.actions.get(action_ix)?;
6287 let title = action.label();
6288 let buffer = actions_menu.buffer;
6289 let workspace = self.workspace()?;
6290
6291 match action {
6292 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6293 workspace.update(cx, |workspace, cx| {
6294 workspace.schedule_resolved_task(
6295 task_source_kind,
6296 resolved_task,
6297 false,
6298 window,
6299 cx,
6300 );
6301
6302 Some(Task::ready(Ok(())))
6303 })
6304 }
6305 CodeActionsItem::CodeAction {
6306 excerpt_id,
6307 action,
6308 provider,
6309 } => {
6310 let apply_code_action =
6311 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6312 let workspace = workspace.downgrade();
6313 Some(cx.spawn_in(window, async move |editor, cx| {
6314 let project_transaction = apply_code_action.await?;
6315 Self::open_project_transaction(
6316 &editor,
6317 workspace,
6318 project_transaction,
6319 title,
6320 cx,
6321 )
6322 .await
6323 }))
6324 }
6325 CodeActionsItem::DebugScenario(scenario) => {
6326 let context = actions_menu.actions.context;
6327
6328 workspace.update(cx, |workspace, cx| {
6329 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6330 workspace.start_debug_session(
6331 scenario,
6332 context,
6333 Some(buffer),
6334 None,
6335 window,
6336 cx,
6337 );
6338 });
6339 Some(Task::ready(Ok(())))
6340 }
6341 }
6342 }
6343
6344 pub async fn open_project_transaction(
6345 editor: &WeakEntity<Editor>,
6346 workspace: WeakEntity<Workspace>,
6347 transaction: ProjectTransaction,
6348 title: String,
6349 cx: &mut AsyncWindowContext,
6350 ) -> Result<()> {
6351 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6352 cx.update(|_, cx| {
6353 entries.sort_unstable_by_key(|(buffer, _)| {
6354 buffer.read(cx).file().map(|f| f.path().clone())
6355 });
6356 })?;
6357 if entries.is_empty() {
6358 return Ok(());
6359 }
6360
6361 // If the project transaction's edits are all contained within this editor, then
6362 // avoid opening a new editor to display them.
6363
6364 if let [(buffer, transaction)] = &*entries {
6365 let excerpt = editor.update(cx, |editor, cx| {
6366 editor
6367 .buffer()
6368 .read(cx)
6369 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6370 })?;
6371 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6372 && excerpted_buffer == *buffer
6373 {
6374 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6375 let excerpt_range = excerpt_range.to_offset(buffer);
6376 buffer
6377 .edited_ranges_for_transaction::<usize>(transaction)
6378 .all(|range| {
6379 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6380 })
6381 })?;
6382
6383 if all_edits_within_excerpt {
6384 return Ok(());
6385 }
6386 }
6387 }
6388
6389 let mut ranges_to_highlight = Vec::new();
6390 let excerpt_buffer = cx.new(|cx| {
6391 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6392 for (buffer_handle, transaction) in &entries {
6393 let edited_ranges = buffer_handle
6394 .read(cx)
6395 .edited_ranges_for_transaction::<Point>(transaction)
6396 .collect::<Vec<_>>();
6397 let (ranges, _) = multibuffer.set_excerpts_for_path(
6398 PathKey::for_buffer(buffer_handle, cx),
6399 buffer_handle.clone(),
6400 edited_ranges,
6401 multibuffer_context_lines(cx),
6402 cx,
6403 );
6404
6405 ranges_to_highlight.extend(ranges);
6406 }
6407 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6408 multibuffer
6409 })?;
6410
6411 workspace.update_in(cx, |workspace, window, cx| {
6412 let project = workspace.project().clone();
6413 let editor =
6414 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6415 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6416 editor.update(cx, |editor, cx| {
6417 editor.highlight_background::<Self>(
6418 &ranges_to_highlight,
6419 |theme| theme.colors().editor_highlighted_line_background,
6420 cx,
6421 );
6422 });
6423 })?;
6424
6425 Ok(())
6426 }
6427
6428 pub fn clear_code_action_providers(&mut self) {
6429 self.code_action_providers.clear();
6430 self.available_code_actions.take();
6431 }
6432
6433 pub fn add_code_action_provider(
6434 &mut self,
6435 provider: Rc<dyn CodeActionProvider>,
6436 window: &mut Window,
6437 cx: &mut Context<Self>,
6438 ) {
6439 if self
6440 .code_action_providers
6441 .iter()
6442 .any(|existing_provider| existing_provider.id() == provider.id())
6443 {
6444 return;
6445 }
6446
6447 self.code_action_providers.push(provider);
6448 self.refresh_code_actions(window, cx);
6449 }
6450
6451 pub fn remove_code_action_provider(
6452 &mut self,
6453 id: Arc<str>,
6454 window: &mut Window,
6455 cx: &mut Context<Self>,
6456 ) {
6457 self.code_action_providers
6458 .retain(|provider| provider.id() != id);
6459 self.refresh_code_actions(window, cx);
6460 }
6461
6462 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6463 !self.code_action_providers.is_empty()
6464 && EditorSettings::get_global(cx).toolbar.code_actions
6465 }
6466
6467 pub fn has_available_code_actions(&self) -> bool {
6468 self.available_code_actions
6469 .as_ref()
6470 .is_some_and(|(_, actions)| !actions.is_empty())
6471 }
6472
6473 fn render_inline_code_actions(
6474 &self,
6475 icon_size: ui::IconSize,
6476 display_row: DisplayRow,
6477 is_active: bool,
6478 cx: &mut Context<Self>,
6479 ) -> AnyElement {
6480 let show_tooltip = !self.context_menu_visible();
6481 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6482 .icon_size(icon_size)
6483 .shape(ui::IconButtonShape::Square)
6484 .icon_color(ui::Color::Hidden)
6485 .toggle_state(is_active)
6486 .when(show_tooltip, |this| {
6487 this.tooltip({
6488 let focus_handle = self.focus_handle.clone();
6489 move |_window, cx| {
6490 Tooltip::for_action_in(
6491 "Toggle Code Actions",
6492 &ToggleCodeActions {
6493 deployed_from: None,
6494 quick_launch: false,
6495 },
6496 &focus_handle,
6497 cx,
6498 )
6499 }
6500 })
6501 })
6502 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6503 window.focus(&editor.focus_handle(cx));
6504 editor.toggle_code_actions(
6505 &crate::actions::ToggleCodeActions {
6506 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6507 display_row,
6508 )),
6509 quick_launch: false,
6510 },
6511 window,
6512 cx,
6513 );
6514 }))
6515 .into_any_element()
6516 }
6517
6518 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6519 &self.context_menu
6520 }
6521
6522 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6523 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6524 cx.background_executor()
6525 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6526 .await;
6527
6528 let (start_buffer, start, _, end, newest_selection) = this
6529 .update(cx, |this, cx| {
6530 let newest_selection = this.selections.newest_anchor().clone();
6531 if newest_selection.head().diff_base_anchor.is_some() {
6532 return None;
6533 }
6534 let display_snapshot = this.display_snapshot(cx);
6535 let newest_selection_adjusted =
6536 this.selections.newest_adjusted(&display_snapshot);
6537 let buffer = this.buffer.read(cx);
6538
6539 let (start_buffer, start) =
6540 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6541 let (end_buffer, end) =
6542 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6543
6544 Some((start_buffer, start, end_buffer, end, newest_selection))
6545 })?
6546 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6547 .context(
6548 "Expected selection to lie in a single buffer when refreshing code actions",
6549 )?;
6550 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6551 let providers = this.code_action_providers.clone();
6552 let tasks = this
6553 .code_action_providers
6554 .iter()
6555 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6556 .collect::<Vec<_>>();
6557 (providers, tasks)
6558 })?;
6559
6560 let mut actions = Vec::new();
6561 for (provider, provider_actions) in
6562 providers.into_iter().zip(future::join_all(tasks).await)
6563 {
6564 if let Some(provider_actions) = provider_actions.log_err() {
6565 actions.extend(provider_actions.into_iter().map(|action| {
6566 AvailableCodeAction {
6567 excerpt_id: newest_selection.start.excerpt_id,
6568 action,
6569 provider: provider.clone(),
6570 }
6571 }));
6572 }
6573 }
6574
6575 this.update(cx, |this, cx| {
6576 this.available_code_actions = if actions.is_empty() {
6577 None
6578 } else {
6579 Some((
6580 Location {
6581 buffer: start_buffer,
6582 range: start..end,
6583 },
6584 actions.into(),
6585 ))
6586 };
6587 cx.notify();
6588 })
6589 }));
6590 }
6591
6592 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6593 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6594 self.show_git_blame_inline = false;
6595
6596 self.show_git_blame_inline_delay_task =
6597 Some(cx.spawn_in(window, async move |this, cx| {
6598 cx.background_executor().timer(delay).await;
6599
6600 this.update(cx, |this, cx| {
6601 this.show_git_blame_inline = true;
6602 cx.notify();
6603 })
6604 .log_err();
6605 }));
6606 }
6607 }
6608
6609 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6610 let snapshot = self.snapshot(window, cx);
6611 let cursor = self
6612 .selections
6613 .newest::<Point>(&snapshot.display_snapshot)
6614 .head();
6615 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6616 else {
6617 return;
6618 };
6619
6620 let Some(blame) = self.blame.as_ref() else {
6621 return;
6622 };
6623
6624 let row_info = RowInfo {
6625 buffer_id: Some(buffer.remote_id()),
6626 buffer_row: Some(point.row),
6627 ..Default::default()
6628 };
6629 let Some((buffer, blame_entry)) = blame
6630 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6631 .flatten()
6632 else {
6633 return;
6634 };
6635
6636 let anchor = self.selections.newest_anchor().head();
6637 let position = self.to_pixel_point(anchor, &snapshot, window);
6638 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6639 self.show_blame_popover(
6640 buffer,
6641 &blame_entry,
6642 position + last_bounds.origin,
6643 true,
6644 cx,
6645 );
6646 };
6647 }
6648
6649 fn show_blame_popover(
6650 &mut self,
6651 buffer: BufferId,
6652 blame_entry: &BlameEntry,
6653 position: gpui::Point<Pixels>,
6654 ignore_timeout: bool,
6655 cx: &mut Context<Self>,
6656 ) {
6657 if let Some(state) = &mut self.inline_blame_popover {
6658 state.hide_task.take();
6659 } else {
6660 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6661 let blame_entry = blame_entry.clone();
6662 let show_task = cx.spawn(async move |editor, cx| {
6663 if !ignore_timeout {
6664 cx.background_executor()
6665 .timer(std::time::Duration::from_millis(blame_popover_delay))
6666 .await;
6667 }
6668 editor
6669 .update(cx, |editor, cx| {
6670 editor.inline_blame_popover_show_task.take();
6671 let Some(blame) = editor.blame.as_ref() else {
6672 return;
6673 };
6674 let blame = blame.read(cx);
6675 let details = blame.details_for_entry(buffer, &blame_entry);
6676 let markdown = cx.new(|cx| {
6677 Markdown::new(
6678 details
6679 .as_ref()
6680 .map(|message| message.message.clone())
6681 .unwrap_or_default(),
6682 None,
6683 None,
6684 cx,
6685 )
6686 });
6687 editor.inline_blame_popover = Some(InlineBlamePopover {
6688 position,
6689 hide_task: None,
6690 popover_bounds: None,
6691 popover_state: InlineBlamePopoverState {
6692 scroll_handle: ScrollHandle::new(),
6693 commit_message: details,
6694 markdown,
6695 },
6696 keyboard_grace: ignore_timeout,
6697 });
6698 cx.notify();
6699 })
6700 .ok();
6701 });
6702 self.inline_blame_popover_show_task = Some(show_task);
6703 }
6704 }
6705
6706 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6707 self.inline_blame_popover_show_task.take();
6708 if let Some(state) = &mut self.inline_blame_popover {
6709 let hide_task = cx.spawn(async move |editor, cx| {
6710 if !ignore_timeout {
6711 cx.background_executor()
6712 .timer(std::time::Duration::from_millis(100))
6713 .await;
6714 }
6715 editor
6716 .update(cx, |editor, cx| {
6717 editor.inline_blame_popover.take();
6718 cx.notify();
6719 })
6720 .ok();
6721 });
6722 state.hide_task = Some(hide_task);
6723 true
6724 } else {
6725 false
6726 }
6727 }
6728
6729 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6730 if self.pending_rename.is_some() {
6731 return None;
6732 }
6733
6734 let provider = self.semantics_provider.clone()?;
6735 let buffer = self.buffer.read(cx);
6736 let newest_selection = self.selections.newest_anchor().clone();
6737 let cursor_position = newest_selection.head();
6738 let (cursor_buffer, cursor_buffer_position) =
6739 buffer.text_anchor_for_position(cursor_position, cx)?;
6740 let (tail_buffer, tail_buffer_position) =
6741 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6742 if cursor_buffer != tail_buffer {
6743 return None;
6744 }
6745
6746 let snapshot = cursor_buffer.read(cx).snapshot();
6747 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6748 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6749 if start_word_range != end_word_range {
6750 self.document_highlights_task.take();
6751 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6752 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6753 return None;
6754 }
6755
6756 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6757 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6758 cx.background_executor()
6759 .timer(Duration::from_millis(debounce))
6760 .await;
6761
6762 let highlights = if let Some(highlights) = cx
6763 .update(|cx| {
6764 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6765 })
6766 .ok()
6767 .flatten()
6768 {
6769 highlights.await.log_err()
6770 } else {
6771 None
6772 };
6773
6774 if let Some(highlights) = highlights {
6775 this.update(cx, |this, cx| {
6776 if this.pending_rename.is_some() {
6777 return;
6778 }
6779
6780 let buffer = this.buffer.read(cx);
6781 if buffer
6782 .text_anchor_for_position(cursor_position, cx)
6783 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6784 {
6785 return;
6786 }
6787
6788 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6789 let mut write_ranges = Vec::new();
6790 let mut read_ranges = Vec::new();
6791 for highlight in highlights {
6792 let buffer_id = cursor_buffer.read(cx).remote_id();
6793 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6794 {
6795 let start = highlight
6796 .range
6797 .start
6798 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6799 let end = highlight
6800 .range
6801 .end
6802 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6803 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6804 continue;
6805 }
6806
6807 let range =
6808 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6809 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6810 write_ranges.push(range);
6811 } else {
6812 read_ranges.push(range);
6813 }
6814 }
6815 }
6816
6817 this.highlight_background::<DocumentHighlightRead>(
6818 &read_ranges,
6819 |theme| theme.colors().editor_document_highlight_read_background,
6820 cx,
6821 );
6822 this.highlight_background::<DocumentHighlightWrite>(
6823 &write_ranges,
6824 |theme| theme.colors().editor_document_highlight_write_background,
6825 cx,
6826 );
6827 cx.notify();
6828 })
6829 .log_err();
6830 }
6831 }));
6832 None
6833 }
6834
6835 fn prepare_highlight_query_from_selection(
6836 &mut self,
6837 window: &Window,
6838 cx: &mut Context<Editor>,
6839 ) -> Option<(String, Range<Anchor>)> {
6840 if matches!(self.mode, EditorMode::SingleLine) {
6841 return None;
6842 }
6843 if !EditorSettings::get_global(cx).selection_highlight {
6844 return None;
6845 }
6846 if self.selections.count() != 1 || self.selections.line_mode() {
6847 return None;
6848 }
6849 let snapshot = self.snapshot(window, cx);
6850 let selection = self.selections.newest::<Point>(&snapshot);
6851 // If the selection spans multiple rows OR it is empty
6852 if selection.start.row != selection.end.row
6853 || selection.start.column == selection.end.column
6854 {
6855 return None;
6856 }
6857 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6858 let query = snapshot
6859 .buffer_snapshot()
6860 .text_for_range(selection_anchor_range.clone())
6861 .collect::<String>();
6862 if query.trim().is_empty() {
6863 return None;
6864 }
6865 Some((query, selection_anchor_range))
6866 }
6867
6868 fn update_selection_occurrence_highlights(
6869 &mut self,
6870 query_text: String,
6871 query_range: Range<Anchor>,
6872 multi_buffer_range_to_query: Range<Point>,
6873 use_debounce: bool,
6874 window: &mut Window,
6875 cx: &mut Context<Editor>,
6876 ) -> Task<()> {
6877 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6878 cx.spawn_in(window, async move |editor, cx| {
6879 if use_debounce {
6880 cx.background_executor()
6881 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6882 .await;
6883 }
6884 let match_task = cx.background_spawn(async move {
6885 let buffer_ranges = multi_buffer_snapshot
6886 .range_to_buffer_ranges(multi_buffer_range_to_query)
6887 .into_iter()
6888 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6889 let mut match_ranges = Vec::new();
6890 let Ok(regex) = project::search::SearchQuery::text(
6891 query_text.clone(),
6892 false,
6893 false,
6894 false,
6895 Default::default(),
6896 Default::default(),
6897 false,
6898 None,
6899 ) else {
6900 return Vec::default();
6901 };
6902 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6903 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6904 match_ranges.extend(
6905 regex
6906 .search(buffer_snapshot, Some(search_range.clone()))
6907 .await
6908 .into_iter()
6909 .filter_map(|match_range| {
6910 let match_start = buffer_snapshot
6911 .anchor_after(search_range.start + match_range.start);
6912 let match_end = buffer_snapshot
6913 .anchor_before(search_range.start + match_range.end);
6914 let match_anchor_range = Anchor::range_in_buffer(
6915 excerpt_id,
6916 buffer_snapshot.remote_id(),
6917 match_start..match_end,
6918 );
6919 (match_anchor_range != query_range).then_some(match_anchor_range)
6920 }),
6921 );
6922 }
6923 match_ranges
6924 });
6925 let match_ranges = match_task.await;
6926 editor
6927 .update_in(cx, |editor, _, cx| {
6928 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6929 if !match_ranges.is_empty() {
6930 editor.highlight_background::<SelectedTextHighlight>(
6931 &match_ranges,
6932 |theme| theme.colors().editor_document_highlight_bracket_background,
6933 cx,
6934 )
6935 }
6936 })
6937 .log_err();
6938 })
6939 }
6940
6941 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6942 struct NewlineFold;
6943 let type_id = std::any::TypeId::of::<NewlineFold>();
6944 if !self.mode.is_single_line() {
6945 return;
6946 }
6947 let snapshot = self.snapshot(window, cx);
6948 if snapshot.buffer_snapshot().max_point().row == 0 {
6949 return;
6950 }
6951 let task = cx.background_spawn(async move {
6952 let new_newlines = snapshot
6953 .buffer_chars_at(0)
6954 .filter_map(|(c, i)| {
6955 if c == '\n' {
6956 Some(
6957 snapshot.buffer_snapshot().anchor_after(i)
6958 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6959 )
6960 } else {
6961 None
6962 }
6963 })
6964 .collect::<Vec<_>>();
6965 let existing_newlines = snapshot
6966 .folds_in_range(0..snapshot.buffer_snapshot().len())
6967 .filter_map(|fold| {
6968 if fold.placeholder.type_tag == Some(type_id) {
6969 Some(fold.range.start..fold.range.end)
6970 } else {
6971 None
6972 }
6973 })
6974 .collect::<Vec<_>>();
6975
6976 (new_newlines, existing_newlines)
6977 });
6978 self.folding_newlines = cx.spawn(async move |this, cx| {
6979 let (new_newlines, existing_newlines) = task.await;
6980 if new_newlines == existing_newlines {
6981 return;
6982 }
6983 let placeholder = FoldPlaceholder {
6984 render: Arc::new(move |_, _, cx| {
6985 div()
6986 .bg(cx.theme().status().hint_background)
6987 .border_b_1()
6988 .size_full()
6989 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6990 .border_color(cx.theme().status().hint)
6991 .child("\\n")
6992 .into_any()
6993 }),
6994 constrain_width: false,
6995 merge_adjacent: false,
6996 type_tag: Some(type_id),
6997 };
6998 let creases = new_newlines
6999 .into_iter()
7000 .map(|range| Crease::simple(range, placeholder.clone()))
7001 .collect();
7002 this.update(cx, |this, cx| {
7003 this.display_map.update(cx, |display_map, cx| {
7004 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7005 display_map.fold(creases, cx);
7006 });
7007 })
7008 .ok();
7009 });
7010 }
7011
7012 fn refresh_selected_text_highlights(
7013 &mut self,
7014 on_buffer_edit: bool,
7015 window: &mut Window,
7016 cx: &mut Context<Editor>,
7017 ) {
7018 let Some((query_text, query_range)) =
7019 self.prepare_highlight_query_from_selection(window, cx)
7020 else {
7021 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7022 self.quick_selection_highlight_task.take();
7023 self.debounced_selection_highlight_task.take();
7024 return;
7025 };
7026 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7027 if on_buffer_edit
7028 || self
7029 .quick_selection_highlight_task
7030 .as_ref()
7031 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7032 {
7033 let multi_buffer_visible_start = self
7034 .scroll_manager
7035 .anchor()
7036 .anchor
7037 .to_point(&multi_buffer_snapshot);
7038 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7039 multi_buffer_visible_start
7040 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7041 Bias::Left,
7042 );
7043 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7044 self.quick_selection_highlight_task = Some((
7045 query_range.clone(),
7046 self.update_selection_occurrence_highlights(
7047 query_text.clone(),
7048 query_range.clone(),
7049 multi_buffer_visible_range,
7050 false,
7051 window,
7052 cx,
7053 ),
7054 ));
7055 }
7056 if on_buffer_edit
7057 || self
7058 .debounced_selection_highlight_task
7059 .as_ref()
7060 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7061 {
7062 let multi_buffer_start = multi_buffer_snapshot
7063 .anchor_before(0)
7064 .to_point(&multi_buffer_snapshot);
7065 let multi_buffer_end = multi_buffer_snapshot
7066 .anchor_after(multi_buffer_snapshot.len())
7067 .to_point(&multi_buffer_snapshot);
7068 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7069 self.debounced_selection_highlight_task = Some((
7070 query_range.clone(),
7071 self.update_selection_occurrence_highlights(
7072 query_text,
7073 query_range,
7074 multi_buffer_full_range,
7075 true,
7076 window,
7077 cx,
7078 ),
7079 ));
7080 }
7081 }
7082
7083 pub fn refresh_edit_prediction(
7084 &mut self,
7085 debounce: bool,
7086 user_requested: bool,
7087 window: &mut Window,
7088 cx: &mut Context<Self>,
7089 ) -> Option<()> {
7090 if DisableAiSettings::get_global(cx).disable_ai {
7091 return None;
7092 }
7093
7094 let provider = self.edit_prediction_provider()?;
7095 let cursor = self.selections.newest_anchor().head();
7096 let (buffer, cursor_buffer_position) =
7097 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7098
7099 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7100 self.discard_edit_prediction(false, cx);
7101 return None;
7102 }
7103
7104 self.update_visible_edit_prediction(window, cx);
7105
7106 if !user_requested
7107 && (!self.should_show_edit_predictions()
7108 || !self.is_focused(window)
7109 || buffer.read(cx).is_empty())
7110 {
7111 self.discard_edit_prediction(false, cx);
7112 return None;
7113 }
7114
7115 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7116 Some(())
7117 }
7118
7119 fn show_edit_predictions_in_menu(&self) -> bool {
7120 match self.edit_prediction_settings {
7121 EditPredictionSettings::Disabled => false,
7122 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7123 }
7124 }
7125
7126 pub fn edit_predictions_enabled(&self) -> bool {
7127 match self.edit_prediction_settings {
7128 EditPredictionSettings::Disabled => false,
7129 EditPredictionSettings::Enabled { .. } => true,
7130 }
7131 }
7132
7133 fn edit_prediction_requires_modifier(&self) -> bool {
7134 match self.edit_prediction_settings {
7135 EditPredictionSettings::Disabled => false,
7136 EditPredictionSettings::Enabled {
7137 preview_requires_modifier,
7138 ..
7139 } => preview_requires_modifier,
7140 }
7141 }
7142
7143 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7144 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7145 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7146 self.discard_edit_prediction(false, cx);
7147 } else {
7148 let selection = self.selections.newest_anchor();
7149 let cursor = selection.head();
7150
7151 if let Some((buffer, cursor_buffer_position)) =
7152 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7153 {
7154 self.edit_prediction_settings =
7155 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7156 }
7157 }
7158 }
7159
7160 fn edit_prediction_settings_at_position(
7161 &self,
7162 buffer: &Entity<Buffer>,
7163 buffer_position: language::Anchor,
7164 cx: &App,
7165 ) -> EditPredictionSettings {
7166 if !self.mode.is_full()
7167 || !self.show_edit_predictions_override.unwrap_or(true)
7168 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7169 {
7170 return EditPredictionSettings::Disabled;
7171 }
7172
7173 let buffer = buffer.read(cx);
7174
7175 let file = buffer.file();
7176
7177 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7178 return EditPredictionSettings::Disabled;
7179 };
7180
7181 let by_provider = matches!(
7182 self.menu_edit_predictions_policy,
7183 MenuEditPredictionsPolicy::ByProvider
7184 );
7185
7186 let show_in_menu = by_provider
7187 && self
7188 .edit_prediction_provider
7189 .as_ref()
7190 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7191
7192 let preview_requires_modifier =
7193 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7194
7195 EditPredictionSettings::Enabled {
7196 show_in_menu,
7197 preview_requires_modifier,
7198 }
7199 }
7200
7201 fn should_show_edit_predictions(&self) -> bool {
7202 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7203 }
7204
7205 pub fn edit_prediction_preview_is_active(&self) -> bool {
7206 matches!(
7207 self.edit_prediction_preview,
7208 EditPredictionPreview::Active { .. }
7209 )
7210 }
7211
7212 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7213 let cursor = self.selections.newest_anchor().head();
7214 if let Some((buffer, cursor_position)) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7216 {
7217 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7218 } else {
7219 false
7220 }
7221 }
7222
7223 pub fn supports_minimap(&self, cx: &App) -> bool {
7224 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7225 }
7226
7227 fn edit_predictions_enabled_in_buffer(
7228 &self,
7229 buffer: &Entity<Buffer>,
7230 buffer_position: language::Anchor,
7231 cx: &App,
7232 ) -> bool {
7233 maybe!({
7234 if self.read_only(cx) {
7235 return Some(false);
7236 }
7237 let provider = self.edit_prediction_provider()?;
7238 if !provider.is_enabled(buffer, buffer_position, cx) {
7239 return Some(false);
7240 }
7241 let buffer = buffer.read(cx);
7242 let Some(file) = buffer.file() else {
7243 return Some(true);
7244 };
7245 let settings = all_language_settings(Some(file), cx);
7246 Some(settings.edit_predictions_enabled_for_file(file, cx))
7247 })
7248 .unwrap_or(false)
7249 }
7250
7251 fn cycle_edit_prediction(
7252 &mut self,
7253 direction: Direction,
7254 window: &mut Window,
7255 cx: &mut Context<Self>,
7256 ) -> Option<()> {
7257 let provider = self.edit_prediction_provider()?;
7258 let cursor = self.selections.newest_anchor().head();
7259 let (buffer, cursor_buffer_position) =
7260 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7261 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7262 return None;
7263 }
7264
7265 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7266 self.update_visible_edit_prediction(window, cx);
7267
7268 Some(())
7269 }
7270
7271 pub fn show_edit_prediction(
7272 &mut self,
7273 _: &ShowEditPrediction,
7274 window: &mut Window,
7275 cx: &mut Context<Self>,
7276 ) {
7277 if !self.has_active_edit_prediction() {
7278 self.refresh_edit_prediction(false, true, window, cx);
7279 return;
7280 }
7281
7282 self.update_visible_edit_prediction(window, cx);
7283 }
7284
7285 pub fn display_cursor_names(
7286 &mut self,
7287 _: &DisplayCursorNames,
7288 window: &mut Window,
7289 cx: &mut Context<Self>,
7290 ) {
7291 self.show_cursor_names(window, cx);
7292 }
7293
7294 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7295 self.show_cursor_names = true;
7296 cx.notify();
7297 cx.spawn_in(window, async move |this, cx| {
7298 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7299 this.update(cx, |this, cx| {
7300 this.show_cursor_names = false;
7301 cx.notify()
7302 })
7303 .ok()
7304 })
7305 .detach();
7306 }
7307
7308 pub fn next_edit_prediction(
7309 &mut self,
7310 _: &NextEditPrediction,
7311 window: &mut Window,
7312 cx: &mut Context<Self>,
7313 ) {
7314 if self.has_active_edit_prediction() {
7315 self.cycle_edit_prediction(Direction::Next, window, cx);
7316 } else {
7317 let is_copilot_disabled = self
7318 .refresh_edit_prediction(false, true, window, cx)
7319 .is_none();
7320 if is_copilot_disabled {
7321 cx.propagate();
7322 }
7323 }
7324 }
7325
7326 pub fn previous_edit_prediction(
7327 &mut self,
7328 _: &PreviousEditPrediction,
7329 window: &mut Window,
7330 cx: &mut Context<Self>,
7331 ) {
7332 if self.has_active_edit_prediction() {
7333 self.cycle_edit_prediction(Direction::Prev, window, cx);
7334 } else {
7335 let is_copilot_disabled = self
7336 .refresh_edit_prediction(false, true, window, cx)
7337 .is_none();
7338 if is_copilot_disabled {
7339 cx.propagate();
7340 }
7341 }
7342 }
7343
7344 pub fn accept_edit_prediction(
7345 &mut self,
7346 _: &AcceptEditPrediction,
7347 window: &mut Window,
7348 cx: &mut Context<Self>,
7349 ) {
7350 if self.show_edit_predictions_in_menu() {
7351 self.hide_context_menu(window, cx);
7352 }
7353
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357
7358 match &active_edit_prediction.completion {
7359 EditPrediction::MoveWithin { target, .. } => {
7360 let target = *target;
7361
7362 if let Some(position_map) = &self.last_position_map {
7363 if position_map
7364 .visible_row_range
7365 .contains(&target.to_display_point(&position_map.snapshot).row())
7366 || !self.edit_prediction_requires_modifier()
7367 {
7368 self.unfold_ranges(&[target..target], true, false, cx);
7369 // Note that this is also done in vim's handler of the Tab action.
7370 self.change_selections(
7371 SelectionEffects::scroll(Autoscroll::newest()),
7372 window,
7373 cx,
7374 |selections| {
7375 selections.select_anchor_ranges([target..target]);
7376 },
7377 );
7378 self.clear_row_highlights::<EditPredictionPreview>();
7379
7380 self.edit_prediction_preview
7381 .set_previous_scroll_position(None);
7382 } else {
7383 self.edit_prediction_preview
7384 .set_previous_scroll_position(Some(
7385 position_map.snapshot.scroll_anchor,
7386 ));
7387
7388 self.highlight_rows::<EditPredictionPreview>(
7389 target..target,
7390 cx.theme().colors().editor_highlighted_line_background,
7391 RowHighlightOptions {
7392 autoscroll: true,
7393 ..Default::default()
7394 },
7395 cx,
7396 );
7397 self.request_autoscroll(Autoscroll::fit(), cx);
7398 }
7399 }
7400 }
7401 EditPrediction::MoveOutside { snapshot, target } => {
7402 if let Some(workspace) = self.workspace() {
7403 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7404 .detach_and_log_err(cx);
7405 }
7406 }
7407 EditPrediction::Edit { edits, .. } => {
7408 self.report_edit_prediction_event(
7409 active_edit_prediction.completion_id.clone(),
7410 true,
7411 cx,
7412 );
7413
7414 if let Some(provider) = self.edit_prediction_provider() {
7415 provider.accept(cx);
7416 }
7417
7418 // Store the transaction ID and selections before applying the edit
7419 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7420
7421 let snapshot = self.buffer.read(cx).snapshot(cx);
7422 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7423
7424 self.buffer.update(cx, |buffer, cx| {
7425 buffer.edit(edits.iter().cloned(), None, cx)
7426 });
7427
7428 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7429 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7430 });
7431
7432 let selections = self.selections.disjoint_anchors_arc();
7433 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7434 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7435 if has_new_transaction {
7436 self.selection_history
7437 .insert_transaction(transaction_id_now, selections);
7438 }
7439 }
7440
7441 self.update_visible_edit_prediction(window, cx);
7442 if self.active_edit_prediction.is_none() {
7443 self.refresh_edit_prediction(true, true, window, cx);
7444 }
7445
7446 cx.notify();
7447 }
7448 }
7449
7450 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7451 }
7452
7453 pub fn accept_partial_edit_prediction(
7454 &mut self,
7455 _: &AcceptPartialEditPrediction,
7456 window: &mut Window,
7457 cx: &mut Context<Self>,
7458 ) {
7459 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7460 return;
7461 };
7462 if self.selections.count() != 1 {
7463 return;
7464 }
7465
7466 match &active_edit_prediction.completion {
7467 EditPrediction::MoveWithin { target, .. } => {
7468 let target = *target;
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 }
7478 EditPrediction::MoveOutside { snapshot, target } => {
7479 if let Some(workspace) = self.workspace() {
7480 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7481 .detach_and_log_err(cx);
7482 }
7483 }
7484 EditPrediction::Edit { edits, .. } => {
7485 self.report_edit_prediction_event(
7486 active_edit_prediction.completion_id.clone(),
7487 true,
7488 cx,
7489 );
7490
7491 // Find an insertion that starts at the cursor position.
7492 let snapshot = self.buffer.read(cx).snapshot(cx);
7493 let cursor_offset = self
7494 .selections
7495 .newest::<usize>(&self.display_snapshot(cx))
7496 .head();
7497 let insertion = edits.iter().find_map(|(range, text)| {
7498 let range = range.to_offset(&snapshot);
7499 if range.is_empty() && range.start == cursor_offset {
7500 Some(text)
7501 } else {
7502 None
7503 }
7504 });
7505
7506 if let Some(text) = insertion {
7507 let mut partial_completion = text
7508 .chars()
7509 .by_ref()
7510 .take_while(|c| c.is_alphabetic())
7511 .collect::<String>();
7512 if partial_completion.is_empty() {
7513 partial_completion = text
7514 .chars()
7515 .by_ref()
7516 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7517 .collect::<String>();
7518 }
7519
7520 cx.emit(EditorEvent::InputHandled {
7521 utf16_range_to_replace: None,
7522 text: partial_completion.clone().into(),
7523 });
7524
7525 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7526
7527 self.refresh_edit_prediction(true, true, window, cx);
7528 cx.notify();
7529 } else {
7530 self.accept_edit_prediction(&Default::default(), window, cx);
7531 }
7532 }
7533 }
7534 }
7535
7536 fn discard_edit_prediction(
7537 &mut self,
7538 should_report_edit_prediction_event: bool,
7539 cx: &mut Context<Self>,
7540 ) -> bool {
7541 if should_report_edit_prediction_event {
7542 let completion_id = self
7543 .active_edit_prediction
7544 .as_ref()
7545 .and_then(|active_completion| active_completion.completion_id.clone());
7546
7547 self.report_edit_prediction_event(completion_id, false, cx);
7548 }
7549
7550 if let Some(provider) = self.edit_prediction_provider() {
7551 provider.discard(cx);
7552 }
7553
7554 self.take_active_edit_prediction(cx)
7555 }
7556
7557 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7558 let Some(provider) = self.edit_prediction_provider() else {
7559 return;
7560 };
7561
7562 let Some((_, buffer, _)) = self
7563 .buffer
7564 .read(cx)
7565 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7566 else {
7567 return;
7568 };
7569
7570 let extension = buffer
7571 .read(cx)
7572 .file()
7573 .and_then(|file| Some(file.path().extension()?.to_string()));
7574
7575 let event_type = match accepted {
7576 true => "Edit Prediction Accepted",
7577 false => "Edit Prediction Discarded",
7578 };
7579 telemetry::event!(
7580 event_type,
7581 provider = provider.name(),
7582 prediction_id = id,
7583 suggestion_accepted = accepted,
7584 file_extension = extension,
7585 );
7586 }
7587
7588 fn open_editor_at_anchor(
7589 snapshot: &language::BufferSnapshot,
7590 target: language::Anchor,
7591 workspace: &Entity<Workspace>,
7592 window: &mut Window,
7593 cx: &mut App,
7594 ) -> Task<Result<()>> {
7595 workspace.update(cx, |workspace, cx| {
7596 let path = snapshot.file().map(|file| file.full_path(cx));
7597 let Some(path) =
7598 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7599 else {
7600 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7601 };
7602 let target = text::ToPoint::to_point(&target, snapshot);
7603 let item = workspace.open_path(path, None, true, window, cx);
7604 window.spawn(cx, async move |cx| {
7605 let Some(editor) = item.await?.downcast::<Editor>() else {
7606 return Ok(());
7607 };
7608 editor
7609 .update_in(cx, |editor, window, cx| {
7610 editor.go_to_singleton_buffer_point(target, window, cx);
7611 })
7612 .ok();
7613 anyhow::Ok(())
7614 })
7615 })
7616 }
7617
7618 pub fn has_active_edit_prediction(&self) -> bool {
7619 self.active_edit_prediction.is_some()
7620 }
7621
7622 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7623 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7624 return false;
7625 };
7626
7627 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7628 self.clear_highlights::<EditPredictionHighlight>(cx);
7629 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7630 true
7631 }
7632
7633 /// Returns true when we're displaying the edit prediction popover below the cursor
7634 /// like we are not previewing and the LSP autocomplete menu is visible
7635 /// or we are in `when_holding_modifier` mode.
7636 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7637 if self.edit_prediction_preview_is_active()
7638 || !self.show_edit_predictions_in_menu()
7639 || !self.edit_predictions_enabled()
7640 {
7641 return false;
7642 }
7643
7644 if self.has_visible_completions_menu() {
7645 return true;
7646 }
7647
7648 has_completion && self.edit_prediction_requires_modifier()
7649 }
7650
7651 fn handle_modifiers_changed(
7652 &mut self,
7653 modifiers: Modifiers,
7654 position_map: &PositionMap,
7655 window: &mut Window,
7656 cx: &mut Context<Self>,
7657 ) {
7658 // Ensure that the edit prediction preview is updated, even when not
7659 // enabled, if there's an active edit prediction preview.
7660 if self.show_edit_predictions_in_menu()
7661 || matches!(
7662 self.edit_prediction_preview,
7663 EditPredictionPreview::Active { .. }
7664 )
7665 {
7666 self.update_edit_prediction_preview(&modifiers, window, cx);
7667 }
7668
7669 self.update_selection_mode(&modifiers, position_map, window, cx);
7670
7671 let mouse_position = window.mouse_position();
7672 if !position_map.text_hitbox.is_hovered(window) {
7673 return;
7674 }
7675
7676 self.update_hovered_link(
7677 position_map.point_for_position(mouse_position),
7678 &position_map.snapshot,
7679 modifiers,
7680 window,
7681 cx,
7682 )
7683 }
7684
7685 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7686 match EditorSettings::get_global(cx).multi_cursor_modifier {
7687 MultiCursorModifier::Alt => modifiers.secondary(),
7688 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7689 }
7690 }
7691
7692 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7693 match EditorSettings::get_global(cx).multi_cursor_modifier {
7694 MultiCursorModifier::Alt => modifiers.alt,
7695 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7696 }
7697 }
7698
7699 fn columnar_selection_mode(
7700 modifiers: &Modifiers,
7701 cx: &mut Context<Self>,
7702 ) -> Option<ColumnarMode> {
7703 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7704 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7705 Some(ColumnarMode::FromMouse)
7706 } else if Self::is_alt_pressed(modifiers, cx) {
7707 Some(ColumnarMode::FromSelection)
7708 } else {
7709 None
7710 }
7711 } else {
7712 None
7713 }
7714 }
7715
7716 fn update_selection_mode(
7717 &mut self,
7718 modifiers: &Modifiers,
7719 position_map: &PositionMap,
7720 window: &mut Window,
7721 cx: &mut Context<Self>,
7722 ) {
7723 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7724 return;
7725 };
7726 if self.selections.pending_anchor().is_none() {
7727 return;
7728 }
7729
7730 let mouse_position = window.mouse_position();
7731 let point_for_position = position_map.point_for_position(mouse_position);
7732 let position = point_for_position.previous_valid;
7733
7734 self.select(
7735 SelectPhase::BeginColumnar {
7736 position,
7737 reset: false,
7738 mode,
7739 goal_column: point_for_position.exact_unclipped.column(),
7740 },
7741 window,
7742 cx,
7743 );
7744 }
7745
7746 fn update_edit_prediction_preview(
7747 &mut self,
7748 modifiers: &Modifiers,
7749 window: &mut Window,
7750 cx: &mut Context<Self>,
7751 ) {
7752 let mut modifiers_held = false;
7753 if let Some(accept_keystroke) = self
7754 .accept_edit_prediction_keybind(false, window, cx)
7755 .keystroke()
7756 {
7757 modifiers_held = modifiers_held
7758 || (accept_keystroke.modifiers() == modifiers
7759 && accept_keystroke.modifiers().modified());
7760 };
7761 if let Some(accept_partial_keystroke) = self
7762 .accept_edit_prediction_keybind(true, window, cx)
7763 .keystroke()
7764 {
7765 modifiers_held = modifiers_held
7766 || (accept_partial_keystroke.modifiers() == modifiers
7767 && accept_partial_keystroke.modifiers().modified());
7768 }
7769
7770 if modifiers_held {
7771 if matches!(
7772 self.edit_prediction_preview,
7773 EditPredictionPreview::Inactive { .. }
7774 ) {
7775 self.edit_prediction_preview = EditPredictionPreview::Active {
7776 previous_scroll_position: None,
7777 since: Instant::now(),
7778 };
7779
7780 self.update_visible_edit_prediction(window, cx);
7781 cx.notify();
7782 }
7783 } else if let EditPredictionPreview::Active {
7784 previous_scroll_position,
7785 since,
7786 } = self.edit_prediction_preview
7787 {
7788 if let (Some(previous_scroll_position), Some(position_map)) =
7789 (previous_scroll_position, self.last_position_map.as_ref())
7790 {
7791 self.set_scroll_position(
7792 previous_scroll_position
7793 .scroll_position(&position_map.snapshot.display_snapshot),
7794 window,
7795 cx,
7796 );
7797 }
7798
7799 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7800 released_too_fast: since.elapsed() < Duration::from_millis(200),
7801 };
7802 self.clear_row_highlights::<EditPredictionPreview>();
7803 self.update_visible_edit_prediction(window, cx);
7804 cx.notify();
7805 }
7806 }
7807
7808 fn update_visible_edit_prediction(
7809 &mut self,
7810 _window: &mut Window,
7811 cx: &mut Context<Self>,
7812 ) -> Option<()> {
7813 if DisableAiSettings::get_global(cx).disable_ai {
7814 return None;
7815 }
7816
7817 if self.ime_transaction.is_some() {
7818 self.discard_edit_prediction(false, cx);
7819 return None;
7820 }
7821
7822 let selection = self.selections.newest_anchor();
7823 let cursor = selection.head();
7824 let multibuffer = self.buffer.read(cx).snapshot(cx);
7825 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7826 let excerpt_id = cursor.excerpt_id;
7827
7828 let show_in_menu = self.show_edit_predictions_in_menu();
7829 let completions_menu_has_precedence = !show_in_menu
7830 && (self.context_menu.borrow().is_some()
7831 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7832
7833 if completions_menu_has_precedence
7834 || !offset_selection.is_empty()
7835 || self
7836 .active_edit_prediction
7837 .as_ref()
7838 .is_some_and(|completion| {
7839 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7840 return false;
7841 };
7842 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7843 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7844 !invalidation_range.contains(&offset_selection.head())
7845 })
7846 {
7847 self.discard_edit_prediction(false, cx);
7848 return None;
7849 }
7850
7851 self.take_active_edit_prediction(cx);
7852 let Some(provider) = self.edit_prediction_provider() else {
7853 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7854 return None;
7855 };
7856
7857 let (buffer, cursor_buffer_position) =
7858 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7859
7860 self.edit_prediction_settings =
7861 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7862
7863 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7864
7865 if self.edit_prediction_indent_conflict {
7866 let cursor_point = cursor.to_point(&multibuffer);
7867
7868 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7869
7870 if let Some((_, indent)) = indents.iter().next()
7871 && indent.len == cursor_point.column
7872 {
7873 self.edit_prediction_indent_conflict = false;
7874 }
7875 }
7876
7877 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7878
7879 let (completion_id, edits, edit_preview) = match edit_prediction {
7880 edit_prediction::EditPrediction::Local {
7881 id,
7882 edits,
7883 edit_preview,
7884 } => (id, edits, edit_preview),
7885 edit_prediction::EditPrediction::Jump {
7886 id,
7887 snapshot,
7888 target,
7889 } => {
7890 self.stale_edit_prediction_in_menu = None;
7891 self.active_edit_prediction = Some(EditPredictionState {
7892 inlay_ids: vec![],
7893 completion: EditPrediction::MoveOutside { snapshot, target },
7894 completion_id: id,
7895 invalidation_range: None,
7896 });
7897 cx.notify();
7898 return Some(());
7899 }
7900 };
7901
7902 let edits = edits
7903 .into_iter()
7904 .flat_map(|(range, new_text)| {
7905 Some((
7906 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7907 new_text,
7908 ))
7909 })
7910 .collect::<Vec<_>>();
7911 if edits.is_empty() {
7912 return None;
7913 }
7914
7915 let first_edit_start = edits.first().unwrap().0.start;
7916 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7917 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7918
7919 let last_edit_end = edits.last().unwrap().0.end;
7920 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7921 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7922
7923 let cursor_row = cursor.to_point(&multibuffer).row;
7924
7925 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7926
7927 let mut inlay_ids = Vec::new();
7928 let invalidation_row_range;
7929 let move_invalidation_row_range = if cursor_row < edit_start_row {
7930 Some(cursor_row..edit_end_row)
7931 } else if cursor_row > edit_end_row {
7932 Some(edit_start_row..cursor_row)
7933 } else {
7934 None
7935 };
7936 let supports_jump = self
7937 .edit_prediction_provider
7938 .as_ref()
7939 .map(|provider| provider.provider.supports_jump_to_edit())
7940 .unwrap_or(true);
7941
7942 let is_move = supports_jump
7943 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7944 let completion = if is_move {
7945 invalidation_row_range =
7946 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7947 let target = first_edit_start;
7948 EditPrediction::MoveWithin { target, snapshot }
7949 } else {
7950 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7951 && !self.edit_predictions_hidden_for_vim_mode;
7952
7953 if show_completions_in_buffer {
7954 if edits
7955 .iter()
7956 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7957 {
7958 let mut inlays = Vec::new();
7959 for (range, new_text) in &edits {
7960 let inlay = Inlay::edit_prediction(
7961 post_inc(&mut self.next_inlay_id),
7962 range.start,
7963 new_text.as_ref(),
7964 );
7965 inlay_ids.push(inlay.id);
7966 inlays.push(inlay);
7967 }
7968
7969 self.splice_inlays(&[], inlays, cx);
7970 } else {
7971 let background_color = cx.theme().status().deleted_background;
7972 self.highlight_text::<EditPredictionHighlight>(
7973 edits.iter().map(|(range, _)| range.clone()).collect(),
7974 HighlightStyle {
7975 background_color: Some(background_color),
7976 ..Default::default()
7977 },
7978 cx,
7979 );
7980 }
7981 }
7982
7983 invalidation_row_range = edit_start_row..edit_end_row;
7984
7985 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7986 if provider.show_tab_accept_marker() {
7987 EditDisplayMode::TabAccept
7988 } else {
7989 EditDisplayMode::Inline
7990 }
7991 } else {
7992 EditDisplayMode::DiffPopover
7993 };
7994
7995 EditPrediction::Edit {
7996 edits,
7997 edit_preview,
7998 display_mode,
7999 snapshot,
8000 }
8001 };
8002
8003 let invalidation_range = multibuffer
8004 .anchor_before(Point::new(invalidation_row_range.start, 0))
8005 ..multibuffer.anchor_after(Point::new(
8006 invalidation_row_range.end,
8007 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8008 ));
8009
8010 self.stale_edit_prediction_in_menu = None;
8011 self.active_edit_prediction = Some(EditPredictionState {
8012 inlay_ids,
8013 completion,
8014 completion_id,
8015 invalidation_range: Some(invalidation_range),
8016 });
8017
8018 cx.notify();
8019
8020 Some(())
8021 }
8022
8023 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8024 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8025 }
8026
8027 fn clear_tasks(&mut self) {
8028 self.tasks.clear()
8029 }
8030
8031 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8032 if self.tasks.insert(key, value).is_some() {
8033 // This case should hopefully be rare, but just in case...
8034 log::error!(
8035 "multiple different run targets found on a single line, only the last target will be rendered"
8036 )
8037 }
8038 }
8039
8040 /// Get all display points of breakpoints that will be rendered within editor
8041 ///
8042 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8043 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8044 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8045 fn active_breakpoints(
8046 &self,
8047 range: Range<DisplayRow>,
8048 window: &mut Window,
8049 cx: &mut Context<Self>,
8050 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8051 let mut breakpoint_display_points = HashMap::default();
8052
8053 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8054 return breakpoint_display_points;
8055 };
8056
8057 let snapshot = self.snapshot(window, cx);
8058
8059 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8060 let Some(project) = self.project() else {
8061 return breakpoint_display_points;
8062 };
8063
8064 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8065 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8066
8067 for (buffer_snapshot, range, excerpt_id) in
8068 multi_buffer_snapshot.range_to_buffer_ranges(range)
8069 {
8070 let Some(buffer) = project
8071 .read(cx)
8072 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8073 else {
8074 continue;
8075 };
8076 let breakpoints = breakpoint_store.read(cx).breakpoints(
8077 &buffer,
8078 Some(
8079 buffer_snapshot.anchor_before(range.start)
8080 ..buffer_snapshot.anchor_after(range.end),
8081 ),
8082 buffer_snapshot,
8083 cx,
8084 );
8085 for (breakpoint, state) in breakpoints {
8086 let multi_buffer_anchor =
8087 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8088 let position = multi_buffer_anchor
8089 .to_point(&multi_buffer_snapshot)
8090 .to_display_point(&snapshot);
8091
8092 breakpoint_display_points.insert(
8093 position.row(),
8094 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8095 );
8096 }
8097 }
8098
8099 breakpoint_display_points
8100 }
8101
8102 fn breakpoint_context_menu(
8103 &self,
8104 anchor: Anchor,
8105 window: &mut Window,
8106 cx: &mut Context<Self>,
8107 ) -> Entity<ui::ContextMenu> {
8108 let weak_editor = cx.weak_entity();
8109 let focus_handle = self.focus_handle(cx);
8110
8111 let row = self
8112 .buffer
8113 .read(cx)
8114 .snapshot(cx)
8115 .summary_for_anchor::<Point>(&anchor)
8116 .row;
8117
8118 let breakpoint = self
8119 .breakpoint_at_row(row, window, cx)
8120 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8121
8122 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8123 "Edit Log Breakpoint"
8124 } else {
8125 "Set Log Breakpoint"
8126 };
8127
8128 let condition_breakpoint_msg = if breakpoint
8129 .as_ref()
8130 .is_some_and(|bp| bp.1.condition.is_some())
8131 {
8132 "Edit Condition Breakpoint"
8133 } else {
8134 "Set Condition Breakpoint"
8135 };
8136
8137 let hit_condition_breakpoint_msg = if breakpoint
8138 .as_ref()
8139 .is_some_and(|bp| bp.1.hit_condition.is_some())
8140 {
8141 "Edit Hit Condition Breakpoint"
8142 } else {
8143 "Set Hit Condition Breakpoint"
8144 };
8145
8146 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8147 "Unset Breakpoint"
8148 } else {
8149 "Set Breakpoint"
8150 };
8151
8152 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8153
8154 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8155 BreakpointState::Enabled => Some("Disable"),
8156 BreakpointState::Disabled => Some("Enable"),
8157 });
8158
8159 let (anchor, breakpoint) =
8160 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8161
8162 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8163 menu.on_blur_subscription(Subscription::new(|| {}))
8164 .context(focus_handle)
8165 .when(run_to_cursor, |this| {
8166 let weak_editor = weak_editor.clone();
8167 this.entry("Run to cursor", None, move |window, cx| {
8168 weak_editor
8169 .update(cx, |editor, cx| {
8170 editor.change_selections(
8171 SelectionEffects::no_scroll(),
8172 window,
8173 cx,
8174 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8175 );
8176 })
8177 .ok();
8178
8179 window.dispatch_action(Box::new(RunToCursor), cx);
8180 })
8181 .separator()
8182 })
8183 .when_some(toggle_state_msg, |this, msg| {
8184 this.entry(msg, None, {
8185 let weak_editor = weak_editor.clone();
8186 let breakpoint = breakpoint.clone();
8187 move |_window, cx| {
8188 weak_editor
8189 .update(cx, |this, cx| {
8190 this.edit_breakpoint_at_anchor(
8191 anchor,
8192 breakpoint.as_ref().clone(),
8193 BreakpointEditAction::InvertState,
8194 cx,
8195 );
8196 })
8197 .log_err();
8198 }
8199 })
8200 })
8201 .entry(set_breakpoint_msg, None, {
8202 let weak_editor = weak_editor.clone();
8203 let breakpoint = breakpoint.clone();
8204 move |_window, cx| {
8205 weak_editor
8206 .update(cx, |this, cx| {
8207 this.edit_breakpoint_at_anchor(
8208 anchor,
8209 breakpoint.as_ref().clone(),
8210 BreakpointEditAction::Toggle,
8211 cx,
8212 );
8213 })
8214 .log_err();
8215 }
8216 })
8217 .entry(log_breakpoint_msg, None, {
8218 let breakpoint = breakpoint.clone();
8219 let weak_editor = weak_editor.clone();
8220 move |window, cx| {
8221 weak_editor
8222 .update(cx, |this, cx| {
8223 this.add_edit_breakpoint_block(
8224 anchor,
8225 breakpoint.as_ref(),
8226 BreakpointPromptEditAction::Log,
8227 window,
8228 cx,
8229 );
8230 })
8231 .log_err();
8232 }
8233 })
8234 .entry(condition_breakpoint_msg, None, {
8235 let breakpoint = breakpoint.clone();
8236 let weak_editor = weak_editor.clone();
8237 move |window, cx| {
8238 weak_editor
8239 .update(cx, |this, cx| {
8240 this.add_edit_breakpoint_block(
8241 anchor,
8242 breakpoint.as_ref(),
8243 BreakpointPromptEditAction::Condition,
8244 window,
8245 cx,
8246 );
8247 })
8248 .log_err();
8249 }
8250 })
8251 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8252 weak_editor
8253 .update(cx, |this, cx| {
8254 this.add_edit_breakpoint_block(
8255 anchor,
8256 breakpoint.as_ref(),
8257 BreakpointPromptEditAction::HitCondition,
8258 window,
8259 cx,
8260 );
8261 })
8262 .log_err();
8263 })
8264 })
8265 }
8266
8267 fn render_breakpoint(
8268 &self,
8269 position: Anchor,
8270 row: DisplayRow,
8271 breakpoint: &Breakpoint,
8272 state: Option<BreakpointSessionState>,
8273 cx: &mut Context<Self>,
8274 ) -> IconButton {
8275 let is_rejected = state.is_some_and(|s| !s.verified);
8276 // Is it a breakpoint that shows up when hovering over gutter?
8277 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8278 (false, false),
8279 |PhantomBreakpointIndicator {
8280 is_active,
8281 display_row,
8282 collides_with_existing_breakpoint,
8283 }| {
8284 (
8285 is_active && display_row == row,
8286 collides_with_existing_breakpoint,
8287 )
8288 },
8289 );
8290
8291 let (color, icon) = {
8292 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8293 (false, false) => ui::IconName::DebugBreakpoint,
8294 (true, false) => ui::IconName::DebugLogBreakpoint,
8295 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8296 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8297 };
8298
8299 let color = if is_phantom {
8300 Color::Hint
8301 } else if is_rejected {
8302 Color::Disabled
8303 } else {
8304 Color::Debugger
8305 };
8306
8307 (color, icon)
8308 };
8309
8310 let breakpoint = Arc::from(breakpoint.clone());
8311
8312 let alt_as_text = gpui::Keystroke {
8313 modifiers: Modifiers::secondary_key(),
8314 ..Default::default()
8315 };
8316 let primary_action_text = if breakpoint.is_disabled() {
8317 "Enable breakpoint"
8318 } else if is_phantom && !collides_with_existing {
8319 "Set breakpoint"
8320 } else {
8321 "Unset breakpoint"
8322 };
8323 let focus_handle = self.focus_handle.clone();
8324
8325 let meta = if is_rejected {
8326 SharedString::from("No executable code is associated with this line.")
8327 } else if collides_with_existing && !breakpoint.is_disabled() {
8328 SharedString::from(format!(
8329 "{alt_as_text}-click to disable,\nright-click for more options."
8330 ))
8331 } else {
8332 SharedString::from("Right-click for more options.")
8333 };
8334 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8335 .icon_size(IconSize::XSmall)
8336 .size(ui::ButtonSize::None)
8337 .when(is_rejected, |this| {
8338 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8339 })
8340 .icon_color(color)
8341 .style(ButtonStyle::Transparent)
8342 .on_click(cx.listener({
8343 move |editor, event: &ClickEvent, window, cx| {
8344 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8345 BreakpointEditAction::InvertState
8346 } else {
8347 BreakpointEditAction::Toggle
8348 };
8349
8350 window.focus(&editor.focus_handle(cx));
8351 editor.edit_breakpoint_at_anchor(
8352 position,
8353 breakpoint.as_ref().clone(),
8354 edit_action,
8355 cx,
8356 );
8357 }
8358 }))
8359 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8360 editor.set_breakpoint_context_menu(
8361 row,
8362 Some(position),
8363 event.position(),
8364 window,
8365 cx,
8366 );
8367 }))
8368 .tooltip(move |_window, cx| {
8369 Tooltip::with_meta_in(
8370 primary_action_text,
8371 Some(&ToggleBreakpoint),
8372 meta.clone(),
8373 &focus_handle,
8374 cx,
8375 )
8376 })
8377 }
8378
8379 fn build_tasks_context(
8380 project: &Entity<Project>,
8381 buffer: &Entity<Buffer>,
8382 buffer_row: u32,
8383 tasks: &Arc<RunnableTasks>,
8384 cx: &mut Context<Self>,
8385 ) -> Task<Option<task::TaskContext>> {
8386 let position = Point::new(buffer_row, tasks.column);
8387 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8388 let location = Location {
8389 buffer: buffer.clone(),
8390 range: range_start..range_start,
8391 };
8392 // Fill in the environmental variables from the tree-sitter captures
8393 let mut captured_task_variables = TaskVariables::default();
8394 for (capture_name, value) in tasks.extra_variables.clone() {
8395 captured_task_variables.insert(
8396 task::VariableName::Custom(capture_name.into()),
8397 value.clone(),
8398 );
8399 }
8400 project.update(cx, |project, cx| {
8401 project.task_store().update(cx, |task_store, cx| {
8402 task_store.task_context_for_location(captured_task_variables, location, cx)
8403 })
8404 })
8405 }
8406
8407 pub fn spawn_nearest_task(
8408 &mut self,
8409 action: &SpawnNearestTask,
8410 window: &mut Window,
8411 cx: &mut Context<Self>,
8412 ) {
8413 let Some((workspace, _)) = self.workspace.clone() else {
8414 return;
8415 };
8416 let Some(project) = self.project.clone() else {
8417 return;
8418 };
8419
8420 // Try to find a closest, enclosing node using tree-sitter that has a task
8421 let Some((buffer, buffer_row, tasks)) = self
8422 .find_enclosing_node_task(cx)
8423 // Or find the task that's closest in row-distance.
8424 .or_else(|| self.find_closest_task(cx))
8425 else {
8426 return;
8427 };
8428
8429 let reveal_strategy = action.reveal;
8430 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8431 cx.spawn_in(window, async move |_, cx| {
8432 let context = task_context.await?;
8433 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8434
8435 let resolved = &mut resolved_task.resolved;
8436 resolved.reveal = reveal_strategy;
8437
8438 workspace
8439 .update_in(cx, |workspace, window, cx| {
8440 workspace.schedule_resolved_task(
8441 task_source_kind,
8442 resolved_task,
8443 false,
8444 window,
8445 cx,
8446 );
8447 })
8448 .ok()
8449 })
8450 .detach();
8451 }
8452
8453 fn find_closest_task(
8454 &mut self,
8455 cx: &mut Context<Self>,
8456 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8457 let cursor_row = self
8458 .selections
8459 .newest_adjusted(&self.display_snapshot(cx))
8460 .head()
8461 .row;
8462
8463 let ((buffer_id, row), tasks) = self
8464 .tasks
8465 .iter()
8466 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8467
8468 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8469 let tasks = Arc::new(tasks.to_owned());
8470 Some((buffer, *row, tasks))
8471 }
8472
8473 fn find_enclosing_node_task(
8474 &mut self,
8475 cx: &mut Context<Self>,
8476 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8477 let snapshot = self.buffer.read(cx).snapshot(cx);
8478 let offset = self
8479 .selections
8480 .newest::<usize>(&self.display_snapshot(cx))
8481 .head();
8482 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8483 let buffer_id = excerpt.buffer().remote_id();
8484
8485 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8486 let mut cursor = layer.node().walk();
8487
8488 while cursor.goto_first_child_for_byte(offset).is_some() {
8489 if cursor.node().end_byte() == offset {
8490 cursor.goto_next_sibling();
8491 }
8492 }
8493
8494 // Ascend to the smallest ancestor that contains the range and has a task.
8495 loop {
8496 let node = cursor.node();
8497 let node_range = node.byte_range();
8498 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8499
8500 // Check if this node contains our offset
8501 if node_range.start <= offset && node_range.end >= offset {
8502 // If it contains offset, check for task
8503 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8504 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8505 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8506 }
8507 }
8508
8509 if !cursor.goto_parent() {
8510 break;
8511 }
8512 }
8513 None
8514 }
8515
8516 fn render_run_indicator(
8517 &self,
8518 _style: &EditorStyle,
8519 is_active: bool,
8520 row: DisplayRow,
8521 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8522 cx: &mut Context<Self>,
8523 ) -> IconButton {
8524 let color = Color::Muted;
8525 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8526
8527 IconButton::new(
8528 ("run_indicator", row.0 as usize),
8529 ui::IconName::PlayOutlined,
8530 )
8531 .shape(ui::IconButtonShape::Square)
8532 .icon_size(IconSize::XSmall)
8533 .icon_color(color)
8534 .toggle_state(is_active)
8535 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8536 let quick_launch = match e {
8537 ClickEvent::Keyboard(_) => true,
8538 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8539 };
8540
8541 window.focus(&editor.focus_handle(cx));
8542 editor.toggle_code_actions(
8543 &ToggleCodeActions {
8544 deployed_from: Some(CodeActionSource::RunMenu(row)),
8545 quick_launch,
8546 },
8547 window,
8548 cx,
8549 );
8550 }))
8551 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8552 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8553 }))
8554 }
8555
8556 pub fn context_menu_visible(&self) -> bool {
8557 !self.edit_prediction_preview_is_active()
8558 && self
8559 .context_menu
8560 .borrow()
8561 .as_ref()
8562 .is_some_and(|menu| menu.visible())
8563 }
8564
8565 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8566 self.context_menu
8567 .borrow()
8568 .as_ref()
8569 .map(|menu| menu.origin())
8570 }
8571
8572 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8573 self.context_menu_options = Some(options);
8574 }
8575
8576 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8577 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8578
8579 fn render_edit_prediction_popover(
8580 &mut self,
8581 text_bounds: &Bounds<Pixels>,
8582 content_origin: gpui::Point<Pixels>,
8583 right_margin: Pixels,
8584 editor_snapshot: &EditorSnapshot,
8585 visible_row_range: Range<DisplayRow>,
8586 scroll_top: ScrollOffset,
8587 scroll_bottom: ScrollOffset,
8588 line_layouts: &[LineWithInvisibles],
8589 line_height: Pixels,
8590 scroll_position: gpui::Point<ScrollOffset>,
8591 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8592 newest_selection_head: Option<DisplayPoint>,
8593 editor_width: Pixels,
8594 style: &EditorStyle,
8595 window: &mut Window,
8596 cx: &mut App,
8597 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8598 if self.mode().is_minimap() {
8599 return None;
8600 }
8601 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8602
8603 if self.edit_prediction_visible_in_cursor_popover(true) {
8604 return None;
8605 }
8606
8607 match &active_edit_prediction.completion {
8608 EditPrediction::MoveWithin { target, .. } => {
8609 let target_display_point = target.to_display_point(editor_snapshot);
8610
8611 if self.edit_prediction_requires_modifier() {
8612 if !self.edit_prediction_preview_is_active() {
8613 return None;
8614 }
8615
8616 self.render_edit_prediction_modifier_jump_popover(
8617 text_bounds,
8618 content_origin,
8619 visible_row_range,
8620 line_layouts,
8621 line_height,
8622 scroll_pixel_position,
8623 newest_selection_head,
8624 target_display_point,
8625 window,
8626 cx,
8627 )
8628 } else {
8629 self.render_edit_prediction_eager_jump_popover(
8630 text_bounds,
8631 content_origin,
8632 editor_snapshot,
8633 visible_row_range,
8634 scroll_top,
8635 scroll_bottom,
8636 line_height,
8637 scroll_pixel_position,
8638 target_display_point,
8639 editor_width,
8640 window,
8641 cx,
8642 )
8643 }
8644 }
8645 EditPrediction::Edit {
8646 display_mode: EditDisplayMode::Inline,
8647 ..
8648 } => None,
8649 EditPrediction::Edit {
8650 display_mode: EditDisplayMode::TabAccept,
8651 edits,
8652 ..
8653 } => {
8654 let range = &edits.first()?.0;
8655 let target_display_point = range.end.to_display_point(editor_snapshot);
8656
8657 self.render_edit_prediction_end_of_line_popover(
8658 "Accept",
8659 editor_snapshot,
8660 visible_row_range,
8661 target_display_point,
8662 line_height,
8663 scroll_pixel_position,
8664 content_origin,
8665 editor_width,
8666 window,
8667 cx,
8668 )
8669 }
8670 EditPrediction::Edit {
8671 edits,
8672 edit_preview,
8673 display_mode: EditDisplayMode::DiffPopover,
8674 snapshot,
8675 } => self.render_edit_prediction_diff_popover(
8676 text_bounds,
8677 content_origin,
8678 right_margin,
8679 editor_snapshot,
8680 visible_row_range,
8681 line_layouts,
8682 line_height,
8683 scroll_position,
8684 scroll_pixel_position,
8685 newest_selection_head,
8686 editor_width,
8687 style,
8688 edits,
8689 edit_preview,
8690 snapshot,
8691 window,
8692 cx,
8693 ),
8694 EditPrediction::MoveOutside { snapshot, .. } => {
8695 let file_name = snapshot
8696 .file()
8697 .map(|file| file.file_name(cx))
8698 .unwrap_or("untitled");
8699 let mut element = self
8700 .render_edit_prediction_line_popover(
8701 format!("Jump to {file_name}"),
8702 Some(IconName::ZedPredict),
8703 window,
8704 cx,
8705 )
8706 .into_any();
8707
8708 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8709 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8710 let origin_y = text_bounds.size.height - size.height - px(30.);
8711 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8712 element.prepaint_at(origin, window, cx);
8713
8714 Some((element, origin))
8715 }
8716 }
8717 }
8718
8719 fn render_edit_prediction_modifier_jump_popover(
8720 &mut self,
8721 text_bounds: &Bounds<Pixels>,
8722 content_origin: gpui::Point<Pixels>,
8723 visible_row_range: Range<DisplayRow>,
8724 line_layouts: &[LineWithInvisibles],
8725 line_height: Pixels,
8726 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8727 newest_selection_head: Option<DisplayPoint>,
8728 target_display_point: DisplayPoint,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 let scrolled_content_origin =
8733 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8734
8735 const SCROLL_PADDING_Y: Pixels = px(12.);
8736
8737 if target_display_point.row() < visible_row_range.start {
8738 return self.render_edit_prediction_scroll_popover(
8739 |_| SCROLL_PADDING_Y,
8740 IconName::ArrowUp,
8741 visible_row_range,
8742 line_layouts,
8743 newest_selection_head,
8744 scrolled_content_origin,
8745 window,
8746 cx,
8747 );
8748 } else if target_display_point.row() >= visible_row_range.end {
8749 return self.render_edit_prediction_scroll_popover(
8750 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8751 IconName::ArrowDown,
8752 visible_row_range,
8753 line_layouts,
8754 newest_selection_head,
8755 scrolled_content_origin,
8756 window,
8757 cx,
8758 );
8759 }
8760
8761 const POLE_WIDTH: Pixels = px(2.);
8762
8763 let line_layout =
8764 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8765 let target_column = target_display_point.column() as usize;
8766
8767 let target_x = line_layout.x_for_index(target_column);
8768 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8769 - scroll_pixel_position.y;
8770
8771 let flag_on_right = target_x < text_bounds.size.width / 2.;
8772
8773 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8774 border_color.l += 0.001;
8775
8776 let mut element = v_flex()
8777 .items_end()
8778 .when(flag_on_right, |el| el.items_start())
8779 .child(if flag_on_right {
8780 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8781 .rounded_bl(px(0.))
8782 .rounded_tl(px(0.))
8783 .border_l_2()
8784 .border_color(border_color)
8785 } else {
8786 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8787 .rounded_br(px(0.))
8788 .rounded_tr(px(0.))
8789 .border_r_2()
8790 .border_color(border_color)
8791 })
8792 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8793 .into_any();
8794
8795 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8796
8797 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8798 - point(
8799 if flag_on_right {
8800 POLE_WIDTH
8801 } else {
8802 size.width - POLE_WIDTH
8803 },
8804 size.height - line_height,
8805 );
8806
8807 origin.x = origin.x.max(content_origin.x);
8808
8809 element.prepaint_at(origin, window, cx);
8810
8811 Some((element, origin))
8812 }
8813
8814 fn render_edit_prediction_scroll_popover(
8815 &mut self,
8816 to_y: impl Fn(Size<Pixels>) -> Pixels,
8817 scroll_icon: IconName,
8818 visible_row_range: Range<DisplayRow>,
8819 line_layouts: &[LineWithInvisibles],
8820 newest_selection_head: Option<DisplayPoint>,
8821 scrolled_content_origin: gpui::Point<Pixels>,
8822 window: &mut Window,
8823 cx: &mut App,
8824 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8825 let mut element = self
8826 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8827 .into_any();
8828
8829 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8830
8831 let cursor = newest_selection_head?;
8832 let cursor_row_layout =
8833 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8834 let cursor_column = cursor.column() as usize;
8835
8836 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8837
8838 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8839
8840 element.prepaint_at(origin, window, cx);
8841 Some((element, origin))
8842 }
8843
8844 fn render_edit_prediction_eager_jump_popover(
8845 &mut self,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 editor_snapshot: &EditorSnapshot,
8849 visible_row_range: Range<DisplayRow>,
8850 scroll_top: ScrollOffset,
8851 scroll_bottom: ScrollOffset,
8852 line_height: Pixels,
8853 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8854 target_display_point: DisplayPoint,
8855 editor_width: Pixels,
8856 window: &mut Window,
8857 cx: &mut App,
8858 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8859 if target_display_point.row().as_f64() < scroll_top {
8860 let mut element = self
8861 .render_edit_prediction_line_popover(
8862 "Jump to Edit",
8863 Some(IconName::ArrowUp),
8864 window,
8865 cx,
8866 )
8867 .into_any();
8868
8869 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8870 let offset = point(
8871 (text_bounds.size.width - size.width) / 2.,
8872 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8873 );
8874
8875 let origin = text_bounds.origin + offset;
8876 element.prepaint_at(origin, window, cx);
8877 Some((element, origin))
8878 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8879 let mut element = self
8880 .render_edit_prediction_line_popover(
8881 "Jump to Edit",
8882 Some(IconName::ArrowDown),
8883 window,
8884 cx,
8885 )
8886 .into_any();
8887
8888 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8889 let offset = point(
8890 (text_bounds.size.width - size.width) / 2.,
8891 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8892 );
8893
8894 let origin = text_bounds.origin + offset;
8895 element.prepaint_at(origin, window, cx);
8896 Some((element, origin))
8897 } else {
8898 self.render_edit_prediction_end_of_line_popover(
8899 "Jump to Edit",
8900 editor_snapshot,
8901 visible_row_range,
8902 target_display_point,
8903 line_height,
8904 scroll_pixel_position,
8905 content_origin,
8906 editor_width,
8907 window,
8908 cx,
8909 )
8910 }
8911 }
8912
8913 fn render_edit_prediction_end_of_line_popover(
8914 self: &mut Editor,
8915 label: &'static str,
8916 editor_snapshot: &EditorSnapshot,
8917 visible_row_range: Range<DisplayRow>,
8918 target_display_point: DisplayPoint,
8919 line_height: Pixels,
8920 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8921 content_origin: gpui::Point<Pixels>,
8922 editor_width: Pixels,
8923 window: &mut Window,
8924 cx: &mut App,
8925 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8926 let target_line_end = DisplayPoint::new(
8927 target_display_point.row(),
8928 editor_snapshot.line_len(target_display_point.row()),
8929 );
8930
8931 let mut element = self
8932 .render_edit_prediction_line_popover(label, None, window, cx)
8933 .into_any();
8934
8935 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8936
8937 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8938
8939 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8940 let mut origin = start_point
8941 + line_origin
8942 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8943 origin.x = origin.x.max(content_origin.x);
8944
8945 let max_x = content_origin.x + editor_width - size.width;
8946
8947 if origin.x > max_x {
8948 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8949
8950 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8951 origin.y += offset;
8952 IconName::ArrowUp
8953 } else {
8954 origin.y -= offset;
8955 IconName::ArrowDown
8956 };
8957
8958 element = self
8959 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8960 .into_any();
8961
8962 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8963
8964 origin.x = content_origin.x + editor_width - size.width - px(2.);
8965 }
8966
8967 element.prepaint_at(origin, window, cx);
8968 Some((element, origin))
8969 }
8970
8971 fn render_edit_prediction_diff_popover(
8972 self: &Editor,
8973 text_bounds: &Bounds<Pixels>,
8974 content_origin: gpui::Point<Pixels>,
8975 right_margin: Pixels,
8976 editor_snapshot: &EditorSnapshot,
8977 visible_row_range: Range<DisplayRow>,
8978 line_layouts: &[LineWithInvisibles],
8979 line_height: Pixels,
8980 scroll_position: gpui::Point<ScrollOffset>,
8981 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8982 newest_selection_head: Option<DisplayPoint>,
8983 editor_width: Pixels,
8984 style: &EditorStyle,
8985 edits: &Vec<(Range<Anchor>, Arc<str>)>,
8986 edit_preview: &Option<language::EditPreview>,
8987 snapshot: &language::BufferSnapshot,
8988 window: &mut Window,
8989 cx: &mut App,
8990 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8991 let edit_start = edits
8992 .first()
8993 .unwrap()
8994 .0
8995 .start
8996 .to_display_point(editor_snapshot);
8997 let edit_end = edits
8998 .last()
8999 .unwrap()
9000 .0
9001 .end
9002 .to_display_point(editor_snapshot);
9003
9004 let is_visible = visible_row_range.contains(&edit_start.row())
9005 || visible_row_range.contains(&edit_end.row());
9006 if !is_visible {
9007 return None;
9008 }
9009
9010 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9011 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9012 } else {
9013 // Fallback for providers without edit_preview
9014 crate::edit_prediction_fallback_text(edits, cx)
9015 };
9016
9017 let styled_text = highlighted_edits.to_styled_text(&style.text);
9018 let line_count = highlighted_edits.text.lines().count();
9019
9020 const BORDER_WIDTH: Pixels = px(1.);
9021
9022 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9023 let has_keybind = keybind.is_some();
9024
9025 let mut element = h_flex()
9026 .items_start()
9027 .child(
9028 h_flex()
9029 .bg(cx.theme().colors().editor_background)
9030 .border(BORDER_WIDTH)
9031 .shadow_xs()
9032 .border_color(cx.theme().colors().border)
9033 .rounded_l_lg()
9034 .when(line_count > 1, |el| el.rounded_br_lg())
9035 .pr_1()
9036 .child(styled_text),
9037 )
9038 .child(
9039 h_flex()
9040 .h(line_height + BORDER_WIDTH * 2.)
9041 .px_1p5()
9042 .gap_1()
9043 // Workaround: For some reason, there's a gap if we don't do this
9044 .ml(-BORDER_WIDTH)
9045 .shadow(vec![gpui::BoxShadow {
9046 color: gpui::black().opacity(0.05),
9047 offset: point(px(1.), px(1.)),
9048 blur_radius: px(2.),
9049 spread_radius: px(0.),
9050 }])
9051 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9052 .border(BORDER_WIDTH)
9053 .border_color(cx.theme().colors().border)
9054 .rounded_r_lg()
9055 .id("edit_prediction_diff_popover_keybind")
9056 .when(!has_keybind, |el| {
9057 let status_colors = cx.theme().status();
9058
9059 el.bg(status_colors.error_background)
9060 .border_color(status_colors.error.opacity(0.6))
9061 .child(Icon::new(IconName::Info).color(Color::Error))
9062 .cursor_default()
9063 .hoverable_tooltip(move |_window, cx| {
9064 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9065 })
9066 })
9067 .children(keybind),
9068 )
9069 .into_any();
9070
9071 let longest_row =
9072 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9073 let longest_line_width = if visible_row_range.contains(&longest_row) {
9074 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9075 } else {
9076 layout_line(
9077 longest_row,
9078 editor_snapshot,
9079 style,
9080 editor_width,
9081 |_| false,
9082 window,
9083 cx,
9084 )
9085 .width
9086 };
9087
9088 let viewport_bounds =
9089 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9090 right: -right_margin,
9091 ..Default::default()
9092 });
9093
9094 let x_after_longest = Pixels::from(
9095 ScrollPixelOffset::from(
9096 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9097 ) - scroll_pixel_position.x,
9098 );
9099
9100 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9101
9102 // Fully visible if it can be displayed within the window (allow overlapping other
9103 // panes). However, this is only allowed if the popover starts within text_bounds.
9104 let can_position_to_the_right = x_after_longest < text_bounds.right()
9105 && x_after_longest + element_bounds.width < viewport_bounds.right();
9106
9107 let mut origin = if can_position_to_the_right {
9108 point(
9109 x_after_longest,
9110 text_bounds.origin.y
9111 + Pixels::from(
9112 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9113 - scroll_pixel_position.y,
9114 ),
9115 )
9116 } else {
9117 let cursor_row = newest_selection_head.map(|head| head.row());
9118 let above_edit = edit_start
9119 .row()
9120 .0
9121 .checked_sub(line_count as u32)
9122 .map(DisplayRow);
9123 let below_edit = Some(edit_end.row() + 1);
9124 let above_cursor =
9125 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9126 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9127
9128 // Place the edit popover adjacent to the edit if there is a location
9129 // available that is onscreen and does not obscure the cursor. Otherwise,
9130 // place it adjacent to the cursor.
9131 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9132 .into_iter()
9133 .flatten()
9134 .find(|&start_row| {
9135 let end_row = start_row + line_count as u32;
9136 visible_row_range.contains(&start_row)
9137 && visible_row_range.contains(&end_row)
9138 && cursor_row
9139 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9140 })?;
9141
9142 content_origin
9143 + point(
9144 Pixels::from(-scroll_pixel_position.x),
9145 Pixels::from(
9146 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9147 ),
9148 )
9149 };
9150
9151 origin.x -= BORDER_WIDTH;
9152
9153 window.defer_draw(element, origin, 1);
9154
9155 // Do not return an element, since it will already be drawn due to defer_draw.
9156 None
9157 }
9158
9159 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9160 px(30.)
9161 }
9162
9163 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9164 if self.read_only(cx) {
9165 cx.theme().players().read_only()
9166 } else {
9167 self.style.as_ref().unwrap().local_player
9168 }
9169 }
9170
9171 fn render_edit_prediction_accept_keybind(
9172 &self,
9173 window: &mut Window,
9174 cx: &mut App,
9175 ) -> Option<AnyElement> {
9176 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9177 let accept_keystroke = accept_binding.keystroke()?;
9178
9179 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9180
9181 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9182 Color::Accent
9183 } else {
9184 Color::Muted
9185 };
9186
9187 h_flex()
9188 .px_0p5()
9189 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9190 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9191 .text_size(TextSize::XSmall.rems(cx))
9192 .child(h_flex().children(ui::render_modifiers(
9193 accept_keystroke.modifiers(),
9194 PlatformStyle::platform(),
9195 Some(modifiers_color),
9196 Some(IconSize::XSmall.rems().into()),
9197 true,
9198 )))
9199 .when(is_platform_style_mac, |parent| {
9200 parent.child(accept_keystroke.key().to_string())
9201 })
9202 .when(!is_platform_style_mac, |parent| {
9203 parent.child(
9204 Key::new(
9205 util::capitalize(accept_keystroke.key()),
9206 Some(Color::Default),
9207 )
9208 .size(Some(IconSize::XSmall.rems().into())),
9209 )
9210 })
9211 .into_any()
9212 .into()
9213 }
9214
9215 fn render_edit_prediction_line_popover(
9216 &self,
9217 label: impl Into<SharedString>,
9218 icon: Option<IconName>,
9219 window: &mut Window,
9220 cx: &mut App,
9221 ) -> Stateful<Div> {
9222 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9223
9224 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9225 let has_keybind = keybind.is_some();
9226
9227 h_flex()
9228 .id("ep-line-popover")
9229 .py_0p5()
9230 .pl_1()
9231 .pr(padding_right)
9232 .gap_1()
9233 .rounded_md()
9234 .border_1()
9235 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9236 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9237 .shadow_xs()
9238 .when(!has_keybind, |el| {
9239 let status_colors = cx.theme().status();
9240
9241 el.bg(status_colors.error_background)
9242 .border_color(status_colors.error.opacity(0.6))
9243 .pl_2()
9244 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9245 .cursor_default()
9246 .hoverable_tooltip(move |_window, cx| {
9247 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9248 })
9249 })
9250 .children(keybind)
9251 .child(
9252 Label::new(label)
9253 .size(LabelSize::Small)
9254 .when(!has_keybind, |el| {
9255 el.color(cx.theme().status().error.into()).strikethrough()
9256 }),
9257 )
9258 .when(!has_keybind, |el| {
9259 el.child(
9260 h_flex().ml_1().child(
9261 Icon::new(IconName::Info)
9262 .size(IconSize::Small)
9263 .color(cx.theme().status().error.into()),
9264 ),
9265 )
9266 })
9267 .when_some(icon, |element, icon| {
9268 element.child(
9269 div()
9270 .mt(px(1.5))
9271 .child(Icon::new(icon).size(IconSize::Small)),
9272 )
9273 })
9274 }
9275
9276 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9277 let accent_color = cx.theme().colors().text_accent;
9278 let editor_bg_color = cx.theme().colors().editor_background;
9279 editor_bg_color.blend(accent_color.opacity(0.1))
9280 }
9281
9282 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9283 let accent_color = cx.theme().colors().text_accent;
9284 let editor_bg_color = cx.theme().colors().editor_background;
9285 editor_bg_color.blend(accent_color.opacity(0.6))
9286 }
9287 fn get_prediction_provider_icon_name(
9288 provider: &Option<RegisteredEditPredictionProvider>,
9289 ) -> IconName {
9290 match provider {
9291 Some(provider) => match provider.provider.name() {
9292 "copilot" => IconName::Copilot,
9293 "supermaven" => IconName::Supermaven,
9294 _ => IconName::ZedPredict,
9295 },
9296 None => IconName::ZedPredict,
9297 }
9298 }
9299
9300 fn render_edit_prediction_cursor_popover(
9301 &self,
9302 min_width: Pixels,
9303 max_width: Pixels,
9304 cursor_point: Point,
9305 style: &EditorStyle,
9306 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9307 _window: &Window,
9308 cx: &mut Context<Editor>,
9309 ) -> Option<AnyElement> {
9310 let provider = self.edit_prediction_provider.as_ref()?;
9311 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9312
9313 let is_refreshing = provider.provider.is_refreshing(cx);
9314
9315 fn pending_completion_container(icon: IconName) -> Div {
9316 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9317 }
9318
9319 let completion = match &self.active_edit_prediction {
9320 Some(prediction) => {
9321 if !self.has_visible_completions_menu() {
9322 const RADIUS: Pixels = px(6.);
9323 const BORDER_WIDTH: Pixels = px(1.);
9324
9325 return Some(
9326 h_flex()
9327 .elevation_2(cx)
9328 .border(BORDER_WIDTH)
9329 .border_color(cx.theme().colors().border)
9330 .when(accept_keystroke.is_none(), |el| {
9331 el.border_color(cx.theme().status().error)
9332 })
9333 .rounded(RADIUS)
9334 .rounded_tl(px(0.))
9335 .overflow_hidden()
9336 .child(div().px_1p5().child(match &prediction.completion {
9337 EditPrediction::MoveWithin { target, snapshot } => {
9338 use text::ToPoint as _;
9339 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9340 {
9341 Icon::new(IconName::ZedPredictDown)
9342 } else {
9343 Icon::new(IconName::ZedPredictUp)
9344 }
9345 }
9346 EditPrediction::MoveOutside { .. } => {
9347 // TODO [zeta2] custom icon for external jump?
9348 Icon::new(provider_icon)
9349 }
9350 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9351 }))
9352 .child(
9353 h_flex()
9354 .gap_1()
9355 .py_1()
9356 .px_2()
9357 .rounded_r(RADIUS - BORDER_WIDTH)
9358 .border_l_1()
9359 .border_color(cx.theme().colors().border)
9360 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9361 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9362 el.child(
9363 Label::new("Hold")
9364 .size(LabelSize::Small)
9365 .when(accept_keystroke.is_none(), |el| {
9366 el.strikethrough()
9367 })
9368 .line_height_style(LineHeightStyle::UiLabel),
9369 )
9370 })
9371 .id("edit_prediction_cursor_popover_keybind")
9372 .when(accept_keystroke.is_none(), |el| {
9373 let status_colors = cx.theme().status();
9374
9375 el.bg(status_colors.error_background)
9376 .border_color(status_colors.error.opacity(0.6))
9377 .child(Icon::new(IconName::Info).color(Color::Error))
9378 .cursor_default()
9379 .hoverable_tooltip(move |_window, cx| {
9380 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9381 .into()
9382 })
9383 })
9384 .when_some(
9385 accept_keystroke.as_ref(),
9386 |el, accept_keystroke| {
9387 el.child(h_flex().children(ui::render_modifiers(
9388 accept_keystroke.modifiers(),
9389 PlatformStyle::platform(),
9390 Some(Color::Default),
9391 Some(IconSize::XSmall.rems().into()),
9392 false,
9393 )))
9394 },
9395 ),
9396 )
9397 .into_any(),
9398 );
9399 }
9400
9401 self.render_edit_prediction_cursor_popover_preview(
9402 prediction,
9403 cursor_point,
9404 style,
9405 cx,
9406 )?
9407 }
9408
9409 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9410 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9411 stale_completion,
9412 cursor_point,
9413 style,
9414 cx,
9415 )?,
9416
9417 None => pending_completion_container(provider_icon)
9418 .child(Label::new("...").size(LabelSize::Small)),
9419 },
9420
9421 None => pending_completion_container(provider_icon)
9422 .child(Label::new("...").size(LabelSize::Small)),
9423 };
9424
9425 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9426 completion
9427 .with_animation(
9428 "loading-completion",
9429 Animation::new(Duration::from_secs(2))
9430 .repeat()
9431 .with_easing(pulsating_between(0.4, 0.8)),
9432 |label, delta| label.opacity(delta),
9433 )
9434 .into_any_element()
9435 } else {
9436 completion.into_any_element()
9437 };
9438
9439 let has_completion = self.active_edit_prediction.is_some();
9440
9441 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9442 Some(
9443 h_flex()
9444 .min_w(min_width)
9445 .max_w(max_width)
9446 .flex_1()
9447 .elevation_2(cx)
9448 .border_color(cx.theme().colors().border)
9449 .child(
9450 div()
9451 .flex_1()
9452 .py_1()
9453 .px_2()
9454 .overflow_hidden()
9455 .child(completion),
9456 )
9457 .when_some(accept_keystroke, |el, accept_keystroke| {
9458 if !accept_keystroke.modifiers().modified() {
9459 return el;
9460 }
9461
9462 el.child(
9463 h_flex()
9464 .h_full()
9465 .border_l_1()
9466 .rounded_r_lg()
9467 .border_color(cx.theme().colors().border)
9468 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9469 .gap_1()
9470 .py_1()
9471 .px_2()
9472 .child(
9473 h_flex()
9474 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9475 .when(is_platform_style_mac, |parent| parent.gap_1())
9476 .child(h_flex().children(ui::render_modifiers(
9477 accept_keystroke.modifiers(),
9478 PlatformStyle::platform(),
9479 Some(if !has_completion {
9480 Color::Muted
9481 } else {
9482 Color::Default
9483 }),
9484 None,
9485 false,
9486 ))),
9487 )
9488 .child(Label::new("Preview").into_any_element())
9489 .opacity(if has_completion { 1.0 } else { 0.4 }),
9490 )
9491 })
9492 .into_any(),
9493 )
9494 }
9495
9496 fn render_edit_prediction_cursor_popover_preview(
9497 &self,
9498 completion: &EditPredictionState,
9499 cursor_point: Point,
9500 style: &EditorStyle,
9501 cx: &mut Context<Editor>,
9502 ) -> Option<Div> {
9503 use text::ToPoint as _;
9504
9505 fn render_relative_row_jump(
9506 prefix: impl Into<String>,
9507 current_row: u32,
9508 target_row: u32,
9509 ) -> Div {
9510 let (row_diff, arrow) = if target_row < current_row {
9511 (current_row - target_row, IconName::ArrowUp)
9512 } else {
9513 (target_row - current_row, IconName::ArrowDown)
9514 };
9515
9516 h_flex()
9517 .child(
9518 Label::new(format!("{}{}", prefix.into(), row_diff))
9519 .color(Color::Muted)
9520 .size(LabelSize::Small),
9521 )
9522 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9523 }
9524
9525 let supports_jump = self
9526 .edit_prediction_provider
9527 .as_ref()
9528 .map(|provider| provider.provider.supports_jump_to_edit())
9529 .unwrap_or(true);
9530
9531 match &completion.completion {
9532 EditPrediction::MoveWithin {
9533 target, snapshot, ..
9534 } => {
9535 if !supports_jump {
9536 return None;
9537 }
9538
9539 Some(
9540 h_flex()
9541 .px_2()
9542 .gap_2()
9543 .flex_1()
9544 .child(
9545 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9546 Icon::new(IconName::ZedPredictDown)
9547 } else {
9548 Icon::new(IconName::ZedPredictUp)
9549 },
9550 )
9551 .child(Label::new("Jump to Edit")),
9552 )
9553 }
9554 EditPrediction::MoveOutside { snapshot, .. } => {
9555 let file_name = snapshot
9556 .file()
9557 .map(|file| file.file_name(cx))
9558 .unwrap_or("untitled");
9559 Some(
9560 h_flex()
9561 .px_2()
9562 .gap_2()
9563 .flex_1()
9564 .child(Icon::new(IconName::ZedPredict))
9565 .child(Label::new(format!("Jump to {file_name}"))),
9566 )
9567 }
9568 EditPrediction::Edit {
9569 edits,
9570 edit_preview,
9571 snapshot,
9572 display_mode: _,
9573 } => {
9574 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9575
9576 let (highlighted_edits, has_more_lines) =
9577 if let Some(edit_preview) = edit_preview.as_ref() {
9578 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9579 .first_line_preview()
9580 } else {
9581 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9582 };
9583
9584 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9585 .with_default_highlights(&style.text, highlighted_edits.highlights);
9586
9587 let preview = h_flex()
9588 .gap_1()
9589 .min_w_16()
9590 .child(styled_text)
9591 .when(has_more_lines, |parent| parent.child("…"));
9592
9593 let left = if supports_jump && first_edit_row != cursor_point.row {
9594 render_relative_row_jump("", cursor_point.row, first_edit_row)
9595 .into_any_element()
9596 } else {
9597 let icon_name =
9598 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9599 Icon::new(icon_name).into_any_element()
9600 };
9601
9602 Some(
9603 h_flex()
9604 .h_full()
9605 .flex_1()
9606 .gap_2()
9607 .pr_1()
9608 .overflow_x_hidden()
9609 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9610 .child(left)
9611 .child(preview),
9612 )
9613 }
9614 }
9615 }
9616
9617 pub fn render_context_menu(
9618 &self,
9619 style: &EditorStyle,
9620 max_height_in_lines: u32,
9621 window: &mut Window,
9622 cx: &mut Context<Editor>,
9623 ) -> Option<AnyElement> {
9624 let menu = self.context_menu.borrow();
9625 let menu = menu.as_ref()?;
9626 if !menu.visible() {
9627 return None;
9628 };
9629 Some(menu.render(style, max_height_in_lines, window, cx))
9630 }
9631
9632 fn render_context_menu_aside(
9633 &mut self,
9634 max_size: Size<Pixels>,
9635 window: &mut Window,
9636 cx: &mut Context<Editor>,
9637 ) -> Option<AnyElement> {
9638 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9639 if menu.visible() {
9640 menu.render_aside(max_size, window, cx)
9641 } else {
9642 None
9643 }
9644 })
9645 }
9646
9647 fn hide_context_menu(
9648 &mut self,
9649 window: &mut Window,
9650 cx: &mut Context<Self>,
9651 ) -> Option<CodeContextMenu> {
9652 cx.notify();
9653 self.completion_tasks.clear();
9654 let context_menu = self.context_menu.borrow_mut().take();
9655 self.stale_edit_prediction_in_menu.take();
9656 self.update_visible_edit_prediction(window, cx);
9657 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9658 && let Some(completion_provider) = &self.completion_provider
9659 {
9660 completion_provider.selection_changed(None, window, cx);
9661 }
9662 context_menu
9663 }
9664
9665 fn show_snippet_choices(
9666 &mut self,
9667 choices: &Vec<String>,
9668 selection: Range<Anchor>,
9669 cx: &mut Context<Self>,
9670 ) {
9671 let Some((_, buffer, _)) = self
9672 .buffer()
9673 .read(cx)
9674 .excerpt_containing(selection.start, cx)
9675 else {
9676 return;
9677 };
9678 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9679 else {
9680 return;
9681 };
9682 if buffer != end_buffer {
9683 log::error!("expected anchor range to have matching buffer IDs");
9684 return;
9685 }
9686
9687 let id = post_inc(&mut self.next_completion_id);
9688 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9689 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9690 CompletionsMenu::new_snippet_choices(
9691 id,
9692 true,
9693 choices,
9694 selection,
9695 buffer,
9696 snippet_sort_order,
9697 ),
9698 ));
9699 }
9700
9701 pub fn insert_snippet(
9702 &mut self,
9703 insertion_ranges: &[Range<usize>],
9704 snippet: Snippet,
9705 window: &mut Window,
9706 cx: &mut Context<Self>,
9707 ) -> Result<()> {
9708 struct Tabstop<T> {
9709 is_end_tabstop: bool,
9710 ranges: Vec<Range<T>>,
9711 choices: Option<Vec<String>>,
9712 }
9713
9714 let tabstops = self.buffer.update(cx, |buffer, cx| {
9715 let snippet_text: Arc<str> = snippet.text.clone().into();
9716 let edits = insertion_ranges
9717 .iter()
9718 .cloned()
9719 .map(|range| (range, snippet_text.clone()));
9720 let autoindent_mode = AutoindentMode::Block {
9721 original_indent_columns: Vec::new(),
9722 };
9723 buffer.edit(edits, Some(autoindent_mode), cx);
9724
9725 let snapshot = &*buffer.read(cx);
9726 let snippet = &snippet;
9727 snippet
9728 .tabstops
9729 .iter()
9730 .map(|tabstop| {
9731 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9732 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9733 });
9734 let mut tabstop_ranges = tabstop
9735 .ranges
9736 .iter()
9737 .flat_map(|tabstop_range| {
9738 let mut delta = 0_isize;
9739 insertion_ranges.iter().map(move |insertion_range| {
9740 let insertion_start = insertion_range.start as isize + delta;
9741 delta +=
9742 snippet.text.len() as isize - insertion_range.len() as isize;
9743
9744 let start = ((insertion_start + tabstop_range.start) as usize)
9745 .min(snapshot.len());
9746 let end = ((insertion_start + tabstop_range.end) as usize)
9747 .min(snapshot.len());
9748 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9749 })
9750 })
9751 .collect::<Vec<_>>();
9752 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9753
9754 Tabstop {
9755 is_end_tabstop,
9756 ranges: tabstop_ranges,
9757 choices: tabstop.choices.clone(),
9758 }
9759 })
9760 .collect::<Vec<_>>()
9761 });
9762 if let Some(tabstop) = tabstops.first() {
9763 self.change_selections(Default::default(), window, cx, |s| {
9764 // Reverse order so that the first range is the newest created selection.
9765 // Completions will use it and autoscroll will prioritize it.
9766 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9767 });
9768
9769 if let Some(choices) = &tabstop.choices
9770 && let Some(selection) = tabstop.ranges.first()
9771 {
9772 self.show_snippet_choices(choices, selection.clone(), cx)
9773 }
9774
9775 // If we're already at the last tabstop and it's at the end of the snippet,
9776 // we're done, we don't need to keep the state around.
9777 if !tabstop.is_end_tabstop {
9778 let choices = tabstops
9779 .iter()
9780 .map(|tabstop| tabstop.choices.clone())
9781 .collect();
9782
9783 let ranges = tabstops
9784 .into_iter()
9785 .map(|tabstop| tabstop.ranges)
9786 .collect::<Vec<_>>();
9787
9788 self.snippet_stack.push(SnippetState {
9789 active_index: 0,
9790 ranges,
9791 choices,
9792 });
9793 }
9794
9795 // Check whether the just-entered snippet ends with an auto-closable bracket.
9796 if self.autoclose_regions.is_empty() {
9797 let snapshot = self.buffer.read(cx).snapshot(cx);
9798 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9799 let selection_head = selection.head();
9800 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9801 continue;
9802 };
9803
9804 let mut bracket_pair = None;
9805 let max_lookup_length = scope
9806 .brackets()
9807 .map(|(pair, _)| {
9808 pair.start
9809 .as_str()
9810 .chars()
9811 .count()
9812 .max(pair.end.as_str().chars().count())
9813 })
9814 .max();
9815 if let Some(max_lookup_length) = max_lookup_length {
9816 let next_text = snapshot
9817 .chars_at(selection_head)
9818 .take(max_lookup_length)
9819 .collect::<String>();
9820 let prev_text = snapshot
9821 .reversed_chars_at(selection_head)
9822 .take(max_lookup_length)
9823 .collect::<String>();
9824
9825 for (pair, enabled) in scope.brackets() {
9826 if enabled
9827 && pair.close
9828 && prev_text.starts_with(pair.start.as_str())
9829 && next_text.starts_with(pair.end.as_str())
9830 {
9831 bracket_pair = Some(pair.clone());
9832 break;
9833 }
9834 }
9835 }
9836
9837 if let Some(pair) = bracket_pair {
9838 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9839 let autoclose_enabled =
9840 self.use_autoclose && snapshot_settings.use_autoclose;
9841 if autoclose_enabled {
9842 let start = snapshot.anchor_after(selection_head);
9843 let end = snapshot.anchor_after(selection_head);
9844 self.autoclose_regions.push(AutocloseRegion {
9845 selection_id: selection.id,
9846 range: start..end,
9847 pair,
9848 });
9849 }
9850 }
9851 }
9852 }
9853 }
9854 Ok(())
9855 }
9856
9857 pub fn move_to_next_snippet_tabstop(
9858 &mut self,
9859 window: &mut Window,
9860 cx: &mut Context<Self>,
9861 ) -> bool {
9862 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9863 }
9864
9865 pub fn move_to_prev_snippet_tabstop(
9866 &mut self,
9867 window: &mut Window,
9868 cx: &mut Context<Self>,
9869 ) -> bool {
9870 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9871 }
9872
9873 pub fn move_to_snippet_tabstop(
9874 &mut self,
9875 bias: Bias,
9876 window: &mut Window,
9877 cx: &mut Context<Self>,
9878 ) -> bool {
9879 if let Some(mut snippet) = self.snippet_stack.pop() {
9880 match bias {
9881 Bias::Left => {
9882 if snippet.active_index > 0 {
9883 snippet.active_index -= 1;
9884 } else {
9885 self.snippet_stack.push(snippet);
9886 return false;
9887 }
9888 }
9889 Bias::Right => {
9890 if snippet.active_index + 1 < snippet.ranges.len() {
9891 snippet.active_index += 1;
9892 } else {
9893 self.snippet_stack.push(snippet);
9894 return false;
9895 }
9896 }
9897 }
9898 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9899 self.change_selections(Default::default(), window, cx, |s| {
9900 // Reverse order so that the first range is the newest created selection.
9901 // Completions will use it and autoscroll will prioritize it.
9902 s.select_ranges(current_ranges.iter().rev().cloned())
9903 });
9904
9905 if let Some(choices) = &snippet.choices[snippet.active_index]
9906 && let Some(selection) = current_ranges.first()
9907 {
9908 self.show_snippet_choices(choices, selection.clone(), cx);
9909 }
9910
9911 // If snippet state is not at the last tabstop, push it back on the stack
9912 if snippet.active_index + 1 < snippet.ranges.len() {
9913 self.snippet_stack.push(snippet);
9914 }
9915 return true;
9916 }
9917 }
9918
9919 false
9920 }
9921
9922 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9923 self.transact(window, cx, |this, window, cx| {
9924 this.select_all(&SelectAll, window, cx);
9925 this.insert("", window, cx);
9926 });
9927 }
9928
9929 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9930 if self.read_only(cx) {
9931 return;
9932 }
9933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9934 self.transact(window, cx, |this, window, cx| {
9935 this.select_autoclose_pair(window, cx);
9936
9937 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9938
9939 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9940 if !this.linked_edit_ranges.is_empty() {
9941 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9942 let snapshot = this.buffer.read(cx).snapshot(cx);
9943
9944 for selection in selections.iter() {
9945 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9946 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9947 if selection_start.buffer_id != selection_end.buffer_id {
9948 continue;
9949 }
9950 if let Some(ranges) =
9951 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9952 {
9953 for (buffer, entries) in ranges {
9954 linked_ranges.entry(buffer).or_default().extend(entries);
9955 }
9956 }
9957 }
9958 }
9959
9960 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9961 for selection in &mut selections {
9962 if selection.is_empty() {
9963 let old_head = selection.head();
9964 let mut new_head =
9965 movement::left(&display_map, old_head.to_display_point(&display_map))
9966 .to_point(&display_map);
9967 if let Some((buffer, line_buffer_range)) = display_map
9968 .buffer_snapshot()
9969 .buffer_line_for_row(MultiBufferRow(old_head.row))
9970 {
9971 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9972 let indent_len = match indent_size.kind {
9973 IndentKind::Space => {
9974 buffer.settings_at(line_buffer_range.start, cx).tab_size
9975 }
9976 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9977 };
9978 if old_head.column <= indent_size.len && old_head.column > 0 {
9979 let indent_len = indent_len.get();
9980 new_head = cmp::min(
9981 new_head,
9982 MultiBufferPoint::new(
9983 old_head.row,
9984 ((old_head.column - 1) / indent_len) * indent_len,
9985 ),
9986 );
9987 }
9988 }
9989
9990 selection.set_head(new_head, SelectionGoal::None);
9991 }
9992 }
9993
9994 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9995 this.insert("", window, cx);
9996 let empty_str: Arc<str> = Arc::from("");
9997 for (buffer, edits) in linked_ranges {
9998 let snapshot = buffer.read(cx).snapshot();
9999 use text::ToPoint as TP;
10000
10001 let edits = edits
10002 .into_iter()
10003 .map(|range| {
10004 let end_point = TP::to_point(&range.end, &snapshot);
10005 let mut start_point = TP::to_point(&range.start, &snapshot);
10006
10007 if end_point == start_point {
10008 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10009 .saturating_sub(1);
10010 start_point =
10011 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10012 };
10013
10014 (start_point..end_point, empty_str.clone())
10015 })
10016 .sorted_by_key(|(range, _)| range.start)
10017 .collect::<Vec<_>>();
10018 buffer.update(cx, |this, cx| {
10019 this.edit(edits, None, cx);
10020 })
10021 }
10022 this.refresh_edit_prediction(true, false, window, cx);
10023 refresh_linked_ranges(this, window, cx);
10024 });
10025 }
10026
10027 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10028 if self.read_only(cx) {
10029 return;
10030 }
10031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10032 self.transact(window, cx, |this, window, cx| {
10033 this.change_selections(Default::default(), window, cx, |s| {
10034 s.move_with(|map, selection| {
10035 if selection.is_empty() {
10036 let cursor = movement::right(map, selection.head());
10037 selection.end = cursor;
10038 selection.reversed = true;
10039 selection.goal = SelectionGoal::None;
10040 }
10041 })
10042 });
10043 this.insert("", window, cx);
10044 this.refresh_edit_prediction(true, false, window, cx);
10045 });
10046 }
10047
10048 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10049 if self.mode.is_single_line() {
10050 cx.propagate();
10051 return;
10052 }
10053
10054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10055 if self.move_to_prev_snippet_tabstop(window, cx) {
10056 return;
10057 }
10058 self.outdent(&Outdent, window, cx);
10059 }
10060
10061 pub fn next_snippet_tabstop(
10062 &mut self,
10063 _: &NextSnippetTabstop,
10064 window: &mut Window,
10065 cx: &mut Context<Self>,
10066 ) {
10067 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10068 cx.propagate();
10069 return;
10070 }
10071
10072 if self.move_to_next_snippet_tabstop(window, cx) {
10073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10074 return;
10075 }
10076 cx.propagate();
10077 }
10078
10079 pub fn previous_snippet_tabstop(
10080 &mut self,
10081 _: &PreviousSnippetTabstop,
10082 window: &mut Window,
10083 cx: &mut Context<Self>,
10084 ) {
10085 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10086 cx.propagate();
10087 return;
10088 }
10089
10090 if self.move_to_prev_snippet_tabstop(window, cx) {
10091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10092 return;
10093 }
10094 cx.propagate();
10095 }
10096
10097 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10098 if self.mode.is_single_line() {
10099 cx.propagate();
10100 return;
10101 }
10102
10103 if self.move_to_next_snippet_tabstop(window, cx) {
10104 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10105 return;
10106 }
10107 if self.read_only(cx) {
10108 return;
10109 }
10110 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10111 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10112 let buffer = self.buffer.read(cx);
10113 let snapshot = buffer.snapshot(cx);
10114 let rows_iter = selections.iter().map(|s| s.head().row);
10115 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10116
10117 let has_some_cursor_in_whitespace = selections
10118 .iter()
10119 .filter(|selection| selection.is_empty())
10120 .any(|selection| {
10121 let cursor = selection.head();
10122 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10123 cursor.column < current_indent.len
10124 });
10125
10126 let mut edits = Vec::new();
10127 let mut prev_edited_row = 0;
10128 let mut row_delta = 0;
10129 for selection in &mut selections {
10130 if selection.start.row != prev_edited_row {
10131 row_delta = 0;
10132 }
10133 prev_edited_row = selection.end.row;
10134
10135 // If the selection is non-empty, then increase the indentation of the selected lines.
10136 if !selection.is_empty() {
10137 row_delta =
10138 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10139 continue;
10140 }
10141
10142 let cursor = selection.head();
10143 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10144 if let Some(suggested_indent) =
10145 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10146 {
10147 // Don't do anything if already at suggested indent
10148 // and there is any other cursor which is not
10149 if has_some_cursor_in_whitespace
10150 && cursor.column == current_indent.len
10151 && current_indent.len == suggested_indent.len
10152 {
10153 continue;
10154 }
10155
10156 // Adjust line and move cursor to suggested indent
10157 // if cursor is not at suggested indent
10158 if cursor.column < suggested_indent.len
10159 && cursor.column <= current_indent.len
10160 && current_indent.len <= suggested_indent.len
10161 {
10162 selection.start = Point::new(cursor.row, suggested_indent.len);
10163 selection.end = selection.start;
10164 if row_delta == 0 {
10165 edits.extend(Buffer::edit_for_indent_size_adjustment(
10166 cursor.row,
10167 current_indent,
10168 suggested_indent,
10169 ));
10170 row_delta = suggested_indent.len - current_indent.len;
10171 }
10172 continue;
10173 }
10174
10175 // If current indent is more than suggested indent
10176 // only move cursor to current indent and skip indent
10177 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10178 selection.start = Point::new(cursor.row, current_indent.len);
10179 selection.end = selection.start;
10180 continue;
10181 }
10182 }
10183
10184 // Otherwise, insert a hard or soft tab.
10185 let settings = buffer.language_settings_at(cursor, cx);
10186 let tab_size = if settings.hard_tabs {
10187 IndentSize::tab()
10188 } else {
10189 let tab_size = settings.tab_size.get();
10190 let indent_remainder = snapshot
10191 .text_for_range(Point::new(cursor.row, 0)..cursor)
10192 .flat_map(str::chars)
10193 .fold(row_delta % tab_size, |counter: u32, c| {
10194 if c == '\t' {
10195 0
10196 } else {
10197 (counter + 1) % tab_size
10198 }
10199 });
10200
10201 let chars_to_next_tab_stop = tab_size - indent_remainder;
10202 IndentSize::spaces(chars_to_next_tab_stop)
10203 };
10204 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10205 selection.end = selection.start;
10206 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10207 row_delta += tab_size.len;
10208 }
10209
10210 self.transact(window, cx, |this, window, cx| {
10211 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10212 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10213 this.refresh_edit_prediction(true, false, window, cx);
10214 });
10215 }
10216
10217 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10218 if self.read_only(cx) {
10219 return;
10220 }
10221 if self.mode.is_single_line() {
10222 cx.propagate();
10223 return;
10224 }
10225
10226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10227 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10228 let mut prev_edited_row = 0;
10229 let mut row_delta = 0;
10230 let mut edits = Vec::new();
10231 let buffer = self.buffer.read(cx);
10232 let snapshot = buffer.snapshot(cx);
10233 for selection in &mut selections {
10234 if selection.start.row != prev_edited_row {
10235 row_delta = 0;
10236 }
10237 prev_edited_row = selection.end.row;
10238
10239 row_delta =
10240 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10241 }
10242
10243 self.transact(window, cx, |this, window, cx| {
10244 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10245 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10246 });
10247 }
10248
10249 fn indent_selection(
10250 buffer: &MultiBuffer,
10251 snapshot: &MultiBufferSnapshot,
10252 selection: &mut Selection<Point>,
10253 edits: &mut Vec<(Range<Point>, String)>,
10254 delta_for_start_row: u32,
10255 cx: &App,
10256 ) -> u32 {
10257 let settings = buffer.language_settings_at(selection.start, cx);
10258 let tab_size = settings.tab_size.get();
10259 let indent_kind = if settings.hard_tabs {
10260 IndentKind::Tab
10261 } else {
10262 IndentKind::Space
10263 };
10264 let mut start_row = selection.start.row;
10265 let mut end_row = selection.end.row + 1;
10266
10267 // If a selection ends at the beginning of a line, don't indent
10268 // that last line.
10269 if selection.end.column == 0 && selection.end.row > selection.start.row {
10270 end_row -= 1;
10271 }
10272
10273 // Avoid re-indenting a row that has already been indented by a
10274 // previous selection, but still update this selection's column
10275 // to reflect that indentation.
10276 if delta_for_start_row > 0 {
10277 start_row += 1;
10278 selection.start.column += delta_for_start_row;
10279 if selection.end.row == selection.start.row {
10280 selection.end.column += delta_for_start_row;
10281 }
10282 }
10283
10284 let mut delta_for_end_row = 0;
10285 let has_multiple_rows = start_row + 1 != end_row;
10286 for row in start_row..end_row {
10287 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10288 let indent_delta = match (current_indent.kind, indent_kind) {
10289 (IndentKind::Space, IndentKind::Space) => {
10290 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10291 IndentSize::spaces(columns_to_next_tab_stop)
10292 }
10293 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10294 (_, IndentKind::Tab) => IndentSize::tab(),
10295 };
10296
10297 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10298 0
10299 } else {
10300 selection.start.column
10301 };
10302 let row_start = Point::new(row, start);
10303 edits.push((
10304 row_start..row_start,
10305 indent_delta.chars().collect::<String>(),
10306 ));
10307
10308 // Update this selection's endpoints to reflect the indentation.
10309 if row == selection.start.row {
10310 selection.start.column += indent_delta.len;
10311 }
10312 if row == selection.end.row {
10313 selection.end.column += indent_delta.len;
10314 delta_for_end_row = indent_delta.len;
10315 }
10316 }
10317
10318 if selection.start.row == selection.end.row {
10319 delta_for_start_row + delta_for_end_row
10320 } else {
10321 delta_for_end_row
10322 }
10323 }
10324
10325 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10326 if self.read_only(cx) {
10327 return;
10328 }
10329 if self.mode.is_single_line() {
10330 cx.propagate();
10331 return;
10332 }
10333
10334 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10336 let selections = self.selections.all::<Point>(&display_map);
10337 let mut deletion_ranges = Vec::new();
10338 let mut last_outdent = None;
10339 {
10340 let buffer = self.buffer.read(cx);
10341 let snapshot = buffer.snapshot(cx);
10342 for selection in &selections {
10343 let settings = buffer.language_settings_at(selection.start, cx);
10344 let tab_size = settings.tab_size.get();
10345 let mut rows = selection.spanned_rows(false, &display_map);
10346
10347 // Avoid re-outdenting a row that has already been outdented by a
10348 // previous selection.
10349 if let Some(last_row) = last_outdent
10350 && last_row == rows.start
10351 {
10352 rows.start = rows.start.next_row();
10353 }
10354 let has_multiple_rows = rows.len() > 1;
10355 for row in rows.iter_rows() {
10356 let indent_size = snapshot.indent_size_for_line(row);
10357 if indent_size.len > 0 {
10358 let deletion_len = match indent_size.kind {
10359 IndentKind::Space => {
10360 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10361 if columns_to_prev_tab_stop == 0 {
10362 tab_size
10363 } else {
10364 columns_to_prev_tab_stop
10365 }
10366 }
10367 IndentKind::Tab => 1,
10368 };
10369 let start = if has_multiple_rows
10370 || deletion_len > selection.start.column
10371 || indent_size.len < selection.start.column
10372 {
10373 0
10374 } else {
10375 selection.start.column - deletion_len
10376 };
10377 deletion_ranges.push(
10378 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10379 );
10380 last_outdent = Some(row);
10381 }
10382 }
10383 }
10384 }
10385
10386 self.transact(window, cx, |this, window, cx| {
10387 this.buffer.update(cx, |buffer, cx| {
10388 let empty_str: Arc<str> = Arc::default();
10389 buffer.edit(
10390 deletion_ranges
10391 .into_iter()
10392 .map(|range| (range, empty_str.clone())),
10393 None,
10394 cx,
10395 );
10396 });
10397 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10398 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10399 });
10400 }
10401
10402 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10403 if self.read_only(cx) {
10404 return;
10405 }
10406 if self.mode.is_single_line() {
10407 cx.propagate();
10408 return;
10409 }
10410
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 let selections = self
10413 .selections
10414 .all::<usize>(&self.display_snapshot(cx))
10415 .into_iter()
10416 .map(|s| s.range());
10417
10418 self.transact(window, cx, |this, window, cx| {
10419 this.buffer.update(cx, |buffer, cx| {
10420 buffer.autoindent_ranges(selections, cx);
10421 });
10422 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10423 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10424 });
10425 }
10426
10427 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10428 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10430 let selections = self.selections.all::<Point>(&display_map);
10431
10432 let mut new_cursors = Vec::new();
10433 let mut edit_ranges = Vec::new();
10434 let mut selections = selections.iter().peekable();
10435 while let Some(selection) = selections.next() {
10436 let mut rows = selection.spanned_rows(false, &display_map);
10437
10438 // Accumulate contiguous regions of rows that we want to delete.
10439 while let Some(next_selection) = selections.peek() {
10440 let next_rows = next_selection.spanned_rows(false, &display_map);
10441 if next_rows.start <= rows.end {
10442 rows.end = next_rows.end;
10443 selections.next().unwrap();
10444 } else {
10445 break;
10446 }
10447 }
10448
10449 let buffer = display_map.buffer_snapshot();
10450 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10451 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10452 // If there's a line after the range, delete the \n from the end of the row range
10453 (
10454 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10455 rows.end,
10456 )
10457 } else {
10458 // If there isn't a line after the range, delete the \n from the line before the
10459 // start of the row range
10460 edit_start = edit_start.saturating_sub(1);
10461 (buffer.len(), rows.start.previous_row())
10462 };
10463
10464 let text_layout_details = self.text_layout_details(window);
10465 let x = display_map.x_for_display_point(
10466 selection.head().to_display_point(&display_map),
10467 &text_layout_details,
10468 );
10469 let row = Point::new(target_row.0, 0)
10470 .to_display_point(&display_map)
10471 .row();
10472 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10473
10474 new_cursors.push((
10475 selection.id,
10476 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10477 SelectionGoal::None,
10478 ));
10479 edit_ranges.push(edit_start..edit_end);
10480 }
10481
10482 self.transact(window, cx, |this, window, cx| {
10483 let buffer = this.buffer.update(cx, |buffer, cx| {
10484 let empty_str: Arc<str> = Arc::default();
10485 buffer.edit(
10486 edit_ranges
10487 .into_iter()
10488 .map(|range| (range, empty_str.clone())),
10489 None,
10490 cx,
10491 );
10492 buffer.snapshot(cx)
10493 });
10494 let new_selections = new_cursors
10495 .into_iter()
10496 .map(|(id, cursor, goal)| {
10497 let cursor = cursor.to_point(&buffer);
10498 Selection {
10499 id,
10500 start: cursor,
10501 end: cursor,
10502 reversed: false,
10503 goal,
10504 }
10505 })
10506 .collect();
10507
10508 this.change_selections(Default::default(), window, cx, |s| {
10509 s.select(new_selections);
10510 });
10511 });
10512 }
10513
10514 pub fn join_lines_impl(
10515 &mut self,
10516 insert_whitespace: bool,
10517 window: &mut Window,
10518 cx: &mut Context<Self>,
10519 ) {
10520 if self.read_only(cx) {
10521 return;
10522 }
10523 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10524 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10525 let start = MultiBufferRow(selection.start.row);
10526 // Treat single line selections as if they include the next line. Otherwise this action
10527 // would do nothing for single line selections individual cursors.
10528 let end = if selection.start.row == selection.end.row {
10529 MultiBufferRow(selection.start.row + 1)
10530 } else {
10531 MultiBufferRow(selection.end.row)
10532 };
10533
10534 if let Some(last_row_range) = row_ranges.last_mut()
10535 && start <= last_row_range.end
10536 {
10537 last_row_range.end = end;
10538 continue;
10539 }
10540 row_ranges.push(start..end);
10541 }
10542
10543 let snapshot = self.buffer.read(cx).snapshot(cx);
10544 let mut cursor_positions = Vec::new();
10545 for row_range in &row_ranges {
10546 let anchor = snapshot.anchor_before(Point::new(
10547 row_range.end.previous_row().0,
10548 snapshot.line_len(row_range.end.previous_row()),
10549 ));
10550 cursor_positions.push(anchor..anchor);
10551 }
10552
10553 self.transact(window, cx, |this, window, cx| {
10554 for row_range in row_ranges.into_iter().rev() {
10555 for row in row_range.iter_rows().rev() {
10556 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10557 let next_line_row = row.next_row();
10558 let indent = snapshot.indent_size_for_line(next_line_row);
10559 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10560
10561 let replace =
10562 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10563 " "
10564 } else {
10565 ""
10566 };
10567
10568 this.buffer.update(cx, |buffer, cx| {
10569 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10570 });
10571 }
10572 }
10573
10574 this.change_selections(Default::default(), window, cx, |s| {
10575 s.select_anchor_ranges(cursor_positions)
10576 });
10577 });
10578 }
10579
10580 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10581 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10582 self.join_lines_impl(true, window, cx);
10583 }
10584
10585 pub fn sort_lines_case_sensitive(
10586 &mut self,
10587 _: &SortLinesCaseSensitive,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10592 }
10593
10594 pub fn sort_lines_by_length(
10595 &mut self,
10596 _: &SortLinesByLength,
10597 window: &mut Window,
10598 cx: &mut Context<Self>,
10599 ) {
10600 self.manipulate_immutable_lines(window, cx, |lines| {
10601 lines.sort_by_key(|&line| line.chars().count())
10602 })
10603 }
10604
10605 pub fn sort_lines_case_insensitive(
10606 &mut self,
10607 _: &SortLinesCaseInsensitive,
10608 window: &mut Window,
10609 cx: &mut Context<Self>,
10610 ) {
10611 self.manipulate_immutable_lines(window, cx, |lines| {
10612 lines.sort_by_key(|line| line.to_lowercase())
10613 })
10614 }
10615
10616 pub fn unique_lines_case_insensitive(
10617 &mut self,
10618 _: &UniqueLinesCaseInsensitive,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.manipulate_immutable_lines(window, cx, |lines| {
10623 let mut seen = HashSet::default();
10624 lines.retain(|line| seen.insert(line.to_lowercase()));
10625 })
10626 }
10627
10628 pub fn unique_lines_case_sensitive(
10629 &mut self,
10630 _: &UniqueLinesCaseSensitive,
10631 window: &mut Window,
10632 cx: &mut Context<Self>,
10633 ) {
10634 self.manipulate_immutable_lines(window, cx, |lines| {
10635 let mut seen = HashSet::default();
10636 lines.retain(|line| seen.insert(*line));
10637 })
10638 }
10639
10640 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10641 let snapshot = self.buffer.read(cx).snapshot(cx);
10642 for selection in self.selections.disjoint_anchors_arc().iter() {
10643 if snapshot
10644 .language_at(selection.start)
10645 .and_then(|lang| lang.config().wrap_characters.as_ref())
10646 .is_some()
10647 {
10648 return true;
10649 }
10650 }
10651 false
10652 }
10653
10654 fn wrap_selections_in_tag(
10655 &mut self,
10656 _: &WrapSelectionsInTag,
10657 window: &mut Window,
10658 cx: &mut Context<Self>,
10659 ) {
10660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10661
10662 let snapshot = self.buffer.read(cx).snapshot(cx);
10663
10664 let mut edits = Vec::new();
10665 let mut boundaries = Vec::new();
10666
10667 for selection in self
10668 .selections
10669 .all_adjusted(&self.display_snapshot(cx))
10670 .iter()
10671 {
10672 let Some(wrap_config) = snapshot
10673 .language_at(selection.start)
10674 .and_then(|lang| lang.config().wrap_characters.clone())
10675 else {
10676 continue;
10677 };
10678
10679 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10680 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10681
10682 let start_before = snapshot.anchor_before(selection.start);
10683 let end_after = snapshot.anchor_after(selection.end);
10684
10685 edits.push((start_before..start_before, open_tag));
10686 edits.push((end_after..end_after, close_tag));
10687
10688 boundaries.push((
10689 start_before,
10690 end_after,
10691 wrap_config.start_prefix.len(),
10692 wrap_config.end_suffix.len(),
10693 ));
10694 }
10695
10696 if edits.is_empty() {
10697 return;
10698 }
10699
10700 self.transact(window, cx, |this, window, cx| {
10701 let buffer = this.buffer.update(cx, |buffer, cx| {
10702 buffer.edit(edits, None, cx);
10703 buffer.snapshot(cx)
10704 });
10705
10706 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10707 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10708 boundaries.into_iter()
10709 {
10710 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10711 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10712 new_selections.push(open_offset..open_offset);
10713 new_selections.push(close_offset..close_offset);
10714 }
10715
10716 this.change_selections(Default::default(), window, cx, |s| {
10717 s.select_ranges(new_selections);
10718 });
10719
10720 this.request_autoscroll(Autoscroll::fit(), cx);
10721 });
10722 }
10723
10724 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10725 let Some(project) = self.project.clone() else {
10726 return;
10727 };
10728 self.reload(project, window, cx)
10729 .detach_and_notify_err(window, cx);
10730 }
10731
10732 pub fn restore_file(
10733 &mut self,
10734 _: &::git::RestoreFile,
10735 window: &mut Window,
10736 cx: &mut Context<Self>,
10737 ) {
10738 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10739 let mut buffer_ids = HashSet::default();
10740 let snapshot = self.buffer().read(cx).snapshot(cx);
10741 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10742 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10743 }
10744
10745 let buffer = self.buffer().read(cx);
10746 let ranges = buffer_ids
10747 .into_iter()
10748 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10749 .collect::<Vec<_>>();
10750
10751 self.restore_hunks_in_ranges(ranges, window, cx);
10752 }
10753
10754 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10755 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10756 let selections = self
10757 .selections
10758 .all(&self.display_snapshot(cx))
10759 .into_iter()
10760 .map(|s| s.range())
10761 .collect();
10762 self.restore_hunks_in_ranges(selections, window, cx);
10763 }
10764
10765 pub fn restore_hunks_in_ranges(
10766 &mut self,
10767 ranges: Vec<Range<Point>>,
10768 window: &mut Window,
10769 cx: &mut Context<Editor>,
10770 ) {
10771 let mut revert_changes = HashMap::default();
10772 let chunk_by = self
10773 .snapshot(window, cx)
10774 .hunks_for_ranges(ranges)
10775 .into_iter()
10776 .chunk_by(|hunk| hunk.buffer_id);
10777 for (buffer_id, hunks) in &chunk_by {
10778 let hunks = hunks.collect::<Vec<_>>();
10779 for hunk in &hunks {
10780 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10781 }
10782 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10783 }
10784 drop(chunk_by);
10785 if !revert_changes.is_empty() {
10786 self.transact(window, cx, |editor, window, cx| {
10787 editor.restore(revert_changes, window, cx);
10788 });
10789 }
10790 }
10791
10792 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10793 if let Some(status) = self
10794 .addons
10795 .iter()
10796 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10797 {
10798 return Some(status);
10799 }
10800 self.project
10801 .as_ref()?
10802 .read(cx)
10803 .status_for_buffer_id(buffer_id, cx)
10804 }
10805
10806 pub fn open_active_item_in_terminal(
10807 &mut self,
10808 _: &OpenInTerminal,
10809 window: &mut Window,
10810 cx: &mut Context<Self>,
10811 ) {
10812 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10813 let project_path = buffer.read(cx).project_path(cx)?;
10814 let project = self.project()?.read(cx);
10815 let entry = project.entry_for_path(&project_path, cx)?;
10816 let parent = match &entry.canonical_path {
10817 Some(canonical_path) => canonical_path.to_path_buf(),
10818 None => project.absolute_path(&project_path, cx)?,
10819 }
10820 .parent()?
10821 .to_path_buf();
10822 Some(parent)
10823 }) {
10824 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10825 }
10826 }
10827
10828 fn set_breakpoint_context_menu(
10829 &mut self,
10830 display_row: DisplayRow,
10831 position: Option<Anchor>,
10832 clicked_point: gpui::Point<Pixels>,
10833 window: &mut Window,
10834 cx: &mut Context<Self>,
10835 ) {
10836 let source = self
10837 .buffer
10838 .read(cx)
10839 .snapshot(cx)
10840 .anchor_before(Point::new(display_row.0, 0u32));
10841
10842 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10843
10844 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10845 self,
10846 source,
10847 clicked_point,
10848 context_menu,
10849 window,
10850 cx,
10851 );
10852 }
10853
10854 fn add_edit_breakpoint_block(
10855 &mut self,
10856 anchor: Anchor,
10857 breakpoint: &Breakpoint,
10858 edit_action: BreakpointPromptEditAction,
10859 window: &mut Window,
10860 cx: &mut Context<Self>,
10861 ) {
10862 let weak_editor = cx.weak_entity();
10863 let bp_prompt = cx.new(|cx| {
10864 BreakpointPromptEditor::new(
10865 weak_editor,
10866 anchor,
10867 breakpoint.clone(),
10868 edit_action,
10869 window,
10870 cx,
10871 )
10872 });
10873
10874 let height = bp_prompt.update(cx, |this, cx| {
10875 this.prompt
10876 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10877 });
10878 let cloned_prompt = bp_prompt.clone();
10879 let blocks = vec![BlockProperties {
10880 style: BlockStyle::Sticky,
10881 placement: BlockPlacement::Above(anchor),
10882 height: Some(height),
10883 render: Arc::new(move |cx| {
10884 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10885 cloned_prompt.clone().into_any_element()
10886 }),
10887 priority: 0,
10888 }];
10889
10890 let focus_handle = bp_prompt.focus_handle(cx);
10891 window.focus(&focus_handle);
10892
10893 let block_ids = self.insert_blocks(blocks, None, cx);
10894 bp_prompt.update(cx, |prompt, _| {
10895 prompt.add_block_ids(block_ids);
10896 });
10897 }
10898
10899 pub(crate) fn breakpoint_at_row(
10900 &self,
10901 row: u32,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) -> Option<(Anchor, Breakpoint)> {
10905 let snapshot = self.snapshot(window, cx);
10906 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10907
10908 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10909 }
10910
10911 pub(crate) fn breakpoint_at_anchor(
10912 &self,
10913 breakpoint_position: Anchor,
10914 snapshot: &EditorSnapshot,
10915 cx: &mut Context<Self>,
10916 ) -> Option<(Anchor, Breakpoint)> {
10917 let buffer = self
10918 .buffer
10919 .read(cx)
10920 .buffer_for_anchor(breakpoint_position, cx)?;
10921
10922 let enclosing_excerpt = breakpoint_position.excerpt_id;
10923 let buffer_snapshot = buffer.read(cx).snapshot();
10924
10925 let row = buffer_snapshot
10926 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10927 .row;
10928
10929 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10930 let anchor_end = snapshot
10931 .buffer_snapshot()
10932 .anchor_after(Point::new(row, line_len));
10933
10934 self.breakpoint_store
10935 .as_ref()?
10936 .read_with(cx, |breakpoint_store, cx| {
10937 breakpoint_store
10938 .breakpoints(
10939 &buffer,
10940 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10941 &buffer_snapshot,
10942 cx,
10943 )
10944 .next()
10945 .and_then(|(bp, _)| {
10946 let breakpoint_row = buffer_snapshot
10947 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10948 .row;
10949
10950 if breakpoint_row == row {
10951 snapshot
10952 .buffer_snapshot()
10953 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10954 .map(|position| (position, bp.bp.clone()))
10955 } else {
10956 None
10957 }
10958 })
10959 })
10960 }
10961
10962 pub fn edit_log_breakpoint(
10963 &mut self,
10964 _: &EditLogBreakpoint,
10965 window: &mut Window,
10966 cx: &mut Context<Self>,
10967 ) {
10968 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10969 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10970 message: None,
10971 state: BreakpointState::Enabled,
10972 condition: None,
10973 hit_condition: None,
10974 });
10975
10976 self.add_edit_breakpoint_block(
10977 anchor,
10978 &breakpoint,
10979 BreakpointPromptEditAction::Log,
10980 window,
10981 cx,
10982 );
10983 }
10984 }
10985
10986 fn breakpoints_at_cursors(
10987 &self,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10991 let snapshot = self.snapshot(window, cx);
10992 let cursors = self
10993 .selections
10994 .disjoint_anchors_arc()
10995 .iter()
10996 .map(|selection| {
10997 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10998
10999 let breakpoint_position = self
11000 .breakpoint_at_row(cursor_position.row, window, cx)
11001 .map(|bp| bp.0)
11002 .unwrap_or_else(|| {
11003 snapshot
11004 .display_snapshot
11005 .buffer_snapshot()
11006 .anchor_after(Point::new(cursor_position.row, 0))
11007 });
11008
11009 let breakpoint = self
11010 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11011 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11012
11013 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11014 })
11015 // 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.
11016 .collect::<HashMap<Anchor, _>>();
11017
11018 cursors.into_iter().collect()
11019 }
11020
11021 pub fn enable_breakpoint(
11022 &mut self,
11023 _: &crate::actions::EnableBreakpoint,
11024 window: &mut Window,
11025 cx: &mut Context<Self>,
11026 ) {
11027 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11028 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11029 continue;
11030 };
11031 self.edit_breakpoint_at_anchor(
11032 anchor,
11033 breakpoint,
11034 BreakpointEditAction::InvertState,
11035 cx,
11036 );
11037 }
11038 }
11039
11040 pub fn disable_breakpoint(
11041 &mut self,
11042 _: &crate::actions::DisableBreakpoint,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11047 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11048 continue;
11049 };
11050 self.edit_breakpoint_at_anchor(
11051 anchor,
11052 breakpoint,
11053 BreakpointEditAction::InvertState,
11054 cx,
11055 );
11056 }
11057 }
11058
11059 pub fn toggle_breakpoint(
11060 &mut self,
11061 _: &crate::actions::ToggleBreakpoint,
11062 window: &mut Window,
11063 cx: &mut Context<Self>,
11064 ) {
11065 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11066 if let Some(breakpoint) = breakpoint {
11067 self.edit_breakpoint_at_anchor(
11068 anchor,
11069 breakpoint,
11070 BreakpointEditAction::Toggle,
11071 cx,
11072 );
11073 } else {
11074 self.edit_breakpoint_at_anchor(
11075 anchor,
11076 Breakpoint::new_standard(),
11077 BreakpointEditAction::Toggle,
11078 cx,
11079 );
11080 }
11081 }
11082 }
11083
11084 pub fn edit_breakpoint_at_anchor(
11085 &mut self,
11086 breakpoint_position: Anchor,
11087 breakpoint: Breakpoint,
11088 edit_action: BreakpointEditAction,
11089 cx: &mut Context<Self>,
11090 ) {
11091 let Some(breakpoint_store) = &self.breakpoint_store else {
11092 return;
11093 };
11094
11095 let Some(buffer) = self
11096 .buffer
11097 .read(cx)
11098 .buffer_for_anchor(breakpoint_position, cx)
11099 else {
11100 return;
11101 };
11102
11103 breakpoint_store.update(cx, |breakpoint_store, cx| {
11104 breakpoint_store.toggle_breakpoint(
11105 buffer,
11106 BreakpointWithPosition {
11107 position: breakpoint_position.text_anchor,
11108 bp: breakpoint,
11109 },
11110 edit_action,
11111 cx,
11112 );
11113 });
11114
11115 cx.notify();
11116 }
11117
11118 #[cfg(any(test, feature = "test-support"))]
11119 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11120 self.breakpoint_store.clone()
11121 }
11122
11123 pub fn prepare_restore_change(
11124 &self,
11125 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11126 hunk: &MultiBufferDiffHunk,
11127 cx: &mut App,
11128 ) -> Option<()> {
11129 if hunk.is_created_file() {
11130 return None;
11131 }
11132 let buffer = self.buffer.read(cx);
11133 let diff = buffer.diff_for(hunk.buffer_id)?;
11134 let buffer = buffer.buffer(hunk.buffer_id)?;
11135 let buffer = buffer.read(cx);
11136 let original_text = diff
11137 .read(cx)
11138 .base_text()
11139 .as_rope()
11140 .slice(hunk.diff_base_byte_range.clone());
11141 let buffer_snapshot = buffer.snapshot();
11142 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11143 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11144 probe
11145 .0
11146 .start
11147 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11148 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11149 }) {
11150 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11151 Some(())
11152 } else {
11153 None
11154 }
11155 }
11156
11157 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11158 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11159 }
11160
11161 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11162 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11163 }
11164
11165 fn manipulate_lines<M>(
11166 &mut self,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 mut manipulate: M,
11170 ) where
11171 M: FnMut(&str) -> LineManipulationResult,
11172 {
11173 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11174
11175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11176 let buffer = self.buffer.read(cx).snapshot(cx);
11177
11178 let mut edits = Vec::new();
11179
11180 let selections = self.selections.all::<Point>(&display_map);
11181 let mut selections = selections.iter().peekable();
11182 let mut contiguous_row_selections = Vec::new();
11183 let mut new_selections = Vec::new();
11184 let mut added_lines = 0;
11185 let mut removed_lines = 0;
11186
11187 while let Some(selection) = selections.next() {
11188 let (start_row, end_row) = consume_contiguous_rows(
11189 &mut contiguous_row_selections,
11190 selection,
11191 &display_map,
11192 &mut selections,
11193 );
11194
11195 let start_point = Point::new(start_row.0, 0);
11196 let end_point = Point::new(
11197 end_row.previous_row().0,
11198 buffer.line_len(end_row.previous_row()),
11199 );
11200 let text = buffer
11201 .text_for_range(start_point..end_point)
11202 .collect::<String>();
11203
11204 let LineManipulationResult {
11205 new_text,
11206 line_count_before,
11207 line_count_after,
11208 } = manipulate(&text);
11209
11210 edits.push((start_point..end_point, new_text));
11211
11212 // Selections must change based on added and removed line count
11213 let start_row =
11214 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11215 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11216 new_selections.push(Selection {
11217 id: selection.id,
11218 start: start_row,
11219 end: end_row,
11220 goal: SelectionGoal::None,
11221 reversed: selection.reversed,
11222 });
11223
11224 if line_count_after > line_count_before {
11225 added_lines += line_count_after - line_count_before;
11226 } else if line_count_before > line_count_after {
11227 removed_lines += line_count_before - line_count_after;
11228 }
11229 }
11230
11231 self.transact(window, cx, |this, window, cx| {
11232 let buffer = this.buffer.update(cx, |buffer, cx| {
11233 buffer.edit(edits, None, cx);
11234 buffer.snapshot(cx)
11235 });
11236
11237 // Recalculate offsets on newly edited buffer
11238 let new_selections = new_selections
11239 .iter()
11240 .map(|s| {
11241 let start_point = Point::new(s.start.0, 0);
11242 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11243 Selection {
11244 id: s.id,
11245 start: buffer.point_to_offset(start_point),
11246 end: buffer.point_to_offset(end_point),
11247 goal: s.goal,
11248 reversed: s.reversed,
11249 }
11250 })
11251 .collect();
11252
11253 this.change_selections(Default::default(), window, cx, |s| {
11254 s.select(new_selections);
11255 });
11256
11257 this.request_autoscroll(Autoscroll::fit(), cx);
11258 });
11259 }
11260
11261 fn manipulate_immutable_lines<Fn>(
11262 &mut self,
11263 window: &mut Window,
11264 cx: &mut Context<Self>,
11265 mut callback: Fn,
11266 ) where
11267 Fn: FnMut(&mut Vec<&str>),
11268 {
11269 self.manipulate_lines(window, cx, |text| {
11270 let mut lines: Vec<&str> = text.split('\n').collect();
11271 let line_count_before = lines.len();
11272
11273 callback(&mut lines);
11274
11275 LineManipulationResult {
11276 new_text: lines.join("\n"),
11277 line_count_before,
11278 line_count_after: lines.len(),
11279 }
11280 });
11281 }
11282
11283 fn manipulate_mutable_lines<Fn>(
11284 &mut self,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 mut callback: Fn,
11288 ) where
11289 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11290 {
11291 self.manipulate_lines(window, cx, |text| {
11292 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11293 let line_count_before = lines.len();
11294
11295 callback(&mut lines);
11296
11297 LineManipulationResult {
11298 new_text: lines.join("\n"),
11299 line_count_before,
11300 line_count_after: lines.len(),
11301 }
11302 });
11303 }
11304
11305 pub fn convert_indentation_to_spaces(
11306 &mut self,
11307 _: &ConvertIndentationToSpaces,
11308 window: &mut Window,
11309 cx: &mut Context<Self>,
11310 ) {
11311 let settings = self.buffer.read(cx).language_settings(cx);
11312 let tab_size = settings.tab_size.get() as usize;
11313
11314 self.manipulate_mutable_lines(window, cx, |lines| {
11315 // Allocates a reasonably sized scratch buffer once for the whole loop
11316 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11317 // Avoids recomputing spaces that could be inserted many times
11318 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11319 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11320 .collect();
11321
11322 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11323 let mut chars = line.as_ref().chars();
11324 let mut col = 0;
11325 let mut changed = false;
11326
11327 for ch in chars.by_ref() {
11328 match ch {
11329 ' ' => {
11330 reindented_line.push(' ');
11331 col += 1;
11332 }
11333 '\t' => {
11334 // \t are converted to spaces depending on the current column
11335 let spaces_len = tab_size - (col % tab_size);
11336 reindented_line.extend(&space_cache[spaces_len - 1]);
11337 col += spaces_len;
11338 changed = true;
11339 }
11340 _ => {
11341 // If we dont append before break, the character is consumed
11342 reindented_line.push(ch);
11343 break;
11344 }
11345 }
11346 }
11347
11348 if !changed {
11349 reindented_line.clear();
11350 continue;
11351 }
11352 // Append the rest of the line and replace old reference with new one
11353 reindented_line.extend(chars);
11354 *line = Cow::Owned(reindented_line.clone());
11355 reindented_line.clear();
11356 }
11357 });
11358 }
11359
11360 pub fn convert_indentation_to_tabs(
11361 &mut self,
11362 _: &ConvertIndentationToTabs,
11363 window: &mut Window,
11364 cx: &mut Context<Self>,
11365 ) {
11366 let settings = self.buffer.read(cx).language_settings(cx);
11367 let tab_size = settings.tab_size.get() as usize;
11368
11369 self.manipulate_mutable_lines(window, cx, |lines| {
11370 // Allocates a reasonably sized buffer once for the whole loop
11371 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11372 // Avoids recomputing spaces that could be inserted many times
11373 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11374 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11375 .collect();
11376
11377 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11378 let mut chars = line.chars();
11379 let mut spaces_count = 0;
11380 let mut first_non_indent_char = None;
11381 let mut changed = false;
11382
11383 for ch in chars.by_ref() {
11384 match ch {
11385 ' ' => {
11386 // Keep track of spaces. Append \t when we reach tab_size
11387 spaces_count += 1;
11388 changed = true;
11389 if spaces_count == tab_size {
11390 reindented_line.push('\t');
11391 spaces_count = 0;
11392 }
11393 }
11394 '\t' => {
11395 reindented_line.push('\t');
11396 spaces_count = 0;
11397 }
11398 _ => {
11399 // Dont append it yet, we might have remaining spaces
11400 first_non_indent_char = Some(ch);
11401 break;
11402 }
11403 }
11404 }
11405
11406 if !changed {
11407 reindented_line.clear();
11408 continue;
11409 }
11410 // Remaining spaces that didn't make a full tab stop
11411 if spaces_count > 0 {
11412 reindented_line.extend(&space_cache[spaces_count - 1]);
11413 }
11414 // If we consume an extra character that was not indentation, add it back
11415 if let Some(extra_char) = first_non_indent_char {
11416 reindented_line.push(extra_char);
11417 }
11418 // Append the rest of the line and replace old reference with new one
11419 reindented_line.extend(chars);
11420 *line = Cow::Owned(reindented_line.clone());
11421 reindented_line.clear();
11422 }
11423 });
11424 }
11425
11426 pub fn convert_to_upper_case(
11427 &mut self,
11428 _: &ConvertToUpperCase,
11429 window: &mut Window,
11430 cx: &mut Context<Self>,
11431 ) {
11432 self.manipulate_text(window, cx, |text| text.to_uppercase())
11433 }
11434
11435 pub fn convert_to_lower_case(
11436 &mut self,
11437 _: &ConvertToLowerCase,
11438 window: &mut Window,
11439 cx: &mut Context<Self>,
11440 ) {
11441 self.manipulate_text(window, cx, |text| text.to_lowercase())
11442 }
11443
11444 pub fn convert_to_title_case(
11445 &mut self,
11446 _: &ConvertToTitleCase,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 self.manipulate_text(window, cx, |text| {
11451 text.split('\n')
11452 .map(|line| line.to_case(Case::Title))
11453 .join("\n")
11454 })
11455 }
11456
11457 pub fn convert_to_snake_case(
11458 &mut self,
11459 _: &ConvertToSnakeCase,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11464 }
11465
11466 pub fn convert_to_kebab_case(
11467 &mut self,
11468 _: &ConvertToKebabCase,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11473 }
11474
11475 pub fn convert_to_upper_camel_case(
11476 &mut self,
11477 _: &ConvertToUpperCamelCase,
11478 window: &mut Window,
11479 cx: &mut Context<Self>,
11480 ) {
11481 self.manipulate_text(window, cx, |text| {
11482 text.split('\n')
11483 .map(|line| line.to_case(Case::UpperCamel))
11484 .join("\n")
11485 })
11486 }
11487
11488 pub fn convert_to_lower_camel_case(
11489 &mut self,
11490 _: &ConvertToLowerCamelCase,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11495 }
11496
11497 pub fn convert_to_opposite_case(
11498 &mut self,
11499 _: &ConvertToOppositeCase,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_text(window, cx, |text| {
11504 text.chars()
11505 .fold(String::with_capacity(text.len()), |mut t, c| {
11506 if c.is_uppercase() {
11507 t.extend(c.to_lowercase());
11508 } else {
11509 t.extend(c.to_uppercase());
11510 }
11511 t
11512 })
11513 })
11514 }
11515
11516 pub fn convert_to_sentence_case(
11517 &mut self,
11518 _: &ConvertToSentenceCase,
11519 window: &mut Window,
11520 cx: &mut Context<Self>,
11521 ) {
11522 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11523 }
11524
11525 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11526 self.manipulate_text(window, cx, |text| {
11527 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11528 if has_upper_case_characters {
11529 text.to_lowercase()
11530 } else {
11531 text.to_uppercase()
11532 }
11533 })
11534 }
11535
11536 pub fn convert_to_rot13(
11537 &mut self,
11538 _: &ConvertToRot13,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.manipulate_text(window, cx, |text| {
11543 text.chars()
11544 .map(|c| match c {
11545 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11546 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11547 _ => c,
11548 })
11549 .collect()
11550 })
11551 }
11552
11553 pub fn convert_to_rot47(
11554 &mut self,
11555 _: &ConvertToRot47,
11556 window: &mut Window,
11557 cx: &mut Context<Self>,
11558 ) {
11559 self.manipulate_text(window, cx, |text| {
11560 text.chars()
11561 .map(|c| {
11562 let code_point = c as u32;
11563 if code_point >= 33 && code_point <= 126 {
11564 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11565 }
11566 c
11567 })
11568 .collect()
11569 })
11570 }
11571
11572 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11573 where
11574 Fn: FnMut(&str) -> String,
11575 {
11576 let buffer = self.buffer.read(cx).snapshot(cx);
11577
11578 let mut new_selections = Vec::new();
11579 let mut edits = Vec::new();
11580 let mut selection_adjustment = 0i32;
11581
11582 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11583 let selection_is_empty = selection.is_empty();
11584
11585 let (start, end) = if selection_is_empty {
11586 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11587 (word_range.start, word_range.end)
11588 } else {
11589 (
11590 buffer.point_to_offset(selection.start),
11591 buffer.point_to_offset(selection.end),
11592 )
11593 };
11594
11595 let text = buffer.text_for_range(start..end).collect::<String>();
11596 let old_length = text.len() as i32;
11597 let text = callback(&text);
11598
11599 new_selections.push(Selection {
11600 start: (start as i32 - selection_adjustment) as usize,
11601 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11602 goal: SelectionGoal::None,
11603 id: selection.id,
11604 reversed: selection.reversed,
11605 });
11606
11607 selection_adjustment += old_length - text.len() as i32;
11608
11609 edits.push((start..end, text));
11610 }
11611
11612 self.transact(window, cx, |this, window, cx| {
11613 this.buffer.update(cx, |buffer, cx| {
11614 buffer.edit(edits, None, cx);
11615 });
11616
11617 this.change_selections(Default::default(), window, cx, |s| {
11618 s.select(new_selections);
11619 });
11620
11621 this.request_autoscroll(Autoscroll::fit(), cx);
11622 });
11623 }
11624
11625 pub fn move_selection_on_drop(
11626 &mut self,
11627 selection: &Selection<Anchor>,
11628 target: DisplayPoint,
11629 is_cut: bool,
11630 window: &mut Window,
11631 cx: &mut Context<Self>,
11632 ) {
11633 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11634 let buffer = display_map.buffer_snapshot();
11635 let mut edits = Vec::new();
11636 let insert_point = display_map
11637 .clip_point(target, Bias::Left)
11638 .to_point(&display_map);
11639 let text = buffer
11640 .text_for_range(selection.start..selection.end)
11641 .collect::<String>();
11642 if is_cut {
11643 edits.push(((selection.start..selection.end), String::new()));
11644 }
11645 let insert_anchor = buffer.anchor_before(insert_point);
11646 edits.push(((insert_anchor..insert_anchor), text));
11647 let last_edit_start = insert_anchor.bias_left(buffer);
11648 let last_edit_end = insert_anchor.bias_right(buffer);
11649 self.transact(window, cx, |this, window, cx| {
11650 this.buffer.update(cx, |buffer, cx| {
11651 buffer.edit(edits, None, cx);
11652 });
11653 this.change_selections(Default::default(), window, cx, |s| {
11654 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11655 });
11656 });
11657 }
11658
11659 pub fn clear_selection_drag_state(&mut self) {
11660 self.selection_drag_state = SelectionDragState::None;
11661 }
11662
11663 pub fn duplicate(
11664 &mut self,
11665 upwards: bool,
11666 whole_lines: bool,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11671
11672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11673 let buffer = display_map.buffer_snapshot();
11674 let selections = self.selections.all::<Point>(&display_map);
11675
11676 let mut edits = Vec::new();
11677 let mut selections_iter = selections.iter().peekable();
11678 while let Some(selection) = selections_iter.next() {
11679 let mut rows = selection.spanned_rows(false, &display_map);
11680 // duplicate line-wise
11681 if whole_lines || selection.start == selection.end {
11682 // Avoid duplicating the same lines twice.
11683 while let Some(next_selection) = selections_iter.peek() {
11684 let next_rows = next_selection.spanned_rows(false, &display_map);
11685 if next_rows.start < rows.end {
11686 rows.end = next_rows.end;
11687 selections_iter.next().unwrap();
11688 } else {
11689 break;
11690 }
11691 }
11692
11693 // Copy the text from the selected row region and splice it either at the start
11694 // or end of the region.
11695 let start = Point::new(rows.start.0, 0);
11696 let end = Point::new(
11697 rows.end.previous_row().0,
11698 buffer.line_len(rows.end.previous_row()),
11699 );
11700
11701 let mut text = buffer.text_for_range(start..end).collect::<String>();
11702
11703 let insert_location = if upwards {
11704 // When duplicating upward, we need to insert before the current line.
11705 // If we're on the last line and it doesn't end with a newline,
11706 // we need to add a newline before the duplicated content.
11707 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11708 && buffer.max_point().column > 0
11709 && !text.ends_with('\n');
11710
11711 if needs_leading_newline {
11712 text.insert(0, '\n');
11713 end
11714 } else {
11715 text.push('\n');
11716 Point::new(rows.start.0, 0)
11717 }
11718 } else {
11719 text.push('\n');
11720 start
11721 };
11722 edits.push((insert_location..insert_location, text));
11723 } else {
11724 // duplicate character-wise
11725 let start = selection.start;
11726 let end = selection.end;
11727 let text = buffer.text_for_range(start..end).collect::<String>();
11728 edits.push((selection.end..selection.end, text));
11729 }
11730 }
11731
11732 self.transact(window, cx, |this, window, cx| {
11733 this.buffer.update(cx, |buffer, cx| {
11734 buffer.edit(edits, None, cx);
11735 });
11736
11737 // When duplicating upward with whole lines, move the cursor to the duplicated line
11738 if upwards && whole_lines {
11739 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11740
11741 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11742 let mut new_ranges = Vec::new();
11743 let selections = s.all::<Point>(&display_map);
11744 let mut selections_iter = selections.iter().peekable();
11745
11746 while let Some(first_selection) = selections_iter.next() {
11747 // Group contiguous selections together to find the total row span
11748 let mut group_selections = vec![first_selection];
11749 let mut rows = first_selection.spanned_rows(false, &display_map);
11750
11751 while let Some(next_selection) = selections_iter.peek() {
11752 let next_rows = next_selection.spanned_rows(false, &display_map);
11753 if next_rows.start < rows.end {
11754 rows.end = next_rows.end;
11755 group_selections.push(selections_iter.next().unwrap());
11756 } else {
11757 break;
11758 }
11759 }
11760
11761 let row_count = rows.end.0 - rows.start.0;
11762
11763 // Move all selections in this group up by the total number of duplicated rows
11764 for selection in group_selections {
11765 let new_start = Point::new(
11766 selection.start.row.saturating_sub(row_count),
11767 selection.start.column,
11768 );
11769
11770 let new_end = Point::new(
11771 selection.end.row.saturating_sub(row_count),
11772 selection.end.column,
11773 );
11774
11775 new_ranges.push(new_start..new_end);
11776 }
11777 }
11778
11779 s.select_ranges(new_ranges);
11780 });
11781 }
11782
11783 this.request_autoscroll(Autoscroll::fit(), cx);
11784 });
11785 }
11786
11787 pub fn duplicate_line_up(
11788 &mut self,
11789 _: &DuplicateLineUp,
11790 window: &mut Window,
11791 cx: &mut Context<Self>,
11792 ) {
11793 self.duplicate(true, true, window, cx);
11794 }
11795
11796 pub fn duplicate_line_down(
11797 &mut self,
11798 _: &DuplicateLineDown,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.duplicate(false, true, window, cx);
11803 }
11804
11805 pub fn duplicate_selection(
11806 &mut self,
11807 _: &DuplicateSelection,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 ) {
11811 self.duplicate(false, false, window, cx);
11812 }
11813
11814 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11816 if self.mode.is_single_line() {
11817 cx.propagate();
11818 return;
11819 }
11820
11821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11822 let buffer = self.buffer.read(cx).snapshot(cx);
11823
11824 let mut edits = Vec::new();
11825 let mut unfold_ranges = Vec::new();
11826 let mut refold_creases = Vec::new();
11827
11828 let selections = self.selections.all::<Point>(&display_map);
11829 let mut selections = selections.iter().peekable();
11830 let mut contiguous_row_selections = Vec::new();
11831 let mut new_selections = Vec::new();
11832
11833 while let Some(selection) = selections.next() {
11834 // Find all the selections that span a contiguous row range
11835 let (start_row, end_row) = consume_contiguous_rows(
11836 &mut contiguous_row_selections,
11837 selection,
11838 &display_map,
11839 &mut selections,
11840 );
11841
11842 // Move the text spanned by the row range to be before the line preceding the row range
11843 if start_row.0 > 0 {
11844 let range_to_move = Point::new(
11845 start_row.previous_row().0,
11846 buffer.line_len(start_row.previous_row()),
11847 )
11848 ..Point::new(
11849 end_row.previous_row().0,
11850 buffer.line_len(end_row.previous_row()),
11851 );
11852 let insertion_point = display_map
11853 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11854 .0;
11855
11856 // Don't move lines across excerpts
11857 if buffer
11858 .excerpt_containing(insertion_point..range_to_move.end)
11859 .is_some()
11860 {
11861 let text = buffer
11862 .text_for_range(range_to_move.clone())
11863 .flat_map(|s| s.chars())
11864 .skip(1)
11865 .chain(['\n'])
11866 .collect::<String>();
11867
11868 edits.push((
11869 buffer.anchor_after(range_to_move.start)
11870 ..buffer.anchor_before(range_to_move.end),
11871 String::new(),
11872 ));
11873 let insertion_anchor = buffer.anchor_after(insertion_point);
11874 edits.push((insertion_anchor..insertion_anchor, text));
11875
11876 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11877
11878 // Move selections up
11879 new_selections.extend(contiguous_row_selections.drain(..).map(
11880 |mut selection| {
11881 selection.start.row -= row_delta;
11882 selection.end.row -= row_delta;
11883 selection
11884 },
11885 ));
11886
11887 // Move folds up
11888 unfold_ranges.push(range_to_move.clone());
11889 for fold in display_map.folds_in_range(
11890 buffer.anchor_before(range_to_move.start)
11891 ..buffer.anchor_after(range_to_move.end),
11892 ) {
11893 let mut start = fold.range.start.to_point(&buffer);
11894 let mut end = fold.range.end.to_point(&buffer);
11895 start.row -= row_delta;
11896 end.row -= row_delta;
11897 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11898 }
11899 }
11900 }
11901
11902 // If we didn't move line(s), preserve the existing selections
11903 new_selections.append(&mut contiguous_row_selections);
11904 }
11905
11906 self.transact(window, cx, |this, window, cx| {
11907 this.unfold_ranges(&unfold_ranges, true, true, cx);
11908 this.buffer.update(cx, |buffer, cx| {
11909 for (range, text) in edits {
11910 buffer.edit([(range, text)], None, cx);
11911 }
11912 });
11913 this.fold_creases(refold_creases, true, window, cx);
11914 this.change_selections(Default::default(), window, cx, |s| {
11915 s.select(new_selections);
11916 })
11917 });
11918 }
11919
11920 pub fn move_line_down(
11921 &mut self,
11922 _: &MoveLineDown,
11923 window: &mut Window,
11924 cx: &mut Context<Self>,
11925 ) {
11926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11927 if self.mode.is_single_line() {
11928 cx.propagate();
11929 return;
11930 }
11931
11932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11933 let buffer = self.buffer.read(cx).snapshot(cx);
11934
11935 let mut edits = Vec::new();
11936 let mut unfold_ranges = Vec::new();
11937 let mut refold_creases = Vec::new();
11938
11939 let selections = self.selections.all::<Point>(&display_map);
11940 let mut selections = selections.iter().peekable();
11941 let mut contiguous_row_selections = Vec::new();
11942 let mut new_selections = Vec::new();
11943
11944 while let Some(selection) = selections.next() {
11945 // Find all the selections that span a contiguous row range
11946 let (start_row, end_row) = consume_contiguous_rows(
11947 &mut contiguous_row_selections,
11948 selection,
11949 &display_map,
11950 &mut selections,
11951 );
11952
11953 // Move the text spanned by the row range to be after the last line of the row range
11954 if end_row.0 <= buffer.max_point().row {
11955 let range_to_move =
11956 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11957 let insertion_point = display_map
11958 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11959 .0;
11960
11961 // Don't move lines across excerpt boundaries
11962 if buffer
11963 .excerpt_containing(range_to_move.start..insertion_point)
11964 .is_some()
11965 {
11966 let mut text = String::from("\n");
11967 text.extend(buffer.text_for_range(range_to_move.clone()));
11968 text.pop(); // Drop trailing newline
11969 edits.push((
11970 buffer.anchor_after(range_to_move.start)
11971 ..buffer.anchor_before(range_to_move.end),
11972 String::new(),
11973 ));
11974 let insertion_anchor = buffer.anchor_after(insertion_point);
11975 edits.push((insertion_anchor..insertion_anchor, text));
11976
11977 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11978
11979 // Move selections down
11980 new_selections.extend(contiguous_row_selections.drain(..).map(
11981 |mut selection| {
11982 selection.start.row += row_delta;
11983 selection.end.row += row_delta;
11984 selection
11985 },
11986 ));
11987
11988 // Move folds down
11989 unfold_ranges.push(range_to_move.clone());
11990 for fold in display_map.folds_in_range(
11991 buffer.anchor_before(range_to_move.start)
11992 ..buffer.anchor_after(range_to_move.end),
11993 ) {
11994 let mut start = fold.range.start.to_point(&buffer);
11995 let mut end = fold.range.end.to_point(&buffer);
11996 start.row += row_delta;
11997 end.row += row_delta;
11998 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11999 }
12000 }
12001 }
12002
12003 // If we didn't move line(s), preserve the existing selections
12004 new_selections.append(&mut contiguous_row_selections);
12005 }
12006
12007 self.transact(window, cx, |this, window, cx| {
12008 this.unfold_ranges(&unfold_ranges, true, true, cx);
12009 this.buffer.update(cx, |buffer, cx| {
12010 for (range, text) in edits {
12011 buffer.edit([(range, text)], None, cx);
12012 }
12013 });
12014 this.fold_creases(refold_creases, true, window, cx);
12015 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12016 });
12017 }
12018
12019 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12021 let text_layout_details = &self.text_layout_details(window);
12022 self.transact(window, cx, |this, window, cx| {
12023 let edits = this.change_selections(Default::default(), window, cx, |s| {
12024 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12025 s.move_with(|display_map, selection| {
12026 if !selection.is_empty() {
12027 return;
12028 }
12029
12030 let mut head = selection.head();
12031 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12032 if head.column() == display_map.line_len(head.row()) {
12033 transpose_offset = display_map
12034 .buffer_snapshot()
12035 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12036 }
12037
12038 if transpose_offset == 0 {
12039 return;
12040 }
12041
12042 *head.column_mut() += 1;
12043 head = display_map.clip_point(head, Bias::Right);
12044 let goal = SelectionGoal::HorizontalPosition(
12045 display_map
12046 .x_for_display_point(head, text_layout_details)
12047 .into(),
12048 );
12049 selection.collapse_to(head, goal);
12050
12051 let transpose_start = display_map
12052 .buffer_snapshot()
12053 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12054 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12055 let transpose_end = display_map
12056 .buffer_snapshot()
12057 .clip_offset(transpose_offset + 1, Bias::Right);
12058 if let Some(ch) = display_map
12059 .buffer_snapshot()
12060 .chars_at(transpose_start)
12061 .next()
12062 {
12063 edits.push((transpose_start..transpose_offset, String::new()));
12064 edits.push((transpose_end..transpose_end, ch.to_string()));
12065 }
12066 }
12067 });
12068 edits
12069 });
12070 this.buffer
12071 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12072 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12073 this.change_selections(Default::default(), window, cx, |s| {
12074 s.select(selections);
12075 });
12076 });
12077 }
12078
12079 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12081 if self.mode.is_single_line() {
12082 cx.propagate();
12083 return;
12084 }
12085
12086 self.rewrap_impl(RewrapOptions::default(), cx)
12087 }
12088
12089 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12090 let buffer = self.buffer.read(cx).snapshot(cx);
12091 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12092
12093 #[derive(Clone, Debug, PartialEq)]
12094 enum CommentFormat {
12095 /// single line comment, with prefix for line
12096 Line(String),
12097 /// single line within a block comment, with prefix for line
12098 BlockLine(String),
12099 /// a single line of a block comment that includes the initial delimiter
12100 BlockCommentWithStart(BlockCommentConfig),
12101 /// a single line of a block comment that includes the ending delimiter
12102 BlockCommentWithEnd(BlockCommentConfig),
12103 }
12104
12105 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12106 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12107 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12108 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12109 .peekable();
12110
12111 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12112 row
12113 } else {
12114 return Vec::new();
12115 };
12116
12117 let language_settings = buffer.language_settings_at(selection.head(), cx);
12118 let language_scope = buffer.language_scope_at(selection.head());
12119
12120 let indent_and_prefix_for_row =
12121 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12122 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12123 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12124 &language_scope
12125 {
12126 let indent_end = Point::new(row, indent.len);
12127 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12128 let line_text_after_indent = buffer
12129 .text_for_range(indent_end..line_end)
12130 .collect::<String>();
12131
12132 let is_within_comment_override = buffer
12133 .language_scope_at(indent_end)
12134 .is_some_and(|scope| scope.override_name() == Some("comment"));
12135 let comment_delimiters = if is_within_comment_override {
12136 // we are within a comment syntax node, but we don't
12137 // yet know what kind of comment: block, doc or line
12138 match (
12139 language_scope.documentation_comment(),
12140 language_scope.block_comment(),
12141 ) {
12142 (Some(config), _) | (_, Some(config))
12143 if buffer.contains_str_at(indent_end, &config.start) =>
12144 {
12145 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12146 }
12147 (Some(config), _) | (_, Some(config))
12148 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12149 {
12150 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12151 }
12152 (Some(config), _) | (_, Some(config))
12153 if buffer.contains_str_at(indent_end, &config.prefix) =>
12154 {
12155 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12156 }
12157 (_, _) => language_scope
12158 .line_comment_prefixes()
12159 .iter()
12160 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12161 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12162 }
12163 } else {
12164 // we not in an overridden comment node, but we may
12165 // be within a non-overridden line comment node
12166 language_scope
12167 .line_comment_prefixes()
12168 .iter()
12169 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12170 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12171 };
12172
12173 let rewrap_prefix = language_scope
12174 .rewrap_prefixes()
12175 .iter()
12176 .find_map(|prefix_regex| {
12177 prefix_regex.find(&line_text_after_indent).map(|mat| {
12178 if mat.start() == 0 {
12179 Some(mat.as_str().to_string())
12180 } else {
12181 None
12182 }
12183 })
12184 })
12185 .flatten();
12186 (comment_delimiters, rewrap_prefix)
12187 } else {
12188 (None, None)
12189 };
12190 (indent, comment_prefix, rewrap_prefix)
12191 };
12192
12193 let mut ranges = Vec::new();
12194 let from_empty_selection = selection.is_empty();
12195
12196 let mut current_range_start = first_row;
12197 let mut prev_row = first_row;
12198 let (
12199 mut current_range_indent,
12200 mut current_range_comment_delimiters,
12201 mut current_range_rewrap_prefix,
12202 ) = indent_and_prefix_for_row(first_row);
12203
12204 for row in non_blank_rows_iter.skip(1) {
12205 let has_paragraph_break = row > prev_row + 1;
12206
12207 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12208 indent_and_prefix_for_row(row);
12209
12210 let has_indent_change = row_indent != current_range_indent;
12211 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12212
12213 let has_boundary_change = has_comment_change
12214 || row_rewrap_prefix.is_some()
12215 || (has_indent_change && current_range_comment_delimiters.is_some());
12216
12217 if has_paragraph_break || has_boundary_change {
12218 ranges.push((
12219 language_settings.clone(),
12220 Point::new(current_range_start, 0)
12221 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12222 current_range_indent,
12223 current_range_comment_delimiters.clone(),
12224 current_range_rewrap_prefix.clone(),
12225 from_empty_selection,
12226 ));
12227 current_range_start = row;
12228 current_range_indent = row_indent;
12229 current_range_comment_delimiters = row_comment_delimiters;
12230 current_range_rewrap_prefix = row_rewrap_prefix;
12231 }
12232 prev_row = row;
12233 }
12234
12235 ranges.push((
12236 language_settings.clone(),
12237 Point::new(current_range_start, 0)
12238 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12239 current_range_indent,
12240 current_range_comment_delimiters,
12241 current_range_rewrap_prefix,
12242 from_empty_selection,
12243 ));
12244
12245 ranges
12246 });
12247
12248 let mut edits = Vec::new();
12249 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12250
12251 for (
12252 language_settings,
12253 wrap_range,
12254 mut indent_size,
12255 comment_prefix,
12256 rewrap_prefix,
12257 from_empty_selection,
12258 ) in wrap_ranges
12259 {
12260 let mut start_row = wrap_range.start.row;
12261 let mut end_row = wrap_range.end.row;
12262
12263 // Skip selections that overlap with a range that has already been rewrapped.
12264 let selection_range = start_row..end_row;
12265 if rewrapped_row_ranges
12266 .iter()
12267 .any(|range| range.overlaps(&selection_range))
12268 {
12269 continue;
12270 }
12271
12272 let tab_size = language_settings.tab_size;
12273
12274 let (line_prefix, inside_comment) = match &comment_prefix {
12275 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12276 (Some(prefix.as_str()), true)
12277 }
12278 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12279 (Some(prefix.as_ref()), true)
12280 }
12281 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12282 start: _,
12283 end: _,
12284 prefix,
12285 tab_size,
12286 })) => {
12287 indent_size.len += tab_size;
12288 (Some(prefix.as_ref()), true)
12289 }
12290 None => (None, false),
12291 };
12292 let indent_prefix = indent_size.chars().collect::<String>();
12293 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12294
12295 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12296 RewrapBehavior::InComments => inside_comment,
12297 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12298 RewrapBehavior::Anywhere => true,
12299 };
12300
12301 let should_rewrap = options.override_language_settings
12302 || allow_rewrap_based_on_language
12303 || self.hard_wrap.is_some();
12304 if !should_rewrap {
12305 continue;
12306 }
12307
12308 if from_empty_selection {
12309 'expand_upwards: while start_row > 0 {
12310 let prev_row = start_row - 1;
12311 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12312 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12313 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12314 {
12315 start_row = prev_row;
12316 } else {
12317 break 'expand_upwards;
12318 }
12319 }
12320
12321 'expand_downwards: while end_row < buffer.max_point().row {
12322 let next_row = end_row + 1;
12323 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12324 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12325 && !buffer.is_line_blank(MultiBufferRow(next_row))
12326 {
12327 end_row = next_row;
12328 } else {
12329 break 'expand_downwards;
12330 }
12331 }
12332 }
12333
12334 let start = Point::new(start_row, 0);
12335 let start_offset = ToOffset::to_offset(&start, &buffer);
12336 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12337 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12338 let mut first_line_delimiter = None;
12339 let mut last_line_delimiter = None;
12340 let Some(lines_without_prefixes) = selection_text
12341 .lines()
12342 .enumerate()
12343 .map(|(ix, line)| {
12344 let line_trimmed = line.trim_start();
12345 if rewrap_prefix.is_some() && ix > 0 {
12346 Ok(line_trimmed)
12347 } else if let Some(
12348 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12349 start,
12350 prefix,
12351 end,
12352 tab_size,
12353 })
12354 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12355 start,
12356 prefix,
12357 end,
12358 tab_size,
12359 }),
12360 ) = &comment_prefix
12361 {
12362 let line_trimmed = line_trimmed
12363 .strip_prefix(start.as_ref())
12364 .map(|s| {
12365 let mut indent_size = indent_size;
12366 indent_size.len -= tab_size;
12367 let indent_prefix: String = indent_size.chars().collect();
12368 first_line_delimiter = Some((indent_prefix, start));
12369 s.trim_start()
12370 })
12371 .unwrap_or(line_trimmed);
12372 let line_trimmed = line_trimmed
12373 .strip_suffix(end.as_ref())
12374 .map(|s| {
12375 last_line_delimiter = Some(end);
12376 s.trim_end()
12377 })
12378 .unwrap_or(line_trimmed);
12379 let line_trimmed = line_trimmed
12380 .strip_prefix(prefix.as_ref())
12381 .unwrap_or(line_trimmed);
12382 Ok(line_trimmed)
12383 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12384 line_trimmed.strip_prefix(prefix).with_context(|| {
12385 format!("line did not start with prefix {prefix:?}: {line:?}")
12386 })
12387 } else {
12388 line_trimmed
12389 .strip_prefix(&line_prefix.trim_start())
12390 .with_context(|| {
12391 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12392 })
12393 }
12394 })
12395 .collect::<Result<Vec<_>, _>>()
12396 .log_err()
12397 else {
12398 continue;
12399 };
12400
12401 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12402 buffer
12403 .language_settings_at(Point::new(start_row, 0), cx)
12404 .preferred_line_length as usize
12405 });
12406
12407 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12408 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12409 } else {
12410 line_prefix.clone()
12411 };
12412
12413 let wrapped_text = {
12414 let mut wrapped_text = wrap_with_prefix(
12415 line_prefix,
12416 subsequent_lines_prefix,
12417 lines_without_prefixes.join("\n"),
12418 wrap_column,
12419 tab_size,
12420 options.preserve_existing_whitespace,
12421 );
12422
12423 if let Some((indent, delimiter)) = first_line_delimiter {
12424 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12425 }
12426 if let Some(last_line) = last_line_delimiter {
12427 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12428 }
12429
12430 wrapped_text
12431 };
12432
12433 // TODO: should always use char-based diff while still supporting cursor behavior that
12434 // matches vim.
12435 let mut diff_options = DiffOptions::default();
12436 if options.override_language_settings {
12437 diff_options.max_word_diff_len = 0;
12438 diff_options.max_word_diff_line_count = 0;
12439 } else {
12440 diff_options.max_word_diff_len = usize::MAX;
12441 diff_options.max_word_diff_line_count = usize::MAX;
12442 }
12443
12444 for (old_range, new_text) in
12445 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12446 {
12447 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12448 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12449 edits.push((edit_start..edit_end, new_text));
12450 }
12451
12452 rewrapped_row_ranges.push(start_row..=end_row);
12453 }
12454
12455 self.buffer
12456 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12457 }
12458
12459 pub fn cut_common(
12460 &mut self,
12461 cut_no_selection_line: bool,
12462 window: &mut Window,
12463 cx: &mut Context<Self>,
12464 ) -> ClipboardItem {
12465 let mut text = String::new();
12466 let buffer = self.buffer.read(cx).snapshot(cx);
12467 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12468 let mut clipboard_selections = Vec::with_capacity(selections.len());
12469 {
12470 let max_point = buffer.max_point();
12471 let mut is_first = true;
12472 for selection in &mut selections {
12473 let is_entire_line =
12474 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12475 if is_entire_line {
12476 selection.start = Point::new(selection.start.row, 0);
12477 if !selection.is_empty() && selection.end.column == 0 {
12478 selection.end = cmp::min(max_point, selection.end);
12479 } else {
12480 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12481 }
12482 selection.goal = SelectionGoal::None;
12483 }
12484 if is_first {
12485 is_first = false;
12486 } else {
12487 text += "\n";
12488 }
12489 let mut len = 0;
12490 for chunk in buffer.text_for_range(selection.start..selection.end) {
12491 text.push_str(chunk);
12492 len += chunk.len();
12493 }
12494 clipboard_selections.push(ClipboardSelection {
12495 len,
12496 is_entire_line,
12497 first_line_indent: buffer
12498 .indent_size_for_line(MultiBufferRow(selection.start.row))
12499 .len,
12500 });
12501 }
12502 }
12503
12504 self.transact(window, cx, |this, window, cx| {
12505 this.change_selections(Default::default(), window, cx, |s| {
12506 s.select(selections);
12507 });
12508 this.insert("", window, cx);
12509 });
12510 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12511 }
12512
12513 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12515 let item = self.cut_common(true, window, cx);
12516 cx.write_to_clipboard(item);
12517 }
12518
12519 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12520 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12521 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12522 s.move_with(|snapshot, sel| {
12523 if sel.is_empty() {
12524 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12525 }
12526 if sel.is_empty() {
12527 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12528 }
12529 });
12530 });
12531 let item = self.cut_common(false, window, cx);
12532 cx.set_global(KillRing(item))
12533 }
12534
12535 pub fn kill_ring_yank(
12536 &mut self,
12537 _: &KillRingYank,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12542 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12543 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12544 (kill_ring.text().to_string(), kill_ring.metadata_json())
12545 } else {
12546 return;
12547 }
12548 } else {
12549 return;
12550 };
12551 self.do_paste(&text, metadata, false, window, cx);
12552 }
12553
12554 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12555 self.do_copy(true, cx);
12556 }
12557
12558 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12559 self.do_copy(false, cx);
12560 }
12561
12562 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12563 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12564 let buffer = self.buffer.read(cx).read(cx);
12565 let mut text = String::new();
12566
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 &selections {
12572 let mut start = selection.start;
12573 let mut end = selection.end;
12574 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12575 let mut add_trailing_newline = false;
12576 if is_entire_line {
12577 start = Point::new(start.row, 0);
12578 let next_line_start = Point::new(end.row + 1, 0);
12579 if next_line_start <= max_point {
12580 end = next_line_start;
12581 } else {
12582 // We're on the last line without a trailing newline.
12583 // Copy to the end of the line and add a newline afterwards.
12584 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12585 add_trailing_newline = true;
12586 }
12587 }
12588
12589 let mut trimmed_selections = Vec::new();
12590 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12591 let row = MultiBufferRow(start.row);
12592 let first_indent = buffer.indent_size_for_line(row);
12593 if first_indent.len == 0 || start.column > first_indent.len {
12594 trimmed_selections.push(start..end);
12595 } else {
12596 trimmed_selections.push(
12597 Point::new(row.0, first_indent.len)
12598 ..Point::new(row.0, buffer.line_len(row)),
12599 );
12600 for row in start.row + 1..=end.row {
12601 let mut line_len = buffer.line_len(MultiBufferRow(row));
12602 if row == end.row {
12603 line_len = end.column;
12604 }
12605 if line_len == 0 {
12606 trimmed_selections
12607 .push(Point::new(row, 0)..Point::new(row, line_len));
12608 continue;
12609 }
12610 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12611 if row_indent_size.len >= first_indent.len {
12612 trimmed_selections.push(
12613 Point::new(row, first_indent.len)..Point::new(row, line_len),
12614 );
12615 } else {
12616 trimmed_selections.clear();
12617 trimmed_selections.push(start..end);
12618 break;
12619 }
12620 }
12621 }
12622 } else {
12623 trimmed_selections.push(start..end);
12624 }
12625
12626 for trimmed_range in trimmed_selections {
12627 if is_first {
12628 is_first = false;
12629 } else {
12630 text += "\n";
12631 }
12632 let mut len = 0;
12633 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12634 text.push_str(chunk);
12635 len += chunk.len();
12636 }
12637 if add_trailing_newline {
12638 text.push('\n');
12639 len += 1;
12640 }
12641 clipboard_selections.push(ClipboardSelection {
12642 len,
12643 is_entire_line,
12644 first_line_indent: buffer
12645 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12646 .len,
12647 });
12648 }
12649 }
12650 }
12651
12652 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12653 text,
12654 clipboard_selections,
12655 ));
12656 }
12657
12658 pub fn do_paste(
12659 &mut self,
12660 text: &String,
12661 clipboard_selections: Option<Vec<ClipboardSelection>>,
12662 handle_entire_lines: bool,
12663 window: &mut Window,
12664 cx: &mut Context<Self>,
12665 ) {
12666 if self.read_only(cx) {
12667 return;
12668 }
12669
12670 let clipboard_text = Cow::Borrowed(text.as_str());
12671
12672 self.transact(window, cx, |this, window, cx| {
12673 let had_active_edit_prediction = this.has_active_edit_prediction();
12674 let display_map = this.display_snapshot(cx);
12675 let old_selections = this.selections.all::<usize>(&display_map);
12676 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12677
12678 if let Some(mut clipboard_selections) = clipboard_selections {
12679 let all_selections_were_entire_line =
12680 clipboard_selections.iter().all(|s| s.is_entire_line);
12681 let first_selection_indent_column =
12682 clipboard_selections.first().map(|s| s.first_line_indent);
12683 if clipboard_selections.len() != old_selections.len() {
12684 clipboard_selections.drain(..);
12685 }
12686 let mut auto_indent_on_paste = true;
12687
12688 this.buffer.update(cx, |buffer, cx| {
12689 let snapshot = buffer.read(cx);
12690 auto_indent_on_paste = snapshot
12691 .language_settings_at(cursor_offset, cx)
12692 .auto_indent_on_paste;
12693
12694 let mut start_offset = 0;
12695 let mut edits = Vec::new();
12696 let mut original_indent_columns = Vec::new();
12697 for (ix, selection) in old_selections.iter().enumerate() {
12698 let to_insert;
12699 let entire_line;
12700 let original_indent_column;
12701 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12702 let end_offset = start_offset + clipboard_selection.len;
12703 to_insert = &clipboard_text[start_offset..end_offset];
12704 entire_line = clipboard_selection.is_entire_line;
12705 start_offset = end_offset + 1;
12706 original_indent_column = Some(clipboard_selection.first_line_indent);
12707 } else {
12708 to_insert = &*clipboard_text;
12709 entire_line = all_selections_were_entire_line;
12710 original_indent_column = first_selection_indent_column
12711 }
12712
12713 let (range, to_insert) =
12714 if selection.is_empty() && handle_entire_lines && entire_line {
12715 // If the corresponding selection was empty when this slice of the
12716 // clipboard text was written, then the entire line containing the
12717 // selection was copied. If this selection is also currently empty,
12718 // then paste the line before the current line of the buffer.
12719 let column = selection.start.to_point(&snapshot).column as usize;
12720 let line_start = selection.start - column;
12721 (line_start..line_start, Cow::Borrowed(to_insert))
12722 } else {
12723 let language = snapshot.language_at(selection.head());
12724 let range = selection.range();
12725 if let Some(language) = language
12726 && language.name() == "Markdown".into()
12727 {
12728 edit_for_markdown_paste(
12729 &snapshot,
12730 range,
12731 to_insert,
12732 url::Url::parse(to_insert).ok(),
12733 )
12734 } else {
12735 (range, Cow::Borrowed(to_insert))
12736 }
12737 };
12738
12739 edits.push((range, to_insert));
12740 original_indent_columns.push(original_indent_column);
12741 }
12742 drop(snapshot);
12743
12744 buffer.edit(
12745 edits,
12746 if auto_indent_on_paste {
12747 Some(AutoindentMode::Block {
12748 original_indent_columns,
12749 })
12750 } else {
12751 None
12752 },
12753 cx,
12754 );
12755 });
12756
12757 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12758 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12759 } else {
12760 let url = url::Url::parse(&clipboard_text).ok();
12761
12762 let auto_indent_mode = if !clipboard_text.is_empty() {
12763 Some(AutoindentMode::Block {
12764 original_indent_columns: Vec::new(),
12765 })
12766 } else {
12767 None
12768 };
12769
12770 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12771 let snapshot = buffer.snapshot(cx);
12772
12773 let anchors = old_selections
12774 .iter()
12775 .map(|s| {
12776 let anchor = snapshot.anchor_after(s.head());
12777 s.map(|_| anchor)
12778 })
12779 .collect::<Vec<_>>();
12780
12781 let mut edits = Vec::new();
12782
12783 for selection in old_selections.iter() {
12784 let language = snapshot.language_at(selection.head());
12785 let range = selection.range();
12786
12787 let (edit_range, edit_text) = if let Some(language) = language
12788 && language.name() == "Markdown".into()
12789 {
12790 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12791 } else {
12792 (range, clipboard_text.clone())
12793 };
12794
12795 edits.push((edit_range, edit_text));
12796 }
12797
12798 drop(snapshot);
12799 buffer.edit(edits, auto_indent_mode, cx);
12800
12801 anchors
12802 });
12803
12804 this.change_selections(Default::default(), window, cx, |s| {
12805 s.select_anchors(selection_anchors);
12806 });
12807 }
12808
12809 let trigger_in_words =
12810 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12811
12812 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12813 });
12814 }
12815
12816 pub fn diff_clipboard_with_selection(
12817 &mut self,
12818 _: &DiffClipboardWithSelection,
12819 window: &mut Window,
12820 cx: &mut Context<Self>,
12821 ) {
12822 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12823
12824 if selections.is_empty() {
12825 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12826 return;
12827 };
12828
12829 let clipboard_text = match cx.read_from_clipboard() {
12830 Some(item) => match item.entries().first() {
12831 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12832 _ => None,
12833 },
12834 None => None,
12835 };
12836
12837 let Some(clipboard_text) = clipboard_text else {
12838 log::warn!("Clipboard doesn't contain text.");
12839 return;
12840 };
12841
12842 window.dispatch_action(
12843 Box::new(DiffClipboardWithSelectionData {
12844 clipboard_text,
12845 editor: cx.entity(),
12846 }),
12847 cx,
12848 );
12849 }
12850
12851 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12853 if let Some(item) = cx.read_from_clipboard() {
12854 let entries = item.entries();
12855
12856 match entries.first() {
12857 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12858 // of all the pasted entries.
12859 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12860 .do_paste(
12861 clipboard_string.text(),
12862 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12863 true,
12864 window,
12865 cx,
12866 ),
12867 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12868 }
12869 }
12870 }
12871
12872 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12873 if self.read_only(cx) {
12874 return;
12875 }
12876
12877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12878
12879 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12880 if let Some((selections, _)) =
12881 self.selection_history.transaction(transaction_id).cloned()
12882 {
12883 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12884 s.select_anchors(selections.to_vec());
12885 });
12886 } else {
12887 log::error!(
12888 "No entry in selection_history found for undo. \
12889 This may correspond to a bug where undo does not update the selection. \
12890 If this is occurring, please add details to \
12891 https://github.com/zed-industries/zed/issues/22692"
12892 );
12893 }
12894 self.request_autoscroll(Autoscroll::fit(), cx);
12895 self.unmark_text(window, cx);
12896 self.refresh_edit_prediction(true, false, window, cx);
12897 cx.emit(EditorEvent::Edited { transaction_id });
12898 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12899 }
12900 }
12901
12902 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12903 if self.read_only(cx) {
12904 return;
12905 }
12906
12907 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12908
12909 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12910 if let Some((_, Some(selections))) =
12911 self.selection_history.transaction(transaction_id).cloned()
12912 {
12913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12914 s.select_anchors(selections.to_vec());
12915 });
12916 } else {
12917 log::error!(
12918 "No entry in selection_history found for redo. \
12919 This may correspond to a bug where undo does not update the selection. \
12920 If this is occurring, please add details to \
12921 https://github.com/zed-industries/zed/issues/22692"
12922 );
12923 }
12924 self.request_autoscroll(Autoscroll::fit(), cx);
12925 self.unmark_text(window, cx);
12926 self.refresh_edit_prediction(true, false, window, cx);
12927 cx.emit(EditorEvent::Edited { transaction_id });
12928 }
12929 }
12930
12931 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12932 self.buffer
12933 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12934 }
12935
12936 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12937 self.buffer
12938 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12939 }
12940
12941 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12942 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12943 self.change_selections(Default::default(), window, cx, |s| {
12944 s.move_with(|map, selection| {
12945 let cursor = if selection.is_empty() {
12946 movement::left(map, selection.start)
12947 } else {
12948 selection.start
12949 };
12950 selection.collapse_to(cursor, SelectionGoal::None);
12951 });
12952 })
12953 }
12954
12955 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12957 self.change_selections(Default::default(), window, cx, |s| {
12958 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12959 })
12960 }
12961
12962 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12964 self.change_selections(Default::default(), window, cx, |s| {
12965 s.move_with(|map, selection| {
12966 let cursor = if selection.is_empty() {
12967 movement::right(map, selection.end)
12968 } else {
12969 selection.end
12970 };
12971 selection.collapse_to(cursor, SelectionGoal::None)
12972 });
12973 })
12974 }
12975
12976 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12978 self.change_selections(Default::default(), window, cx, |s| {
12979 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12980 });
12981 }
12982
12983 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12984 if self.take_rename(true, window, cx).is_some() {
12985 return;
12986 }
12987
12988 if self.mode.is_single_line() {
12989 cx.propagate();
12990 return;
12991 }
12992
12993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12994
12995 let text_layout_details = &self.text_layout_details(window);
12996 let selection_count = self.selections.count();
12997 let first_selection = self.selections.first_anchor();
12998
12999 self.change_selections(Default::default(), window, cx, |s| {
13000 s.move_with(|map, selection| {
13001 if !selection.is_empty() {
13002 selection.goal = SelectionGoal::None;
13003 }
13004 let (cursor, goal) = movement::up(
13005 map,
13006 selection.start,
13007 selection.goal,
13008 false,
13009 text_layout_details,
13010 );
13011 selection.collapse_to(cursor, goal);
13012 });
13013 });
13014
13015 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13016 {
13017 cx.propagate();
13018 }
13019 }
13020
13021 pub fn move_up_by_lines(
13022 &mut self,
13023 action: &MoveUpByLines,
13024 window: &mut Window,
13025 cx: &mut Context<Self>,
13026 ) {
13027 if self.take_rename(true, window, cx).is_some() {
13028 return;
13029 }
13030
13031 if self.mode.is_single_line() {
13032 cx.propagate();
13033 return;
13034 }
13035
13036 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13037
13038 let text_layout_details = &self.text_layout_details(window);
13039
13040 self.change_selections(Default::default(), window, cx, |s| {
13041 s.move_with(|map, selection| {
13042 if !selection.is_empty() {
13043 selection.goal = SelectionGoal::None;
13044 }
13045 let (cursor, goal) = movement::up_by_rows(
13046 map,
13047 selection.start,
13048 action.lines,
13049 selection.goal,
13050 false,
13051 text_layout_details,
13052 );
13053 selection.collapse_to(cursor, goal);
13054 });
13055 })
13056 }
13057
13058 pub fn move_down_by_lines(
13059 &mut self,
13060 action: &MoveDownByLines,
13061 window: &mut Window,
13062 cx: &mut Context<Self>,
13063 ) {
13064 if self.take_rename(true, window, cx).is_some() {
13065 return;
13066 }
13067
13068 if self.mode.is_single_line() {
13069 cx.propagate();
13070 return;
13071 }
13072
13073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13074
13075 let text_layout_details = &self.text_layout_details(window);
13076
13077 self.change_selections(Default::default(), window, cx, |s| {
13078 s.move_with(|map, selection| {
13079 if !selection.is_empty() {
13080 selection.goal = SelectionGoal::None;
13081 }
13082 let (cursor, goal) = movement::down_by_rows(
13083 map,
13084 selection.start,
13085 action.lines,
13086 selection.goal,
13087 false,
13088 text_layout_details,
13089 );
13090 selection.collapse_to(cursor, goal);
13091 });
13092 })
13093 }
13094
13095 pub fn select_down_by_lines(
13096 &mut self,
13097 action: &SelectDownByLines,
13098 window: &mut Window,
13099 cx: &mut Context<Self>,
13100 ) {
13101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13102 let text_layout_details = &self.text_layout_details(window);
13103 self.change_selections(Default::default(), window, cx, |s| {
13104 s.move_heads_with(|map, head, goal| {
13105 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13106 })
13107 })
13108 }
13109
13110 pub fn select_up_by_lines(
13111 &mut self,
13112 action: &SelectUpByLines,
13113 window: &mut Window,
13114 cx: &mut Context<Self>,
13115 ) {
13116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13117 let text_layout_details = &self.text_layout_details(window);
13118 self.change_selections(Default::default(), window, cx, |s| {
13119 s.move_heads_with(|map, head, goal| {
13120 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13121 })
13122 })
13123 }
13124
13125 pub fn select_page_up(
13126 &mut self,
13127 _: &SelectPageUp,
13128 window: &mut Window,
13129 cx: &mut Context<Self>,
13130 ) {
13131 let Some(row_count) = self.visible_row_count() else {
13132 return;
13133 };
13134
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13136
13137 let text_layout_details = &self.text_layout_details(window);
13138
13139 self.change_selections(Default::default(), window, cx, |s| {
13140 s.move_heads_with(|map, head, goal| {
13141 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13142 })
13143 })
13144 }
13145
13146 pub fn move_page_up(
13147 &mut self,
13148 action: &MovePageUp,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 if self.take_rename(true, window, cx).is_some() {
13153 return;
13154 }
13155
13156 if self
13157 .context_menu
13158 .borrow_mut()
13159 .as_mut()
13160 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13161 .unwrap_or(false)
13162 {
13163 return;
13164 }
13165
13166 if matches!(self.mode, EditorMode::SingleLine) {
13167 cx.propagate();
13168 return;
13169 }
13170
13171 let Some(row_count) = self.visible_row_count() else {
13172 return;
13173 };
13174
13175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13176
13177 let effects = if action.center_cursor {
13178 SelectionEffects::scroll(Autoscroll::center())
13179 } else {
13180 SelectionEffects::default()
13181 };
13182
13183 let text_layout_details = &self.text_layout_details(window);
13184
13185 self.change_selections(effects, window, cx, |s| {
13186 s.move_with(|map, selection| {
13187 if !selection.is_empty() {
13188 selection.goal = SelectionGoal::None;
13189 }
13190 let (cursor, goal) = movement::up_by_rows(
13191 map,
13192 selection.end,
13193 row_count,
13194 selection.goal,
13195 false,
13196 text_layout_details,
13197 );
13198 selection.collapse_to(cursor, goal);
13199 });
13200 });
13201 }
13202
13203 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
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::up(map, head, goal, false, text_layout_details)
13209 })
13210 })
13211 }
13212
13213 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13214 self.take_rename(true, window, cx);
13215
13216 if self.mode.is_single_line() {
13217 cx.propagate();
13218 return;
13219 }
13220
13221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13222
13223 let text_layout_details = &self.text_layout_details(window);
13224 let selection_count = self.selections.count();
13225 let first_selection = self.selections.first_anchor();
13226
13227 self.change_selections(Default::default(), window, cx, |s| {
13228 s.move_with(|map, selection| {
13229 if !selection.is_empty() {
13230 selection.goal = SelectionGoal::None;
13231 }
13232 let (cursor, goal) = movement::down(
13233 map,
13234 selection.end,
13235 selection.goal,
13236 false,
13237 text_layout_details,
13238 );
13239 selection.collapse_to(cursor, goal);
13240 });
13241 });
13242
13243 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13244 {
13245 cx.propagate();
13246 }
13247 }
13248
13249 pub fn select_page_down(
13250 &mut self,
13251 _: &SelectPageDown,
13252 window: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 let Some(row_count) = self.visible_row_count() else {
13256 return;
13257 };
13258
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260
13261 let text_layout_details = &self.text_layout_details(window);
13262
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_heads_with(|map, head, goal| {
13265 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13266 })
13267 })
13268 }
13269
13270 pub fn move_page_down(
13271 &mut self,
13272 action: &MovePageDown,
13273 window: &mut Window,
13274 cx: &mut Context<Self>,
13275 ) {
13276 if self.take_rename(true, window, cx).is_some() {
13277 return;
13278 }
13279
13280 if self
13281 .context_menu
13282 .borrow_mut()
13283 .as_mut()
13284 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13285 .unwrap_or(false)
13286 {
13287 return;
13288 }
13289
13290 if matches!(self.mode, EditorMode::SingleLine) {
13291 cx.propagate();
13292 return;
13293 }
13294
13295 let Some(row_count) = self.visible_row_count() else {
13296 return;
13297 };
13298
13299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13300
13301 let effects = if action.center_cursor {
13302 SelectionEffects::scroll(Autoscroll::center())
13303 } else {
13304 SelectionEffects::default()
13305 };
13306
13307 let text_layout_details = &self.text_layout_details(window);
13308 self.change_selections(effects, window, cx, |s| {
13309 s.move_with(|map, selection| {
13310 if !selection.is_empty() {
13311 selection.goal = SelectionGoal::None;
13312 }
13313 let (cursor, goal) = movement::down_by_rows(
13314 map,
13315 selection.end,
13316 row_count,
13317 selection.goal,
13318 false,
13319 text_layout_details,
13320 );
13321 selection.collapse_to(cursor, goal);
13322 });
13323 });
13324 }
13325
13326 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13328 let text_layout_details = &self.text_layout_details(window);
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.move_heads_with(|map, head, goal| {
13331 movement::down(map, head, goal, false, text_layout_details)
13332 })
13333 });
13334 }
13335
13336 pub fn context_menu_first(
13337 &mut self,
13338 _: &ContextMenuFirst,
13339 window: &mut Window,
13340 cx: &mut Context<Self>,
13341 ) {
13342 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13343 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13344 }
13345 }
13346
13347 pub fn context_menu_prev(
13348 &mut self,
13349 _: &ContextMenuPrevious,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13354 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13355 }
13356 }
13357
13358 pub fn context_menu_next(
13359 &mut self,
13360 _: &ContextMenuNext,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13365 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13366 }
13367 }
13368
13369 pub fn context_menu_last(
13370 &mut self,
13371 _: &ContextMenuLast,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13376 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13377 }
13378 }
13379
13380 pub fn signature_help_prev(
13381 &mut self,
13382 _: &SignatureHelpPrevious,
13383 _: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 if let Some(popover) = self.signature_help_state.popover_mut() {
13387 if popover.current_signature == 0 {
13388 popover.current_signature = popover.signatures.len() - 1;
13389 } else {
13390 popover.current_signature -= 1;
13391 }
13392 cx.notify();
13393 }
13394 }
13395
13396 pub fn signature_help_next(
13397 &mut self,
13398 _: &SignatureHelpNext,
13399 _: &mut Window,
13400 cx: &mut Context<Self>,
13401 ) {
13402 if let Some(popover) = self.signature_help_state.popover_mut() {
13403 if popover.current_signature + 1 == popover.signatures.len() {
13404 popover.current_signature = 0;
13405 } else {
13406 popover.current_signature += 1;
13407 }
13408 cx.notify();
13409 }
13410 }
13411
13412 pub fn move_to_previous_word_start(
13413 &mut self,
13414 _: &MoveToPreviousWordStart,
13415 window: &mut Window,
13416 cx: &mut Context<Self>,
13417 ) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 self.change_selections(Default::default(), window, cx, |s| {
13420 s.move_cursors_with(|map, head, _| {
13421 (
13422 movement::previous_word_start(map, head),
13423 SelectionGoal::None,
13424 )
13425 });
13426 })
13427 }
13428
13429 pub fn move_to_previous_subword_start(
13430 &mut self,
13431 _: &MoveToPreviousSubwordStart,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_cursors_with(|map, head, _| {
13438 (
13439 movement::previous_subword_start(map, head),
13440 SelectionGoal::None,
13441 )
13442 });
13443 })
13444 }
13445
13446 pub fn select_to_previous_word_start(
13447 &mut self,
13448 _: &SelectToPreviousWordStart,
13449 window: &mut Window,
13450 cx: &mut Context<Self>,
13451 ) {
13452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.move_heads_with(|map, head, _| {
13455 (
13456 movement::previous_word_start(map, head),
13457 SelectionGoal::None,
13458 )
13459 });
13460 })
13461 }
13462
13463 pub fn select_to_previous_subword_start(
13464 &mut self,
13465 _: &SelectToPreviousSubwordStart,
13466 window: &mut Window,
13467 cx: &mut Context<Self>,
13468 ) {
13469 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13470 self.change_selections(Default::default(), window, cx, |s| {
13471 s.move_heads_with(|map, head, _| {
13472 (
13473 movement::previous_subword_start(map, head),
13474 SelectionGoal::None,
13475 )
13476 });
13477 })
13478 }
13479
13480 pub fn delete_to_previous_word_start(
13481 &mut self,
13482 action: &DeleteToPreviousWordStart,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13487 self.transact(window, cx, |this, window, cx| {
13488 this.select_autoclose_pair(window, cx);
13489 this.change_selections(Default::default(), window, cx, |s| {
13490 s.move_with(|map, selection| {
13491 if selection.is_empty() {
13492 let mut cursor = if action.ignore_newlines {
13493 movement::previous_word_start(map, selection.head())
13494 } else {
13495 movement::previous_word_start_or_newline(map, selection.head())
13496 };
13497 cursor = movement::adjust_greedy_deletion(
13498 map,
13499 selection.head(),
13500 cursor,
13501 action.ignore_brackets,
13502 );
13503 selection.set_head(cursor, SelectionGoal::None);
13504 }
13505 });
13506 });
13507 this.insert("", window, cx);
13508 });
13509 }
13510
13511 pub fn delete_to_previous_subword_start(
13512 &mut self,
13513 _: &DeleteToPreviousSubwordStart,
13514 window: &mut Window,
13515 cx: &mut Context<Self>,
13516 ) {
13517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13518 self.transact(window, cx, |this, window, cx| {
13519 this.select_autoclose_pair(window, cx);
13520 this.change_selections(Default::default(), window, cx, |s| {
13521 s.move_with(|map, selection| {
13522 if selection.is_empty() {
13523 let mut cursor = movement::previous_subword_start(map, selection.head());
13524 cursor =
13525 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13526 selection.set_head(cursor, SelectionGoal::None);
13527 }
13528 });
13529 });
13530 this.insert("", window, cx);
13531 });
13532 }
13533
13534 pub fn move_to_next_word_end(
13535 &mut self,
13536 _: &MoveToNextWordEnd,
13537 window: &mut Window,
13538 cx: &mut Context<Self>,
13539 ) {
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13541 self.change_selections(Default::default(), window, cx, |s| {
13542 s.move_cursors_with(|map, head, _| {
13543 (movement::next_word_end(map, head), SelectionGoal::None)
13544 });
13545 })
13546 }
13547
13548 pub fn move_to_next_subword_end(
13549 &mut self,
13550 _: &MoveToNextSubwordEnd,
13551 window: &mut Window,
13552 cx: &mut Context<Self>,
13553 ) {
13554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13555 self.change_selections(Default::default(), window, cx, |s| {
13556 s.move_cursors_with(|map, head, _| {
13557 (movement::next_subword_end(map, head), SelectionGoal::None)
13558 });
13559 })
13560 }
13561
13562 pub fn select_to_next_word_end(
13563 &mut self,
13564 _: &SelectToNextWordEnd,
13565 window: &mut Window,
13566 cx: &mut Context<Self>,
13567 ) {
13568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13569 self.change_selections(Default::default(), window, cx, |s| {
13570 s.move_heads_with(|map, head, _| {
13571 (movement::next_word_end(map, head), SelectionGoal::None)
13572 });
13573 })
13574 }
13575
13576 pub fn select_to_next_subword_end(
13577 &mut self,
13578 _: &SelectToNextSubwordEnd,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13583 self.change_selections(Default::default(), window, cx, |s| {
13584 s.move_heads_with(|map, head, _| {
13585 (movement::next_subword_end(map, head), SelectionGoal::None)
13586 });
13587 })
13588 }
13589
13590 pub fn delete_to_next_word_end(
13591 &mut self,
13592 action: &DeleteToNextWordEnd,
13593 window: &mut Window,
13594 cx: &mut Context<Self>,
13595 ) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13597 self.transact(window, cx, |this, window, cx| {
13598 this.change_selections(Default::default(), window, cx, |s| {
13599 s.move_with(|map, selection| {
13600 if selection.is_empty() {
13601 let mut cursor = if action.ignore_newlines {
13602 movement::next_word_end(map, selection.head())
13603 } else {
13604 movement::next_word_end_or_newline(map, selection.head())
13605 };
13606 cursor = movement::adjust_greedy_deletion(
13607 map,
13608 selection.head(),
13609 cursor,
13610 action.ignore_brackets,
13611 );
13612 selection.set_head(cursor, SelectionGoal::None);
13613 }
13614 });
13615 });
13616 this.insert("", window, cx);
13617 });
13618 }
13619
13620 pub fn delete_to_next_subword_end(
13621 &mut self,
13622 _: &DeleteToNextSubwordEnd,
13623 window: &mut Window,
13624 cx: &mut Context<Self>,
13625 ) {
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13627 self.transact(window, cx, |this, window, cx| {
13628 this.change_selections(Default::default(), window, cx, |s| {
13629 s.move_with(|map, selection| {
13630 if selection.is_empty() {
13631 let mut cursor = movement::next_subword_end(map, selection.head());
13632 cursor =
13633 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13634 selection.set_head(cursor, SelectionGoal::None);
13635 }
13636 });
13637 });
13638 this.insert("", window, cx);
13639 });
13640 }
13641
13642 pub fn move_to_beginning_of_line(
13643 &mut self,
13644 action: &MoveToBeginningOfLine,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13649 self.change_selections(Default::default(), window, cx, |s| {
13650 s.move_cursors_with(|map, head, _| {
13651 (
13652 movement::indented_line_beginning(
13653 map,
13654 head,
13655 action.stop_at_soft_wraps,
13656 action.stop_at_indent,
13657 ),
13658 SelectionGoal::None,
13659 )
13660 });
13661 })
13662 }
13663
13664 pub fn select_to_beginning_of_line(
13665 &mut self,
13666 action: &SelectToBeginningOfLine,
13667 window: &mut Window,
13668 cx: &mut Context<Self>,
13669 ) {
13670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13671 self.change_selections(Default::default(), window, cx, |s| {
13672 s.move_heads_with(|map, head, _| {
13673 (
13674 movement::indented_line_beginning(
13675 map,
13676 head,
13677 action.stop_at_soft_wraps,
13678 action.stop_at_indent,
13679 ),
13680 SelectionGoal::None,
13681 )
13682 });
13683 });
13684 }
13685
13686 pub fn delete_to_beginning_of_line(
13687 &mut self,
13688 action: &DeleteToBeginningOfLine,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13693 self.transact(window, cx, |this, window, cx| {
13694 this.change_selections(Default::default(), window, cx, |s| {
13695 s.move_with(|_, selection| {
13696 selection.reversed = true;
13697 });
13698 });
13699
13700 this.select_to_beginning_of_line(
13701 &SelectToBeginningOfLine {
13702 stop_at_soft_wraps: false,
13703 stop_at_indent: action.stop_at_indent,
13704 },
13705 window,
13706 cx,
13707 );
13708 this.backspace(&Backspace, window, cx);
13709 });
13710 }
13711
13712 pub fn move_to_end_of_line(
13713 &mut self,
13714 action: &MoveToEndOfLine,
13715 window: &mut Window,
13716 cx: &mut Context<Self>,
13717 ) {
13718 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13719 self.change_selections(Default::default(), window, cx, |s| {
13720 s.move_cursors_with(|map, head, _| {
13721 (
13722 movement::line_end(map, head, action.stop_at_soft_wraps),
13723 SelectionGoal::None,
13724 )
13725 });
13726 })
13727 }
13728
13729 pub fn select_to_end_of_line(
13730 &mut self,
13731 action: &SelectToEndOfLine,
13732 window: &mut Window,
13733 cx: &mut Context<Self>,
13734 ) {
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_heads_with(|map, head, _| {
13738 (
13739 movement::line_end(map, head, action.stop_at_soft_wraps),
13740 SelectionGoal::None,
13741 )
13742 });
13743 })
13744 }
13745
13746 pub fn delete_to_end_of_line(
13747 &mut self,
13748 _: &DeleteToEndOfLine,
13749 window: &mut Window,
13750 cx: &mut Context<Self>,
13751 ) {
13752 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13753 self.transact(window, cx, |this, window, cx| {
13754 this.select_to_end_of_line(
13755 &SelectToEndOfLine {
13756 stop_at_soft_wraps: false,
13757 },
13758 window,
13759 cx,
13760 );
13761 this.delete(&Delete, window, cx);
13762 });
13763 }
13764
13765 pub fn cut_to_end_of_line(
13766 &mut self,
13767 action: &CutToEndOfLine,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13772 self.transact(window, cx, |this, window, cx| {
13773 this.select_to_end_of_line(
13774 &SelectToEndOfLine {
13775 stop_at_soft_wraps: false,
13776 },
13777 window,
13778 cx,
13779 );
13780 if !action.stop_at_newlines {
13781 this.change_selections(Default::default(), window, cx, |s| {
13782 s.move_with(|_, sel| {
13783 if sel.is_empty() {
13784 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13785 }
13786 });
13787 });
13788 }
13789 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13790 let item = this.cut_common(false, window, cx);
13791 cx.write_to_clipboard(item);
13792 });
13793 }
13794
13795 pub fn move_to_start_of_paragraph(
13796 &mut self,
13797 _: &MoveToStartOfParagraph,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 if matches!(self.mode, EditorMode::SingleLine) {
13802 cx.propagate();
13803 return;
13804 }
13805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.move_with(|map, selection| {
13808 selection.collapse_to(
13809 movement::start_of_paragraph(map, selection.head(), 1),
13810 SelectionGoal::None,
13811 )
13812 });
13813 })
13814 }
13815
13816 pub fn move_to_end_of_paragraph(
13817 &mut self,
13818 _: &MoveToEndOfParagraph,
13819 window: &mut Window,
13820 cx: &mut Context<Self>,
13821 ) {
13822 if matches!(self.mode, EditorMode::SingleLine) {
13823 cx.propagate();
13824 return;
13825 }
13826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13827 self.change_selections(Default::default(), window, cx, |s| {
13828 s.move_with(|map, selection| {
13829 selection.collapse_to(
13830 movement::end_of_paragraph(map, selection.head(), 1),
13831 SelectionGoal::None,
13832 )
13833 });
13834 })
13835 }
13836
13837 pub fn select_to_start_of_paragraph(
13838 &mut self,
13839 _: &SelectToStartOfParagraph,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 if matches!(self.mode, EditorMode::SingleLine) {
13844 cx.propagate();
13845 return;
13846 }
13847 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13848 self.change_selections(Default::default(), window, cx, |s| {
13849 s.move_heads_with(|map, head, _| {
13850 (
13851 movement::start_of_paragraph(map, head, 1),
13852 SelectionGoal::None,
13853 )
13854 });
13855 })
13856 }
13857
13858 pub fn select_to_end_of_paragraph(
13859 &mut self,
13860 _: &SelectToEndOfParagraph,
13861 window: &mut Window,
13862 cx: &mut Context<Self>,
13863 ) {
13864 if matches!(self.mode, EditorMode::SingleLine) {
13865 cx.propagate();
13866 return;
13867 }
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869 self.change_selections(Default::default(), window, cx, |s| {
13870 s.move_heads_with(|map, head, _| {
13871 (
13872 movement::end_of_paragraph(map, head, 1),
13873 SelectionGoal::None,
13874 )
13875 });
13876 })
13877 }
13878
13879 pub fn move_to_start_of_excerpt(
13880 &mut self,
13881 _: &MoveToStartOfExcerpt,
13882 window: &mut Window,
13883 cx: &mut Context<Self>,
13884 ) {
13885 if matches!(self.mode, EditorMode::SingleLine) {
13886 cx.propagate();
13887 return;
13888 }
13889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13890 self.change_selections(Default::default(), window, cx, |s| {
13891 s.move_with(|map, selection| {
13892 selection.collapse_to(
13893 movement::start_of_excerpt(
13894 map,
13895 selection.head(),
13896 workspace::searchable::Direction::Prev,
13897 ),
13898 SelectionGoal::None,
13899 )
13900 });
13901 })
13902 }
13903
13904 pub fn move_to_start_of_next_excerpt(
13905 &mut self,
13906 _: &MoveToStartOfNextExcerpt,
13907 window: &mut Window,
13908 cx: &mut Context<Self>,
13909 ) {
13910 if matches!(self.mode, EditorMode::SingleLine) {
13911 cx.propagate();
13912 return;
13913 }
13914
13915 self.change_selections(Default::default(), window, cx, |s| {
13916 s.move_with(|map, selection| {
13917 selection.collapse_to(
13918 movement::start_of_excerpt(
13919 map,
13920 selection.head(),
13921 workspace::searchable::Direction::Next,
13922 ),
13923 SelectionGoal::None,
13924 )
13925 });
13926 })
13927 }
13928
13929 pub fn move_to_end_of_excerpt(
13930 &mut self,
13931 _: &MoveToEndOfExcerpt,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 if matches!(self.mode, EditorMode::SingleLine) {
13936 cx.propagate();
13937 return;
13938 }
13939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13940 self.change_selections(Default::default(), window, cx, |s| {
13941 s.move_with(|map, selection| {
13942 selection.collapse_to(
13943 movement::end_of_excerpt(
13944 map,
13945 selection.head(),
13946 workspace::searchable::Direction::Next,
13947 ),
13948 SelectionGoal::None,
13949 )
13950 });
13951 })
13952 }
13953
13954 pub fn move_to_end_of_previous_excerpt(
13955 &mut self,
13956 _: &MoveToEndOfPreviousExcerpt,
13957 window: &mut Window,
13958 cx: &mut Context<Self>,
13959 ) {
13960 if matches!(self.mode, EditorMode::SingleLine) {
13961 cx.propagate();
13962 return;
13963 }
13964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13965 self.change_selections(Default::default(), window, cx, |s| {
13966 s.move_with(|map, selection| {
13967 selection.collapse_to(
13968 movement::end_of_excerpt(
13969 map,
13970 selection.head(),
13971 workspace::searchable::Direction::Prev,
13972 ),
13973 SelectionGoal::None,
13974 )
13975 });
13976 })
13977 }
13978
13979 pub fn select_to_start_of_excerpt(
13980 &mut self,
13981 _: &SelectToStartOfExcerpt,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 if matches!(self.mode, EditorMode::SingleLine) {
13986 cx.propagate();
13987 return;
13988 }
13989 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13990 self.change_selections(Default::default(), window, cx, |s| {
13991 s.move_heads_with(|map, head, _| {
13992 (
13993 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13994 SelectionGoal::None,
13995 )
13996 });
13997 })
13998 }
13999
14000 pub fn select_to_start_of_next_excerpt(
14001 &mut self,
14002 _: &SelectToStartOfNextExcerpt,
14003 window: &mut Window,
14004 cx: &mut Context<Self>,
14005 ) {
14006 if matches!(self.mode, EditorMode::SingleLine) {
14007 cx.propagate();
14008 return;
14009 }
14010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14011 self.change_selections(Default::default(), window, cx, |s| {
14012 s.move_heads_with(|map, head, _| {
14013 (
14014 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14015 SelectionGoal::None,
14016 )
14017 });
14018 })
14019 }
14020
14021 pub fn select_to_end_of_excerpt(
14022 &mut self,
14023 _: &SelectToEndOfExcerpt,
14024 window: &mut Window,
14025 cx: &mut Context<Self>,
14026 ) {
14027 if matches!(self.mode, EditorMode::SingleLine) {
14028 cx.propagate();
14029 return;
14030 }
14031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14032 self.change_selections(Default::default(), window, cx, |s| {
14033 s.move_heads_with(|map, head, _| {
14034 (
14035 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14036 SelectionGoal::None,
14037 )
14038 });
14039 })
14040 }
14041
14042 pub fn select_to_end_of_previous_excerpt(
14043 &mut self,
14044 _: &SelectToEndOfPreviousExcerpt,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 if matches!(self.mode, EditorMode::SingleLine) {
14049 cx.propagate();
14050 return;
14051 }
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14053 self.change_selections(Default::default(), window, cx, |s| {
14054 s.move_heads_with(|map, head, _| {
14055 (
14056 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14057 SelectionGoal::None,
14058 )
14059 });
14060 })
14061 }
14062
14063 pub fn move_to_beginning(
14064 &mut self,
14065 _: &MoveToBeginning,
14066 window: &mut Window,
14067 cx: &mut Context<Self>,
14068 ) {
14069 if matches!(self.mode, EditorMode::SingleLine) {
14070 cx.propagate();
14071 return;
14072 }
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.select_ranges(vec![0..0]);
14076 });
14077 }
14078
14079 pub fn select_to_beginning(
14080 &mut self,
14081 _: &SelectToBeginning,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14086 selection.set_head(Point::zero(), SelectionGoal::None);
14087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14088 self.change_selections(Default::default(), window, cx, |s| {
14089 s.select(vec![selection]);
14090 });
14091 }
14092
14093 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14094 if matches!(self.mode, EditorMode::SingleLine) {
14095 cx.propagate();
14096 return;
14097 }
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 let cursor = self.buffer.read(cx).read(cx).len();
14100 self.change_selections(Default::default(), window, cx, |s| {
14101 s.select_ranges(vec![cursor..cursor])
14102 });
14103 }
14104
14105 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14106 self.nav_history = nav_history;
14107 }
14108
14109 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14110 self.nav_history.as_ref()
14111 }
14112
14113 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14114 self.push_to_nav_history(
14115 self.selections.newest_anchor().head(),
14116 None,
14117 false,
14118 true,
14119 cx,
14120 );
14121 }
14122
14123 fn push_to_nav_history(
14124 &mut self,
14125 cursor_anchor: Anchor,
14126 new_position: Option<Point>,
14127 is_deactivate: bool,
14128 always: bool,
14129 cx: &mut Context<Self>,
14130 ) {
14131 if let Some(nav_history) = self.nav_history.as_mut() {
14132 let buffer = self.buffer.read(cx).read(cx);
14133 let cursor_position = cursor_anchor.to_point(&buffer);
14134 let scroll_state = self.scroll_manager.anchor();
14135 let scroll_top_row = scroll_state.top_row(&buffer);
14136 drop(buffer);
14137
14138 if let Some(new_position) = new_position {
14139 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14140 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14141 return;
14142 }
14143 }
14144
14145 nav_history.push(
14146 Some(NavigationData {
14147 cursor_anchor,
14148 cursor_position,
14149 scroll_anchor: scroll_state,
14150 scroll_top_row,
14151 }),
14152 cx,
14153 );
14154 cx.emit(EditorEvent::PushedToNavHistory {
14155 anchor: cursor_anchor,
14156 is_deactivate,
14157 })
14158 }
14159 }
14160
14161 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14163 let buffer = self.buffer.read(cx).snapshot(cx);
14164 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14165 selection.set_head(buffer.len(), SelectionGoal::None);
14166 self.change_selections(Default::default(), window, cx, |s| {
14167 s.select(vec![selection]);
14168 });
14169 }
14170
14171 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173 let end = self.buffer.read(cx).read(cx).len();
14174 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14175 s.select_ranges(vec![0..end]);
14176 });
14177 }
14178
14179 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14182 let mut selections = self.selections.all::<Point>(&display_map);
14183 let max_point = display_map.buffer_snapshot().max_point();
14184 for selection in &mut selections {
14185 let rows = selection.spanned_rows(true, &display_map);
14186 selection.start = Point::new(rows.start.0, 0);
14187 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14188 selection.reversed = false;
14189 }
14190 self.change_selections(Default::default(), window, cx, |s| {
14191 s.select(selections);
14192 });
14193 }
14194
14195 pub fn split_selection_into_lines(
14196 &mut self,
14197 action: &SplitSelectionIntoLines,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 let selections = self
14202 .selections
14203 .all::<Point>(&self.display_snapshot(cx))
14204 .into_iter()
14205 .map(|selection| selection.start..selection.end)
14206 .collect::<Vec<_>>();
14207 self.unfold_ranges(&selections, true, true, cx);
14208
14209 let mut new_selection_ranges = Vec::new();
14210 {
14211 let buffer = self.buffer.read(cx).read(cx);
14212 for selection in selections {
14213 for row in selection.start.row..selection.end.row {
14214 let line_start = Point::new(row, 0);
14215 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14216
14217 if action.keep_selections {
14218 // Keep the selection range for each line
14219 let selection_start = if row == selection.start.row {
14220 selection.start
14221 } else {
14222 line_start
14223 };
14224 new_selection_ranges.push(selection_start..line_end);
14225 } else {
14226 // Collapse to cursor at end of line
14227 new_selection_ranges.push(line_end..line_end);
14228 }
14229 }
14230
14231 let is_multiline_selection = selection.start.row != selection.end.row;
14232 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14233 // so this action feels more ergonomic when paired with other selection operations
14234 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14235 if !should_skip_last {
14236 if action.keep_selections {
14237 if is_multiline_selection {
14238 let line_start = Point::new(selection.end.row, 0);
14239 new_selection_ranges.push(line_start..selection.end);
14240 } else {
14241 new_selection_ranges.push(selection.start..selection.end);
14242 }
14243 } else {
14244 new_selection_ranges.push(selection.end..selection.end);
14245 }
14246 }
14247 }
14248 }
14249 self.change_selections(Default::default(), window, cx, |s| {
14250 s.select_ranges(new_selection_ranges);
14251 });
14252 }
14253
14254 pub fn add_selection_above(
14255 &mut self,
14256 action: &AddSelectionAbove,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 self.add_selection(true, action.skip_soft_wrap, window, cx);
14261 }
14262
14263 pub fn add_selection_below(
14264 &mut self,
14265 action: &AddSelectionBelow,
14266 window: &mut Window,
14267 cx: &mut Context<Self>,
14268 ) {
14269 self.add_selection(false, action.skip_soft_wrap, window, cx);
14270 }
14271
14272 fn add_selection(
14273 &mut self,
14274 above: bool,
14275 skip_soft_wrap: bool,
14276 window: &mut Window,
14277 cx: &mut Context<Self>,
14278 ) {
14279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14280
14281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14282 let all_selections = self.selections.all::<Point>(&display_map);
14283 let text_layout_details = self.text_layout_details(window);
14284
14285 let (mut columnar_selections, new_selections_to_columnarize) = {
14286 if let Some(state) = self.add_selections_state.as_ref() {
14287 let columnar_selection_ids: HashSet<_> = state
14288 .groups
14289 .iter()
14290 .flat_map(|group| group.stack.iter())
14291 .copied()
14292 .collect();
14293
14294 all_selections
14295 .into_iter()
14296 .partition(|s| columnar_selection_ids.contains(&s.id))
14297 } else {
14298 (Vec::new(), all_selections)
14299 }
14300 };
14301
14302 let mut state = self
14303 .add_selections_state
14304 .take()
14305 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14306
14307 for selection in new_selections_to_columnarize {
14308 let range = selection.display_range(&display_map).sorted();
14309 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14310 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14311 let positions = start_x.min(end_x)..start_x.max(end_x);
14312 let mut stack = Vec::new();
14313 for row in range.start.row().0..=range.end.row().0 {
14314 if let Some(selection) = self.selections.build_columnar_selection(
14315 &display_map,
14316 DisplayRow(row),
14317 &positions,
14318 selection.reversed,
14319 &text_layout_details,
14320 ) {
14321 stack.push(selection.id);
14322 columnar_selections.push(selection);
14323 }
14324 }
14325 if !stack.is_empty() {
14326 if above {
14327 stack.reverse();
14328 }
14329 state.groups.push(AddSelectionsGroup { above, stack });
14330 }
14331 }
14332
14333 let mut final_selections = Vec::new();
14334 let end_row = if above {
14335 DisplayRow(0)
14336 } else {
14337 display_map.max_point().row()
14338 };
14339
14340 let mut last_added_item_per_group = HashMap::default();
14341 for group in state.groups.iter_mut() {
14342 if let Some(last_id) = group.stack.last() {
14343 last_added_item_per_group.insert(*last_id, group);
14344 }
14345 }
14346
14347 for selection in columnar_selections {
14348 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14349 if above == group.above {
14350 let range = selection.display_range(&display_map).sorted();
14351 debug_assert_eq!(range.start.row(), range.end.row());
14352 let mut row = range.start.row();
14353 let positions =
14354 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14355 Pixels::from(start)..Pixels::from(end)
14356 } else {
14357 let start_x =
14358 display_map.x_for_display_point(range.start, &text_layout_details);
14359 let end_x =
14360 display_map.x_for_display_point(range.end, &text_layout_details);
14361 start_x.min(end_x)..start_x.max(end_x)
14362 };
14363
14364 let mut maybe_new_selection = None;
14365 let direction = if above { -1 } else { 1 };
14366
14367 while row != end_row {
14368 if skip_soft_wrap {
14369 row = display_map
14370 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14371 .row();
14372 } else if above {
14373 row.0 -= 1;
14374 } else {
14375 row.0 += 1;
14376 }
14377
14378 if let Some(new_selection) = self.selections.build_columnar_selection(
14379 &display_map,
14380 row,
14381 &positions,
14382 selection.reversed,
14383 &text_layout_details,
14384 ) {
14385 maybe_new_selection = Some(new_selection);
14386 break;
14387 }
14388 }
14389
14390 if let Some(new_selection) = maybe_new_selection {
14391 group.stack.push(new_selection.id);
14392 if above {
14393 final_selections.push(new_selection);
14394 final_selections.push(selection);
14395 } else {
14396 final_selections.push(selection);
14397 final_selections.push(new_selection);
14398 }
14399 } else {
14400 final_selections.push(selection);
14401 }
14402 } else {
14403 group.stack.pop();
14404 }
14405 } else {
14406 final_selections.push(selection);
14407 }
14408 }
14409
14410 self.change_selections(Default::default(), window, cx, |s| {
14411 s.select(final_selections);
14412 });
14413
14414 let final_selection_ids: HashSet<_> = self
14415 .selections
14416 .all::<Point>(&display_map)
14417 .iter()
14418 .map(|s| s.id)
14419 .collect();
14420 state.groups.retain_mut(|group| {
14421 // selections might get merged above so we remove invalid items from stacks
14422 group.stack.retain(|id| final_selection_ids.contains(id));
14423
14424 // single selection in stack can be treated as initial state
14425 group.stack.len() > 1
14426 });
14427
14428 if !state.groups.is_empty() {
14429 self.add_selections_state = Some(state);
14430 }
14431 }
14432
14433 fn select_match_ranges(
14434 &mut self,
14435 range: Range<usize>,
14436 reversed: bool,
14437 replace_newest: bool,
14438 auto_scroll: Option<Autoscroll>,
14439 window: &mut Window,
14440 cx: &mut Context<Editor>,
14441 ) {
14442 self.unfold_ranges(
14443 std::slice::from_ref(&range),
14444 false,
14445 auto_scroll.is_some(),
14446 cx,
14447 );
14448 let effects = if let Some(scroll) = auto_scroll {
14449 SelectionEffects::scroll(scroll)
14450 } else {
14451 SelectionEffects::no_scroll()
14452 };
14453 self.change_selections(effects, window, cx, |s| {
14454 if replace_newest {
14455 s.delete(s.newest_anchor().id);
14456 }
14457 if reversed {
14458 s.insert_range(range.end..range.start);
14459 } else {
14460 s.insert_range(range);
14461 }
14462 });
14463 }
14464
14465 pub fn select_next_match_internal(
14466 &mut self,
14467 display_map: &DisplaySnapshot,
14468 replace_newest: bool,
14469 autoscroll: Option<Autoscroll>,
14470 window: &mut Window,
14471 cx: &mut Context<Self>,
14472 ) -> Result<()> {
14473 let buffer = display_map.buffer_snapshot();
14474 let mut selections = self.selections.all::<usize>(&display_map);
14475 if let Some(mut select_next_state) = self.select_next_state.take() {
14476 let query = &select_next_state.query;
14477 if !select_next_state.done {
14478 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14479 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14480 let mut next_selected_range = None;
14481
14482 let bytes_after_last_selection =
14483 buffer.bytes_in_range(last_selection.end..buffer.len());
14484 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14485 let query_matches = query
14486 .stream_find_iter(bytes_after_last_selection)
14487 .map(|result| (last_selection.end, result))
14488 .chain(
14489 query
14490 .stream_find_iter(bytes_before_first_selection)
14491 .map(|result| (0, result)),
14492 );
14493
14494 for (start_offset, query_match) in query_matches {
14495 let query_match = query_match.unwrap(); // can only fail due to I/O
14496 let offset_range =
14497 start_offset + query_match.start()..start_offset + query_match.end();
14498
14499 if !select_next_state.wordwise
14500 || (!buffer.is_inside_word(offset_range.start, None)
14501 && !buffer.is_inside_word(offset_range.end, None))
14502 {
14503 let idx = selections
14504 .partition_point(|selection| selection.end <= offset_range.start);
14505 let overlaps = selections
14506 .get(idx)
14507 .map_or(false, |selection| selection.start < offset_range.end);
14508
14509 if !overlaps {
14510 next_selected_range = Some(offset_range);
14511 break;
14512 }
14513 }
14514 }
14515
14516 if let Some(next_selected_range) = next_selected_range {
14517 self.select_match_ranges(
14518 next_selected_range,
14519 last_selection.reversed,
14520 replace_newest,
14521 autoscroll,
14522 window,
14523 cx,
14524 );
14525 } else {
14526 select_next_state.done = true;
14527 }
14528 }
14529
14530 self.select_next_state = Some(select_next_state);
14531 } else {
14532 let mut only_carets = true;
14533 let mut same_text_selected = true;
14534 let mut selected_text = None;
14535
14536 let mut selections_iter = selections.iter().peekable();
14537 while let Some(selection) = selections_iter.next() {
14538 if selection.start != selection.end {
14539 only_carets = false;
14540 }
14541
14542 if same_text_selected {
14543 if selected_text.is_none() {
14544 selected_text =
14545 Some(buffer.text_for_range(selection.range()).collect::<String>());
14546 }
14547
14548 if let Some(next_selection) = selections_iter.peek() {
14549 if next_selection.range().len() == selection.range().len() {
14550 let next_selected_text = buffer
14551 .text_for_range(next_selection.range())
14552 .collect::<String>();
14553 if Some(next_selected_text) != selected_text {
14554 same_text_selected = false;
14555 selected_text = None;
14556 }
14557 } else {
14558 same_text_selected = false;
14559 selected_text = None;
14560 }
14561 }
14562 }
14563 }
14564
14565 if only_carets {
14566 for selection in &mut selections {
14567 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14568 selection.start = word_range.start;
14569 selection.end = word_range.end;
14570 selection.goal = SelectionGoal::None;
14571 selection.reversed = false;
14572 self.select_match_ranges(
14573 selection.start..selection.end,
14574 selection.reversed,
14575 replace_newest,
14576 autoscroll,
14577 window,
14578 cx,
14579 );
14580 }
14581
14582 if selections.len() == 1 {
14583 let selection = selections
14584 .last()
14585 .expect("ensured that there's only one selection");
14586 let query = buffer
14587 .text_for_range(selection.start..selection.end)
14588 .collect::<String>();
14589 let is_empty = query.is_empty();
14590 let select_state = SelectNextState {
14591 query: AhoCorasick::new(&[query])?,
14592 wordwise: true,
14593 done: is_empty,
14594 };
14595 self.select_next_state = Some(select_state);
14596 } else {
14597 self.select_next_state = None;
14598 }
14599 } else if let Some(selected_text) = selected_text {
14600 self.select_next_state = Some(SelectNextState {
14601 query: AhoCorasick::new(&[selected_text])?,
14602 wordwise: false,
14603 done: false,
14604 });
14605 self.select_next_match_internal(
14606 display_map,
14607 replace_newest,
14608 autoscroll,
14609 window,
14610 cx,
14611 )?;
14612 }
14613 }
14614 Ok(())
14615 }
14616
14617 pub fn select_all_matches(
14618 &mut self,
14619 _action: &SelectAllMatches,
14620 window: &mut Window,
14621 cx: &mut Context<Self>,
14622 ) -> Result<()> {
14623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14624
14625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14626
14627 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14628 let Some(select_next_state) = self.select_next_state.as_mut() else {
14629 return Ok(());
14630 };
14631 if select_next_state.done {
14632 return Ok(());
14633 }
14634
14635 let mut new_selections = Vec::new();
14636
14637 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14638 let buffer = display_map.buffer_snapshot();
14639 let query_matches = select_next_state
14640 .query
14641 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14642
14643 for query_match in query_matches.into_iter() {
14644 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14645 let offset_range = if reversed {
14646 query_match.end()..query_match.start()
14647 } else {
14648 query_match.start()..query_match.end()
14649 };
14650
14651 if !select_next_state.wordwise
14652 || (!buffer.is_inside_word(offset_range.start, None)
14653 && !buffer.is_inside_word(offset_range.end, None))
14654 {
14655 new_selections.push(offset_range.start..offset_range.end);
14656 }
14657 }
14658
14659 select_next_state.done = true;
14660
14661 if new_selections.is_empty() {
14662 log::error!("bug: new_selections is empty in select_all_matches");
14663 return Ok(());
14664 }
14665
14666 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14667 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14668 selections.select_ranges(new_selections)
14669 });
14670
14671 Ok(())
14672 }
14673
14674 pub fn select_next(
14675 &mut self,
14676 action: &SelectNext,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) -> Result<()> {
14680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14681 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14682 self.select_next_match_internal(
14683 &display_map,
14684 action.replace_newest,
14685 Some(Autoscroll::newest()),
14686 window,
14687 cx,
14688 )?;
14689 Ok(())
14690 }
14691
14692 pub fn select_previous(
14693 &mut self,
14694 action: &SelectPrevious,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) -> Result<()> {
14698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14700 let buffer = display_map.buffer_snapshot();
14701 let mut selections = self.selections.all::<usize>(&display_map);
14702 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14703 let query = &select_prev_state.query;
14704 if !select_prev_state.done {
14705 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14706 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14707 let mut next_selected_range = None;
14708 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14709 let bytes_before_last_selection =
14710 buffer.reversed_bytes_in_range(0..last_selection.start);
14711 let bytes_after_first_selection =
14712 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14713 let query_matches = query
14714 .stream_find_iter(bytes_before_last_selection)
14715 .map(|result| (last_selection.start, result))
14716 .chain(
14717 query
14718 .stream_find_iter(bytes_after_first_selection)
14719 .map(|result| (buffer.len(), result)),
14720 );
14721 for (end_offset, query_match) in query_matches {
14722 let query_match = query_match.unwrap(); // can only fail due to I/O
14723 let offset_range =
14724 end_offset - query_match.end()..end_offset - query_match.start();
14725
14726 if !select_prev_state.wordwise
14727 || (!buffer.is_inside_word(offset_range.start, None)
14728 && !buffer.is_inside_word(offset_range.end, None))
14729 {
14730 next_selected_range = Some(offset_range);
14731 break;
14732 }
14733 }
14734
14735 if let Some(next_selected_range) = next_selected_range {
14736 self.select_match_ranges(
14737 next_selected_range,
14738 last_selection.reversed,
14739 action.replace_newest,
14740 Some(Autoscroll::newest()),
14741 window,
14742 cx,
14743 );
14744 } else {
14745 select_prev_state.done = true;
14746 }
14747 }
14748
14749 self.select_prev_state = Some(select_prev_state);
14750 } else {
14751 let mut only_carets = true;
14752 let mut same_text_selected = true;
14753 let mut selected_text = None;
14754
14755 let mut selections_iter = selections.iter().peekable();
14756 while let Some(selection) = selections_iter.next() {
14757 if selection.start != selection.end {
14758 only_carets = false;
14759 }
14760
14761 if same_text_selected {
14762 if selected_text.is_none() {
14763 selected_text =
14764 Some(buffer.text_for_range(selection.range()).collect::<String>());
14765 }
14766
14767 if let Some(next_selection) = selections_iter.peek() {
14768 if next_selection.range().len() == selection.range().len() {
14769 let next_selected_text = buffer
14770 .text_for_range(next_selection.range())
14771 .collect::<String>();
14772 if Some(next_selected_text) != selected_text {
14773 same_text_selected = false;
14774 selected_text = None;
14775 }
14776 } else {
14777 same_text_selected = false;
14778 selected_text = None;
14779 }
14780 }
14781 }
14782 }
14783
14784 if only_carets {
14785 for selection in &mut selections {
14786 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14787 selection.start = word_range.start;
14788 selection.end = word_range.end;
14789 selection.goal = SelectionGoal::None;
14790 selection.reversed = false;
14791 self.select_match_ranges(
14792 selection.start..selection.end,
14793 selection.reversed,
14794 action.replace_newest,
14795 Some(Autoscroll::newest()),
14796 window,
14797 cx,
14798 );
14799 }
14800 if selections.len() == 1 {
14801 let selection = selections
14802 .last()
14803 .expect("ensured that there's only one selection");
14804 let query = buffer
14805 .text_for_range(selection.start..selection.end)
14806 .collect::<String>();
14807 let is_empty = query.is_empty();
14808 let select_state = SelectNextState {
14809 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14810 wordwise: true,
14811 done: is_empty,
14812 };
14813 self.select_prev_state = Some(select_state);
14814 } else {
14815 self.select_prev_state = None;
14816 }
14817 } else if let Some(selected_text) = selected_text {
14818 self.select_prev_state = Some(SelectNextState {
14819 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14820 wordwise: false,
14821 done: false,
14822 });
14823 self.select_previous(action, window, cx)?;
14824 }
14825 }
14826 Ok(())
14827 }
14828
14829 pub fn find_next_match(
14830 &mut self,
14831 _: &FindNextMatch,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) -> Result<()> {
14835 let selections = self.selections.disjoint_anchors_arc();
14836 match selections.first() {
14837 Some(first) if selections.len() >= 2 => {
14838 self.change_selections(Default::default(), window, cx, |s| {
14839 s.select_ranges([first.range()]);
14840 });
14841 }
14842 _ => self.select_next(
14843 &SelectNext {
14844 replace_newest: true,
14845 },
14846 window,
14847 cx,
14848 )?,
14849 }
14850 Ok(())
14851 }
14852
14853 pub fn find_previous_match(
14854 &mut self,
14855 _: &FindPreviousMatch,
14856 window: &mut Window,
14857 cx: &mut Context<Self>,
14858 ) -> Result<()> {
14859 let selections = self.selections.disjoint_anchors_arc();
14860 match selections.last() {
14861 Some(last) if selections.len() >= 2 => {
14862 self.change_selections(Default::default(), window, cx, |s| {
14863 s.select_ranges([last.range()]);
14864 });
14865 }
14866 _ => self.select_previous(
14867 &SelectPrevious {
14868 replace_newest: true,
14869 },
14870 window,
14871 cx,
14872 )?,
14873 }
14874 Ok(())
14875 }
14876
14877 pub fn toggle_comments(
14878 &mut self,
14879 action: &ToggleComments,
14880 window: &mut Window,
14881 cx: &mut Context<Self>,
14882 ) {
14883 if self.read_only(cx) {
14884 return;
14885 }
14886 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14887 let text_layout_details = &self.text_layout_details(window);
14888 self.transact(window, cx, |this, window, cx| {
14889 let mut selections = this
14890 .selections
14891 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14892 let mut edits = Vec::new();
14893 let mut selection_edit_ranges = Vec::new();
14894 let mut last_toggled_row = None;
14895 let snapshot = this.buffer.read(cx).read(cx);
14896 let empty_str: Arc<str> = Arc::default();
14897 let mut suffixes_inserted = Vec::new();
14898 let ignore_indent = action.ignore_indent;
14899
14900 fn comment_prefix_range(
14901 snapshot: &MultiBufferSnapshot,
14902 row: MultiBufferRow,
14903 comment_prefix: &str,
14904 comment_prefix_whitespace: &str,
14905 ignore_indent: bool,
14906 ) -> Range<Point> {
14907 let indent_size = if ignore_indent {
14908 0
14909 } else {
14910 snapshot.indent_size_for_line(row).len
14911 };
14912
14913 let start = Point::new(row.0, indent_size);
14914
14915 let mut line_bytes = snapshot
14916 .bytes_in_range(start..snapshot.max_point())
14917 .flatten()
14918 .copied();
14919
14920 // If this line currently begins with the line comment prefix, then record
14921 // the range containing the prefix.
14922 if line_bytes
14923 .by_ref()
14924 .take(comment_prefix.len())
14925 .eq(comment_prefix.bytes())
14926 {
14927 // Include any whitespace that matches the comment prefix.
14928 let matching_whitespace_len = line_bytes
14929 .zip(comment_prefix_whitespace.bytes())
14930 .take_while(|(a, b)| a == b)
14931 .count() as u32;
14932 let end = Point::new(
14933 start.row,
14934 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14935 );
14936 start..end
14937 } else {
14938 start..start
14939 }
14940 }
14941
14942 fn comment_suffix_range(
14943 snapshot: &MultiBufferSnapshot,
14944 row: MultiBufferRow,
14945 comment_suffix: &str,
14946 comment_suffix_has_leading_space: bool,
14947 ) -> Range<Point> {
14948 let end = Point::new(row.0, snapshot.line_len(row));
14949 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14950
14951 let mut line_end_bytes = snapshot
14952 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14953 .flatten()
14954 .copied();
14955
14956 let leading_space_len = if suffix_start_column > 0
14957 && line_end_bytes.next() == Some(b' ')
14958 && comment_suffix_has_leading_space
14959 {
14960 1
14961 } else {
14962 0
14963 };
14964
14965 // If this line currently begins with the line comment prefix, then record
14966 // the range containing the prefix.
14967 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14968 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14969 start..end
14970 } else {
14971 end..end
14972 }
14973 }
14974
14975 // TODO: Handle selections that cross excerpts
14976 for selection in &mut selections {
14977 let start_column = snapshot
14978 .indent_size_for_line(MultiBufferRow(selection.start.row))
14979 .len;
14980 let language = if let Some(language) =
14981 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14982 {
14983 language
14984 } else {
14985 continue;
14986 };
14987
14988 selection_edit_ranges.clear();
14989
14990 // If multiple selections contain a given row, avoid processing that
14991 // row more than once.
14992 let mut start_row = MultiBufferRow(selection.start.row);
14993 if last_toggled_row == Some(start_row) {
14994 start_row = start_row.next_row();
14995 }
14996 let end_row =
14997 if selection.end.row > selection.start.row && selection.end.column == 0 {
14998 MultiBufferRow(selection.end.row - 1)
14999 } else {
15000 MultiBufferRow(selection.end.row)
15001 };
15002 last_toggled_row = Some(end_row);
15003
15004 if start_row > end_row {
15005 continue;
15006 }
15007
15008 // If the language has line comments, toggle those.
15009 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15010
15011 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15012 if ignore_indent {
15013 full_comment_prefixes = full_comment_prefixes
15014 .into_iter()
15015 .map(|s| Arc::from(s.trim_end()))
15016 .collect();
15017 }
15018
15019 if !full_comment_prefixes.is_empty() {
15020 let first_prefix = full_comment_prefixes
15021 .first()
15022 .expect("prefixes is non-empty");
15023 let prefix_trimmed_lengths = full_comment_prefixes
15024 .iter()
15025 .map(|p| p.trim_end_matches(' ').len())
15026 .collect::<SmallVec<[usize; 4]>>();
15027
15028 let mut all_selection_lines_are_comments = true;
15029
15030 for row in start_row.0..=end_row.0 {
15031 let row = MultiBufferRow(row);
15032 if start_row < end_row && snapshot.is_line_blank(row) {
15033 continue;
15034 }
15035
15036 let prefix_range = full_comment_prefixes
15037 .iter()
15038 .zip(prefix_trimmed_lengths.iter().copied())
15039 .map(|(prefix, trimmed_prefix_len)| {
15040 comment_prefix_range(
15041 snapshot.deref(),
15042 row,
15043 &prefix[..trimmed_prefix_len],
15044 &prefix[trimmed_prefix_len..],
15045 ignore_indent,
15046 )
15047 })
15048 .max_by_key(|range| range.end.column - range.start.column)
15049 .expect("prefixes is non-empty");
15050
15051 if prefix_range.is_empty() {
15052 all_selection_lines_are_comments = false;
15053 }
15054
15055 selection_edit_ranges.push(prefix_range);
15056 }
15057
15058 if all_selection_lines_are_comments {
15059 edits.extend(
15060 selection_edit_ranges
15061 .iter()
15062 .cloned()
15063 .map(|range| (range, empty_str.clone())),
15064 );
15065 } else {
15066 let min_column = selection_edit_ranges
15067 .iter()
15068 .map(|range| range.start.column)
15069 .min()
15070 .unwrap_or(0);
15071 edits.extend(selection_edit_ranges.iter().map(|range| {
15072 let position = Point::new(range.start.row, min_column);
15073 (position..position, first_prefix.clone())
15074 }));
15075 }
15076 } else if let Some(BlockCommentConfig {
15077 start: full_comment_prefix,
15078 end: comment_suffix,
15079 ..
15080 }) = language.block_comment()
15081 {
15082 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15083 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15084 let prefix_range = comment_prefix_range(
15085 snapshot.deref(),
15086 start_row,
15087 comment_prefix,
15088 comment_prefix_whitespace,
15089 ignore_indent,
15090 );
15091 let suffix_range = comment_suffix_range(
15092 snapshot.deref(),
15093 end_row,
15094 comment_suffix.trim_start_matches(' '),
15095 comment_suffix.starts_with(' '),
15096 );
15097
15098 if prefix_range.is_empty() || suffix_range.is_empty() {
15099 edits.push((
15100 prefix_range.start..prefix_range.start,
15101 full_comment_prefix.clone(),
15102 ));
15103 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15104 suffixes_inserted.push((end_row, comment_suffix.len()));
15105 } else {
15106 edits.push((prefix_range, empty_str.clone()));
15107 edits.push((suffix_range, empty_str.clone()));
15108 }
15109 } else {
15110 continue;
15111 }
15112 }
15113
15114 drop(snapshot);
15115 this.buffer.update(cx, |buffer, cx| {
15116 buffer.edit(edits, None, cx);
15117 });
15118
15119 // Adjust selections so that they end before any comment suffixes that
15120 // were inserted.
15121 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15122 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15123 let snapshot = this.buffer.read(cx).read(cx);
15124 for selection in &mut selections {
15125 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15126 match row.cmp(&MultiBufferRow(selection.end.row)) {
15127 Ordering::Less => {
15128 suffixes_inserted.next();
15129 continue;
15130 }
15131 Ordering::Greater => break,
15132 Ordering::Equal => {
15133 if selection.end.column == snapshot.line_len(row) {
15134 if selection.is_empty() {
15135 selection.start.column -= suffix_len as u32;
15136 }
15137 selection.end.column -= suffix_len as u32;
15138 }
15139 break;
15140 }
15141 }
15142 }
15143 }
15144
15145 drop(snapshot);
15146 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15147
15148 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15149 let selections_on_single_row = selections.windows(2).all(|selections| {
15150 selections[0].start.row == selections[1].start.row
15151 && selections[0].end.row == selections[1].end.row
15152 && selections[0].start.row == selections[0].end.row
15153 });
15154 let selections_selecting = selections
15155 .iter()
15156 .any(|selection| selection.start != selection.end);
15157 let advance_downwards = action.advance_downwards
15158 && selections_on_single_row
15159 && !selections_selecting
15160 && !matches!(this.mode, EditorMode::SingleLine);
15161
15162 if advance_downwards {
15163 let snapshot = this.buffer.read(cx).snapshot(cx);
15164
15165 this.change_selections(Default::default(), window, cx, |s| {
15166 s.move_cursors_with(|display_snapshot, display_point, _| {
15167 let mut point = display_point.to_point(display_snapshot);
15168 point.row += 1;
15169 point = snapshot.clip_point(point, Bias::Left);
15170 let display_point = point.to_display_point(display_snapshot);
15171 let goal = SelectionGoal::HorizontalPosition(
15172 display_snapshot
15173 .x_for_display_point(display_point, text_layout_details)
15174 .into(),
15175 );
15176 (display_point, goal)
15177 })
15178 });
15179 }
15180 });
15181 }
15182
15183 pub fn select_enclosing_symbol(
15184 &mut self,
15185 _: &SelectEnclosingSymbol,
15186 window: &mut Window,
15187 cx: &mut Context<Self>,
15188 ) {
15189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15190
15191 let buffer = self.buffer.read(cx).snapshot(cx);
15192 let old_selections = self
15193 .selections
15194 .all::<usize>(&self.display_snapshot(cx))
15195 .into_boxed_slice();
15196
15197 fn update_selection(
15198 selection: &Selection<usize>,
15199 buffer_snap: &MultiBufferSnapshot,
15200 ) -> Option<Selection<usize>> {
15201 let cursor = selection.head();
15202 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15203 for symbol in symbols.iter().rev() {
15204 let start = symbol.range.start.to_offset(buffer_snap);
15205 let end = symbol.range.end.to_offset(buffer_snap);
15206 let new_range = start..end;
15207 if start < selection.start || end > selection.end {
15208 return Some(Selection {
15209 id: selection.id,
15210 start: new_range.start,
15211 end: new_range.end,
15212 goal: SelectionGoal::None,
15213 reversed: selection.reversed,
15214 });
15215 }
15216 }
15217 None
15218 }
15219
15220 let mut selected_larger_symbol = false;
15221 let new_selections = old_selections
15222 .iter()
15223 .map(|selection| match update_selection(selection, &buffer) {
15224 Some(new_selection) => {
15225 if new_selection.range() != selection.range() {
15226 selected_larger_symbol = true;
15227 }
15228 new_selection
15229 }
15230 None => selection.clone(),
15231 })
15232 .collect::<Vec<_>>();
15233
15234 if selected_larger_symbol {
15235 self.change_selections(Default::default(), window, cx, |s| {
15236 s.select(new_selections);
15237 });
15238 }
15239 }
15240
15241 pub fn select_larger_syntax_node(
15242 &mut self,
15243 _: &SelectLargerSyntaxNode,
15244 window: &mut Window,
15245 cx: &mut Context<Self>,
15246 ) {
15247 let Some(visible_row_count) = self.visible_row_count() else {
15248 return;
15249 };
15250 let old_selections: Box<[_]> = self
15251 .selections
15252 .all::<usize>(&self.display_snapshot(cx))
15253 .into();
15254 if old_selections.is_empty() {
15255 return;
15256 }
15257
15258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15259
15260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15261 let buffer = self.buffer.read(cx).snapshot(cx);
15262
15263 let mut selected_larger_node = false;
15264 let mut new_selections = old_selections
15265 .iter()
15266 .map(|selection| {
15267 let old_range = selection.start..selection.end;
15268
15269 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15270 // manually select word at selection
15271 if ["string_content", "inline"].contains(&node.kind()) {
15272 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15273 // ignore if word is already selected
15274 if !word_range.is_empty() && old_range != word_range {
15275 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15276 // only select word if start and end point belongs to same word
15277 if word_range == last_word_range {
15278 selected_larger_node = true;
15279 return Selection {
15280 id: selection.id,
15281 start: word_range.start,
15282 end: word_range.end,
15283 goal: SelectionGoal::None,
15284 reversed: selection.reversed,
15285 };
15286 }
15287 }
15288 }
15289 }
15290
15291 let mut new_range = old_range.clone();
15292 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15293 new_range = range;
15294 if !node.is_named() {
15295 continue;
15296 }
15297 if !display_map.intersects_fold(new_range.start)
15298 && !display_map.intersects_fold(new_range.end)
15299 {
15300 break;
15301 }
15302 }
15303
15304 selected_larger_node |= new_range != old_range;
15305 Selection {
15306 id: selection.id,
15307 start: new_range.start,
15308 end: new_range.end,
15309 goal: SelectionGoal::None,
15310 reversed: selection.reversed,
15311 }
15312 })
15313 .collect::<Vec<_>>();
15314
15315 if !selected_larger_node {
15316 return; // don't put this call in the history
15317 }
15318
15319 // scroll based on transformation done to the last selection created by the user
15320 let (last_old, last_new) = old_selections
15321 .last()
15322 .zip(new_selections.last().cloned())
15323 .expect("old_selections isn't empty");
15324
15325 // revert selection
15326 let is_selection_reversed = {
15327 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15328 new_selections.last_mut().expect("checked above").reversed =
15329 should_newest_selection_be_reversed;
15330 should_newest_selection_be_reversed
15331 };
15332
15333 if selected_larger_node {
15334 self.select_syntax_node_history.disable_clearing = true;
15335 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15336 s.select(new_selections.clone());
15337 });
15338 self.select_syntax_node_history.disable_clearing = false;
15339 }
15340
15341 let start_row = last_new.start.to_display_point(&display_map).row().0;
15342 let end_row = last_new.end.to_display_point(&display_map).row().0;
15343 let selection_height = end_row - start_row + 1;
15344 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15345
15346 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15347 let scroll_behavior = if fits_on_the_screen {
15348 self.request_autoscroll(Autoscroll::fit(), cx);
15349 SelectSyntaxNodeScrollBehavior::FitSelection
15350 } else if is_selection_reversed {
15351 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15352 SelectSyntaxNodeScrollBehavior::CursorTop
15353 } else {
15354 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15355 SelectSyntaxNodeScrollBehavior::CursorBottom
15356 };
15357
15358 self.select_syntax_node_history.push((
15359 old_selections,
15360 scroll_behavior,
15361 is_selection_reversed,
15362 ));
15363 }
15364
15365 pub fn select_smaller_syntax_node(
15366 &mut self,
15367 _: &SelectSmallerSyntaxNode,
15368 window: &mut Window,
15369 cx: &mut Context<Self>,
15370 ) {
15371 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15372
15373 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15374 self.select_syntax_node_history.pop()
15375 {
15376 if let Some(selection) = selections.last_mut() {
15377 selection.reversed = is_selection_reversed;
15378 }
15379
15380 self.select_syntax_node_history.disable_clearing = true;
15381 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15382 s.select(selections.to_vec());
15383 });
15384 self.select_syntax_node_history.disable_clearing = false;
15385
15386 match scroll_behavior {
15387 SelectSyntaxNodeScrollBehavior::CursorTop => {
15388 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15389 }
15390 SelectSyntaxNodeScrollBehavior::FitSelection => {
15391 self.request_autoscroll(Autoscroll::fit(), cx);
15392 }
15393 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15394 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15395 }
15396 }
15397 }
15398 }
15399
15400 pub fn unwrap_syntax_node(
15401 &mut self,
15402 _: &UnwrapSyntaxNode,
15403 window: &mut Window,
15404 cx: &mut Context<Self>,
15405 ) {
15406 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15407
15408 let buffer = self.buffer.read(cx).snapshot(cx);
15409 let selections = self
15410 .selections
15411 .all::<usize>(&self.display_snapshot(cx))
15412 .into_iter()
15413 // subtracting the offset requires sorting
15414 .sorted_by_key(|i| i.start);
15415
15416 let full_edits = selections
15417 .into_iter()
15418 .filter_map(|selection| {
15419 let child = if selection.is_empty()
15420 && let Some((_, ancestor_range)) =
15421 buffer.syntax_ancestor(selection.start..selection.end)
15422 {
15423 ancestor_range
15424 } else {
15425 selection.range()
15426 };
15427
15428 let mut parent = child.clone();
15429 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15430 parent = ancestor_range;
15431 if parent.start < child.start || parent.end > child.end {
15432 break;
15433 }
15434 }
15435
15436 if parent == child {
15437 return None;
15438 }
15439 let text = buffer.text_for_range(child).collect::<String>();
15440 Some((selection.id, parent, text))
15441 })
15442 .collect::<Vec<_>>();
15443 if full_edits.is_empty() {
15444 return;
15445 }
15446
15447 self.transact(window, cx, |this, window, cx| {
15448 this.buffer.update(cx, |buffer, cx| {
15449 buffer.edit(
15450 full_edits
15451 .iter()
15452 .map(|(_, p, t)| (p.clone(), t.clone()))
15453 .collect::<Vec<_>>(),
15454 None,
15455 cx,
15456 );
15457 });
15458 this.change_selections(Default::default(), window, cx, |s| {
15459 let mut offset = 0;
15460 let mut selections = vec![];
15461 for (id, parent, text) in full_edits {
15462 let start = parent.start - offset;
15463 offset += parent.len() - text.len();
15464 selections.push(Selection {
15465 id,
15466 start,
15467 end: start + text.len(),
15468 reversed: false,
15469 goal: Default::default(),
15470 });
15471 }
15472 s.select(selections);
15473 });
15474 });
15475 }
15476
15477 pub fn select_next_syntax_node(
15478 &mut self,
15479 _: &SelectNextSyntaxNode,
15480 window: &mut Window,
15481 cx: &mut Context<Self>,
15482 ) {
15483 let old_selections: Box<[_]> = self
15484 .selections
15485 .all::<usize>(&self.display_snapshot(cx))
15486 .into();
15487 if old_selections.is_empty() {
15488 return;
15489 }
15490
15491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15492
15493 let buffer = self.buffer.read(cx).snapshot(cx);
15494 let mut selected_sibling = false;
15495
15496 let new_selections = old_selections
15497 .iter()
15498 .map(|selection| {
15499 let old_range = selection.start..selection.end;
15500
15501 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15502 let new_range = node.byte_range();
15503 selected_sibling = true;
15504 Selection {
15505 id: selection.id,
15506 start: new_range.start,
15507 end: new_range.end,
15508 goal: SelectionGoal::None,
15509 reversed: selection.reversed,
15510 }
15511 } else {
15512 selection.clone()
15513 }
15514 })
15515 .collect::<Vec<_>>();
15516
15517 if selected_sibling {
15518 self.change_selections(
15519 SelectionEffects::scroll(Autoscroll::fit()),
15520 window,
15521 cx,
15522 |s| {
15523 s.select(new_selections);
15524 },
15525 );
15526 }
15527 }
15528
15529 pub fn select_prev_syntax_node(
15530 &mut self,
15531 _: &SelectPreviousSyntaxNode,
15532 window: &mut Window,
15533 cx: &mut Context<Self>,
15534 ) {
15535 let old_selections: Box<[_]> = self
15536 .selections
15537 .all::<usize>(&self.display_snapshot(cx))
15538 .into();
15539 if old_selections.is_empty() {
15540 return;
15541 }
15542
15543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15544
15545 let buffer = self.buffer.read(cx).snapshot(cx);
15546 let mut selected_sibling = false;
15547
15548 let new_selections = old_selections
15549 .iter()
15550 .map(|selection| {
15551 let old_range = selection.start..selection.end;
15552
15553 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15554 let new_range = node.byte_range();
15555 selected_sibling = true;
15556 Selection {
15557 id: selection.id,
15558 start: new_range.start,
15559 end: new_range.end,
15560 goal: SelectionGoal::None,
15561 reversed: selection.reversed,
15562 }
15563 } else {
15564 selection.clone()
15565 }
15566 })
15567 .collect::<Vec<_>>();
15568
15569 if selected_sibling {
15570 self.change_selections(
15571 SelectionEffects::scroll(Autoscroll::fit()),
15572 window,
15573 cx,
15574 |s| {
15575 s.select(new_selections);
15576 },
15577 );
15578 }
15579 }
15580
15581 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15582 if !EditorSettings::get_global(cx).gutter.runnables {
15583 self.clear_tasks();
15584 return Task::ready(());
15585 }
15586 let project = self.project().map(Entity::downgrade);
15587 let task_sources = self.lsp_task_sources(cx);
15588 let multi_buffer = self.buffer.downgrade();
15589 cx.spawn_in(window, async move |editor, cx| {
15590 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15591 let Some(project) = project.and_then(|p| p.upgrade()) else {
15592 return;
15593 };
15594 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15595 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15596 }) else {
15597 return;
15598 };
15599
15600 let hide_runnables = project
15601 .update(cx, |project, _| project.is_via_collab())
15602 .unwrap_or(true);
15603 if hide_runnables {
15604 return;
15605 }
15606 let new_rows =
15607 cx.background_spawn({
15608 let snapshot = display_snapshot.clone();
15609 async move {
15610 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15611 }
15612 })
15613 .await;
15614 let Ok(lsp_tasks) =
15615 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15616 else {
15617 return;
15618 };
15619 let lsp_tasks = lsp_tasks.await;
15620
15621 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15622 lsp_tasks
15623 .into_iter()
15624 .flat_map(|(kind, tasks)| {
15625 tasks.into_iter().filter_map(move |(location, task)| {
15626 Some((kind.clone(), location?, task))
15627 })
15628 })
15629 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15630 let buffer = location.target.buffer;
15631 let buffer_snapshot = buffer.read(cx).snapshot();
15632 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15633 |(excerpt_id, snapshot, _)| {
15634 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15635 display_snapshot
15636 .buffer_snapshot()
15637 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15638 } else {
15639 None
15640 }
15641 },
15642 );
15643 if let Some(offset) = offset {
15644 let task_buffer_range =
15645 location.target.range.to_point(&buffer_snapshot);
15646 let context_buffer_range =
15647 task_buffer_range.to_offset(&buffer_snapshot);
15648 let context_range = BufferOffset(context_buffer_range.start)
15649 ..BufferOffset(context_buffer_range.end);
15650
15651 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15652 .or_insert_with(|| RunnableTasks {
15653 templates: Vec::new(),
15654 offset,
15655 column: task_buffer_range.start.column,
15656 extra_variables: HashMap::default(),
15657 context_range,
15658 })
15659 .templates
15660 .push((kind, task.original_task().clone()));
15661 }
15662
15663 acc
15664 })
15665 }) else {
15666 return;
15667 };
15668
15669 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15670 buffer.language_settings(cx).tasks.prefer_lsp
15671 }) else {
15672 return;
15673 };
15674
15675 let rows = Self::runnable_rows(
15676 project,
15677 display_snapshot,
15678 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15679 new_rows,
15680 cx.clone(),
15681 )
15682 .await;
15683 editor
15684 .update(cx, |editor, _| {
15685 editor.clear_tasks();
15686 for (key, mut value) in rows {
15687 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15688 value.templates.extend(lsp_tasks.templates);
15689 }
15690
15691 editor.insert_tasks(key, value);
15692 }
15693 for (key, value) in lsp_tasks_by_rows {
15694 editor.insert_tasks(key, value);
15695 }
15696 })
15697 .ok();
15698 })
15699 }
15700 fn fetch_runnable_ranges(
15701 snapshot: &DisplaySnapshot,
15702 range: Range<Anchor>,
15703 ) -> Vec<language::RunnableRange> {
15704 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15705 }
15706
15707 fn runnable_rows(
15708 project: Entity<Project>,
15709 snapshot: DisplaySnapshot,
15710 prefer_lsp: bool,
15711 runnable_ranges: Vec<RunnableRange>,
15712 cx: AsyncWindowContext,
15713 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15714 cx.spawn(async move |cx| {
15715 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15716 for mut runnable in runnable_ranges {
15717 let Some(tasks) = cx
15718 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15719 .ok()
15720 else {
15721 continue;
15722 };
15723 let mut tasks = tasks.await;
15724
15725 if prefer_lsp {
15726 tasks.retain(|(task_kind, _)| {
15727 !matches!(task_kind, TaskSourceKind::Language { .. })
15728 });
15729 }
15730 if tasks.is_empty() {
15731 continue;
15732 }
15733
15734 let point = runnable
15735 .run_range
15736 .start
15737 .to_point(&snapshot.buffer_snapshot());
15738 let Some(row) = snapshot
15739 .buffer_snapshot()
15740 .buffer_line_for_row(MultiBufferRow(point.row))
15741 .map(|(_, range)| range.start.row)
15742 else {
15743 continue;
15744 };
15745
15746 let context_range =
15747 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15748 runnable_rows.push((
15749 (runnable.buffer_id, row),
15750 RunnableTasks {
15751 templates: tasks,
15752 offset: snapshot
15753 .buffer_snapshot()
15754 .anchor_before(runnable.run_range.start),
15755 context_range,
15756 column: point.column,
15757 extra_variables: runnable.extra_captures,
15758 },
15759 ));
15760 }
15761 runnable_rows
15762 })
15763 }
15764
15765 fn templates_with_tags(
15766 project: &Entity<Project>,
15767 runnable: &mut Runnable,
15768 cx: &mut App,
15769 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15770 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15771 let (worktree_id, file) = project
15772 .buffer_for_id(runnable.buffer, cx)
15773 .and_then(|buffer| buffer.read(cx).file())
15774 .map(|file| (file.worktree_id(cx), file.clone()))
15775 .unzip();
15776
15777 (
15778 project.task_store().read(cx).task_inventory().cloned(),
15779 worktree_id,
15780 file,
15781 )
15782 });
15783
15784 let tags = mem::take(&mut runnable.tags);
15785 let language = runnable.language.clone();
15786 cx.spawn(async move |cx| {
15787 let mut templates_with_tags = Vec::new();
15788 if let Some(inventory) = inventory {
15789 for RunnableTag(tag) in tags {
15790 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15791 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15792 }) else {
15793 return templates_with_tags;
15794 };
15795 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15796 move |(_, template)| {
15797 template.tags.iter().any(|source_tag| source_tag == &tag)
15798 },
15799 ));
15800 }
15801 }
15802 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15803
15804 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15805 // Strongest source wins; if we have worktree tag binding, prefer that to
15806 // global and language bindings;
15807 // if we have a global binding, prefer that to language binding.
15808 let first_mismatch = templates_with_tags
15809 .iter()
15810 .position(|(tag_source, _)| tag_source != leading_tag_source);
15811 if let Some(index) = first_mismatch {
15812 templates_with_tags.truncate(index);
15813 }
15814 }
15815
15816 templates_with_tags
15817 })
15818 }
15819
15820 pub fn move_to_enclosing_bracket(
15821 &mut self,
15822 _: &MoveToEnclosingBracket,
15823 window: &mut Window,
15824 cx: &mut Context<Self>,
15825 ) {
15826 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15827 self.change_selections(Default::default(), window, cx, |s| {
15828 s.move_offsets_with(|snapshot, selection| {
15829 let Some(enclosing_bracket_ranges) =
15830 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15831 else {
15832 return;
15833 };
15834
15835 let mut best_length = usize::MAX;
15836 let mut best_inside = false;
15837 let mut best_in_bracket_range = false;
15838 let mut best_destination = None;
15839 for (open, close) in enclosing_bracket_ranges {
15840 let close = close.to_inclusive();
15841 let length = close.end() - open.start;
15842 let inside = selection.start >= open.end && selection.end <= *close.start();
15843 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15844 || close.contains(&selection.head());
15845
15846 // If best is next to a bracket and current isn't, skip
15847 if !in_bracket_range && best_in_bracket_range {
15848 continue;
15849 }
15850
15851 // Prefer smaller lengths unless best is inside and current isn't
15852 if length > best_length && (best_inside || !inside) {
15853 continue;
15854 }
15855
15856 best_length = length;
15857 best_inside = inside;
15858 best_in_bracket_range = in_bracket_range;
15859 best_destination = Some(
15860 if close.contains(&selection.start) && close.contains(&selection.end) {
15861 if inside { open.end } else { open.start }
15862 } else if inside {
15863 *close.start()
15864 } else {
15865 *close.end()
15866 },
15867 );
15868 }
15869
15870 if let Some(destination) = best_destination {
15871 selection.collapse_to(destination, SelectionGoal::None);
15872 }
15873 })
15874 });
15875 }
15876
15877 pub fn undo_selection(
15878 &mut self,
15879 _: &UndoSelection,
15880 window: &mut Window,
15881 cx: &mut Context<Self>,
15882 ) {
15883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15884 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15885 self.selection_history.mode = SelectionHistoryMode::Undoing;
15886 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15887 this.end_selection(window, cx);
15888 this.change_selections(
15889 SelectionEffects::scroll(Autoscroll::newest()),
15890 window,
15891 cx,
15892 |s| s.select_anchors(entry.selections.to_vec()),
15893 );
15894 });
15895 self.selection_history.mode = SelectionHistoryMode::Normal;
15896
15897 self.select_next_state = entry.select_next_state;
15898 self.select_prev_state = entry.select_prev_state;
15899 self.add_selections_state = entry.add_selections_state;
15900 }
15901 }
15902
15903 pub fn redo_selection(
15904 &mut self,
15905 _: &RedoSelection,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15911 self.selection_history.mode = SelectionHistoryMode::Redoing;
15912 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15913 this.end_selection(window, cx);
15914 this.change_selections(
15915 SelectionEffects::scroll(Autoscroll::newest()),
15916 window,
15917 cx,
15918 |s| s.select_anchors(entry.selections.to_vec()),
15919 );
15920 });
15921 self.selection_history.mode = SelectionHistoryMode::Normal;
15922
15923 self.select_next_state = entry.select_next_state;
15924 self.select_prev_state = entry.select_prev_state;
15925 self.add_selections_state = entry.add_selections_state;
15926 }
15927 }
15928
15929 pub fn expand_excerpts(
15930 &mut self,
15931 action: &ExpandExcerpts,
15932 _: &mut Window,
15933 cx: &mut Context<Self>,
15934 ) {
15935 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15936 }
15937
15938 pub fn expand_excerpts_down(
15939 &mut self,
15940 action: &ExpandExcerptsDown,
15941 _: &mut Window,
15942 cx: &mut Context<Self>,
15943 ) {
15944 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15945 }
15946
15947 pub fn expand_excerpts_up(
15948 &mut self,
15949 action: &ExpandExcerptsUp,
15950 _: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15954 }
15955
15956 pub fn expand_excerpts_for_direction(
15957 &mut self,
15958 lines: u32,
15959 direction: ExpandExcerptDirection,
15960
15961 cx: &mut Context<Self>,
15962 ) {
15963 let selections = self.selections.disjoint_anchors_arc();
15964
15965 let lines = if lines == 0 {
15966 EditorSettings::get_global(cx).expand_excerpt_lines
15967 } else {
15968 lines
15969 };
15970
15971 self.buffer.update(cx, |buffer, cx| {
15972 let snapshot = buffer.snapshot(cx);
15973 let mut excerpt_ids = selections
15974 .iter()
15975 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15976 .collect::<Vec<_>>();
15977 excerpt_ids.sort();
15978 excerpt_ids.dedup();
15979 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15980 })
15981 }
15982
15983 pub fn expand_excerpt(
15984 &mut self,
15985 excerpt: ExcerptId,
15986 direction: ExpandExcerptDirection,
15987 window: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) {
15990 let current_scroll_position = self.scroll_position(cx);
15991 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15992 let mut scroll = None;
15993
15994 if direction == ExpandExcerptDirection::Down {
15995 let multi_buffer = self.buffer.read(cx);
15996 let snapshot = multi_buffer.snapshot(cx);
15997 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15998 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15999 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16000 {
16001 let buffer_snapshot = buffer.read(cx).snapshot();
16002 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16003 let last_row = buffer_snapshot.max_point().row;
16004 let lines_below = last_row.saturating_sub(excerpt_end_row);
16005 if lines_below >= lines_to_expand {
16006 scroll = Some(
16007 current_scroll_position
16008 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16009 );
16010 }
16011 }
16012 }
16013 if direction == ExpandExcerptDirection::Up
16014 && self
16015 .buffer
16016 .read(cx)
16017 .snapshot(cx)
16018 .excerpt_before(excerpt)
16019 .is_none()
16020 {
16021 scroll = Some(current_scroll_position);
16022 }
16023
16024 self.buffer.update(cx, |buffer, cx| {
16025 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16026 });
16027
16028 if let Some(new_scroll_position) = scroll {
16029 self.set_scroll_position(new_scroll_position, window, cx);
16030 }
16031 }
16032
16033 pub fn go_to_singleton_buffer_point(
16034 &mut self,
16035 point: Point,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 self.go_to_singleton_buffer_range(point..point, window, cx);
16040 }
16041
16042 pub fn go_to_singleton_buffer_range(
16043 &mut self,
16044 range: Range<Point>,
16045 window: &mut Window,
16046 cx: &mut Context<Self>,
16047 ) {
16048 let multibuffer = self.buffer().read(cx);
16049 let Some(buffer) = multibuffer.as_singleton() else {
16050 return;
16051 };
16052 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16053 return;
16054 };
16055 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16056 return;
16057 };
16058 self.change_selections(
16059 SelectionEffects::default().nav_history(true),
16060 window,
16061 cx,
16062 |s| s.select_anchor_ranges([start..end]),
16063 );
16064 }
16065
16066 pub fn go_to_diagnostic(
16067 &mut self,
16068 action: &GoToDiagnostic,
16069 window: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) {
16072 if !self.diagnostics_enabled() {
16073 return;
16074 }
16075 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16076 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16077 }
16078
16079 pub fn go_to_prev_diagnostic(
16080 &mut self,
16081 action: &GoToPreviousDiagnostic,
16082 window: &mut Window,
16083 cx: &mut Context<Self>,
16084 ) {
16085 if !self.diagnostics_enabled() {
16086 return;
16087 }
16088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16089 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16090 }
16091
16092 pub fn go_to_diagnostic_impl(
16093 &mut self,
16094 direction: Direction,
16095 severity: GoToDiagnosticSeverityFilter,
16096 window: &mut Window,
16097 cx: &mut Context<Self>,
16098 ) {
16099 let buffer = self.buffer.read(cx).snapshot(cx);
16100 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16101
16102 let mut active_group_id = None;
16103 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16104 && active_group.active_range.start.to_offset(&buffer) == selection.start
16105 {
16106 active_group_id = Some(active_group.group_id);
16107 }
16108
16109 fn filtered<'a>(
16110 severity: GoToDiagnosticSeverityFilter,
16111 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16112 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16113 diagnostics
16114 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16115 .filter(|entry| entry.range.start != entry.range.end)
16116 .filter(|entry| !entry.diagnostic.is_unnecessary)
16117 }
16118
16119 let before = filtered(
16120 severity,
16121 buffer
16122 .diagnostics_in_range(0..selection.start)
16123 .filter(|entry| entry.range.start <= selection.start),
16124 );
16125 let after = filtered(
16126 severity,
16127 buffer
16128 .diagnostics_in_range(selection.start..buffer.len())
16129 .filter(|entry| entry.range.start >= selection.start),
16130 );
16131
16132 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16133 if direction == Direction::Prev {
16134 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16135 {
16136 for diagnostic in prev_diagnostics.into_iter().rev() {
16137 if diagnostic.range.start != selection.start
16138 || active_group_id
16139 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16140 {
16141 found = Some(diagnostic);
16142 break 'outer;
16143 }
16144 }
16145 }
16146 } else {
16147 for diagnostic in after.chain(before) {
16148 if diagnostic.range.start != selection.start
16149 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16150 {
16151 found = Some(diagnostic);
16152 break;
16153 }
16154 }
16155 }
16156 let Some(next_diagnostic) = found else {
16157 return;
16158 };
16159
16160 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16161 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16162 return;
16163 };
16164 let snapshot = self.snapshot(window, cx);
16165 if snapshot.intersects_fold(next_diagnostic.range.start) {
16166 self.unfold_ranges(
16167 std::slice::from_ref(&next_diagnostic.range),
16168 true,
16169 false,
16170 cx,
16171 );
16172 }
16173 self.change_selections(Default::default(), window, cx, |s| {
16174 s.select_ranges(vec![
16175 next_diagnostic.range.start..next_diagnostic.range.start,
16176 ])
16177 });
16178 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16179 self.refresh_edit_prediction(false, true, window, cx);
16180 }
16181
16182 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16184 let snapshot = self.snapshot(window, cx);
16185 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16186 self.go_to_hunk_before_or_after_position(
16187 &snapshot,
16188 selection.head(),
16189 Direction::Next,
16190 window,
16191 cx,
16192 );
16193 }
16194
16195 pub fn go_to_hunk_before_or_after_position(
16196 &mut self,
16197 snapshot: &EditorSnapshot,
16198 position: Point,
16199 direction: Direction,
16200 window: &mut Window,
16201 cx: &mut Context<Editor>,
16202 ) {
16203 let row = if direction == Direction::Next {
16204 self.hunk_after_position(snapshot, position)
16205 .map(|hunk| hunk.row_range.start)
16206 } else {
16207 self.hunk_before_position(snapshot, position)
16208 };
16209
16210 if let Some(row) = row {
16211 let destination = Point::new(row.0, 0);
16212 let autoscroll = Autoscroll::center();
16213
16214 self.unfold_ranges(&[destination..destination], false, false, cx);
16215 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16216 s.select_ranges([destination..destination]);
16217 });
16218 }
16219 }
16220
16221 fn hunk_after_position(
16222 &mut self,
16223 snapshot: &EditorSnapshot,
16224 position: Point,
16225 ) -> Option<MultiBufferDiffHunk> {
16226 snapshot
16227 .buffer_snapshot()
16228 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16229 .find(|hunk| hunk.row_range.start.0 > position.row)
16230 .or_else(|| {
16231 snapshot
16232 .buffer_snapshot()
16233 .diff_hunks_in_range(Point::zero()..position)
16234 .find(|hunk| hunk.row_range.end.0 < position.row)
16235 })
16236 }
16237
16238 fn go_to_prev_hunk(
16239 &mut self,
16240 _: &GoToPreviousHunk,
16241 window: &mut Window,
16242 cx: &mut Context<Self>,
16243 ) {
16244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16245 let snapshot = self.snapshot(window, cx);
16246 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16247 self.go_to_hunk_before_or_after_position(
16248 &snapshot,
16249 selection.head(),
16250 Direction::Prev,
16251 window,
16252 cx,
16253 );
16254 }
16255
16256 fn hunk_before_position(
16257 &mut self,
16258 snapshot: &EditorSnapshot,
16259 position: Point,
16260 ) -> Option<MultiBufferRow> {
16261 snapshot
16262 .buffer_snapshot()
16263 .diff_hunk_before(position)
16264 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16265 }
16266
16267 fn go_to_next_change(
16268 &mut self,
16269 _: &GoToNextChange,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 if let Some(selections) = self
16274 .change_list
16275 .next_change(1, Direction::Next)
16276 .map(|s| s.to_vec())
16277 {
16278 self.change_selections(Default::default(), window, cx, |s| {
16279 let map = s.display_snapshot();
16280 s.select_display_ranges(selections.iter().map(|a| {
16281 let point = a.to_display_point(&map);
16282 point..point
16283 }))
16284 })
16285 }
16286 }
16287
16288 fn go_to_previous_change(
16289 &mut self,
16290 _: &GoToPreviousChange,
16291 window: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 if let Some(selections) = self
16295 .change_list
16296 .next_change(1, Direction::Prev)
16297 .map(|s| s.to_vec())
16298 {
16299 self.change_selections(Default::default(), window, cx, |s| {
16300 let map = s.display_snapshot();
16301 s.select_display_ranges(selections.iter().map(|a| {
16302 let point = a.to_display_point(&map);
16303 point..point
16304 }))
16305 })
16306 }
16307 }
16308
16309 pub fn go_to_next_document_highlight(
16310 &mut self,
16311 _: &GoToNextDocumentHighlight,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16316 }
16317
16318 pub fn go_to_prev_document_highlight(
16319 &mut self,
16320 _: &GoToPreviousDocumentHighlight,
16321 window: &mut Window,
16322 cx: &mut Context<Self>,
16323 ) {
16324 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16325 }
16326
16327 pub fn go_to_document_highlight_before_or_after_position(
16328 &mut self,
16329 direction: Direction,
16330 window: &mut Window,
16331 cx: &mut Context<Editor>,
16332 ) {
16333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16334 let snapshot = self.snapshot(window, cx);
16335 let buffer = &snapshot.buffer_snapshot();
16336 let position = self
16337 .selections
16338 .newest::<Point>(&snapshot.display_snapshot)
16339 .head();
16340 let anchor_position = buffer.anchor_after(position);
16341
16342 // Get all document highlights (both read and write)
16343 let mut all_highlights = Vec::new();
16344
16345 if let Some((_, read_highlights)) = self
16346 .background_highlights
16347 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16348 {
16349 all_highlights.extend(read_highlights.iter());
16350 }
16351
16352 if let Some((_, write_highlights)) = self
16353 .background_highlights
16354 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16355 {
16356 all_highlights.extend(write_highlights.iter());
16357 }
16358
16359 if all_highlights.is_empty() {
16360 return;
16361 }
16362
16363 // Sort highlights by position
16364 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16365
16366 let target_highlight = match direction {
16367 Direction::Next => {
16368 // Find the first highlight after the current position
16369 all_highlights
16370 .iter()
16371 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16372 }
16373 Direction::Prev => {
16374 // Find the last highlight before the current position
16375 all_highlights
16376 .iter()
16377 .rev()
16378 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16379 }
16380 };
16381
16382 if let Some(highlight) = target_highlight {
16383 let destination = highlight.start.to_point(buffer);
16384 let autoscroll = Autoscroll::center();
16385
16386 self.unfold_ranges(&[destination..destination], false, false, cx);
16387 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16388 s.select_ranges([destination..destination]);
16389 });
16390 }
16391 }
16392
16393 fn go_to_line<T: 'static>(
16394 &mut self,
16395 position: Anchor,
16396 highlight_color: Option<Hsla>,
16397 window: &mut Window,
16398 cx: &mut Context<Self>,
16399 ) {
16400 let snapshot = self.snapshot(window, cx).display_snapshot;
16401 let position = position.to_point(&snapshot.buffer_snapshot());
16402 let start = snapshot
16403 .buffer_snapshot()
16404 .clip_point(Point::new(position.row, 0), Bias::Left);
16405 let end = start + Point::new(1, 0);
16406 let start = snapshot.buffer_snapshot().anchor_before(start);
16407 let end = snapshot.buffer_snapshot().anchor_before(end);
16408
16409 self.highlight_rows::<T>(
16410 start..end,
16411 highlight_color
16412 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16413 Default::default(),
16414 cx,
16415 );
16416
16417 if self.buffer.read(cx).is_singleton() {
16418 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16419 }
16420 }
16421
16422 pub fn go_to_definition(
16423 &mut self,
16424 _: &GoToDefinition,
16425 window: &mut Window,
16426 cx: &mut Context<Self>,
16427 ) -> Task<Result<Navigated>> {
16428 let definition =
16429 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16430 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16431 cx.spawn_in(window, async move |editor, cx| {
16432 if definition.await? == Navigated::Yes {
16433 return Ok(Navigated::Yes);
16434 }
16435 match fallback_strategy {
16436 GoToDefinitionFallback::None => Ok(Navigated::No),
16437 GoToDefinitionFallback::FindAllReferences => {
16438 match editor.update_in(cx, |editor, window, cx| {
16439 editor.find_all_references(&FindAllReferences, window, cx)
16440 })? {
16441 Some(references) => references.await,
16442 None => Ok(Navigated::No),
16443 }
16444 }
16445 }
16446 })
16447 }
16448
16449 pub fn go_to_declaration(
16450 &mut self,
16451 _: &GoToDeclaration,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) -> Task<Result<Navigated>> {
16455 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16456 }
16457
16458 pub fn go_to_declaration_split(
16459 &mut self,
16460 _: &GoToDeclaration,
16461 window: &mut Window,
16462 cx: &mut Context<Self>,
16463 ) -> Task<Result<Navigated>> {
16464 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16465 }
16466
16467 pub fn go_to_implementation(
16468 &mut self,
16469 _: &GoToImplementation,
16470 window: &mut Window,
16471 cx: &mut Context<Self>,
16472 ) -> Task<Result<Navigated>> {
16473 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16474 }
16475
16476 pub fn go_to_implementation_split(
16477 &mut self,
16478 _: &GoToImplementationSplit,
16479 window: &mut Window,
16480 cx: &mut Context<Self>,
16481 ) -> Task<Result<Navigated>> {
16482 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16483 }
16484
16485 pub fn go_to_type_definition(
16486 &mut self,
16487 _: &GoToTypeDefinition,
16488 window: &mut Window,
16489 cx: &mut Context<Self>,
16490 ) -> Task<Result<Navigated>> {
16491 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16492 }
16493
16494 pub fn go_to_definition_split(
16495 &mut self,
16496 _: &GoToDefinitionSplit,
16497 window: &mut Window,
16498 cx: &mut Context<Self>,
16499 ) -> Task<Result<Navigated>> {
16500 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16501 }
16502
16503 pub fn go_to_type_definition_split(
16504 &mut self,
16505 _: &GoToTypeDefinitionSplit,
16506 window: &mut Window,
16507 cx: &mut Context<Self>,
16508 ) -> Task<Result<Navigated>> {
16509 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16510 }
16511
16512 fn go_to_definition_of_kind(
16513 &mut self,
16514 kind: GotoDefinitionKind,
16515 split: bool,
16516 window: &mut Window,
16517 cx: &mut Context<Self>,
16518 ) -> Task<Result<Navigated>> {
16519 let Some(provider) = self.semantics_provider.clone() else {
16520 return Task::ready(Ok(Navigated::No));
16521 };
16522 let head = self
16523 .selections
16524 .newest::<usize>(&self.display_snapshot(cx))
16525 .head();
16526 let buffer = self.buffer.read(cx);
16527 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16528 return Task::ready(Ok(Navigated::No));
16529 };
16530 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16531 return Task::ready(Ok(Navigated::No));
16532 };
16533
16534 cx.spawn_in(window, async move |editor, cx| {
16535 let Some(definitions) = definitions.await? else {
16536 return Ok(Navigated::No);
16537 };
16538 let navigated = editor
16539 .update_in(cx, |editor, window, cx| {
16540 editor.navigate_to_hover_links(
16541 Some(kind),
16542 definitions
16543 .into_iter()
16544 .filter(|location| {
16545 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16546 })
16547 .map(HoverLink::Text)
16548 .collect::<Vec<_>>(),
16549 split,
16550 window,
16551 cx,
16552 )
16553 })?
16554 .await?;
16555 anyhow::Ok(navigated)
16556 })
16557 }
16558
16559 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16560 let selection = self.selections.newest_anchor();
16561 let head = selection.head();
16562 let tail = selection.tail();
16563
16564 let Some((buffer, start_position)) =
16565 self.buffer.read(cx).text_anchor_for_position(head, cx)
16566 else {
16567 return;
16568 };
16569
16570 let end_position = if head != tail {
16571 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16572 return;
16573 };
16574 Some(pos)
16575 } else {
16576 None
16577 };
16578
16579 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16580 let url = if let Some(end_pos) = end_position {
16581 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16582 } else {
16583 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16584 };
16585
16586 if let Some(url) = url {
16587 cx.update(|window, cx| {
16588 if parse_zed_link(&url, cx).is_some() {
16589 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16590 } else {
16591 cx.open_url(&url);
16592 }
16593 })?;
16594 }
16595
16596 anyhow::Ok(())
16597 });
16598
16599 url_finder.detach();
16600 }
16601
16602 pub fn open_selected_filename(
16603 &mut self,
16604 _: &OpenSelectedFilename,
16605 window: &mut Window,
16606 cx: &mut Context<Self>,
16607 ) {
16608 let Some(workspace) = self.workspace() else {
16609 return;
16610 };
16611
16612 let position = self.selections.newest_anchor().head();
16613
16614 let Some((buffer, buffer_position)) =
16615 self.buffer.read(cx).text_anchor_for_position(position, cx)
16616 else {
16617 return;
16618 };
16619
16620 let project = self.project.clone();
16621
16622 cx.spawn_in(window, async move |_, cx| {
16623 let result = find_file(&buffer, project, buffer_position, cx).await;
16624
16625 if let Some((_, path)) = result {
16626 workspace
16627 .update_in(cx, |workspace, window, cx| {
16628 workspace.open_resolved_path(path, window, cx)
16629 })?
16630 .await?;
16631 }
16632 anyhow::Ok(())
16633 })
16634 .detach();
16635 }
16636
16637 pub(crate) fn navigate_to_hover_links(
16638 &mut self,
16639 kind: Option<GotoDefinitionKind>,
16640 definitions: Vec<HoverLink>,
16641 split: bool,
16642 window: &mut Window,
16643 cx: &mut Context<Editor>,
16644 ) -> Task<Result<Navigated>> {
16645 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16646 let mut first_url_or_file = None;
16647 let definitions: Vec<_> = definitions
16648 .into_iter()
16649 .filter_map(|def| match def {
16650 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16651 HoverLink::InlayHint(lsp_location, server_id) => {
16652 let computation =
16653 self.compute_target_location(lsp_location, server_id, window, cx);
16654 Some(cx.background_spawn(computation))
16655 }
16656 HoverLink::Url(url) => {
16657 first_url_or_file = Some(Either::Left(url));
16658 None
16659 }
16660 HoverLink::File(path) => {
16661 first_url_or_file = Some(Either::Right(path));
16662 None
16663 }
16664 })
16665 .collect();
16666
16667 let workspace = self.workspace();
16668
16669 cx.spawn_in(window, async move |editor, cx| {
16670 let locations: Vec<Location> = future::join_all(definitions)
16671 .await
16672 .into_iter()
16673 .filter_map(|location| location.transpose())
16674 .collect::<Result<_>>()
16675 .context("location tasks")?;
16676 let mut locations = cx.update(|_, cx| {
16677 locations
16678 .into_iter()
16679 .map(|location| {
16680 let buffer = location.buffer.read(cx);
16681 (location.buffer, location.range.to_point(buffer))
16682 })
16683 .into_group_map()
16684 })?;
16685 let mut num_locations = 0;
16686 for ranges in locations.values_mut() {
16687 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16688 ranges.dedup();
16689 num_locations += ranges.len();
16690 }
16691
16692 if num_locations > 1 {
16693 let Some(workspace) = workspace else {
16694 return Ok(Navigated::No);
16695 };
16696
16697 let tab_kind = match kind {
16698 Some(GotoDefinitionKind::Implementation) => "Implementations",
16699 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16700 Some(GotoDefinitionKind::Declaration) => "Declarations",
16701 Some(GotoDefinitionKind::Type) => "Types",
16702 };
16703 let title = editor
16704 .update_in(cx, |_, _, cx| {
16705 let target = locations
16706 .iter()
16707 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16708 .map(|(buffer, location)| {
16709 buffer
16710 .read(cx)
16711 .text_for_range(location.clone())
16712 .collect::<String>()
16713 })
16714 .filter(|text| !text.contains('\n'))
16715 .unique()
16716 .take(3)
16717 .join(", ");
16718 if target.is_empty() {
16719 tab_kind.to_owned()
16720 } else {
16721 format!("{tab_kind} for {target}")
16722 }
16723 })
16724 .context("buffer title")?;
16725
16726 let opened = workspace
16727 .update_in(cx, |workspace, window, cx| {
16728 Self::open_locations_in_multibuffer(
16729 workspace,
16730 locations,
16731 title,
16732 split,
16733 MultibufferSelectionMode::First,
16734 window,
16735 cx,
16736 )
16737 })
16738 .is_ok();
16739
16740 anyhow::Ok(Navigated::from_bool(opened))
16741 } else if num_locations == 0 {
16742 // If there is one url or file, open it directly
16743 match first_url_or_file {
16744 Some(Either::Left(url)) => {
16745 cx.update(|_, cx| cx.open_url(&url))?;
16746 Ok(Navigated::Yes)
16747 }
16748 Some(Either::Right(path)) => {
16749 let Some(workspace) = workspace else {
16750 return Ok(Navigated::No);
16751 };
16752
16753 workspace
16754 .update_in(cx, |workspace, window, cx| {
16755 workspace.open_resolved_path(path, window, cx)
16756 })?
16757 .await?;
16758 Ok(Navigated::Yes)
16759 }
16760 None => Ok(Navigated::No),
16761 }
16762 } else {
16763 let Some(workspace) = workspace else {
16764 return Ok(Navigated::No);
16765 };
16766
16767 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16768 let target_range = target_ranges.first().unwrap().clone();
16769
16770 editor.update_in(cx, |editor, window, cx| {
16771 let range = target_range.to_point(target_buffer.read(cx));
16772 let range = editor.range_for_match(&range, false);
16773 let range = collapse_multiline_range(range);
16774
16775 if !split
16776 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16777 {
16778 editor.go_to_singleton_buffer_range(range, window, cx);
16779 } else {
16780 let pane = workspace.read(cx).active_pane().clone();
16781 window.defer(cx, move |window, cx| {
16782 let target_editor: Entity<Self> =
16783 workspace.update(cx, |workspace, cx| {
16784 let pane = if split {
16785 workspace.adjacent_pane(window, cx)
16786 } else {
16787 workspace.active_pane().clone()
16788 };
16789
16790 workspace.open_project_item(
16791 pane,
16792 target_buffer.clone(),
16793 true,
16794 true,
16795 window,
16796 cx,
16797 )
16798 });
16799 target_editor.update(cx, |target_editor, cx| {
16800 // When selecting a definition in a different buffer, disable the nav history
16801 // to avoid creating a history entry at the previous cursor location.
16802 pane.update(cx, |pane, _| pane.disable_history());
16803 target_editor.go_to_singleton_buffer_range(range, window, cx);
16804 pane.update(cx, |pane, _| pane.enable_history());
16805 });
16806 });
16807 }
16808 Navigated::Yes
16809 })
16810 }
16811 })
16812 }
16813
16814 fn compute_target_location(
16815 &self,
16816 lsp_location: lsp::Location,
16817 server_id: LanguageServerId,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) -> Task<anyhow::Result<Option<Location>>> {
16821 let Some(project) = self.project.clone() else {
16822 return Task::ready(Ok(None));
16823 };
16824
16825 cx.spawn_in(window, async move |editor, cx| {
16826 let location_task = editor.update(cx, |_, cx| {
16827 project.update(cx, |project, cx| {
16828 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16829 })
16830 })?;
16831 let location = Some({
16832 let target_buffer_handle = location_task.await.context("open local buffer")?;
16833 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16834 let target_start = target_buffer
16835 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16836 let target_end = target_buffer
16837 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16838 target_buffer.anchor_after(target_start)
16839 ..target_buffer.anchor_before(target_end)
16840 })?;
16841 Location {
16842 buffer: target_buffer_handle,
16843 range,
16844 }
16845 });
16846 Ok(location)
16847 })
16848 }
16849
16850 fn go_to_next_reference(
16851 &mut self,
16852 _: &GoToNextReference,
16853 window: &mut Window,
16854 cx: &mut Context<Self>,
16855 ) {
16856 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16857 if let Some(task) = task {
16858 task.detach();
16859 };
16860 }
16861
16862 fn go_to_prev_reference(
16863 &mut self,
16864 _: &GoToPreviousReference,
16865 window: &mut Window,
16866 cx: &mut Context<Self>,
16867 ) {
16868 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16869 if let Some(task) = task {
16870 task.detach();
16871 };
16872 }
16873
16874 pub fn go_to_reference_before_or_after_position(
16875 &mut self,
16876 direction: Direction,
16877 count: usize,
16878 window: &mut Window,
16879 cx: &mut Context<Self>,
16880 ) -> Option<Task<Result<()>>> {
16881 let selection = self.selections.newest_anchor();
16882 let head = selection.head();
16883
16884 let multi_buffer = self.buffer.read(cx);
16885
16886 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16887 let workspace = self.workspace()?;
16888 let project = workspace.read(cx).project().clone();
16889 let references =
16890 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16891 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16892 let Some(locations) = references.await? else {
16893 return Ok(());
16894 };
16895
16896 if locations.is_empty() {
16897 // totally normal - the cursor may be on something which is not
16898 // a symbol (e.g. a keyword)
16899 log::info!("no references found under cursor");
16900 return Ok(());
16901 }
16902
16903 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16904
16905 let multi_buffer_snapshot =
16906 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16907
16908 let (locations, current_location_index) =
16909 multi_buffer.update(cx, |multi_buffer, cx| {
16910 let mut locations = locations
16911 .into_iter()
16912 .filter_map(|loc| {
16913 let start = multi_buffer.buffer_anchor_to_anchor(
16914 &loc.buffer,
16915 loc.range.start,
16916 cx,
16917 )?;
16918 let end = multi_buffer.buffer_anchor_to_anchor(
16919 &loc.buffer,
16920 loc.range.end,
16921 cx,
16922 )?;
16923 Some(start..end)
16924 })
16925 .collect::<Vec<_>>();
16926
16927 // There is an O(n) implementation, but given this list will be
16928 // small (usually <100 items), the extra O(log(n)) factor isn't
16929 // worth the (surprisingly large amount of) extra complexity.
16930 locations
16931 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16932
16933 let head_offset = head.to_offset(&multi_buffer_snapshot);
16934
16935 let current_location_index = locations.iter().position(|loc| {
16936 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16937 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16938 });
16939
16940 (locations, current_location_index)
16941 })?;
16942
16943 let Some(current_location_index) = current_location_index else {
16944 // This indicates something has gone wrong, because we already
16945 // handle the "no references" case above
16946 log::error!(
16947 "failed to find current reference under cursor. Total references: {}",
16948 locations.len()
16949 );
16950 return Ok(());
16951 };
16952
16953 let destination_location_index = match direction {
16954 Direction::Next => (current_location_index + count) % locations.len(),
16955 Direction::Prev => {
16956 (current_location_index + locations.len() - count % locations.len())
16957 % locations.len()
16958 }
16959 };
16960
16961 // TODO(cameron): is this needed?
16962 // the thinking is to avoid "jumping to the current location" (avoid
16963 // polluting "jumplist" in vim terms)
16964 if current_location_index == destination_location_index {
16965 return Ok(());
16966 }
16967
16968 let Range { start, end } = locations[destination_location_index];
16969
16970 editor.update_in(cx, |editor, window, cx| {
16971 let effects = SelectionEffects::default();
16972
16973 editor.unfold_ranges(&[start..end], false, false, cx);
16974 editor.change_selections(effects, window, cx, |s| {
16975 s.select_ranges([start..start]);
16976 });
16977 })?;
16978
16979 Ok(())
16980 }))
16981 }
16982
16983 pub fn find_all_references(
16984 &mut self,
16985 _: &FindAllReferences,
16986 window: &mut Window,
16987 cx: &mut Context<Self>,
16988 ) -> Option<Task<Result<Navigated>>> {
16989 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16990 let multi_buffer = self.buffer.read(cx);
16991 let head = selection.head();
16992
16993 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16994 let head_anchor = multi_buffer_snapshot.anchor_at(
16995 head,
16996 if head < selection.tail() {
16997 Bias::Right
16998 } else {
16999 Bias::Left
17000 },
17001 );
17002
17003 match self
17004 .find_all_references_task_sources
17005 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17006 {
17007 Ok(_) => {
17008 log::info!(
17009 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17010 );
17011 return None;
17012 }
17013 Err(i) => {
17014 self.find_all_references_task_sources.insert(i, head_anchor);
17015 }
17016 }
17017
17018 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17019 let workspace = self.workspace()?;
17020 let project = workspace.read(cx).project().clone();
17021 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17022 Some(cx.spawn_in(window, async move |editor, cx| {
17023 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17024 if let Ok(i) = editor
17025 .find_all_references_task_sources
17026 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17027 {
17028 editor.find_all_references_task_sources.remove(i);
17029 }
17030 });
17031
17032 let Some(locations) = references.await? else {
17033 return anyhow::Ok(Navigated::No);
17034 };
17035 let mut locations = cx.update(|_, cx| {
17036 locations
17037 .into_iter()
17038 .map(|location| {
17039 let buffer = location.buffer.read(cx);
17040 (location.buffer, location.range.to_point(buffer))
17041 })
17042 .into_group_map()
17043 })?;
17044 if locations.is_empty() {
17045 return anyhow::Ok(Navigated::No);
17046 }
17047 for ranges in locations.values_mut() {
17048 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17049 ranges.dedup();
17050 }
17051
17052 workspace.update_in(cx, |workspace, window, cx| {
17053 let target = locations
17054 .iter()
17055 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17056 .map(|(buffer, location)| {
17057 buffer
17058 .read(cx)
17059 .text_for_range(location.clone())
17060 .collect::<String>()
17061 })
17062 .filter(|text| !text.contains('\n'))
17063 .unique()
17064 .take(3)
17065 .join(", ");
17066 let title = if target.is_empty() {
17067 "References".to_owned()
17068 } else {
17069 format!("References to {target}")
17070 };
17071 Self::open_locations_in_multibuffer(
17072 workspace,
17073 locations,
17074 title,
17075 false,
17076 MultibufferSelectionMode::First,
17077 window,
17078 cx,
17079 );
17080 Navigated::Yes
17081 })
17082 }))
17083 }
17084
17085 /// Opens a multibuffer with the given project locations in it
17086 pub fn open_locations_in_multibuffer(
17087 workspace: &mut Workspace,
17088 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17089 title: String,
17090 split: bool,
17091 multibuffer_selection_mode: MultibufferSelectionMode,
17092 window: &mut Window,
17093 cx: &mut Context<Workspace>,
17094 ) {
17095 if locations.is_empty() {
17096 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17097 return;
17098 }
17099
17100 let capability = workspace.project().read(cx).capability();
17101 let mut ranges = <Vec<Range<Anchor>>>::new();
17102
17103 // a key to find existing multibuffer editors with the same set of locations
17104 // to prevent us from opening more and more multibuffer tabs for searches and the like
17105 let mut key = (title.clone(), vec![]);
17106 let excerpt_buffer = cx.new(|cx| {
17107 let key = &mut key.1;
17108 let mut multibuffer = MultiBuffer::new(capability);
17109 for (buffer, mut ranges_for_buffer) in locations {
17110 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17111 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17112 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17113 PathKey::for_buffer(&buffer, cx),
17114 buffer.clone(),
17115 ranges_for_buffer,
17116 multibuffer_context_lines(cx),
17117 cx,
17118 );
17119 ranges.extend(new_ranges)
17120 }
17121
17122 multibuffer.with_title(title)
17123 });
17124 let existing = workspace.active_pane().update(cx, |pane, cx| {
17125 pane.items()
17126 .filter_map(|item| item.downcast::<Editor>())
17127 .find(|editor| {
17128 editor
17129 .read(cx)
17130 .lookup_key
17131 .as_ref()
17132 .and_then(|it| {
17133 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17134 })
17135 .is_some_and(|it| *it == key)
17136 })
17137 });
17138 let editor = existing.unwrap_or_else(|| {
17139 cx.new(|cx| {
17140 let mut editor = Editor::for_multibuffer(
17141 excerpt_buffer,
17142 Some(workspace.project().clone()),
17143 window,
17144 cx,
17145 );
17146 editor.lookup_key = Some(Box::new(key));
17147 editor
17148 })
17149 });
17150 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17151 MultibufferSelectionMode::First => {
17152 if let Some(first_range) = ranges.first() {
17153 editor.change_selections(
17154 SelectionEffects::no_scroll(),
17155 window,
17156 cx,
17157 |selections| {
17158 selections.clear_disjoint();
17159 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17160 },
17161 );
17162 }
17163 editor.highlight_background::<Self>(
17164 &ranges,
17165 |theme| theme.colors().editor_highlighted_line_background,
17166 cx,
17167 );
17168 }
17169 MultibufferSelectionMode::All => {
17170 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17171 selections.clear_disjoint();
17172 selections.select_anchor_ranges(ranges);
17173 });
17174 }
17175 });
17176
17177 let item = Box::new(editor);
17178 let item_id = item.item_id();
17179
17180 if split {
17181 let pane = workspace.adjacent_pane(window, cx);
17182 workspace.add_item(pane, item, None, true, true, window, cx);
17183 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17184 let (preview_item_id, preview_item_idx) =
17185 workspace.active_pane().read_with(cx, |pane, _| {
17186 (pane.preview_item_id(), pane.preview_item_idx())
17187 });
17188
17189 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17190
17191 if let Some(preview_item_id) = preview_item_id {
17192 workspace.active_pane().update(cx, |pane, cx| {
17193 pane.remove_item(preview_item_id, false, false, window, cx);
17194 });
17195 }
17196 } else {
17197 workspace.add_item_to_active_pane(item, None, true, window, cx);
17198 }
17199 workspace.active_pane().update(cx, |pane, cx| {
17200 pane.set_preview_item_id(Some(item_id), cx);
17201 });
17202 }
17203
17204 pub fn rename(
17205 &mut self,
17206 _: &Rename,
17207 window: &mut Window,
17208 cx: &mut Context<Self>,
17209 ) -> Option<Task<Result<()>>> {
17210 use language::ToOffset as _;
17211
17212 let provider = self.semantics_provider.clone()?;
17213 let selection = self.selections.newest_anchor().clone();
17214 let (cursor_buffer, cursor_buffer_position) = self
17215 .buffer
17216 .read(cx)
17217 .text_anchor_for_position(selection.head(), cx)?;
17218 let (tail_buffer, cursor_buffer_position_end) = self
17219 .buffer
17220 .read(cx)
17221 .text_anchor_for_position(selection.tail(), cx)?;
17222 if tail_buffer != cursor_buffer {
17223 return None;
17224 }
17225
17226 let snapshot = cursor_buffer.read(cx).snapshot();
17227 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17228 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17229 let prepare_rename = provider
17230 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17231 .unwrap_or_else(|| Task::ready(Ok(None)));
17232 drop(snapshot);
17233
17234 Some(cx.spawn_in(window, async move |this, cx| {
17235 let rename_range = if let Some(range) = prepare_rename.await? {
17236 Some(range)
17237 } else {
17238 this.update(cx, |this, cx| {
17239 let buffer = this.buffer.read(cx).snapshot(cx);
17240 let mut buffer_highlights = this
17241 .document_highlights_for_position(selection.head(), &buffer)
17242 .filter(|highlight| {
17243 highlight.start.excerpt_id == selection.head().excerpt_id
17244 && highlight.end.excerpt_id == selection.head().excerpt_id
17245 });
17246 buffer_highlights
17247 .next()
17248 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17249 })?
17250 };
17251 if let Some(rename_range) = rename_range {
17252 this.update_in(cx, |this, window, cx| {
17253 let snapshot = cursor_buffer.read(cx).snapshot();
17254 let rename_buffer_range = rename_range.to_offset(&snapshot);
17255 let cursor_offset_in_rename_range =
17256 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17257 let cursor_offset_in_rename_range_end =
17258 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17259
17260 this.take_rename(false, window, cx);
17261 let buffer = this.buffer.read(cx).read(cx);
17262 let cursor_offset = selection.head().to_offset(&buffer);
17263 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17264 let rename_end = rename_start + rename_buffer_range.len();
17265 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17266 let mut old_highlight_id = None;
17267 let old_name: Arc<str> = buffer
17268 .chunks(rename_start..rename_end, true)
17269 .map(|chunk| {
17270 if old_highlight_id.is_none() {
17271 old_highlight_id = chunk.syntax_highlight_id;
17272 }
17273 chunk.text
17274 })
17275 .collect::<String>()
17276 .into();
17277
17278 drop(buffer);
17279
17280 // Position the selection in the rename editor so that it matches the current selection.
17281 this.show_local_selections = false;
17282 let rename_editor = cx.new(|cx| {
17283 let mut editor = Editor::single_line(window, cx);
17284 editor.buffer.update(cx, |buffer, cx| {
17285 buffer.edit([(0..0, old_name.clone())], None, cx)
17286 });
17287 let rename_selection_range = match cursor_offset_in_rename_range
17288 .cmp(&cursor_offset_in_rename_range_end)
17289 {
17290 Ordering::Equal => {
17291 editor.select_all(&SelectAll, window, cx);
17292 return editor;
17293 }
17294 Ordering::Less => {
17295 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17296 }
17297 Ordering::Greater => {
17298 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17299 }
17300 };
17301 if rename_selection_range.end > old_name.len() {
17302 editor.select_all(&SelectAll, window, cx);
17303 } else {
17304 editor.change_selections(Default::default(), window, cx, |s| {
17305 s.select_ranges([rename_selection_range]);
17306 });
17307 }
17308 editor
17309 });
17310 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17311 if e == &EditorEvent::Focused {
17312 cx.emit(EditorEvent::FocusedIn)
17313 }
17314 })
17315 .detach();
17316
17317 let write_highlights =
17318 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17319 let read_highlights =
17320 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17321 let ranges = write_highlights
17322 .iter()
17323 .flat_map(|(_, ranges)| ranges.iter())
17324 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17325 .cloned()
17326 .collect();
17327
17328 this.highlight_text::<Rename>(
17329 ranges,
17330 HighlightStyle {
17331 fade_out: Some(0.6),
17332 ..Default::default()
17333 },
17334 cx,
17335 );
17336 let rename_focus_handle = rename_editor.focus_handle(cx);
17337 window.focus(&rename_focus_handle);
17338 let block_id = this.insert_blocks(
17339 [BlockProperties {
17340 style: BlockStyle::Flex,
17341 placement: BlockPlacement::Below(range.start),
17342 height: Some(1),
17343 render: Arc::new({
17344 let rename_editor = rename_editor.clone();
17345 move |cx: &mut BlockContext| {
17346 let mut text_style = cx.editor_style.text.clone();
17347 if let Some(highlight_style) = old_highlight_id
17348 .and_then(|h| h.style(&cx.editor_style.syntax))
17349 {
17350 text_style = text_style.highlight(highlight_style);
17351 }
17352 div()
17353 .block_mouse_except_scroll()
17354 .pl(cx.anchor_x)
17355 .child(EditorElement::new(
17356 &rename_editor,
17357 EditorStyle {
17358 background: cx.theme().system().transparent,
17359 local_player: cx.editor_style.local_player,
17360 text: text_style,
17361 scrollbar_width: cx.editor_style.scrollbar_width,
17362 syntax: cx.editor_style.syntax.clone(),
17363 status: cx.editor_style.status.clone(),
17364 inlay_hints_style: HighlightStyle {
17365 font_weight: Some(FontWeight::BOLD),
17366 ..make_inlay_hints_style(cx.app)
17367 },
17368 edit_prediction_styles: make_suggestion_styles(
17369 cx.app,
17370 ),
17371 ..EditorStyle::default()
17372 },
17373 ))
17374 .into_any_element()
17375 }
17376 }),
17377 priority: 0,
17378 }],
17379 Some(Autoscroll::fit()),
17380 cx,
17381 )[0];
17382 this.pending_rename = Some(RenameState {
17383 range,
17384 old_name,
17385 editor: rename_editor,
17386 block_id,
17387 });
17388 })?;
17389 }
17390
17391 Ok(())
17392 }))
17393 }
17394
17395 pub fn confirm_rename(
17396 &mut self,
17397 _: &ConfirmRename,
17398 window: &mut Window,
17399 cx: &mut Context<Self>,
17400 ) -> Option<Task<Result<()>>> {
17401 let rename = self.take_rename(false, window, cx)?;
17402 let workspace = self.workspace()?.downgrade();
17403 let (buffer, start) = self
17404 .buffer
17405 .read(cx)
17406 .text_anchor_for_position(rename.range.start, cx)?;
17407 let (end_buffer, _) = self
17408 .buffer
17409 .read(cx)
17410 .text_anchor_for_position(rename.range.end, cx)?;
17411 if buffer != end_buffer {
17412 return None;
17413 }
17414
17415 let old_name = rename.old_name;
17416 let new_name = rename.editor.read(cx).text(cx);
17417
17418 let rename = self.semantics_provider.as_ref()?.perform_rename(
17419 &buffer,
17420 start,
17421 new_name.clone(),
17422 cx,
17423 )?;
17424
17425 Some(cx.spawn_in(window, async move |editor, cx| {
17426 let project_transaction = rename.await?;
17427 Self::open_project_transaction(
17428 &editor,
17429 workspace,
17430 project_transaction,
17431 format!("Rename: {} → {}", old_name, new_name),
17432 cx,
17433 )
17434 .await?;
17435
17436 editor.update(cx, |editor, cx| {
17437 editor.refresh_document_highlights(cx);
17438 })?;
17439 Ok(())
17440 }))
17441 }
17442
17443 fn take_rename(
17444 &mut self,
17445 moving_cursor: bool,
17446 window: &mut Window,
17447 cx: &mut Context<Self>,
17448 ) -> Option<RenameState> {
17449 let rename = self.pending_rename.take()?;
17450 if rename.editor.focus_handle(cx).is_focused(window) {
17451 window.focus(&self.focus_handle);
17452 }
17453
17454 self.remove_blocks(
17455 [rename.block_id].into_iter().collect(),
17456 Some(Autoscroll::fit()),
17457 cx,
17458 );
17459 self.clear_highlights::<Rename>(cx);
17460 self.show_local_selections = true;
17461
17462 if moving_cursor {
17463 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17464 editor
17465 .selections
17466 .newest::<usize>(&editor.display_snapshot(cx))
17467 .head()
17468 });
17469
17470 // Update the selection to match the position of the selection inside
17471 // the rename editor.
17472 let snapshot = self.buffer.read(cx).read(cx);
17473 let rename_range = rename.range.to_offset(&snapshot);
17474 let cursor_in_editor = snapshot
17475 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17476 .min(rename_range.end);
17477 drop(snapshot);
17478
17479 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17480 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17481 });
17482 } else {
17483 self.refresh_document_highlights(cx);
17484 }
17485
17486 Some(rename)
17487 }
17488
17489 pub fn pending_rename(&self) -> Option<&RenameState> {
17490 self.pending_rename.as_ref()
17491 }
17492
17493 fn format(
17494 &mut self,
17495 _: &Format,
17496 window: &mut Window,
17497 cx: &mut Context<Self>,
17498 ) -> Option<Task<Result<()>>> {
17499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17500
17501 let project = match &self.project {
17502 Some(project) => project.clone(),
17503 None => return None,
17504 };
17505
17506 Some(self.perform_format(
17507 project,
17508 FormatTrigger::Manual,
17509 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17510 window,
17511 cx,
17512 ))
17513 }
17514
17515 fn format_selections(
17516 &mut self,
17517 _: &FormatSelections,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) -> Option<Task<Result<()>>> {
17521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17522
17523 let project = match &self.project {
17524 Some(project) => project.clone(),
17525 None => return None,
17526 };
17527
17528 let ranges = self
17529 .selections
17530 .all_adjusted(&self.display_snapshot(cx))
17531 .into_iter()
17532 .map(|selection| selection.range())
17533 .collect_vec();
17534
17535 Some(self.perform_format(
17536 project,
17537 FormatTrigger::Manual,
17538 FormatTarget::Ranges(ranges),
17539 window,
17540 cx,
17541 ))
17542 }
17543
17544 fn perform_format(
17545 &mut self,
17546 project: Entity<Project>,
17547 trigger: FormatTrigger,
17548 target: FormatTarget,
17549 window: &mut Window,
17550 cx: &mut Context<Self>,
17551 ) -> Task<Result<()>> {
17552 let buffer = self.buffer.clone();
17553 let (buffers, target) = match target {
17554 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17555 FormatTarget::Ranges(selection_ranges) => {
17556 let multi_buffer = buffer.read(cx);
17557 let snapshot = multi_buffer.read(cx);
17558 let mut buffers = HashSet::default();
17559 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17560 BTreeMap::new();
17561 for selection_range in selection_ranges {
17562 for (buffer, buffer_range, _) in
17563 snapshot.range_to_buffer_ranges(selection_range)
17564 {
17565 let buffer_id = buffer.remote_id();
17566 let start = buffer.anchor_before(buffer_range.start);
17567 let end = buffer.anchor_after(buffer_range.end);
17568 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17569 buffer_id_to_ranges
17570 .entry(buffer_id)
17571 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17572 .or_insert_with(|| vec![start..end]);
17573 }
17574 }
17575 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17576 }
17577 };
17578
17579 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17580 let selections_prev = transaction_id_prev
17581 .and_then(|transaction_id_prev| {
17582 // default to selections as they were after the last edit, if we have them,
17583 // instead of how they are now.
17584 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17585 // will take you back to where you made the last edit, instead of staying where you scrolled
17586 self.selection_history
17587 .transaction(transaction_id_prev)
17588 .map(|t| t.0.clone())
17589 })
17590 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17591
17592 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17593 let format = project.update(cx, |project, cx| {
17594 project.format(buffers, target, true, trigger, cx)
17595 });
17596
17597 cx.spawn_in(window, async move |editor, cx| {
17598 let transaction = futures::select_biased! {
17599 transaction = format.log_err().fuse() => transaction,
17600 () = timeout => {
17601 log::warn!("timed out waiting for formatting");
17602 None
17603 }
17604 };
17605
17606 buffer
17607 .update(cx, |buffer, cx| {
17608 if let Some(transaction) = transaction
17609 && !buffer.is_singleton()
17610 {
17611 buffer.push_transaction(&transaction.0, cx);
17612 }
17613 cx.notify();
17614 })
17615 .ok();
17616
17617 if let Some(transaction_id_now) =
17618 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17619 {
17620 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17621 if has_new_transaction {
17622 _ = editor.update(cx, |editor, _| {
17623 editor
17624 .selection_history
17625 .insert_transaction(transaction_id_now, selections_prev);
17626 });
17627 }
17628 }
17629
17630 Ok(())
17631 })
17632 }
17633
17634 fn organize_imports(
17635 &mut self,
17636 _: &OrganizeImports,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) -> Option<Task<Result<()>>> {
17640 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17641 let project = match &self.project {
17642 Some(project) => project.clone(),
17643 None => return None,
17644 };
17645 Some(self.perform_code_action_kind(
17646 project,
17647 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17648 window,
17649 cx,
17650 ))
17651 }
17652
17653 fn perform_code_action_kind(
17654 &mut self,
17655 project: Entity<Project>,
17656 kind: CodeActionKind,
17657 window: &mut Window,
17658 cx: &mut Context<Self>,
17659 ) -> Task<Result<()>> {
17660 let buffer = self.buffer.clone();
17661 let buffers = buffer.read(cx).all_buffers();
17662 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17663 let apply_action = project.update(cx, |project, cx| {
17664 project.apply_code_action_kind(buffers, kind, true, cx)
17665 });
17666 cx.spawn_in(window, async move |_, cx| {
17667 let transaction = futures::select_biased! {
17668 () = timeout => {
17669 log::warn!("timed out waiting for executing code action");
17670 None
17671 }
17672 transaction = apply_action.log_err().fuse() => transaction,
17673 };
17674 buffer
17675 .update(cx, |buffer, cx| {
17676 // check if we need this
17677 if let Some(transaction) = transaction
17678 && !buffer.is_singleton()
17679 {
17680 buffer.push_transaction(&transaction.0, cx);
17681 }
17682 cx.notify();
17683 })
17684 .ok();
17685 Ok(())
17686 })
17687 }
17688
17689 pub fn restart_language_server(
17690 &mut self,
17691 _: &RestartLanguageServer,
17692 _: &mut Window,
17693 cx: &mut Context<Self>,
17694 ) {
17695 if let Some(project) = self.project.clone() {
17696 self.buffer.update(cx, |multi_buffer, cx| {
17697 project.update(cx, |project, cx| {
17698 project.restart_language_servers_for_buffers(
17699 multi_buffer.all_buffers().into_iter().collect(),
17700 HashSet::default(),
17701 cx,
17702 );
17703 });
17704 })
17705 }
17706 }
17707
17708 pub fn stop_language_server(
17709 &mut self,
17710 _: &StopLanguageServer,
17711 _: &mut Window,
17712 cx: &mut Context<Self>,
17713 ) {
17714 if let Some(project) = self.project.clone() {
17715 self.buffer.update(cx, |multi_buffer, cx| {
17716 project.update(cx, |project, cx| {
17717 project.stop_language_servers_for_buffers(
17718 multi_buffer.all_buffers().into_iter().collect(),
17719 HashSet::default(),
17720 cx,
17721 );
17722 });
17723 });
17724 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17725 }
17726 }
17727
17728 fn cancel_language_server_work(
17729 workspace: &mut Workspace,
17730 _: &actions::CancelLanguageServerWork,
17731 _: &mut Window,
17732 cx: &mut Context<Workspace>,
17733 ) {
17734 let project = workspace.project();
17735 let buffers = workspace
17736 .active_item(cx)
17737 .and_then(|item| item.act_as::<Editor>(cx))
17738 .map_or(HashSet::default(), |editor| {
17739 editor.read(cx).buffer.read(cx).all_buffers()
17740 });
17741 project.update(cx, |project, cx| {
17742 project.cancel_language_server_work_for_buffers(buffers, cx);
17743 });
17744 }
17745
17746 fn show_character_palette(
17747 &mut self,
17748 _: &ShowCharacterPalette,
17749 window: &mut Window,
17750 _: &mut Context<Self>,
17751 ) {
17752 window.show_character_palette();
17753 }
17754
17755 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17756 if !self.diagnostics_enabled() {
17757 return;
17758 }
17759
17760 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17761 let buffer = self.buffer.read(cx).snapshot(cx);
17762 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17763 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17764 let is_valid = buffer
17765 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17766 .any(|entry| {
17767 entry.diagnostic.is_primary
17768 && !entry.range.is_empty()
17769 && entry.range.start == primary_range_start
17770 && entry.diagnostic.message == active_diagnostics.active_message
17771 });
17772
17773 if !is_valid {
17774 self.dismiss_diagnostics(cx);
17775 }
17776 }
17777 }
17778
17779 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17780 match &self.active_diagnostics {
17781 ActiveDiagnostic::Group(group) => Some(group),
17782 _ => None,
17783 }
17784 }
17785
17786 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17787 if !self.diagnostics_enabled() {
17788 return;
17789 }
17790 self.dismiss_diagnostics(cx);
17791 self.active_diagnostics = ActiveDiagnostic::All;
17792 }
17793
17794 fn activate_diagnostics(
17795 &mut self,
17796 buffer_id: BufferId,
17797 diagnostic: DiagnosticEntryRef<'_, usize>,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) {
17801 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17802 return;
17803 }
17804 self.dismiss_diagnostics(cx);
17805 let snapshot = self.snapshot(window, cx);
17806 let buffer = self.buffer.read(cx).snapshot(cx);
17807 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17808 return;
17809 };
17810
17811 let diagnostic_group = buffer
17812 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17813 .collect::<Vec<_>>();
17814
17815 let blocks =
17816 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17817
17818 let blocks = self.display_map.update(cx, |display_map, cx| {
17819 display_map.insert_blocks(blocks, cx).into_iter().collect()
17820 });
17821 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17822 active_range: buffer.anchor_before(diagnostic.range.start)
17823 ..buffer.anchor_after(diagnostic.range.end),
17824 active_message: diagnostic.diagnostic.message.clone(),
17825 group_id: diagnostic.diagnostic.group_id,
17826 blocks,
17827 });
17828 cx.notify();
17829 }
17830
17831 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17832 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17833 return;
17834 };
17835
17836 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17837 if let ActiveDiagnostic::Group(group) = prev {
17838 self.display_map.update(cx, |display_map, cx| {
17839 display_map.remove_blocks(group.blocks, cx);
17840 });
17841 cx.notify();
17842 }
17843 }
17844
17845 /// Disable inline diagnostics rendering for this editor.
17846 pub fn disable_inline_diagnostics(&mut self) {
17847 self.inline_diagnostics_enabled = false;
17848 self.inline_diagnostics_update = Task::ready(());
17849 self.inline_diagnostics.clear();
17850 }
17851
17852 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17853 self.diagnostics_enabled = false;
17854 self.dismiss_diagnostics(cx);
17855 self.inline_diagnostics_update = Task::ready(());
17856 self.inline_diagnostics.clear();
17857 }
17858
17859 pub fn disable_word_completions(&mut self) {
17860 self.word_completions_enabled = false;
17861 }
17862
17863 pub fn diagnostics_enabled(&self) -> bool {
17864 self.diagnostics_enabled && self.mode.is_full()
17865 }
17866
17867 pub fn inline_diagnostics_enabled(&self) -> bool {
17868 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17869 }
17870
17871 pub fn show_inline_diagnostics(&self) -> bool {
17872 self.show_inline_diagnostics
17873 }
17874
17875 pub fn toggle_inline_diagnostics(
17876 &mut self,
17877 _: &ToggleInlineDiagnostics,
17878 window: &mut Window,
17879 cx: &mut Context<Editor>,
17880 ) {
17881 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17882 self.refresh_inline_diagnostics(false, window, cx);
17883 }
17884
17885 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17886 self.diagnostics_max_severity = severity;
17887 self.display_map.update(cx, |display_map, _| {
17888 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17889 });
17890 }
17891
17892 pub fn toggle_diagnostics(
17893 &mut self,
17894 _: &ToggleDiagnostics,
17895 window: &mut Window,
17896 cx: &mut Context<Editor>,
17897 ) {
17898 if !self.diagnostics_enabled() {
17899 return;
17900 }
17901
17902 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17903 EditorSettings::get_global(cx)
17904 .diagnostics_max_severity
17905 .filter(|severity| severity != &DiagnosticSeverity::Off)
17906 .unwrap_or(DiagnosticSeverity::Hint)
17907 } else {
17908 DiagnosticSeverity::Off
17909 };
17910 self.set_max_diagnostics_severity(new_severity, cx);
17911 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17912 self.active_diagnostics = ActiveDiagnostic::None;
17913 self.inline_diagnostics_update = Task::ready(());
17914 self.inline_diagnostics.clear();
17915 } else {
17916 self.refresh_inline_diagnostics(false, window, cx);
17917 }
17918
17919 cx.notify();
17920 }
17921
17922 pub fn toggle_minimap(
17923 &mut self,
17924 _: &ToggleMinimap,
17925 window: &mut Window,
17926 cx: &mut Context<Editor>,
17927 ) {
17928 if self.supports_minimap(cx) {
17929 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17930 }
17931 }
17932
17933 fn refresh_inline_diagnostics(
17934 &mut self,
17935 debounce: bool,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) {
17939 let max_severity = ProjectSettings::get_global(cx)
17940 .diagnostics
17941 .inline
17942 .max_severity
17943 .unwrap_or(self.diagnostics_max_severity);
17944
17945 if !self.inline_diagnostics_enabled()
17946 || !self.diagnostics_enabled()
17947 || !self.show_inline_diagnostics
17948 || max_severity == DiagnosticSeverity::Off
17949 {
17950 self.inline_diagnostics_update = Task::ready(());
17951 self.inline_diagnostics.clear();
17952 return;
17953 }
17954
17955 let debounce_ms = ProjectSettings::get_global(cx)
17956 .diagnostics
17957 .inline
17958 .update_debounce_ms;
17959 let debounce = if debounce && debounce_ms > 0 {
17960 Some(Duration::from_millis(debounce_ms))
17961 } else {
17962 None
17963 };
17964 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17965 if let Some(debounce) = debounce {
17966 cx.background_executor().timer(debounce).await;
17967 }
17968 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17969 editor
17970 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17971 .ok()
17972 }) else {
17973 return;
17974 };
17975
17976 let new_inline_diagnostics = cx
17977 .background_spawn(async move {
17978 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17979 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17980 let message = diagnostic_entry
17981 .diagnostic
17982 .message
17983 .split_once('\n')
17984 .map(|(line, _)| line)
17985 .map(SharedString::new)
17986 .unwrap_or_else(|| {
17987 SharedString::new(&*diagnostic_entry.diagnostic.message)
17988 });
17989 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17990 let (Ok(i) | Err(i)) = inline_diagnostics
17991 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17992 inline_diagnostics.insert(
17993 i,
17994 (
17995 start_anchor,
17996 InlineDiagnostic {
17997 message,
17998 group_id: diagnostic_entry.diagnostic.group_id,
17999 start: diagnostic_entry.range.start.to_point(&snapshot),
18000 is_primary: diagnostic_entry.diagnostic.is_primary,
18001 severity: diagnostic_entry.diagnostic.severity,
18002 },
18003 ),
18004 );
18005 }
18006 inline_diagnostics
18007 })
18008 .await;
18009
18010 editor
18011 .update(cx, |editor, cx| {
18012 editor.inline_diagnostics = new_inline_diagnostics;
18013 cx.notify();
18014 })
18015 .ok();
18016 });
18017 }
18018
18019 fn pull_diagnostics(
18020 &mut self,
18021 buffer_id: Option<BufferId>,
18022 window: &Window,
18023 cx: &mut Context<Self>,
18024 ) -> Option<()> {
18025 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18026 return None;
18027 }
18028 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18029 .diagnostics
18030 .lsp_pull_diagnostics;
18031 if !pull_diagnostics_settings.enabled {
18032 return None;
18033 }
18034 let project = self.project()?.downgrade();
18035 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18036 let mut buffers = self.buffer.read(cx).all_buffers();
18037 buffers.retain(|buffer| {
18038 let buffer_id_to_retain = buffer.read(cx).remote_id();
18039 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18040 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18041 });
18042 if buffers.is_empty() {
18043 self.pull_diagnostics_task = Task::ready(());
18044 return None;
18045 }
18046
18047 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18048 cx.background_executor().timer(debounce).await;
18049
18050 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18051 buffers
18052 .into_iter()
18053 .filter_map(|buffer| {
18054 project
18055 .update(cx, |project, cx| {
18056 project.lsp_store().update(cx, |lsp_store, cx| {
18057 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18058 })
18059 })
18060 .ok()
18061 })
18062 .collect::<FuturesUnordered<_>>()
18063 }) else {
18064 return;
18065 };
18066
18067 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18068 match pull_task {
18069 Ok(()) => {
18070 if editor
18071 .update_in(cx, |editor, window, cx| {
18072 editor.update_diagnostics_state(window, cx);
18073 })
18074 .is_err()
18075 {
18076 return;
18077 }
18078 }
18079 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18080 }
18081 }
18082 });
18083
18084 Some(())
18085 }
18086
18087 pub fn set_selections_from_remote(
18088 &mut self,
18089 selections: Vec<Selection<Anchor>>,
18090 pending_selection: Option<Selection<Anchor>>,
18091 window: &mut Window,
18092 cx: &mut Context<Self>,
18093 ) {
18094 let old_cursor_position = self.selections.newest_anchor().head();
18095 self.selections
18096 .change_with(&self.display_snapshot(cx), |s| {
18097 s.select_anchors(selections);
18098 if let Some(pending_selection) = pending_selection {
18099 s.set_pending(pending_selection, SelectMode::Character);
18100 } else {
18101 s.clear_pending();
18102 }
18103 });
18104 self.selections_did_change(
18105 false,
18106 &old_cursor_position,
18107 SelectionEffects::default(),
18108 window,
18109 cx,
18110 );
18111 }
18112
18113 pub fn transact(
18114 &mut self,
18115 window: &mut Window,
18116 cx: &mut Context<Self>,
18117 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18118 ) -> Option<TransactionId> {
18119 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18120 this.start_transaction_at(Instant::now(), window, cx);
18121 update(this, window, cx);
18122 this.end_transaction_at(Instant::now(), cx)
18123 })
18124 }
18125
18126 pub fn start_transaction_at(
18127 &mut self,
18128 now: Instant,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) -> Option<TransactionId> {
18132 self.end_selection(window, cx);
18133 if let Some(tx_id) = self
18134 .buffer
18135 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18136 {
18137 self.selection_history
18138 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18139 cx.emit(EditorEvent::TransactionBegun {
18140 transaction_id: tx_id,
18141 });
18142 Some(tx_id)
18143 } else {
18144 None
18145 }
18146 }
18147
18148 pub fn end_transaction_at(
18149 &mut self,
18150 now: Instant,
18151 cx: &mut Context<Self>,
18152 ) -> Option<TransactionId> {
18153 if let Some(transaction_id) = self
18154 .buffer
18155 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18156 {
18157 if let Some((_, end_selections)) =
18158 self.selection_history.transaction_mut(transaction_id)
18159 {
18160 *end_selections = Some(self.selections.disjoint_anchors_arc());
18161 } else {
18162 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18163 }
18164
18165 cx.emit(EditorEvent::Edited { transaction_id });
18166 Some(transaction_id)
18167 } else {
18168 None
18169 }
18170 }
18171
18172 pub fn modify_transaction_selection_history(
18173 &mut self,
18174 transaction_id: TransactionId,
18175 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18176 ) -> bool {
18177 self.selection_history
18178 .transaction_mut(transaction_id)
18179 .map(modify)
18180 .is_some()
18181 }
18182
18183 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18184 if self.selection_mark_mode {
18185 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18186 s.move_with(|_, sel| {
18187 sel.collapse_to(sel.head(), SelectionGoal::None);
18188 });
18189 })
18190 }
18191 self.selection_mark_mode = true;
18192 cx.notify();
18193 }
18194
18195 pub fn swap_selection_ends(
18196 &mut self,
18197 _: &actions::SwapSelectionEnds,
18198 window: &mut Window,
18199 cx: &mut Context<Self>,
18200 ) {
18201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18202 s.move_with(|_, sel| {
18203 if sel.start != sel.end {
18204 sel.reversed = !sel.reversed
18205 }
18206 });
18207 });
18208 self.request_autoscroll(Autoscroll::newest(), cx);
18209 cx.notify();
18210 }
18211
18212 pub fn toggle_focus(
18213 workspace: &mut Workspace,
18214 _: &actions::ToggleFocus,
18215 window: &mut Window,
18216 cx: &mut Context<Workspace>,
18217 ) {
18218 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18219 return;
18220 };
18221 workspace.activate_item(&item, true, true, window, cx);
18222 }
18223
18224 pub fn toggle_fold(
18225 &mut self,
18226 _: &actions::ToggleFold,
18227 window: &mut Window,
18228 cx: &mut Context<Self>,
18229 ) {
18230 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18231 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18232 let selection = self.selections.newest::<Point>(&display_map);
18233
18234 let range = if selection.is_empty() {
18235 let point = selection.head().to_display_point(&display_map);
18236 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18237 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18238 .to_point(&display_map);
18239 start..end
18240 } else {
18241 selection.range()
18242 };
18243 if display_map.folds_in_range(range).next().is_some() {
18244 self.unfold_lines(&Default::default(), window, cx)
18245 } else {
18246 self.fold(&Default::default(), window, cx)
18247 }
18248 } else {
18249 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18250 let buffer_ids: HashSet<_> = self
18251 .selections
18252 .disjoint_anchor_ranges()
18253 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18254 .collect();
18255
18256 let should_unfold = buffer_ids
18257 .iter()
18258 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18259
18260 for buffer_id in buffer_ids {
18261 if should_unfold {
18262 self.unfold_buffer(buffer_id, cx);
18263 } else {
18264 self.fold_buffer(buffer_id, cx);
18265 }
18266 }
18267 }
18268 }
18269
18270 pub fn toggle_fold_recursive(
18271 &mut self,
18272 _: &actions::ToggleFoldRecursive,
18273 window: &mut Window,
18274 cx: &mut Context<Self>,
18275 ) {
18276 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18277
18278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18279 let range = if selection.is_empty() {
18280 let point = selection.head().to_display_point(&display_map);
18281 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18282 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18283 .to_point(&display_map);
18284 start..end
18285 } else {
18286 selection.range()
18287 };
18288 if display_map.folds_in_range(range).next().is_some() {
18289 self.unfold_recursive(&Default::default(), window, cx)
18290 } else {
18291 self.fold_recursive(&Default::default(), window, cx)
18292 }
18293 }
18294
18295 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18296 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18297 let mut to_fold = Vec::new();
18298 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18299 let selections = self.selections.all_adjusted(&display_map);
18300
18301 for selection in selections {
18302 let range = selection.range().sorted();
18303 let buffer_start_row = range.start.row;
18304
18305 if range.start.row != range.end.row {
18306 let mut found = false;
18307 let mut row = range.start.row;
18308 while row <= range.end.row {
18309 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18310 {
18311 found = true;
18312 row = crease.range().end.row + 1;
18313 to_fold.push(crease);
18314 } else {
18315 row += 1
18316 }
18317 }
18318 if found {
18319 continue;
18320 }
18321 }
18322
18323 for row in (0..=range.start.row).rev() {
18324 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18325 && crease.range().end.row >= buffer_start_row
18326 {
18327 to_fold.push(crease);
18328 if row <= range.start.row {
18329 break;
18330 }
18331 }
18332 }
18333 }
18334
18335 self.fold_creases(to_fold, true, window, cx);
18336 } else {
18337 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18338 let buffer_ids = self
18339 .selections
18340 .disjoint_anchor_ranges()
18341 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18342 .collect::<HashSet<_>>();
18343 for buffer_id in buffer_ids {
18344 self.fold_buffer(buffer_id, cx);
18345 }
18346 }
18347 }
18348
18349 pub fn toggle_fold_all(
18350 &mut self,
18351 _: &actions::ToggleFoldAll,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 if self.buffer.read(cx).is_singleton() {
18356 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18357 let has_folds = display_map
18358 .folds_in_range(0..display_map.buffer_snapshot().len())
18359 .next()
18360 .is_some();
18361
18362 if has_folds {
18363 self.unfold_all(&actions::UnfoldAll, window, cx);
18364 } else {
18365 self.fold_all(&actions::FoldAll, window, cx);
18366 }
18367 } else {
18368 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18369 let should_unfold = buffer_ids
18370 .iter()
18371 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18372
18373 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18374 editor
18375 .update_in(cx, |editor, _, cx| {
18376 for buffer_id in buffer_ids {
18377 if should_unfold {
18378 editor.unfold_buffer(buffer_id, cx);
18379 } else {
18380 editor.fold_buffer(buffer_id, cx);
18381 }
18382 }
18383 })
18384 .ok();
18385 });
18386 }
18387 }
18388
18389 fn fold_at_level(
18390 &mut self,
18391 fold_at: &FoldAtLevel,
18392 window: &mut Window,
18393 cx: &mut Context<Self>,
18394 ) {
18395 if !self.buffer.read(cx).is_singleton() {
18396 return;
18397 }
18398
18399 let fold_at_level = fold_at.0;
18400 let snapshot = self.buffer.read(cx).snapshot(cx);
18401 let mut to_fold = Vec::new();
18402 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18403
18404 let row_ranges_to_keep: Vec<Range<u32>> = self
18405 .selections
18406 .all::<Point>(&self.display_snapshot(cx))
18407 .into_iter()
18408 .map(|sel| sel.start.row..sel.end.row)
18409 .collect();
18410
18411 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18412 while start_row < end_row {
18413 match self
18414 .snapshot(window, cx)
18415 .crease_for_buffer_row(MultiBufferRow(start_row))
18416 {
18417 Some(crease) => {
18418 let nested_start_row = crease.range().start.row + 1;
18419 let nested_end_row = crease.range().end.row;
18420
18421 if current_level < fold_at_level {
18422 stack.push((nested_start_row, nested_end_row, current_level + 1));
18423 } else if current_level == fold_at_level {
18424 // Fold iff there is no selection completely contained within the fold region
18425 if !row_ranges_to_keep.iter().any(|selection| {
18426 selection.end >= nested_start_row
18427 && selection.start <= nested_end_row
18428 }) {
18429 to_fold.push(crease);
18430 }
18431 }
18432
18433 start_row = nested_end_row + 1;
18434 }
18435 None => start_row += 1,
18436 }
18437 }
18438 }
18439
18440 self.fold_creases(to_fold, true, window, cx);
18441 }
18442
18443 pub fn fold_at_level_1(
18444 &mut self,
18445 _: &actions::FoldAtLevel1,
18446 window: &mut Window,
18447 cx: &mut Context<Self>,
18448 ) {
18449 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18450 }
18451
18452 pub fn fold_at_level_2(
18453 &mut self,
18454 _: &actions::FoldAtLevel2,
18455 window: &mut Window,
18456 cx: &mut Context<Self>,
18457 ) {
18458 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18459 }
18460
18461 pub fn fold_at_level_3(
18462 &mut self,
18463 _: &actions::FoldAtLevel3,
18464 window: &mut Window,
18465 cx: &mut Context<Self>,
18466 ) {
18467 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18468 }
18469
18470 pub fn fold_at_level_4(
18471 &mut self,
18472 _: &actions::FoldAtLevel4,
18473 window: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18477 }
18478
18479 pub fn fold_at_level_5(
18480 &mut self,
18481 _: &actions::FoldAtLevel5,
18482 window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) {
18485 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18486 }
18487
18488 pub fn fold_at_level_6(
18489 &mut self,
18490 _: &actions::FoldAtLevel6,
18491 window: &mut Window,
18492 cx: &mut Context<Self>,
18493 ) {
18494 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18495 }
18496
18497 pub fn fold_at_level_7(
18498 &mut self,
18499 _: &actions::FoldAtLevel7,
18500 window: &mut Window,
18501 cx: &mut Context<Self>,
18502 ) {
18503 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18504 }
18505
18506 pub fn fold_at_level_8(
18507 &mut self,
18508 _: &actions::FoldAtLevel8,
18509 window: &mut Window,
18510 cx: &mut Context<Self>,
18511 ) {
18512 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18513 }
18514
18515 pub fn fold_at_level_9(
18516 &mut self,
18517 _: &actions::FoldAtLevel9,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18522 }
18523
18524 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18525 if self.buffer.read(cx).is_singleton() {
18526 let mut fold_ranges = Vec::new();
18527 let snapshot = self.buffer.read(cx).snapshot(cx);
18528
18529 for row in 0..snapshot.max_row().0 {
18530 if let Some(foldable_range) = self
18531 .snapshot(window, cx)
18532 .crease_for_buffer_row(MultiBufferRow(row))
18533 {
18534 fold_ranges.push(foldable_range);
18535 }
18536 }
18537
18538 self.fold_creases(fold_ranges, true, window, cx);
18539 } else {
18540 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18541 editor
18542 .update_in(cx, |editor, _, cx| {
18543 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18544 editor.fold_buffer(buffer_id, cx);
18545 }
18546 })
18547 .ok();
18548 });
18549 }
18550 }
18551
18552 pub fn fold_function_bodies(
18553 &mut self,
18554 _: &actions::FoldFunctionBodies,
18555 window: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 let snapshot = self.buffer.read(cx).snapshot(cx);
18559
18560 let ranges = snapshot
18561 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18562 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18563 .collect::<Vec<_>>();
18564
18565 let creases = ranges
18566 .into_iter()
18567 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18568 .collect();
18569
18570 self.fold_creases(creases, true, window, cx);
18571 }
18572
18573 pub fn fold_recursive(
18574 &mut self,
18575 _: &actions::FoldRecursive,
18576 window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 let mut to_fold = Vec::new();
18580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18581 let selections = self.selections.all_adjusted(&display_map);
18582
18583 for selection in selections {
18584 let range = selection.range().sorted();
18585 let buffer_start_row = range.start.row;
18586
18587 if range.start.row != range.end.row {
18588 let mut found = false;
18589 for row in range.start.row..=range.end.row {
18590 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18591 found = true;
18592 to_fold.push(crease);
18593 }
18594 }
18595 if found {
18596 continue;
18597 }
18598 }
18599
18600 for row in (0..=range.start.row).rev() {
18601 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18602 if crease.range().end.row >= buffer_start_row {
18603 to_fold.push(crease);
18604 } else {
18605 break;
18606 }
18607 }
18608 }
18609 }
18610
18611 self.fold_creases(to_fold, true, window, cx);
18612 }
18613
18614 pub fn fold_at(
18615 &mut self,
18616 buffer_row: MultiBufferRow,
18617 window: &mut Window,
18618 cx: &mut Context<Self>,
18619 ) {
18620 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18621
18622 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18623 let autoscroll = self
18624 .selections
18625 .all::<Point>(&display_map)
18626 .iter()
18627 .any(|selection| crease.range().overlaps(&selection.range()));
18628
18629 self.fold_creases(vec![crease], autoscroll, window, cx);
18630 }
18631 }
18632
18633 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18634 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18636 let buffer = display_map.buffer_snapshot();
18637 let selections = self.selections.all::<Point>(&display_map);
18638 let ranges = selections
18639 .iter()
18640 .map(|s| {
18641 let range = s.display_range(&display_map).sorted();
18642 let mut start = range.start.to_point(&display_map);
18643 let mut end = range.end.to_point(&display_map);
18644 start.column = 0;
18645 end.column = buffer.line_len(MultiBufferRow(end.row));
18646 start..end
18647 })
18648 .collect::<Vec<_>>();
18649
18650 self.unfold_ranges(&ranges, true, true, cx);
18651 } else {
18652 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18653 let buffer_ids = self
18654 .selections
18655 .disjoint_anchor_ranges()
18656 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18657 .collect::<HashSet<_>>();
18658 for buffer_id in buffer_ids {
18659 self.unfold_buffer(buffer_id, cx);
18660 }
18661 }
18662 }
18663
18664 pub fn unfold_recursive(
18665 &mut self,
18666 _: &UnfoldRecursive,
18667 _window: &mut Window,
18668 cx: &mut Context<Self>,
18669 ) {
18670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18671 let selections = self.selections.all::<Point>(&display_map);
18672 let ranges = selections
18673 .iter()
18674 .map(|s| {
18675 let mut range = s.display_range(&display_map).sorted();
18676 *range.start.column_mut() = 0;
18677 *range.end.column_mut() = display_map.line_len(range.end.row());
18678 let start = range.start.to_point(&display_map);
18679 let end = range.end.to_point(&display_map);
18680 start..end
18681 })
18682 .collect::<Vec<_>>();
18683
18684 self.unfold_ranges(&ranges, true, true, cx);
18685 }
18686
18687 pub fn unfold_at(
18688 &mut self,
18689 buffer_row: MultiBufferRow,
18690 _window: &mut Window,
18691 cx: &mut Context<Self>,
18692 ) {
18693 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18694
18695 let intersection_range = Point::new(buffer_row.0, 0)
18696 ..Point::new(
18697 buffer_row.0,
18698 display_map.buffer_snapshot().line_len(buffer_row),
18699 );
18700
18701 let autoscroll = self
18702 .selections
18703 .all::<Point>(&display_map)
18704 .iter()
18705 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18706
18707 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18708 }
18709
18710 pub fn unfold_all(
18711 &mut self,
18712 _: &actions::UnfoldAll,
18713 _window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 if self.buffer.read(cx).is_singleton() {
18717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18718 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18719 } else {
18720 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18721 editor
18722 .update(cx, |editor, cx| {
18723 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18724 editor.unfold_buffer(buffer_id, cx);
18725 }
18726 })
18727 .ok();
18728 });
18729 }
18730 }
18731
18732 pub fn fold_selected_ranges(
18733 &mut self,
18734 _: &FoldSelectedRanges,
18735 window: &mut Window,
18736 cx: &mut Context<Self>,
18737 ) {
18738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18739 let selections = self.selections.all_adjusted(&display_map);
18740 let ranges = selections
18741 .into_iter()
18742 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18743 .collect::<Vec<_>>();
18744 self.fold_creases(ranges, true, window, cx);
18745 }
18746
18747 pub fn fold_ranges<T: ToOffset + Clone>(
18748 &mut self,
18749 ranges: Vec<Range<T>>,
18750 auto_scroll: bool,
18751 window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18755 let ranges = ranges
18756 .into_iter()
18757 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18758 .collect::<Vec<_>>();
18759 self.fold_creases(ranges, auto_scroll, window, cx);
18760 }
18761
18762 pub fn fold_creases<T: ToOffset + Clone>(
18763 &mut self,
18764 creases: Vec<Crease<T>>,
18765 auto_scroll: bool,
18766 _window: &mut Window,
18767 cx: &mut Context<Self>,
18768 ) {
18769 if creases.is_empty() {
18770 return;
18771 }
18772
18773 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18774
18775 if auto_scroll {
18776 self.request_autoscroll(Autoscroll::fit(), cx);
18777 }
18778
18779 cx.notify();
18780
18781 self.scrollbar_marker_state.dirty = true;
18782 self.folds_did_change(cx);
18783 }
18784
18785 /// Removes any folds whose ranges intersect any of the given ranges.
18786 pub fn unfold_ranges<T: ToOffset + Clone>(
18787 &mut self,
18788 ranges: &[Range<T>],
18789 inclusive: bool,
18790 auto_scroll: bool,
18791 cx: &mut Context<Self>,
18792 ) {
18793 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18794 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18795 });
18796 self.folds_did_change(cx);
18797 }
18798
18799 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18800 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18801 return;
18802 }
18803 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18804 self.display_map.update(cx, |display_map, cx| {
18805 display_map.fold_buffers([buffer_id], cx)
18806 });
18807 cx.emit(EditorEvent::BufferFoldToggled {
18808 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18809 folded: true,
18810 });
18811 cx.notify();
18812 }
18813
18814 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18815 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18816 return;
18817 }
18818 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18819 self.display_map.update(cx, |display_map, cx| {
18820 display_map.unfold_buffers([buffer_id], cx);
18821 });
18822 cx.emit(EditorEvent::BufferFoldToggled {
18823 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18824 folded: false,
18825 });
18826 cx.notify();
18827 }
18828
18829 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18830 self.display_map.read(cx).is_buffer_folded(buffer)
18831 }
18832
18833 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18834 self.display_map.read(cx).folded_buffers()
18835 }
18836
18837 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18838 self.display_map.update(cx, |display_map, cx| {
18839 display_map.disable_header_for_buffer(buffer_id, cx);
18840 });
18841 cx.notify();
18842 }
18843
18844 /// Removes any folds with the given ranges.
18845 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18846 &mut self,
18847 ranges: &[Range<T>],
18848 type_id: TypeId,
18849 auto_scroll: bool,
18850 cx: &mut Context<Self>,
18851 ) {
18852 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18853 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18854 });
18855 self.folds_did_change(cx);
18856 }
18857
18858 fn remove_folds_with<T: ToOffset + Clone>(
18859 &mut self,
18860 ranges: &[Range<T>],
18861 auto_scroll: bool,
18862 cx: &mut Context<Self>,
18863 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18864 ) {
18865 if ranges.is_empty() {
18866 return;
18867 }
18868
18869 let mut buffers_affected = HashSet::default();
18870 let multi_buffer = self.buffer().read(cx);
18871 for range in ranges {
18872 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18873 buffers_affected.insert(buffer.read(cx).remote_id());
18874 };
18875 }
18876
18877 self.display_map.update(cx, update);
18878
18879 if auto_scroll {
18880 self.request_autoscroll(Autoscroll::fit(), cx);
18881 }
18882
18883 cx.notify();
18884 self.scrollbar_marker_state.dirty = true;
18885 self.active_indent_guides_state.dirty = true;
18886 }
18887
18888 pub fn update_renderer_widths(
18889 &mut self,
18890 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18891 cx: &mut Context<Self>,
18892 ) -> bool {
18893 self.display_map
18894 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18895 }
18896
18897 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18898 self.display_map.read(cx).fold_placeholder.clone()
18899 }
18900
18901 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18902 self.buffer.update(cx, |buffer, cx| {
18903 buffer.set_all_diff_hunks_expanded(cx);
18904 });
18905 }
18906
18907 pub fn expand_all_diff_hunks(
18908 &mut self,
18909 _: &ExpandAllDiffHunks,
18910 _window: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.buffer.update(cx, |buffer, cx| {
18914 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18915 });
18916 }
18917
18918 pub fn collapse_all_diff_hunks(
18919 &mut self,
18920 _: &CollapseAllDiffHunks,
18921 _window: &mut Window,
18922 cx: &mut Context<Self>,
18923 ) {
18924 self.buffer.update(cx, |buffer, cx| {
18925 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18926 });
18927 }
18928
18929 pub fn toggle_selected_diff_hunks(
18930 &mut self,
18931 _: &ToggleSelectedDiffHunks,
18932 _window: &mut Window,
18933 cx: &mut Context<Self>,
18934 ) {
18935 let ranges: Vec<_> = self
18936 .selections
18937 .disjoint_anchors()
18938 .iter()
18939 .map(|s| s.range())
18940 .collect();
18941 self.toggle_diff_hunks_in_ranges(ranges, cx);
18942 }
18943
18944 pub fn diff_hunks_in_ranges<'a>(
18945 &'a self,
18946 ranges: &'a [Range<Anchor>],
18947 buffer: &'a MultiBufferSnapshot,
18948 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18949 ranges.iter().flat_map(move |range| {
18950 let end_excerpt_id = range.end.excerpt_id;
18951 let range = range.to_point(buffer);
18952 let mut peek_end = range.end;
18953 if range.end.row < buffer.max_row().0 {
18954 peek_end = Point::new(range.end.row + 1, 0);
18955 }
18956 buffer
18957 .diff_hunks_in_range(range.start..peek_end)
18958 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18959 })
18960 }
18961
18962 pub fn has_stageable_diff_hunks_in_ranges(
18963 &self,
18964 ranges: &[Range<Anchor>],
18965 snapshot: &MultiBufferSnapshot,
18966 ) -> bool {
18967 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18968 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18969 }
18970
18971 pub fn toggle_staged_selected_diff_hunks(
18972 &mut self,
18973 _: &::git::ToggleStaged,
18974 _: &mut Window,
18975 cx: &mut Context<Self>,
18976 ) {
18977 let snapshot = self.buffer.read(cx).snapshot(cx);
18978 let ranges: Vec<_> = self
18979 .selections
18980 .disjoint_anchors()
18981 .iter()
18982 .map(|s| s.range())
18983 .collect();
18984 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18985 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18986 }
18987
18988 pub fn set_render_diff_hunk_controls(
18989 &mut self,
18990 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18991 cx: &mut Context<Self>,
18992 ) {
18993 self.render_diff_hunk_controls = render_diff_hunk_controls;
18994 cx.notify();
18995 }
18996
18997 pub fn stage_and_next(
18998 &mut self,
18999 _: &::git::StageAndNext,
19000 window: &mut Window,
19001 cx: &mut Context<Self>,
19002 ) {
19003 self.do_stage_or_unstage_and_next(true, window, cx);
19004 }
19005
19006 pub fn unstage_and_next(
19007 &mut self,
19008 _: &::git::UnstageAndNext,
19009 window: &mut Window,
19010 cx: &mut Context<Self>,
19011 ) {
19012 self.do_stage_or_unstage_and_next(false, window, cx);
19013 }
19014
19015 pub fn stage_or_unstage_diff_hunks(
19016 &mut self,
19017 stage: bool,
19018 ranges: Vec<Range<Anchor>>,
19019 cx: &mut Context<Self>,
19020 ) {
19021 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19022 cx.spawn(async move |this, cx| {
19023 task.await?;
19024 this.update(cx, |this, cx| {
19025 let snapshot = this.buffer.read(cx).snapshot(cx);
19026 let chunk_by = this
19027 .diff_hunks_in_ranges(&ranges, &snapshot)
19028 .chunk_by(|hunk| hunk.buffer_id);
19029 for (buffer_id, hunks) in &chunk_by {
19030 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19031 }
19032 })
19033 })
19034 .detach_and_log_err(cx);
19035 }
19036
19037 fn save_buffers_for_ranges_if_needed(
19038 &mut self,
19039 ranges: &[Range<Anchor>],
19040 cx: &mut Context<Editor>,
19041 ) -> Task<Result<()>> {
19042 let multibuffer = self.buffer.read(cx);
19043 let snapshot = multibuffer.read(cx);
19044 let buffer_ids: HashSet<_> = ranges
19045 .iter()
19046 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19047 .collect();
19048 drop(snapshot);
19049
19050 let mut buffers = HashSet::default();
19051 for buffer_id in buffer_ids {
19052 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19053 let buffer = buffer_entity.read(cx);
19054 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19055 {
19056 buffers.insert(buffer_entity);
19057 }
19058 }
19059 }
19060
19061 if let Some(project) = &self.project {
19062 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19063 } else {
19064 Task::ready(Ok(()))
19065 }
19066 }
19067
19068 fn do_stage_or_unstage_and_next(
19069 &mut self,
19070 stage: bool,
19071 window: &mut Window,
19072 cx: &mut Context<Self>,
19073 ) {
19074 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19075
19076 if ranges.iter().any(|range| range.start != range.end) {
19077 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19078 return;
19079 }
19080
19081 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19082 let snapshot = self.snapshot(window, cx);
19083 let position = self
19084 .selections
19085 .newest::<Point>(&snapshot.display_snapshot)
19086 .head();
19087 let mut row = snapshot
19088 .buffer_snapshot()
19089 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19090 .find(|hunk| hunk.row_range.start.0 > position.row)
19091 .map(|hunk| hunk.row_range.start);
19092
19093 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19094 // Outside of the project diff editor, wrap around to the beginning.
19095 if !all_diff_hunks_expanded {
19096 row = row.or_else(|| {
19097 snapshot
19098 .buffer_snapshot()
19099 .diff_hunks_in_range(Point::zero()..position)
19100 .find(|hunk| hunk.row_range.end.0 < position.row)
19101 .map(|hunk| hunk.row_range.start)
19102 });
19103 }
19104
19105 if let Some(row) = row {
19106 let destination = Point::new(row.0, 0);
19107 let autoscroll = Autoscroll::center();
19108
19109 self.unfold_ranges(&[destination..destination], false, false, cx);
19110 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19111 s.select_ranges([destination..destination]);
19112 });
19113 }
19114 }
19115
19116 fn do_stage_or_unstage(
19117 &self,
19118 stage: bool,
19119 buffer_id: BufferId,
19120 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19121 cx: &mut App,
19122 ) -> Option<()> {
19123 let project = self.project()?;
19124 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19125 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19126 let buffer_snapshot = buffer.read(cx).snapshot();
19127 let file_exists = buffer_snapshot
19128 .file()
19129 .is_some_and(|file| file.disk_state().exists());
19130 diff.update(cx, |diff, cx| {
19131 diff.stage_or_unstage_hunks(
19132 stage,
19133 &hunks
19134 .map(|hunk| buffer_diff::DiffHunk {
19135 buffer_range: hunk.buffer_range,
19136 diff_base_byte_range: hunk.diff_base_byte_range,
19137 secondary_status: hunk.secondary_status,
19138 range: Point::zero()..Point::zero(), // unused
19139 })
19140 .collect::<Vec<_>>(),
19141 &buffer_snapshot,
19142 file_exists,
19143 cx,
19144 )
19145 });
19146 None
19147 }
19148
19149 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19150 let ranges: Vec<_> = self
19151 .selections
19152 .disjoint_anchors()
19153 .iter()
19154 .map(|s| s.range())
19155 .collect();
19156 self.buffer
19157 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19158 }
19159
19160 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19161 self.buffer.update(cx, |buffer, cx| {
19162 let ranges = vec![Anchor::min()..Anchor::max()];
19163 if !buffer.all_diff_hunks_expanded()
19164 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19165 {
19166 buffer.collapse_diff_hunks(ranges, cx);
19167 true
19168 } else {
19169 false
19170 }
19171 })
19172 }
19173
19174 fn toggle_diff_hunks_in_ranges(
19175 &mut self,
19176 ranges: Vec<Range<Anchor>>,
19177 cx: &mut Context<Editor>,
19178 ) {
19179 self.buffer.update(cx, |buffer, cx| {
19180 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19181 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19182 })
19183 }
19184
19185 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19186 self.buffer.update(cx, |buffer, cx| {
19187 let snapshot = buffer.snapshot(cx);
19188 let excerpt_id = range.end.excerpt_id;
19189 let point_range = range.to_point(&snapshot);
19190 let expand = !buffer.single_hunk_is_expanded(range, cx);
19191 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19192 })
19193 }
19194
19195 pub(crate) fn apply_all_diff_hunks(
19196 &mut self,
19197 _: &ApplyAllDiffHunks,
19198 window: &mut Window,
19199 cx: &mut Context<Self>,
19200 ) {
19201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19202
19203 let buffers = self.buffer.read(cx).all_buffers();
19204 for branch_buffer in buffers {
19205 branch_buffer.update(cx, |branch_buffer, cx| {
19206 branch_buffer.merge_into_base(Vec::new(), cx);
19207 });
19208 }
19209
19210 if let Some(project) = self.project.clone() {
19211 self.save(
19212 SaveOptions {
19213 format: true,
19214 autosave: false,
19215 },
19216 project,
19217 window,
19218 cx,
19219 )
19220 .detach_and_log_err(cx);
19221 }
19222 }
19223
19224 pub(crate) fn apply_selected_diff_hunks(
19225 &mut self,
19226 _: &ApplyDiffHunk,
19227 window: &mut Window,
19228 cx: &mut Context<Self>,
19229 ) {
19230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19231 let snapshot = self.snapshot(window, cx);
19232 let hunks = snapshot.hunks_for_ranges(
19233 self.selections
19234 .all(&snapshot.display_snapshot)
19235 .into_iter()
19236 .map(|selection| selection.range()),
19237 );
19238 let mut ranges_by_buffer = HashMap::default();
19239 self.transact(window, cx, |editor, _window, cx| {
19240 for hunk in hunks {
19241 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19242 ranges_by_buffer
19243 .entry(buffer.clone())
19244 .or_insert_with(Vec::new)
19245 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19246 }
19247 }
19248
19249 for (buffer, ranges) in ranges_by_buffer {
19250 buffer.update(cx, |buffer, cx| {
19251 buffer.merge_into_base(ranges, cx);
19252 });
19253 }
19254 });
19255
19256 if let Some(project) = self.project.clone() {
19257 self.save(
19258 SaveOptions {
19259 format: true,
19260 autosave: false,
19261 },
19262 project,
19263 window,
19264 cx,
19265 )
19266 .detach_and_log_err(cx);
19267 }
19268 }
19269
19270 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19271 if hovered != self.gutter_hovered {
19272 self.gutter_hovered = hovered;
19273 cx.notify();
19274 }
19275 }
19276
19277 pub fn insert_blocks(
19278 &mut self,
19279 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19280 autoscroll: Option<Autoscroll>,
19281 cx: &mut Context<Self>,
19282 ) -> Vec<CustomBlockId> {
19283 let blocks = self
19284 .display_map
19285 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19286 if let Some(autoscroll) = autoscroll {
19287 self.request_autoscroll(autoscroll, cx);
19288 }
19289 cx.notify();
19290 blocks
19291 }
19292
19293 pub fn resize_blocks(
19294 &mut self,
19295 heights: HashMap<CustomBlockId, u32>,
19296 autoscroll: Option<Autoscroll>,
19297 cx: &mut Context<Self>,
19298 ) {
19299 self.display_map
19300 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19301 if let Some(autoscroll) = autoscroll {
19302 self.request_autoscroll(autoscroll, cx);
19303 }
19304 cx.notify();
19305 }
19306
19307 pub fn replace_blocks(
19308 &mut self,
19309 renderers: HashMap<CustomBlockId, RenderBlock>,
19310 autoscroll: Option<Autoscroll>,
19311 cx: &mut Context<Self>,
19312 ) {
19313 self.display_map
19314 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19315 if let Some(autoscroll) = autoscroll {
19316 self.request_autoscroll(autoscroll, cx);
19317 }
19318 cx.notify();
19319 }
19320
19321 pub fn remove_blocks(
19322 &mut self,
19323 block_ids: HashSet<CustomBlockId>,
19324 autoscroll: Option<Autoscroll>,
19325 cx: &mut Context<Self>,
19326 ) {
19327 self.display_map.update(cx, |display_map, cx| {
19328 display_map.remove_blocks(block_ids, cx)
19329 });
19330 if let Some(autoscroll) = autoscroll {
19331 self.request_autoscroll(autoscroll, cx);
19332 }
19333 cx.notify();
19334 }
19335
19336 pub fn row_for_block(
19337 &self,
19338 block_id: CustomBlockId,
19339 cx: &mut Context<Self>,
19340 ) -> Option<DisplayRow> {
19341 self.display_map
19342 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19343 }
19344
19345 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19346 self.focused_block = Some(focused_block);
19347 }
19348
19349 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19350 self.focused_block.take()
19351 }
19352
19353 pub fn insert_creases(
19354 &mut self,
19355 creases: impl IntoIterator<Item = Crease<Anchor>>,
19356 cx: &mut Context<Self>,
19357 ) -> Vec<CreaseId> {
19358 self.display_map
19359 .update(cx, |map, cx| map.insert_creases(creases, cx))
19360 }
19361
19362 pub fn remove_creases(
19363 &mut self,
19364 ids: impl IntoIterator<Item = CreaseId>,
19365 cx: &mut Context<Self>,
19366 ) -> Vec<(CreaseId, Range<Anchor>)> {
19367 self.display_map
19368 .update(cx, |map, cx| map.remove_creases(ids, cx))
19369 }
19370
19371 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19372 self.display_map
19373 .update(cx, |map, cx| map.snapshot(cx))
19374 .longest_row()
19375 }
19376
19377 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19378 self.display_map
19379 .update(cx, |map, cx| map.snapshot(cx))
19380 .max_point()
19381 }
19382
19383 pub fn text(&self, cx: &App) -> String {
19384 self.buffer.read(cx).read(cx).text()
19385 }
19386
19387 pub fn is_empty(&self, cx: &App) -> bool {
19388 self.buffer.read(cx).read(cx).is_empty()
19389 }
19390
19391 pub fn text_option(&self, cx: &App) -> Option<String> {
19392 let text = self.text(cx);
19393 let text = text.trim();
19394
19395 if text.is_empty() {
19396 return None;
19397 }
19398
19399 Some(text.to_string())
19400 }
19401
19402 pub fn set_text(
19403 &mut self,
19404 text: impl Into<Arc<str>>,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) {
19408 self.transact(window, cx, |this, _, cx| {
19409 this.buffer
19410 .read(cx)
19411 .as_singleton()
19412 .expect("you can only call set_text on editors for singleton buffers")
19413 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19414 });
19415 }
19416
19417 pub fn display_text(&self, cx: &mut App) -> String {
19418 self.display_map
19419 .update(cx, |map, cx| map.snapshot(cx))
19420 .text()
19421 }
19422
19423 fn create_minimap(
19424 &self,
19425 minimap_settings: MinimapSettings,
19426 window: &mut Window,
19427 cx: &mut Context<Self>,
19428 ) -> Option<Entity<Self>> {
19429 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19430 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19431 }
19432
19433 fn initialize_new_minimap(
19434 &self,
19435 minimap_settings: MinimapSettings,
19436 window: &mut Window,
19437 cx: &mut Context<Self>,
19438 ) -> Entity<Self> {
19439 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19440
19441 let mut minimap = Editor::new_internal(
19442 EditorMode::Minimap {
19443 parent: cx.weak_entity(),
19444 },
19445 self.buffer.clone(),
19446 None,
19447 Some(self.display_map.clone()),
19448 window,
19449 cx,
19450 );
19451 minimap.scroll_manager.clone_state(&self.scroll_manager);
19452 minimap.set_text_style_refinement(TextStyleRefinement {
19453 font_size: Some(MINIMAP_FONT_SIZE),
19454 font_weight: Some(MINIMAP_FONT_WEIGHT),
19455 ..Default::default()
19456 });
19457 minimap.update_minimap_configuration(minimap_settings, cx);
19458 cx.new(|_| minimap)
19459 }
19460
19461 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19462 let current_line_highlight = minimap_settings
19463 .current_line_highlight
19464 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19465 self.set_current_line_highlight(Some(current_line_highlight));
19466 }
19467
19468 pub fn minimap(&self) -> Option<&Entity<Self>> {
19469 self.minimap
19470 .as_ref()
19471 .filter(|_| self.minimap_visibility.visible())
19472 }
19473
19474 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19475 let mut wrap_guides = smallvec![];
19476
19477 if self.show_wrap_guides == Some(false) {
19478 return wrap_guides;
19479 }
19480
19481 let settings = self.buffer.read(cx).language_settings(cx);
19482 if settings.show_wrap_guides {
19483 match self.soft_wrap_mode(cx) {
19484 SoftWrap::Column(soft_wrap) => {
19485 wrap_guides.push((soft_wrap as usize, true));
19486 }
19487 SoftWrap::Bounded(soft_wrap) => {
19488 wrap_guides.push((soft_wrap as usize, true));
19489 }
19490 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19491 }
19492 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19493 }
19494
19495 wrap_guides
19496 }
19497
19498 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19499 let settings = self.buffer.read(cx).language_settings(cx);
19500 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19501 match mode {
19502 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19503 SoftWrap::None
19504 }
19505 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19506 language_settings::SoftWrap::PreferredLineLength => {
19507 SoftWrap::Column(settings.preferred_line_length)
19508 }
19509 language_settings::SoftWrap::Bounded => {
19510 SoftWrap::Bounded(settings.preferred_line_length)
19511 }
19512 }
19513 }
19514
19515 pub fn set_soft_wrap_mode(
19516 &mut self,
19517 mode: language_settings::SoftWrap,
19518
19519 cx: &mut Context<Self>,
19520 ) {
19521 self.soft_wrap_mode_override = Some(mode);
19522 cx.notify();
19523 }
19524
19525 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19526 self.hard_wrap = hard_wrap;
19527 cx.notify();
19528 }
19529
19530 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19531 self.text_style_refinement = Some(style);
19532 }
19533
19534 /// called by the Element so we know what style we were most recently rendered with.
19535 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19536 // We intentionally do not inform the display map about the minimap style
19537 // so that wrapping is not recalculated and stays consistent for the editor
19538 // and its linked minimap.
19539 if !self.mode.is_minimap() {
19540 let font = style.text.font();
19541 let font_size = style.text.font_size.to_pixels(window.rem_size());
19542 let display_map = self
19543 .placeholder_display_map
19544 .as_ref()
19545 .filter(|_| self.is_empty(cx))
19546 .unwrap_or(&self.display_map);
19547
19548 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19549 }
19550 self.style = Some(style);
19551 }
19552
19553 pub fn style(&self) -> Option<&EditorStyle> {
19554 self.style.as_ref()
19555 }
19556
19557 // Called by the element. This method is not designed to be called outside of the editor
19558 // element's layout code because it does not notify when rewrapping is computed synchronously.
19559 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19560 if self.is_empty(cx) {
19561 self.placeholder_display_map
19562 .as_ref()
19563 .map_or(false, |display_map| {
19564 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19565 })
19566 } else {
19567 self.display_map
19568 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19569 }
19570 }
19571
19572 pub fn set_soft_wrap(&mut self) {
19573 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19574 }
19575
19576 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19577 if self.soft_wrap_mode_override.is_some() {
19578 self.soft_wrap_mode_override.take();
19579 } else {
19580 let soft_wrap = match self.soft_wrap_mode(cx) {
19581 SoftWrap::GitDiff => return,
19582 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19583 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19584 language_settings::SoftWrap::None
19585 }
19586 };
19587 self.soft_wrap_mode_override = Some(soft_wrap);
19588 }
19589 cx.notify();
19590 }
19591
19592 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19593 let Some(workspace) = self.workspace() else {
19594 return;
19595 };
19596 let fs = workspace.read(cx).app_state().fs.clone();
19597 let current_show = TabBarSettings::get_global(cx).show;
19598 update_settings_file(fs, cx, move |setting, _| {
19599 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19600 });
19601 }
19602
19603 pub fn toggle_indent_guides(
19604 &mut self,
19605 _: &ToggleIndentGuides,
19606 _: &mut Window,
19607 cx: &mut Context<Self>,
19608 ) {
19609 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19610 self.buffer
19611 .read(cx)
19612 .language_settings(cx)
19613 .indent_guides
19614 .enabled
19615 });
19616 self.show_indent_guides = Some(!currently_enabled);
19617 cx.notify();
19618 }
19619
19620 fn should_show_indent_guides(&self) -> Option<bool> {
19621 self.show_indent_guides
19622 }
19623
19624 pub fn toggle_line_numbers(
19625 &mut self,
19626 _: &ToggleLineNumbers,
19627 _: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 let mut editor_settings = EditorSettings::get_global(cx).clone();
19631 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19632 EditorSettings::override_global(editor_settings, cx);
19633 }
19634
19635 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19636 if let Some(show_line_numbers) = self.show_line_numbers {
19637 return show_line_numbers;
19638 }
19639 EditorSettings::get_global(cx).gutter.line_numbers
19640 }
19641
19642 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19643 match (
19644 self.use_relative_line_numbers,
19645 EditorSettings::get_global(cx).relative_line_numbers,
19646 ) {
19647 (None, setting) => setting,
19648 (Some(false), _) => RelativeLineNumbers::Disabled,
19649 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19650 (Some(true), _) => RelativeLineNumbers::Enabled,
19651 }
19652 }
19653
19654 pub fn toggle_relative_line_numbers(
19655 &mut self,
19656 _: &ToggleRelativeLineNumbers,
19657 _: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 let is_relative = self.relative_line_numbers(cx);
19661 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19662 }
19663
19664 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19665 self.use_relative_line_numbers = is_relative;
19666 cx.notify();
19667 }
19668
19669 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19670 self.show_gutter = show_gutter;
19671 cx.notify();
19672 }
19673
19674 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19675 self.show_scrollbars = ScrollbarAxes {
19676 horizontal: show,
19677 vertical: show,
19678 };
19679 cx.notify();
19680 }
19681
19682 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19683 self.show_scrollbars.vertical = show;
19684 cx.notify();
19685 }
19686
19687 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19688 self.show_scrollbars.horizontal = show;
19689 cx.notify();
19690 }
19691
19692 pub fn set_minimap_visibility(
19693 &mut self,
19694 minimap_visibility: MinimapVisibility,
19695 window: &mut Window,
19696 cx: &mut Context<Self>,
19697 ) {
19698 if self.minimap_visibility != minimap_visibility {
19699 if minimap_visibility.visible() && self.minimap.is_none() {
19700 let minimap_settings = EditorSettings::get_global(cx).minimap;
19701 self.minimap =
19702 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19703 }
19704 self.minimap_visibility = minimap_visibility;
19705 cx.notify();
19706 }
19707 }
19708
19709 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19710 self.set_show_scrollbars(false, cx);
19711 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19712 }
19713
19714 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19715 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19716 }
19717
19718 /// Normally the text in full mode and auto height editors is padded on the
19719 /// left side by roughly half a character width for improved hit testing.
19720 ///
19721 /// Use this method to disable this for cases where this is not wanted (e.g.
19722 /// if you want to align the editor text with some other text above or below)
19723 /// or if you want to add this padding to single-line editors.
19724 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19725 self.offset_content = offset_content;
19726 cx.notify();
19727 }
19728
19729 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19730 self.show_line_numbers = Some(show_line_numbers);
19731 cx.notify();
19732 }
19733
19734 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19735 self.disable_expand_excerpt_buttons = true;
19736 cx.notify();
19737 }
19738
19739 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19740 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19741 cx.notify();
19742 }
19743
19744 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19745 self.show_code_actions = Some(show_code_actions);
19746 cx.notify();
19747 }
19748
19749 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19750 self.show_runnables = Some(show_runnables);
19751 cx.notify();
19752 }
19753
19754 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19755 self.show_breakpoints = Some(show_breakpoints);
19756 cx.notify();
19757 }
19758
19759 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19760 if self.display_map.read(cx).masked != masked {
19761 self.display_map.update(cx, |map, _| map.masked = masked);
19762 }
19763 cx.notify()
19764 }
19765
19766 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19767 self.show_wrap_guides = Some(show_wrap_guides);
19768 cx.notify();
19769 }
19770
19771 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19772 self.show_indent_guides = Some(show_indent_guides);
19773 cx.notify();
19774 }
19775
19776 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19777 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19778 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19779 && let Some(dir) = file.abs_path(cx).parent()
19780 {
19781 return Some(dir.to_owned());
19782 }
19783 }
19784
19785 None
19786 }
19787
19788 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19789 self.active_excerpt(cx)?
19790 .1
19791 .read(cx)
19792 .file()
19793 .and_then(|f| f.as_local())
19794 }
19795
19796 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19797 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19798 let buffer = buffer.read(cx);
19799 if let Some(project_path) = buffer.project_path(cx) {
19800 let project = self.project()?.read(cx);
19801 project.absolute_path(&project_path, cx)
19802 } else {
19803 buffer
19804 .file()
19805 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19806 }
19807 })
19808 }
19809
19810 pub fn reveal_in_finder(
19811 &mut self,
19812 _: &RevealInFileManager,
19813 _window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 if let Some(target) = self.target_file(cx) {
19817 cx.reveal_path(&target.abs_path(cx));
19818 }
19819 }
19820
19821 pub fn copy_path(
19822 &mut self,
19823 _: &zed_actions::workspace::CopyPath,
19824 _window: &mut Window,
19825 cx: &mut Context<Self>,
19826 ) {
19827 if let Some(path) = self.target_file_abs_path(cx)
19828 && let Some(path) = path.to_str()
19829 {
19830 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19831 } else {
19832 cx.propagate();
19833 }
19834 }
19835
19836 pub fn copy_relative_path(
19837 &mut self,
19838 _: &zed_actions::workspace::CopyRelativePath,
19839 _window: &mut Window,
19840 cx: &mut Context<Self>,
19841 ) {
19842 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19843 let project = self.project()?.read(cx);
19844 let path = buffer.read(cx).file()?.path();
19845 let path = path.display(project.path_style(cx));
19846 Some(path)
19847 }) {
19848 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19849 } else {
19850 cx.propagate();
19851 }
19852 }
19853
19854 /// Returns the project path for the editor's buffer, if any buffer is
19855 /// opened in the editor.
19856 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19857 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19858 buffer.read(cx).project_path(cx)
19859 } else {
19860 None
19861 }
19862 }
19863
19864 // Returns true if the editor handled a go-to-line request
19865 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19866 maybe!({
19867 let breakpoint_store = self.breakpoint_store.as_ref()?;
19868
19869 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19870 else {
19871 self.clear_row_highlights::<ActiveDebugLine>();
19872 return None;
19873 };
19874
19875 let position = active_stack_frame.position;
19876 let buffer_id = position.buffer_id?;
19877 let snapshot = self
19878 .project
19879 .as_ref()?
19880 .read(cx)
19881 .buffer_for_id(buffer_id, cx)?
19882 .read(cx)
19883 .snapshot();
19884
19885 let mut handled = false;
19886 for (id, ExcerptRange { context, .. }) in
19887 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19888 {
19889 if context.start.cmp(&position, &snapshot).is_ge()
19890 || context.end.cmp(&position, &snapshot).is_lt()
19891 {
19892 continue;
19893 }
19894 let snapshot = self.buffer.read(cx).snapshot(cx);
19895 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19896
19897 handled = true;
19898 self.clear_row_highlights::<ActiveDebugLine>();
19899
19900 self.go_to_line::<ActiveDebugLine>(
19901 multibuffer_anchor,
19902 Some(cx.theme().colors().editor_debugger_active_line_background),
19903 window,
19904 cx,
19905 );
19906
19907 cx.notify();
19908 }
19909
19910 handled.then_some(())
19911 })
19912 .is_some()
19913 }
19914
19915 pub fn copy_file_name_without_extension(
19916 &mut self,
19917 _: &CopyFileNameWithoutExtension,
19918 _: &mut Window,
19919 cx: &mut Context<Self>,
19920 ) {
19921 if let Some(file) = self.target_file(cx)
19922 && let Some(file_stem) = file.path().file_stem()
19923 {
19924 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19925 }
19926 }
19927
19928 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19929 if let Some(file) = self.target_file(cx)
19930 && let Some(name) = file.path().file_name()
19931 {
19932 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19933 }
19934 }
19935
19936 pub fn toggle_git_blame(
19937 &mut self,
19938 _: &::git::Blame,
19939 window: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) {
19942 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19943
19944 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19945 self.start_git_blame(true, window, cx);
19946 }
19947
19948 cx.notify();
19949 }
19950
19951 pub fn toggle_git_blame_inline(
19952 &mut self,
19953 _: &ToggleGitBlameInline,
19954 window: &mut Window,
19955 cx: &mut Context<Self>,
19956 ) {
19957 self.toggle_git_blame_inline_internal(true, window, cx);
19958 cx.notify();
19959 }
19960
19961 pub fn open_git_blame_commit(
19962 &mut self,
19963 _: &OpenGitBlameCommit,
19964 window: &mut Window,
19965 cx: &mut Context<Self>,
19966 ) {
19967 self.open_git_blame_commit_internal(window, cx);
19968 }
19969
19970 fn open_git_blame_commit_internal(
19971 &mut self,
19972 window: &mut Window,
19973 cx: &mut Context<Self>,
19974 ) -> Option<()> {
19975 let blame = self.blame.as_ref()?;
19976 let snapshot = self.snapshot(window, cx);
19977 let cursor = self
19978 .selections
19979 .newest::<Point>(&snapshot.display_snapshot)
19980 .head();
19981 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19982 let (_, blame_entry) = blame
19983 .update(cx, |blame, cx| {
19984 blame
19985 .blame_for_rows(
19986 &[RowInfo {
19987 buffer_id: Some(buffer.remote_id()),
19988 buffer_row: Some(point.row),
19989 ..Default::default()
19990 }],
19991 cx,
19992 )
19993 .next()
19994 })
19995 .flatten()?;
19996 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19997 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19998 let workspace = self.workspace()?.downgrade();
19999 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20000 None
20001 }
20002
20003 pub fn git_blame_inline_enabled(&self) -> bool {
20004 self.git_blame_inline_enabled
20005 }
20006
20007 pub fn toggle_selection_menu(
20008 &mut self,
20009 _: &ToggleSelectionMenu,
20010 _: &mut Window,
20011 cx: &mut Context<Self>,
20012 ) {
20013 self.show_selection_menu = self
20014 .show_selection_menu
20015 .map(|show_selections_menu| !show_selections_menu)
20016 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20017
20018 cx.notify();
20019 }
20020
20021 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20022 self.show_selection_menu
20023 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20024 }
20025
20026 fn start_git_blame(
20027 &mut self,
20028 user_triggered: bool,
20029 window: &mut Window,
20030 cx: &mut Context<Self>,
20031 ) {
20032 if let Some(project) = self.project() {
20033 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20034 && buffer.read(cx).file().is_none()
20035 {
20036 return;
20037 }
20038
20039 let focused = self.focus_handle(cx).contains_focused(window, cx);
20040
20041 let project = project.clone();
20042 let blame = cx
20043 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20044 self.blame_subscription =
20045 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20046 self.blame = Some(blame);
20047 }
20048 }
20049
20050 fn toggle_git_blame_inline_internal(
20051 &mut self,
20052 user_triggered: bool,
20053 window: &mut Window,
20054 cx: &mut Context<Self>,
20055 ) {
20056 if self.git_blame_inline_enabled {
20057 self.git_blame_inline_enabled = false;
20058 self.show_git_blame_inline = false;
20059 self.show_git_blame_inline_delay_task.take();
20060 } else {
20061 self.git_blame_inline_enabled = true;
20062 self.start_git_blame_inline(user_triggered, window, cx);
20063 }
20064
20065 cx.notify();
20066 }
20067
20068 fn start_git_blame_inline(
20069 &mut self,
20070 user_triggered: bool,
20071 window: &mut Window,
20072 cx: &mut Context<Self>,
20073 ) {
20074 self.start_git_blame(user_triggered, window, cx);
20075
20076 if ProjectSettings::get_global(cx)
20077 .git
20078 .inline_blame_delay()
20079 .is_some()
20080 {
20081 self.start_inline_blame_timer(window, cx);
20082 } else {
20083 self.show_git_blame_inline = true
20084 }
20085 }
20086
20087 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20088 self.blame.as_ref()
20089 }
20090
20091 pub fn show_git_blame_gutter(&self) -> bool {
20092 self.show_git_blame_gutter
20093 }
20094
20095 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20096 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20097 }
20098
20099 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20100 self.show_git_blame_inline
20101 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20102 && !self.newest_selection_head_on_empty_line(cx)
20103 && self.has_blame_entries(cx)
20104 }
20105
20106 fn has_blame_entries(&self, cx: &App) -> bool {
20107 self.blame()
20108 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20109 }
20110
20111 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20112 let cursor_anchor = self.selections.newest_anchor().head();
20113
20114 let snapshot = self.buffer.read(cx).snapshot(cx);
20115 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20116
20117 snapshot.line_len(buffer_row) == 0
20118 }
20119
20120 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20121 let buffer_and_selection = maybe!({
20122 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20123 let selection_range = selection.range();
20124
20125 let multi_buffer = self.buffer().read(cx);
20126 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20127 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20128
20129 let (buffer, range, _) = if selection.reversed {
20130 buffer_ranges.first()
20131 } else {
20132 buffer_ranges.last()
20133 }?;
20134
20135 let selection = text::ToPoint::to_point(&range.start, buffer).row
20136 ..text::ToPoint::to_point(&range.end, buffer).row;
20137 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20138 });
20139
20140 let Some((buffer, selection)) = buffer_and_selection else {
20141 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20142 };
20143
20144 let Some(project) = self.project() else {
20145 return Task::ready(Err(anyhow!("editor does not have project")));
20146 };
20147
20148 project.update(cx, |project, cx| {
20149 project.get_permalink_to_line(&buffer, selection, cx)
20150 })
20151 }
20152
20153 pub fn copy_permalink_to_line(
20154 &mut self,
20155 _: &CopyPermalinkToLine,
20156 window: &mut Window,
20157 cx: &mut Context<Self>,
20158 ) {
20159 let permalink_task = self.get_permalink_to_line(cx);
20160 let workspace = self.workspace();
20161
20162 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20163 Ok(permalink) => {
20164 cx.update(|_, cx| {
20165 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20166 })
20167 .ok();
20168 }
20169 Err(err) => {
20170 let message = format!("Failed to copy permalink: {err}");
20171
20172 anyhow::Result::<()>::Err(err).log_err();
20173
20174 if let Some(workspace) = workspace {
20175 workspace
20176 .update_in(cx, |workspace, _, cx| {
20177 struct CopyPermalinkToLine;
20178
20179 workspace.show_toast(
20180 Toast::new(
20181 NotificationId::unique::<CopyPermalinkToLine>(),
20182 message,
20183 ),
20184 cx,
20185 )
20186 })
20187 .ok();
20188 }
20189 }
20190 })
20191 .detach();
20192 }
20193
20194 pub fn copy_file_location(
20195 &mut self,
20196 _: &CopyFileLocation,
20197 _: &mut Window,
20198 cx: &mut Context<Self>,
20199 ) {
20200 let selection = self
20201 .selections
20202 .newest::<Point>(&self.display_snapshot(cx))
20203 .start
20204 .row
20205 + 1;
20206 if let Some(file) = self.target_file(cx) {
20207 let path = file.path().display(file.path_style(cx));
20208 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20209 }
20210 }
20211
20212 pub fn open_permalink_to_line(
20213 &mut self,
20214 _: &OpenPermalinkToLine,
20215 window: &mut Window,
20216 cx: &mut Context<Self>,
20217 ) {
20218 let permalink_task = self.get_permalink_to_line(cx);
20219 let workspace = self.workspace();
20220
20221 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20222 Ok(permalink) => {
20223 cx.update(|_, cx| {
20224 cx.open_url(permalink.as_ref());
20225 })
20226 .ok();
20227 }
20228 Err(err) => {
20229 let message = format!("Failed to open permalink: {err}");
20230
20231 anyhow::Result::<()>::Err(err).log_err();
20232
20233 if let Some(workspace) = workspace {
20234 workspace
20235 .update(cx, |workspace, cx| {
20236 struct OpenPermalinkToLine;
20237
20238 workspace.show_toast(
20239 Toast::new(
20240 NotificationId::unique::<OpenPermalinkToLine>(),
20241 message,
20242 ),
20243 cx,
20244 )
20245 })
20246 .ok();
20247 }
20248 }
20249 })
20250 .detach();
20251 }
20252
20253 pub fn insert_uuid_v4(
20254 &mut self,
20255 _: &InsertUuidV4,
20256 window: &mut Window,
20257 cx: &mut Context<Self>,
20258 ) {
20259 self.insert_uuid(UuidVersion::V4, window, cx);
20260 }
20261
20262 pub fn insert_uuid_v7(
20263 &mut self,
20264 _: &InsertUuidV7,
20265 window: &mut Window,
20266 cx: &mut Context<Self>,
20267 ) {
20268 self.insert_uuid(UuidVersion::V7, window, cx);
20269 }
20270
20271 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20273 self.transact(window, cx, |this, window, cx| {
20274 let edits = this
20275 .selections
20276 .all::<Point>(&this.display_snapshot(cx))
20277 .into_iter()
20278 .map(|selection| {
20279 let uuid = match version {
20280 UuidVersion::V4 => uuid::Uuid::new_v4(),
20281 UuidVersion::V7 => uuid::Uuid::now_v7(),
20282 };
20283
20284 (selection.range(), uuid.to_string())
20285 });
20286 this.edit(edits, cx);
20287 this.refresh_edit_prediction(true, false, window, cx);
20288 });
20289 }
20290
20291 pub fn open_selections_in_multibuffer(
20292 &mut self,
20293 _: &OpenSelectionsInMultibuffer,
20294 window: &mut Window,
20295 cx: &mut Context<Self>,
20296 ) {
20297 let multibuffer = self.buffer.read(cx);
20298
20299 let Some(buffer) = multibuffer.as_singleton() else {
20300 return;
20301 };
20302
20303 let Some(workspace) = self.workspace() else {
20304 return;
20305 };
20306
20307 let title = multibuffer.title(cx).to_string();
20308
20309 let locations = self
20310 .selections
20311 .all_anchors(&self.display_snapshot(cx))
20312 .iter()
20313 .map(|selection| {
20314 (
20315 buffer.clone(),
20316 (selection.start.text_anchor..selection.end.text_anchor)
20317 .to_point(buffer.read(cx)),
20318 )
20319 })
20320 .into_group_map();
20321
20322 cx.spawn_in(window, async move |_, cx| {
20323 workspace.update_in(cx, |workspace, window, cx| {
20324 Self::open_locations_in_multibuffer(
20325 workspace,
20326 locations,
20327 format!("Selections for '{title}'"),
20328 false,
20329 MultibufferSelectionMode::All,
20330 window,
20331 cx,
20332 );
20333 })
20334 })
20335 .detach();
20336 }
20337
20338 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20339 /// last highlight added will be used.
20340 ///
20341 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20342 pub fn highlight_rows<T: 'static>(
20343 &mut self,
20344 range: Range<Anchor>,
20345 color: Hsla,
20346 options: RowHighlightOptions,
20347 cx: &mut Context<Self>,
20348 ) {
20349 let snapshot = self.buffer().read(cx).snapshot(cx);
20350 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20351 let ix = row_highlights.binary_search_by(|highlight| {
20352 Ordering::Equal
20353 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20354 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20355 });
20356
20357 if let Err(mut ix) = ix {
20358 let index = post_inc(&mut self.highlight_order);
20359
20360 // If this range intersects with the preceding highlight, then merge it with
20361 // the preceding highlight. Otherwise insert a new highlight.
20362 let mut merged = false;
20363 if ix > 0 {
20364 let prev_highlight = &mut row_highlights[ix - 1];
20365 if prev_highlight
20366 .range
20367 .end
20368 .cmp(&range.start, &snapshot)
20369 .is_ge()
20370 {
20371 ix -= 1;
20372 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20373 prev_highlight.range.end = range.end;
20374 }
20375 merged = true;
20376 prev_highlight.index = index;
20377 prev_highlight.color = color;
20378 prev_highlight.options = options;
20379 }
20380 }
20381
20382 if !merged {
20383 row_highlights.insert(
20384 ix,
20385 RowHighlight {
20386 range,
20387 index,
20388 color,
20389 options,
20390 type_id: TypeId::of::<T>(),
20391 },
20392 );
20393 }
20394
20395 // If any of the following highlights intersect with this one, merge them.
20396 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20397 let highlight = &row_highlights[ix];
20398 if next_highlight
20399 .range
20400 .start
20401 .cmp(&highlight.range.end, &snapshot)
20402 .is_le()
20403 {
20404 if next_highlight
20405 .range
20406 .end
20407 .cmp(&highlight.range.end, &snapshot)
20408 .is_gt()
20409 {
20410 row_highlights[ix].range.end = next_highlight.range.end;
20411 }
20412 row_highlights.remove(ix + 1);
20413 } else {
20414 break;
20415 }
20416 }
20417 }
20418 }
20419
20420 /// Remove any highlighted row ranges of the given type that intersect the
20421 /// given ranges.
20422 pub fn remove_highlighted_rows<T: 'static>(
20423 &mut self,
20424 ranges_to_remove: Vec<Range<Anchor>>,
20425 cx: &mut Context<Self>,
20426 ) {
20427 let snapshot = self.buffer().read(cx).snapshot(cx);
20428 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20429 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20430 row_highlights.retain(|highlight| {
20431 while let Some(range_to_remove) = ranges_to_remove.peek() {
20432 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20433 Ordering::Less | Ordering::Equal => {
20434 ranges_to_remove.next();
20435 }
20436 Ordering::Greater => {
20437 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20438 Ordering::Less | Ordering::Equal => {
20439 return false;
20440 }
20441 Ordering::Greater => break,
20442 }
20443 }
20444 }
20445 }
20446
20447 true
20448 })
20449 }
20450
20451 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20452 pub fn clear_row_highlights<T: 'static>(&mut self) {
20453 self.highlighted_rows.remove(&TypeId::of::<T>());
20454 }
20455
20456 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20457 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20458 self.highlighted_rows
20459 .get(&TypeId::of::<T>())
20460 .map_or(&[] as &[_], |vec| vec.as_slice())
20461 .iter()
20462 .map(|highlight| (highlight.range.clone(), highlight.color))
20463 }
20464
20465 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20466 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20467 /// Allows to ignore certain kinds of highlights.
20468 pub fn highlighted_display_rows(
20469 &self,
20470 window: &mut Window,
20471 cx: &mut App,
20472 ) -> BTreeMap<DisplayRow, LineHighlight> {
20473 let snapshot = self.snapshot(window, cx);
20474 let mut used_highlight_orders = HashMap::default();
20475 self.highlighted_rows
20476 .iter()
20477 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20478 .fold(
20479 BTreeMap::<DisplayRow, LineHighlight>::new(),
20480 |mut unique_rows, highlight| {
20481 let start = highlight.range.start.to_display_point(&snapshot);
20482 let end = highlight.range.end.to_display_point(&snapshot);
20483 let start_row = start.row().0;
20484 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20485 && end.column() == 0
20486 {
20487 end.row().0.saturating_sub(1)
20488 } else {
20489 end.row().0
20490 };
20491 for row in start_row..=end_row {
20492 let used_index =
20493 used_highlight_orders.entry(row).or_insert(highlight.index);
20494 if highlight.index >= *used_index {
20495 *used_index = highlight.index;
20496 unique_rows.insert(
20497 DisplayRow(row),
20498 LineHighlight {
20499 include_gutter: highlight.options.include_gutter,
20500 border: None,
20501 background: highlight.color.into(),
20502 type_id: Some(highlight.type_id),
20503 },
20504 );
20505 }
20506 }
20507 unique_rows
20508 },
20509 )
20510 }
20511
20512 pub fn highlighted_display_row_for_autoscroll(
20513 &self,
20514 snapshot: &DisplaySnapshot,
20515 ) -> Option<DisplayRow> {
20516 self.highlighted_rows
20517 .values()
20518 .flat_map(|highlighted_rows| highlighted_rows.iter())
20519 .filter_map(|highlight| {
20520 if highlight.options.autoscroll {
20521 Some(highlight.range.start.to_display_point(snapshot).row())
20522 } else {
20523 None
20524 }
20525 })
20526 .min()
20527 }
20528
20529 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20530 self.highlight_background::<SearchWithinRange>(
20531 ranges,
20532 |colors| colors.colors().editor_document_highlight_read_background,
20533 cx,
20534 )
20535 }
20536
20537 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20538 self.breadcrumb_header = Some(new_header);
20539 }
20540
20541 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20542 self.clear_background_highlights::<SearchWithinRange>(cx);
20543 }
20544
20545 pub fn highlight_background<T: 'static>(
20546 &mut self,
20547 ranges: &[Range<Anchor>],
20548 color_fetcher: fn(&Theme) -> Hsla,
20549 cx: &mut Context<Self>,
20550 ) {
20551 self.background_highlights.insert(
20552 HighlightKey::Type(TypeId::of::<T>()),
20553 (color_fetcher, Arc::from(ranges)),
20554 );
20555 self.scrollbar_marker_state.dirty = true;
20556 cx.notify();
20557 }
20558
20559 pub fn highlight_background_key<T: 'static>(
20560 &mut self,
20561 key: usize,
20562 ranges: &[Range<Anchor>],
20563 color_fetcher: fn(&Theme) -> Hsla,
20564 cx: &mut Context<Self>,
20565 ) {
20566 self.background_highlights.insert(
20567 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20568 (color_fetcher, Arc::from(ranges)),
20569 );
20570 self.scrollbar_marker_state.dirty = true;
20571 cx.notify();
20572 }
20573
20574 pub fn clear_background_highlights<T: 'static>(
20575 &mut self,
20576 cx: &mut Context<Self>,
20577 ) -> Option<BackgroundHighlight> {
20578 let text_highlights = self
20579 .background_highlights
20580 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20581 if !text_highlights.1.is_empty() {
20582 self.scrollbar_marker_state.dirty = true;
20583 cx.notify();
20584 }
20585 Some(text_highlights)
20586 }
20587
20588 pub fn highlight_gutter<T: 'static>(
20589 &mut self,
20590 ranges: impl Into<Vec<Range<Anchor>>>,
20591 color_fetcher: fn(&App) -> Hsla,
20592 cx: &mut Context<Self>,
20593 ) {
20594 self.gutter_highlights
20595 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20596 cx.notify();
20597 }
20598
20599 pub fn clear_gutter_highlights<T: 'static>(
20600 &mut self,
20601 cx: &mut Context<Self>,
20602 ) -> Option<GutterHighlight> {
20603 cx.notify();
20604 self.gutter_highlights.remove(&TypeId::of::<T>())
20605 }
20606
20607 pub fn insert_gutter_highlight<T: 'static>(
20608 &mut self,
20609 range: Range<Anchor>,
20610 color_fetcher: fn(&App) -> Hsla,
20611 cx: &mut Context<Self>,
20612 ) {
20613 let snapshot = self.buffer().read(cx).snapshot(cx);
20614 let mut highlights = self
20615 .gutter_highlights
20616 .remove(&TypeId::of::<T>())
20617 .map(|(_, highlights)| highlights)
20618 .unwrap_or_default();
20619 let ix = highlights.binary_search_by(|highlight| {
20620 Ordering::Equal
20621 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20622 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20623 });
20624 if let Err(ix) = ix {
20625 highlights.insert(ix, range);
20626 }
20627 self.gutter_highlights
20628 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20629 }
20630
20631 pub fn remove_gutter_highlights<T: 'static>(
20632 &mut self,
20633 ranges_to_remove: Vec<Range<Anchor>>,
20634 cx: &mut Context<Self>,
20635 ) {
20636 let snapshot = self.buffer().read(cx).snapshot(cx);
20637 let Some((color_fetcher, mut gutter_highlights)) =
20638 self.gutter_highlights.remove(&TypeId::of::<T>())
20639 else {
20640 return;
20641 };
20642 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20643 gutter_highlights.retain(|highlight| {
20644 while let Some(range_to_remove) = ranges_to_remove.peek() {
20645 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20646 Ordering::Less | Ordering::Equal => {
20647 ranges_to_remove.next();
20648 }
20649 Ordering::Greater => {
20650 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20651 Ordering::Less | Ordering::Equal => {
20652 return false;
20653 }
20654 Ordering::Greater => break,
20655 }
20656 }
20657 }
20658 }
20659
20660 true
20661 });
20662 self.gutter_highlights
20663 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20664 }
20665
20666 #[cfg(feature = "test-support")]
20667 pub fn all_text_highlights(
20668 &self,
20669 window: &mut Window,
20670 cx: &mut Context<Self>,
20671 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20672 let snapshot = self.snapshot(window, cx);
20673 self.display_map.update(cx, |display_map, _| {
20674 display_map
20675 .all_text_highlights()
20676 .map(|highlight| {
20677 let (style, ranges) = highlight.as_ref();
20678 (
20679 *style,
20680 ranges
20681 .iter()
20682 .map(|range| range.clone().to_display_points(&snapshot))
20683 .collect(),
20684 )
20685 })
20686 .collect()
20687 })
20688 }
20689
20690 #[cfg(feature = "test-support")]
20691 pub fn all_text_background_highlights(
20692 &self,
20693 window: &mut Window,
20694 cx: &mut Context<Self>,
20695 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20696 let snapshot = self.snapshot(window, cx);
20697 let buffer = &snapshot.buffer_snapshot();
20698 let start = buffer.anchor_before(0);
20699 let end = buffer.anchor_after(buffer.len());
20700 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20701 }
20702
20703 #[cfg(any(test, feature = "test-support"))]
20704 pub fn sorted_background_highlights_in_range(
20705 &self,
20706 search_range: Range<Anchor>,
20707 display_snapshot: &DisplaySnapshot,
20708 theme: &Theme,
20709 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20710 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20711 res.sort_by(|a, b| {
20712 a.0.start
20713 .cmp(&b.0.start)
20714 .then_with(|| a.0.end.cmp(&b.0.end))
20715 .then_with(|| a.1.cmp(&b.1))
20716 });
20717 res
20718 }
20719
20720 #[cfg(feature = "test-support")]
20721 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20722 let snapshot = self.buffer().read(cx).snapshot(cx);
20723
20724 let highlights = self
20725 .background_highlights
20726 .get(&HighlightKey::Type(TypeId::of::<
20727 items::BufferSearchHighlights,
20728 >()));
20729
20730 if let Some((_color, ranges)) = highlights {
20731 ranges
20732 .iter()
20733 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20734 .collect_vec()
20735 } else {
20736 vec![]
20737 }
20738 }
20739
20740 fn document_highlights_for_position<'a>(
20741 &'a self,
20742 position: Anchor,
20743 buffer: &'a MultiBufferSnapshot,
20744 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20745 let read_highlights = self
20746 .background_highlights
20747 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20748 .map(|h| &h.1);
20749 let write_highlights = self
20750 .background_highlights
20751 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20752 .map(|h| &h.1);
20753 let left_position = position.bias_left(buffer);
20754 let right_position = position.bias_right(buffer);
20755 read_highlights
20756 .into_iter()
20757 .chain(write_highlights)
20758 .flat_map(move |ranges| {
20759 let start_ix = match ranges.binary_search_by(|probe| {
20760 let cmp = probe.end.cmp(&left_position, buffer);
20761 if cmp.is_ge() {
20762 Ordering::Greater
20763 } else {
20764 Ordering::Less
20765 }
20766 }) {
20767 Ok(i) | Err(i) => i,
20768 };
20769
20770 ranges[start_ix..]
20771 .iter()
20772 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20773 })
20774 }
20775
20776 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20777 self.background_highlights
20778 .get(&HighlightKey::Type(TypeId::of::<T>()))
20779 .is_some_and(|(_, highlights)| !highlights.is_empty())
20780 }
20781
20782 /// Returns all background highlights for a given range.
20783 ///
20784 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20785 pub fn background_highlights_in_range(
20786 &self,
20787 search_range: Range<Anchor>,
20788 display_snapshot: &DisplaySnapshot,
20789 theme: &Theme,
20790 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20791 let mut results = Vec::new();
20792 for (color_fetcher, ranges) in self.background_highlights.values() {
20793 let color = color_fetcher(theme);
20794 let start_ix = match ranges.binary_search_by(|probe| {
20795 let cmp = probe
20796 .end
20797 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20798 if cmp.is_gt() {
20799 Ordering::Greater
20800 } else {
20801 Ordering::Less
20802 }
20803 }) {
20804 Ok(i) | Err(i) => i,
20805 };
20806 for range in &ranges[start_ix..] {
20807 if range
20808 .start
20809 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20810 .is_ge()
20811 {
20812 break;
20813 }
20814
20815 let start = range.start.to_display_point(display_snapshot);
20816 let end = range.end.to_display_point(display_snapshot);
20817 results.push((start..end, color))
20818 }
20819 }
20820 results
20821 }
20822
20823 pub fn gutter_highlights_in_range(
20824 &self,
20825 search_range: Range<Anchor>,
20826 display_snapshot: &DisplaySnapshot,
20827 cx: &App,
20828 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20829 let mut results = Vec::new();
20830 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20831 let color = color_fetcher(cx);
20832 let start_ix = match ranges.binary_search_by(|probe| {
20833 let cmp = probe
20834 .end
20835 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20836 if cmp.is_gt() {
20837 Ordering::Greater
20838 } else {
20839 Ordering::Less
20840 }
20841 }) {
20842 Ok(i) | Err(i) => i,
20843 };
20844 for range in &ranges[start_ix..] {
20845 if range
20846 .start
20847 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20848 .is_ge()
20849 {
20850 break;
20851 }
20852
20853 let start = range.start.to_display_point(display_snapshot);
20854 let end = range.end.to_display_point(display_snapshot);
20855 results.push((start..end, color))
20856 }
20857 }
20858 results
20859 }
20860
20861 /// Get the text ranges corresponding to the redaction query
20862 pub fn redacted_ranges(
20863 &self,
20864 search_range: Range<Anchor>,
20865 display_snapshot: &DisplaySnapshot,
20866 cx: &App,
20867 ) -> Vec<Range<DisplayPoint>> {
20868 display_snapshot
20869 .buffer_snapshot()
20870 .redacted_ranges(search_range, |file| {
20871 if let Some(file) = file {
20872 file.is_private()
20873 && EditorSettings::get(
20874 Some(SettingsLocation {
20875 worktree_id: file.worktree_id(cx),
20876 path: file.path().as_ref(),
20877 }),
20878 cx,
20879 )
20880 .redact_private_values
20881 } else {
20882 false
20883 }
20884 })
20885 .map(|range| {
20886 range.start.to_display_point(display_snapshot)
20887 ..range.end.to_display_point(display_snapshot)
20888 })
20889 .collect()
20890 }
20891
20892 pub fn highlight_text_key<T: 'static>(
20893 &mut self,
20894 key: usize,
20895 ranges: Vec<Range<Anchor>>,
20896 style: HighlightStyle,
20897 cx: &mut Context<Self>,
20898 ) {
20899 self.display_map.update(cx, |map, _| {
20900 map.highlight_text(
20901 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20902 ranges,
20903 style,
20904 );
20905 });
20906 cx.notify();
20907 }
20908
20909 pub fn highlight_text<T: 'static>(
20910 &mut self,
20911 ranges: Vec<Range<Anchor>>,
20912 style: HighlightStyle,
20913 cx: &mut Context<Self>,
20914 ) {
20915 self.display_map.update(cx, |map, _| {
20916 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20917 });
20918 cx.notify();
20919 }
20920
20921 pub fn text_highlights<'a, T: 'static>(
20922 &'a self,
20923 cx: &'a App,
20924 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20925 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20926 }
20927
20928 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20929 let cleared = self
20930 .display_map
20931 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20932 if cleared {
20933 cx.notify();
20934 }
20935 }
20936
20937 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20938 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20939 && self.focus_handle.is_focused(window)
20940 }
20941
20942 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20943 self.show_cursor_when_unfocused = is_enabled;
20944 cx.notify();
20945 }
20946
20947 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20948 cx.notify();
20949 }
20950
20951 fn on_debug_session_event(
20952 &mut self,
20953 _session: Entity<Session>,
20954 event: &SessionEvent,
20955 cx: &mut Context<Self>,
20956 ) {
20957 if let SessionEvent::InvalidateInlineValue = event {
20958 self.refresh_inline_values(cx);
20959 }
20960 }
20961
20962 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20963 let Some(project) = self.project.clone() else {
20964 return;
20965 };
20966
20967 if !self.inline_value_cache.enabled {
20968 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20969 self.splice_inlays(&inlays, Vec::new(), cx);
20970 return;
20971 }
20972
20973 let current_execution_position = self
20974 .highlighted_rows
20975 .get(&TypeId::of::<ActiveDebugLine>())
20976 .and_then(|lines| lines.last().map(|line| line.range.end));
20977
20978 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20979 let inline_values = editor
20980 .update(cx, |editor, cx| {
20981 let Some(current_execution_position) = current_execution_position else {
20982 return Some(Task::ready(Ok(Vec::new())));
20983 };
20984
20985 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20986 let snapshot = buffer.snapshot(cx);
20987
20988 let excerpt = snapshot.excerpt_containing(
20989 current_execution_position..current_execution_position,
20990 )?;
20991
20992 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20993 })?;
20994
20995 let range =
20996 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20997
20998 project.inline_values(buffer, range, cx)
20999 })
21000 .ok()
21001 .flatten()?
21002 .await
21003 .context("refreshing debugger inlays")
21004 .log_err()?;
21005
21006 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21007
21008 for (buffer_id, inline_value) in inline_values
21009 .into_iter()
21010 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21011 {
21012 buffer_inline_values
21013 .entry(buffer_id)
21014 .or_default()
21015 .push(inline_value);
21016 }
21017
21018 editor
21019 .update(cx, |editor, cx| {
21020 let snapshot = editor.buffer.read(cx).snapshot(cx);
21021 let mut new_inlays = Vec::default();
21022
21023 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21024 let buffer_id = buffer_snapshot.remote_id();
21025 buffer_inline_values
21026 .get(&buffer_id)
21027 .into_iter()
21028 .flatten()
21029 .for_each(|hint| {
21030 let inlay = Inlay::debugger(
21031 post_inc(&mut editor.next_inlay_id),
21032 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21033 hint.text(),
21034 );
21035 if !inlay.text().chars().contains(&'\n') {
21036 new_inlays.push(inlay);
21037 }
21038 });
21039 }
21040
21041 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21042 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21043
21044 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21045 })
21046 .ok()?;
21047 Some(())
21048 });
21049 }
21050
21051 fn on_buffer_event(
21052 &mut self,
21053 multibuffer: &Entity<MultiBuffer>,
21054 event: &multi_buffer::Event,
21055 window: &mut Window,
21056 cx: &mut Context<Self>,
21057 ) {
21058 match event {
21059 multi_buffer::Event::Edited { edited_buffer } => {
21060 self.scrollbar_marker_state.dirty = true;
21061 self.active_indent_guides_state.dirty = true;
21062 self.refresh_active_diagnostics(cx);
21063 self.refresh_code_actions(window, cx);
21064 self.refresh_selected_text_highlights(true, window, cx);
21065 self.refresh_single_line_folds(window, cx);
21066 self.refresh_matching_bracket_highlights(window, cx);
21067 if self.has_active_edit_prediction() {
21068 self.update_visible_edit_prediction(window, cx);
21069 }
21070
21071 if let Some(buffer) = edited_buffer {
21072 if buffer.read(cx).file().is_none() {
21073 cx.emit(EditorEvent::TitleChanged);
21074 }
21075
21076 if self.project.is_some() {
21077 let buffer_id = buffer.read(cx).remote_id();
21078 self.register_buffer(buffer_id, cx);
21079 self.update_lsp_data(Some(buffer_id), window, cx);
21080 self.refresh_inlay_hints(
21081 InlayHintRefreshReason::BufferEdited(buffer_id),
21082 cx,
21083 );
21084 }
21085 }
21086
21087 cx.emit(EditorEvent::BufferEdited);
21088 cx.emit(SearchEvent::MatchesInvalidated);
21089
21090 let Some(project) = &self.project else { return };
21091 let (telemetry, is_via_ssh) = {
21092 let project = project.read(cx);
21093 let telemetry = project.client().telemetry().clone();
21094 let is_via_ssh = project.is_via_remote_server();
21095 (telemetry, is_via_ssh)
21096 };
21097 telemetry.log_edit_event("editor", is_via_ssh);
21098 }
21099 multi_buffer::Event::ExcerptsAdded {
21100 buffer,
21101 predecessor,
21102 excerpts,
21103 } => {
21104 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21105 let buffer_id = buffer.read(cx).remote_id();
21106 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21107 && let Some(project) = &self.project
21108 {
21109 update_uncommitted_diff_for_buffer(
21110 cx.entity(),
21111 project,
21112 [buffer.clone()],
21113 self.buffer.clone(),
21114 cx,
21115 )
21116 .detach();
21117 }
21118 self.update_lsp_data(Some(buffer_id), window, cx);
21119 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21120 cx.emit(EditorEvent::ExcerptsAdded {
21121 buffer: buffer.clone(),
21122 predecessor: *predecessor,
21123 excerpts: excerpts.clone(),
21124 });
21125 }
21126 multi_buffer::Event::ExcerptsRemoved {
21127 ids,
21128 removed_buffer_ids,
21129 } => {
21130 if let Some(inlay_hints) = &mut self.inlay_hints {
21131 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21132 }
21133 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21134 for buffer_id in removed_buffer_ids {
21135 self.registered_buffers.remove(buffer_id);
21136 }
21137 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21138 cx.emit(EditorEvent::ExcerptsRemoved {
21139 ids: ids.clone(),
21140 removed_buffer_ids: removed_buffer_ids.clone(),
21141 });
21142 }
21143 multi_buffer::Event::ExcerptsEdited {
21144 excerpt_ids,
21145 buffer_ids,
21146 } => {
21147 self.display_map.update(cx, |map, cx| {
21148 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21149 });
21150 cx.emit(EditorEvent::ExcerptsEdited {
21151 ids: excerpt_ids.clone(),
21152 });
21153 }
21154 multi_buffer::Event::ExcerptsExpanded { ids } => {
21155 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21156 self.refresh_document_highlights(cx);
21157 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21158 }
21159 multi_buffer::Event::Reparsed(buffer_id) => {
21160 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21161 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21162
21163 cx.emit(EditorEvent::Reparsed(*buffer_id));
21164 }
21165 multi_buffer::Event::DiffHunksToggled => {
21166 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21167 }
21168 multi_buffer::Event::LanguageChanged(buffer_id) => {
21169 self.registered_buffers.remove(&buffer_id);
21170 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21171 cx.emit(EditorEvent::Reparsed(*buffer_id));
21172 cx.notify();
21173 }
21174 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21175 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21176 multi_buffer::Event::FileHandleChanged
21177 | multi_buffer::Event::Reloaded
21178 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21179 multi_buffer::Event::DiagnosticsUpdated => {
21180 self.update_diagnostics_state(window, cx);
21181 }
21182 _ => {}
21183 };
21184 }
21185
21186 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21187 if !self.diagnostics_enabled() {
21188 return;
21189 }
21190 self.refresh_active_diagnostics(cx);
21191 self.refresh_inline_diagnostics(true, window, cx);
21192 self.scrollbar_marker_state.dirty = true;
21193 cx.notify();
21194 }
21195
21196 pub fn start_temporary_diff_override(&mut self) {
21197 self.load_diff_task.take();
21198 self.temporary_diff_override = true;
21199 }
21200
21201 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21202 self.temporary_diff_override = false;
21203 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21204 self.buffer.update(cx, |buffer, cx| {
21205 buffer.set_all_diff_hunks_collapsed(cx);
21206 });
21207
21208 if let Some(project) = self.project.clone() {
21209 self.load_diff_task = Some(
21210 update_uncommitted_diff_for_buffer(
21211 cx.entity(),
21212 &project,
21213 self.buffer.read(cx).all_buffers(),
21214 self.buffer.clone(),
21215 cx,
21216 )
21217 .shared(),
21218 );
21219 }
21220 }
21221
21222 fn on_display_map_changed(
21223 &mut self,
21224 _: Entity<DisplayMap>,
21225 _: &mut Window,
21226 cx: &mut Context<Self>,
21227 ) {
21228 cx.notify();
21229 }
21230
21231 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21232 if self.diagnostics_enabled() {
21233 let new_severity = EditorSettings::get_global(cx)
21234 .diagnostics_max_severity
21235 .unwrap_or(DiagnosticSeverity::Hint);
21236 self.set_max_diagnostics_severity(new_severity, cx);
21237 }
21238 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21239 self.update_edit_prediction_settings(cx);
21240 self.refresh_edit_prediction(true, false, window, cx);
21241 self.refresh_inline_values(cx);
21242 self.refresh_inlay_hints(
21243 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21244 self.selections.newest_anchor().head(),
21245 &self.buffer.read(cx).snapshot(cx),
21246 cx,
21247 )),
21248 cx,
21249 );
21250
21251 let old_cursor_shape = self.cursor_shape;
21252 let old_show_breadcrumbs = self.show_breadcrumbs;
21253
21254 {
21255 let editor_settings = EditorSettings::get_global(cx);
21256 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21257 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21258 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21259 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21260 }
21261
21262 if old_cursor_shape != self.cursor_shape {
21263 cx.emit(EditorEvent::CursorShapeChanged);
21264 }
21265
21266 if old_show_breadcrumbs != self.show_breadcrumbs {
21267 cx.emit(EditorEvent::BreadcrumbsChanged);
21268 }
21269
21270 let project_settings = ProjectSettings::get_global(cx);
21271 self.buffer_serialization = self
21272 .should_serialize_buffer()
21273 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21274
21275 if self.mode.is_full() {
21276 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21277 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21278 if self.show_inline_diagnostics != show_inline_diagnostics {
21279 self.show_inline_diagnostics = show_inline_diagnostics;
21280 self.refresh_inline_diagnostics(false, window, cx);
21281 }
21282
21283 if self.git_blame_inline_enabled != inline_blame_enabled {
21284 self.toggle_git_blame_inline_internal(false, window, cx);
21285 }
21286
21287 let minimap_settings = EditorSettings::get_global(cx).minimap;
21288 if self.minimap_visibility != MinimapVisibility::Disabled {
21289 if self.minimap_visibility.settings_visibility()
21290 != minimap_settings.minimap_enabled()
21291 {
21292 self.set_minimap_visibility(
21293 MinimapVisibility::for_mode(self.mode(), cx),
21294 window,
21295 cx,
21296 );
21297 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21298 minimap_entity.update(cx, |minimap_editor, cx| {
21299 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21300 })
21301 }
21302 }
21303 }
21304
21305 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21306 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21307 }) {
21308 if !inlay_splice.is_empty() {
21309 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21310 }
21311 self.refresh_colors_for_visible_range(None, window, cx);
21312 }
21313
21314 cx.notify();
21315 }
21316
21317 pub fn set_searchable(&mut self, searchable: bool) {
21318 self.searchable = searchable;
21319 }
21320
21321 pub fn searchable(&self) -> bool {
21322 self.searchable
21323 }
21324
21325 pub fn open_excerpts_in_split(
21326 &mut self,
21327 _: &OpenExcerptsSplit,
21328 window: &mut Window,
21329 cx: &mut Context<Self>,
21330 ) {
21331 self.open_excerpts_common(None, true, window, cx)
21332 }
21333
21334 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21335 self.open_excerpts_common(None, false, window, cx)
21336 }
21337
21338 fn open_excerpts_common(
21339 &mut self,
21340 jump_data: Option<JumpData>,
21341 split: bool,
21342 window: &mut Window,
21343 cx: &mut Context<Self>,
21344 ) {
21345 let Some(workspace) = self.workspace() else {
21346 cx.propagate();
21347 return;
21348 };
21349
21350 if self.buffer.read(cx).is_singleton() {
21351 cx.propagate();
21352 return;
21353 }
21354
21355 let mut new_selections_by_buffer = HashMap::default();
21356 match &jump_data {
21357 Some(JumpData::MultiBufferPoint {
21358 excerpt_id,
21359 position,
21360 anchor,
21361 line_offset_from_top,
21362 }) => {
21363 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21364 if let Some(buffer) = multi_buffer_snapshot
21365 .buffer_id_for_excerpt(*excerpt_id)
21366 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21367 {
21368 let buffer_snapshot = buffer.read(cx).snapshot();
21369 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21370 language::ToPoint::to_point(anchor, &buffer_snapshot)
21371 } else {
21372 buffer_snapshot.clip_point(*position, Bias::Left)
21373 };
21374 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21375 new_selections_by_buffer.insert(
21376 buffer,
21377 (
21378 vec![jump_to_offset..jump_to_offset],
21379 Some(*line_offset_from_top),
21380 ),
21381 );
21382 }
21383 }
21384 Some(JumpData::MultiBufferRow {
21385 row,
21386 line_offset_from_top,
21387 }) => {
21388 let point = MultiBufferPoint::new(row.0, 0);
21389 if let Some((buffer, buffer_point, _)) =
21390 self.buffer.read(cx).point_to_buffer_point(point, cx)
21391 {
21392 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21393 new_selections_by_buffer
21394 .entry(buffer)
21395 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21396 .0
21397 .push(buffer_offset..buffer_offset)
21398 }
21399 }
21400 None => {
21401 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21402 let multi_buffer = self.buffer.read(cx);
21403 for selection in selections {
21404 for (snapshot, range, _, anchor) in multi_buffer
21405 .snapshot(cx)
21406 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21407 {
21408 if let Some(anchor) = anchor {
21409 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21410 else {
21411 continue;
21412 };
21413 let offset = text::ToOffset::to_offset(
21414 &anchor.text_anchor,
21415 &buffer_handle.read(cx).snapshot(),
21416 );
21417 let range = offset..offset;
21418 new_selections_by_buffer
21419 .entry(buffer_handle)
21420 .or_insert((Vec::new(), None))
21421 .0
21422 .push(range)
21423 } else {
21424 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21425 else {
21426 continue;
21427 };
21428 new_selections_by_buffer
21429 .entry(buffer_handle)
21430 .or_insert((Vec::new(), None))
21431 .0
21432 .push(range)
21433 }
21434 }
21435 }
21436 }
21437 }
21438
21439 new_selections_by_buffer
21440 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21441
21442 if new_selections_by_buffer.is_empty() {
21443 return;
21444 }
21445
21446 // We defer the pane interaction because we ourselves are a workspace item
21447 // and activating a new item causes the pane to call a method on us reentrantly,
21448 // which panics if we're on the stack.
21449 window.defer(cx, move |window, cx| {
21450 workspace.update(cx, |workspace, cx| {
21451 let pane = if split {
21452 workspace.adjacent_pane(window, cx)
21453 } else {
21454 workspace.active_pane().clone()
21455 };
21456
21457 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21458 let editor = buffer
21459 .read(cx)
21460 .file()
21461 .is_none()
21462 .then(|| {
21463 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21464 // so `workspace.open_project_item` will never find them, always opening a new editor.
21465 // Instead, we try to activate the existing editor in the pane first.
21466 let (editor, pane_item_index) =
21467 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21468 let editor = item.downcast::<Editor>()?;
21469 let singleton_buffer =
21470 editor.read(cx).buffer().read(cx).as_singleton()?;
21471 if singleton_buffer == buffer {
21472 Some((editor, i))
21473 } else {
21474 None
21475 }
21476 })?;
21477 pane.update(cx, |pane, cx| {
21478 pane.activate_item(pane_item_index, true, true, window, cx)
21479 });
21480 Some(editor)
21481 })
21482 .flatten()
21483 .unwrap_or_else(|| {
21484 workspace.open_project_item::<Self>(
21485 pane.clone(),
21486 buffer,
21487 true,
21488 true,
21489 window,
21490 cx,
21491 )
21492 });
21493
21494 editor.update(cx, |editor, cx| {
21495 let autoscroll = match scroll_offset {
21496 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21497 None => Autoscroll::newest(),
21498 };
21499 let nav_history = editor.nav_history.take();
21500 editor.change_selections(
21501 SelectionEffects::scroll(autoscroll),
21502 window,
21503 cx,
21504 |s| {
21505 s.select_ranges(ranges);
21506 },
21507 );
21508 editor.nav_history = nav_history;
21509 });
21510 }
21511 })
21512 });
21513 }
21514
21515 // For now, don't allow opening excerpts in buffers that aren't backed by
21516 // regular project files.
21517 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21518 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21519 }
21520
21521 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21522 let snapshot = self.buffer.read(cx).read(cx);
21523 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21524 Some(
21525 ranges
21526 .iter()
21527 .map(move |range| {
21528 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21529 })
21530 .collect(),
21531 )
21532 }
21533
21534 fn selection_replacement_ranges(
21535 &self,
21536 range: Range<OffsetUtf16>,
21537 cx: &mut App,
21538 ) -> Vec<Range<OffsetUtf16>> {
21539 let selections = self
21540 .selections
21541 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21542 let newest_selection = selections
21543 .iter()
21544 .max_by_key(|selection| selection.id)
21545 .unwrap();
21546 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21547 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21548 let snapshot = self.buffer.read(cx).read(cx);
21549 selections
21550 .into_iter()
21551 .map(|mut selection| {
21552 selection.start.0 =
21553 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21554 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21555 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21556 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21557 })
21558 .collect()
21559 }
21560
21561 fn report_editor_event(
21562 &self,
21563 reported_event: ReportEditorEvent,
21564 file_extension: Option<String>,
21565 cx: &App,
21566 ) {
21567 if cfg!(any(test, feature = "test-support")) {
21568 return;
21569 }
21570
21571 let Some(project) = &self.project else { return };
21572
21573 // If None, we are in a file without an extension
21574 let file = self
21575 .buffer
21576 .read(cx)
21577 .as_singleton()
21578 .and_then(|b| b.read(cx).file());
21579 let file_extension = file_extension.or(file
21580 .as_ref()
21581 .and_then(|file| Path::new(file.file_name(cx)).extension())
21582 .and_then(|e| e.to_str())
21583 .map(|a| a.to_string()));
21584
21585 let vim_mode = vim_flavor(cx).is_some();
21586
21587 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21588 let copilot_enabled = edit_predictions_provider
21589 == language::language_settings::EditPredictionProvider::Copilot;
21590 let copilot_enabled_for_language = self
21591 .buffer
21592 .read(cx)
21593 .language_settings(cx)
21594 .show_edit_predictions;
21595
21596 let project = project.read(cx);
21597 let event_type = reported_event.event_type();
21598
21599 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21600 telemetry::event!(
21601 event_type,
21602 type = if auto_saved {"autosave"} else {"manual"},
21603 file_extension,
21604 vim_mode,
21605 copilot_enabled,
21606 copilot_enabled_for_language,
21607 edit_predictions_provider,
21608 is_via_ssh = project.is_via_remote_server(),
21609 );
21610 } else {
21611 telemetry::event!(
21612 event_type,
21613 file_extension,
21614 vim_mode,
21615 copilot_enabled,
21616 copilot_enabled_for_language,
21617 edit_predictions_provider,
21618 is_via_ssh = project.is_via_remote_server(),
21619 );
21620 };
21621 }
21622
21623 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21624 /// with each line being an array of {text, highlight} objects.
21625 fn copy_highlight_json(
21626 &mut self,
21627 _: &CopyHighlightJson,
21628 window: &mut Window,
21629 cx: &mut Context<Self>,
21630 ) {
21631 #[derive(Serialize)]
21632 struct Chunk<'a> {
21633 text: String,
21634 highlight: Option<&'a str>,
21635 }
21636
21637 let snapshot = self.buffer.read(cx).snapshot(cx);
21638 let range = self
21639 .selected_text_range(false, window, cx)
21640 .and_then(|selection| {
21641 if selection.range.is_empty() {
21642 None
21643 } else {
21644 Some(
21645 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21646 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21647 )
21648 }
21649 })
21650 .unwrap_or_else(|| 0..snapshot.len());
21651
21652 let chunks = snapshot.chunks(range, true);
21653 let mut lines = Vec::new();
21654 let mut line: VecDeque<Chunk> = VecDeque::new();
21655
21656 let Some(style) = self.style.as_ref() else {
21657 return;
21658 };
21659
21660 for chunk in chunks {
21661 let highlight = chunk
21662 .syntax_highlight_id
21663 .and_then(|id| id.name(&style.syntax));
21664 let mut chunk_lines = chunk.text.split('\n').peekable();
21665 while let Some(text) = chunk_lines.next() {
21666 let mut merged_with_last_token = false;
21667 if let Some(last_token) = line.back_mut()
21668 && last_token.highlight == highlight
21669 {
21670 last_token.text.push_str(text);
21671 merged_with_last_token = true;
21672 }
21673
21674 if !merged_with_last_token {
21675 line.push_back(Chunk {
21676 text: text.into(),
21677 highlight,
21678 });
21679 }
21680
21681 if chunk_lines.peek().is_some() {
21682 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21683 line.pop_front();
21684 }
21685 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21686 line.pop_back();
21687 }
21688
21689 lines.push(mem::take(&mut line));
21690 }
21691 }
21692 }
21693
21694 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21695 return;
21696 };
21697 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21698 }
21699
21700 pub fn open_context_menu(
21701 &mut self,
21702 _: &OpenContextMenu,
21703 window: &mut Window,
21704 cx: &mut Context<Self>,
21705 ) {
21706 self.request_autoscroll(Autoscroll::newest(), cx);
21707 let position = self
21708 .selections
21709 .newest_display(&self.display_snapshot(cx))
21710 .start;
21711 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21712 }
21713
21714 pub fn replay_insert_event(
21715 &mut self,
21716 text: &str,
21717 relative_utf16_range: Option<Range<isize>>,
21718 window: &mut Window,
21719 cx: &mut Context<Self>,
21720 ) {
21721 if !self.input_enabled {
21722 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21723 return;
21724 }
21725 if let Some(relative_utf16_range) = relative_utf16_range {
21726 let selections = self
21727 .selections
21728 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21729 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21730 let new_ranges = selections.into_iter().map(|range| {
21731 let start = OffsetUtf16(
21732 range
21733 .head()
21734 .0
21735 .saturating_add_signed(relative_utf16_range.start),
21736 );
21737 let end = OffsetUtf16(
21738 range
21739 .head()
21740 .0
21741 .saturating_add_signed(relative_utf16_range.end),
21742 );
21743 start..end
21744 });
21745 s.select_ranges(new_ranges);
21746 });
21747 }
21748
21749 self.handle_input(text, window, cx);
21750 }
21751
21752 pub fn is_focused(&self, window: &Window) -> bool {
21753 self.focus_handle.is_focused(window)
21754 }
21755
21756 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21757 cx.emit(EditorEvent::Focused);
21758
21759 if let Some(descendant) = self
21760 .last_focused_descendant
21761 .take()
21762 .and_then(|descendant| descendant.upgrade())
21763 {
21764 window.focus(&descendant);
21765 } else {
21766 if let Some(blame) = self.blame.as_ref() {
21767 blame.update(cx, GitBlame::focus)
21768 }
21769
21770 self.blink_manager.update(cx, BlinkManager::enable);
21771 self.show_cursor_names(window, cx);
21772 self.buffer.update(cx, |buffer, cx| {
21773 buffer.finalize_last_transaction(cx);
21774 if self.leader_id.is_none() {
21775 buffer.set_active_selections(
21776 &self.selections.disjoint_anchors_arc(),
21777 self.selections.line_mode(),
21778 self.cursor_shape,
21779 cx,
21780 );
21781 }
21782 });
21783 }
21784 }
21785
21786 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21787 cx.emit(EditorEvent::FocusedIn)
21788 }
21789
21790 fn handle_focus_out(
21791 &mut self,
21792 event: FocusOutEvent,
21793 _window: &mut Window,
21794 cx: &mut Context<Self>,
21795 ) {
21796 if event.blurred != self.focus_handle {
21797 self.last_focused_descendant = Some(event.blurred);
21798 }
21799 self.selection_drag_state = SelectionDragState::None;
21800 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21801 }
21802
21803 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21804 self.blink_manager.update(cx, BlinkManager::disable);
21805 self.buffer
21806 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21807
21808 if let Some(blame) = self.blame.as_ref() {
21809 blame.update(cx, GitBlame::blur)
21810 }
21811 if !self.hover_state.focused(window, cx) {
21812 hide_hover(self, cx);
21813 }
21814 if !self
21815 .context_menu
21816 .borrow()
21817 .as_ref()
21818 .is_some_and(|context_menu| context_menu.focused(window, cx))
21819 {
21820 self.hide_context_menu(window, cx);
21821 }
21822 self.take_active_edit_prediction(cx);
21823 cx.emit(EditorEvent::Blurred);
21824 cx.notify();
21825 }
21826
21827 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21828 let mut pending: String = window
21829 .pending_input_keystrokes()
21830 .into_iter()
21831 .flatten()
21832 .filter_map(|keystroke| {
21833 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21834 keystroke.key_char.clone()
21835 } else {
21836 None
21837 }
21838 })
21839 .collect();
21840
21841 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21842 pending = "".to_string();
21843 }
21844
21845 let existing_pending = self
21846 .text_highlights::<PendingInput>(cx)
21847 .map(|(_, ranges)| ranges.to_vec());
21848 if existing_pending.is_none() && pending.is_empty() {
21849 return;
21850 }
21851 let transaction =
21852 self.transact(window, cx, |this, window, cx| {
21853 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21854 let edits = selections
21855 .iter()
21856 .map(|selection| (selection.end..selection.end, pending.clone()));
21857 this.edit(edits, cx);
21858 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21859 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21860 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21861 }));
21862 });
21863 if let Some(existing_ranges) = existing_pending {
21864 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21865 this.edit(edits, cx);
21866 }
21867 });
21868
21869 let snapshot = self.snapshot(window, cx);
21870 let ranges = self
21871 .selections
21872 .all::<usize>(&snapshot.display_snapshot)
21873 .into_iter()
21874 .map(|selection| {
21875 snapshot.buffer_snapshot().anchor_after(selection.end)
21876 ..snapshot
21877 .buffer_snapshot()
21878 .anchor_before(selection.end + pending.len())
21879 })
21880 .collect();
21881
21882 if pending.is_empty() {
21883 self.clear_highlights::<PendingInput>(cx);
21884 } else {
21885 self.highlight_text::<PendingInput>(
21886 ranges,
21887 HighlightStyle {
21888 underline: Some(UnderlineStyle {
21889 thickness: px(1.),
21890 color: None,
21891 wavy: false,
21892 }),
21893 ..Default::default()
21894 },
21895 cx,
21896 );
21897 }
21898
21899 self.ime_transaction = self.ime_transaction.or(transaction);
21900 if let Some(transaction) = self.ime_transaction {
21901 self.buffer.update(cx, |buffer, cx| {
21902 buffer.group_until_transaction(transaction, cx);
21903 });
21904 }
21905
21906 if self.text_highlights::<PendingInput>(cx).is_none() {
21907 self.ime_transaction.take();
21908 }
21909 }
21910
21911 pub fn register_action_renderer(
21912 &mut self,
21913 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21914 ) -> Subscription {
21915 let id = self.next_editor_action_id.post_inc();
21916 self.editor_actions
21917 .borrow_mut()
21918 .insert(id, Box::new(listener));
21919
21920 let editor_actions = self.editor_actions.clone();
21921 Subscription::new(move || {
21922 editor_actions.borrow_mut().remove(&id);
21923 })
21924 }
21925
21926 pub fn register_action<A: Action>(
21927 &mut self,
21928 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21929 ) -> Subscription {
21930 let id = self.next_editor_action_id.post_inc();
21931 let listener = Arc::new(listener);
21932 self.editor_actions.borrow_mut().insert(
21933 id,
21934 Box::new(move |_, window, _| {
21935 let listener = listener.clone();
21936 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21937 let action = action.downcast_ref().unwrap();
21938 if phase == DispatchPhase::Bubble {
21939 listener(action, window, cx)
21940 }
21941 })
21942 }),
21943 );
21944
21945 let editor_actions = self.editor_actions.clone();
21946 Subscription::new(move || {
21947 editor_actions.borrow_mut().remove(&id);
21948 })
21949 }
21950
21951 pub fn file_header_size(&self) -> u32 {
21952 FILE_HEADER_HEIGHT
21953 }
21954
21955 pub fn restore(
21956 &mut self,
21957 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21958 window: &mut Window,
21959 cx: &mut Context<Self>,
21960 ) {
21961 let workspace = self.workspace();
21962 let project = self.project();
21963 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21964 let mut tasks = Vec::new();
21965 for (buffer_id, changes) in revert_changes {
21966 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21967 buffer.update(cx, |buffer, cx| {
21968 buffer.edit(
21969 changes
21970 .into_iter()
21971 .map(|(range, text)| (range, text.to_string())),
21972 None,
21973 cx,
21974 );
21975 });
21976
21977 if let Some(project) =
21978 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21979 {
21980 project.update(cx, |project, cx| {
21981 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21982 })
21983 }
21984 }
21985 }
21986 tasks
21987 });
21988 cx.spawn_in(window, async move |_, cx| {
21989 for (buffer, task) in save_tasks {
21990 let result = task.await;
21991 if result.is_err() {
21992 let Some(path) = buffer
21993 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21994 .ok()
21995 else {
21996 continue;
21997 };
21998 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21999 let Some(task) = cx
22000 .update_window_entity(workspace, |workspace, window, cx| {
22001 workspace
22002 .open_path_preview(path, None, false, false, false, window, cx)
22003 })
22004 .ok()
22005 else {
22006 continue;
22007 };
22008 task.await.log_err();
22009 }
22010 }
22011 }
22012 })
22013 .detach();
22014 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22015 selections.refresh()
22016 });
22017 }
22018
22019 pub fn to_pixel_point(
22020 &self,
22021 source: multi_buffer::Anchor,
22022 editor_snapshot: &EditorSnapshot,
22023 window: &mut Window,
22024 ) -> Option<gpui::Point<Pixels>> {
22025 let source_point = source.to_display_point(editor_snapshot);
22026 self.display_to_pixel_point(source_point, editor_snapshot, window)
22027 }
22028
22029 pub fn display_to_pixel_point(
22030 &self,
22031 source: DisplayPoint,
22032 editor_snapshot: &EditorSnapshot,
22033 window: &mut Window,
22034 ) -> Option<gpui::Point<Pixels>> {
22035 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22036 let text_layout_details = self.text_layout_details(window);
22037 let scroll_top = text_layout_details
22038 .scroll_anchor
22039 .scroll_position(editor_snapshot)
22040 .y;
22041
22042 if source.row().as_f64() < scroll_top.floor() {
22043 return None;
22044 }
22045 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22046 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22047 Some(gpui::Point::new(source_x, source_y))
22048 }
22049
22050 pub fn has_visible_completions_menu(&self) -> bool {
22051 !self.edit_prediction_preview_is_active()
22052 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22053 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22054 })
22055 }
22056
22057 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22058 if self.mode.is_minimap() {
22059 return;
22060 }
22061 self.addons
22062 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22063 }
22064
22065 pub fn unregister_addon<T: Addon>(&mut self) {
22066 self.addons.remove(&std::any::TypeId::of::<T>());
22067 }
22068
22069 pub fn addon<T: Addon>(&self) -> Option<&T> {
22070 let type_id = std::any::TypeId::of::<T>();
22071 self.addons
22072 .get(&type_id)
22073 .and_then(|item| item.to_any().downcast_ref::<T>())
22074 }
22075
22076 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22077 let type_id = std::any::TypeId::of::<T>();
22078 self.addons
22079 .get_mut(&type_id)
22080 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22081 }
22082
22083 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22084 let text_layout_details = self.text_layout_details(window);
22085 let style = &text_layout_details.editor_style;
22086 let font_id = window.text_system().resolve_font(&style.text.font());
22087 let font_size = style.text.font_size.to_pixels(window.rem_size());
22088 let line_height = style.text.line_height_in_pixels(window.rem_size());
22089 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22090 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22091
22092 CharacterDimensions {
22093 em_width,
22094 em_advance,
22095 line_height,
22096 }
22097 }
22098
22099 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22100 self.load_diff_task.clone()
22101 }
22102
22103 fn read_metadata_from_db(
22104 &mut self,
22105 item_id: u64,
22106 workspace_id: WorkspaceId,
22107 window: &mut Window,
22108 cx: &mut Context<Editor>,
22109 ) {
22110 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22111 && !self.mode.is_minimap()
22112 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22113 {
22114 let buffer_snapshot = OnceCell::new();
22115
22116 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22117 && !folds.is_empty()
22118 {
22119 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22120 self.fold_ranges(
22121 folds
22122 .into_iter()
22123 .map(|(start, end)| {
22124 snapshot.clip_offset(start, Bias::Left)
22125 ..snapshot.clip_offset(end, Bias::Right)
22126 })
22127 .collect(),
22128 false,
22129 window,
22130 cx,
22131 );
22132 }
22133
22134 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22135 && !selections.is_empty()
22136 {
22137 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22138 // skip adding the initial selection to selection history
22139 self.selection_history.mode = SelectionHistoryMode::Skipping;
22140 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22141 s.select_ranges(selections.into_iter().map(|(start, end)| {
22142 snapshot.clip_offset(start, Bias::Left)
22143 ..snapshot.clip_offset(end, Bias::Right)
22144 }));
22145 });
22146 self.selection_history.mode = SelectionHistoryMode::Normal;
22147 };
22148 }
22149
22150 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22151 }
22152
22153 fn update_lsp_data(
22154 &mut self,
22155 for_buffer: Option<BufferId>,
22156 window: &mut Window,
22157 cx: &mut Context<'_, Self>,
22158 ) {
22159 self.pull_diagnostics(for_buffer, window, cx);
22160 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22161 }
22162
22163 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22164 if self.ignore_lsp_data() {
22165 return;
22166 }
22167 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22168 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22169 }
22170 }
22171
22172 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22173 if self.ignore_lsp_data() {
22174 return;
22175 }
22176
22177 if !self.registered_buffers.contains_key(&buffer_id)
22178 && let Some(project) = self.project.as_ref()
22179 {
22180 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22181 project.update(cx, |project, cx| {
22182 self.registered_buffers.insert(
22183 buffer_id,
22184 project.register_buffer_with_language_servers(&buffer, cx),
22185 );
22186 });
22187 } else {
22188 self.registered_buffers.remove(&buffer_id);
22189 }
22190 }
22191 }
22192
22193 fn ignore_lsp_data(&self) -> bool {
22194 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22195 // skip any LSP updates for it.
22196 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22197 }
22198}
22199
22200fn edit_for_markdown_paste<'a>(
22201 buffer: &MultiBufferSnapshot,
22202 range: Range<usize>,
22203 to_insert: &'a str,
22204 url: Option<url::Url>,
22205) -> (Range<usize>, Cow<'a, str>) {
22206 if url.is_none() {
22207 return (range, Cow::Borrowed(to_insert));
22208 };
22209
22210 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22211
22212 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22213 Cow::Borrowed(to_insert)
22214 } else {
22215 Cow::Owned(format!("[{old_text}]({to_insert})"))
22216 };
22217 (range, new_text)
22218}
22219
22220#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22221pub enum VimFlavor {
22222 Vim,
22223 Helix,
22224}
22225
22226pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22227 if vim_mode_setting::HelixModeSetting::try_get(cx)
22228 .map(|helix_mode| helix_mode.0)
22229 .unwrap_or(false)
22230 {
22231 Some(VimFlavor::Helix)
22232 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22233 .map(|vim_mode| vim_mode.0)
22234 .unwrap_or(false)
22235 {
22236 Some(VimFlavor::Vim)
22237 } else {
22238 None // neither vim nor helix mode
22239 }
22240}
22241
22242fn process_completion_for_edit(
22243 completion: &Completion,
22244 intent: CompletionIntent,
22245 buffer: &Entity<Buffer>,
22246 cursor_position: &text::Anchor,
22247 cx: &mut Context<Editor>,
22248) -> CompletionEdit {
22249 let buffer = buffer.read(cx);
22250 let buffer_snapshot = buffer.snapshot();
22251 let (snippet, new_text) = if completion.is_snippet() {
22252 let mut snippet_source = completion.new_text.clone();
22253 // Workaround for typescript language server issues so that methods don't expand within
22254 // strings and functions with type expressions. The previous point is used because the query
22255 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22256 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22257 let previous_point = if previous_point.column > 0 {
22258 cursor_position.to_previous_offset(&buffer_snapshot)
22259 } else {
22260 cursor_position.to_offset(&buffer_snapshot)
22261 };
22262 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22263 && scope.prefers_label_for_snippet_in_completion()
22264 && let Some(label) = completion.label()
22265 && matches!(
22266 completion.kind(),
22267 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22268 )
22269 {
22270 snippet_source = label;
22271 }
22272 match Snippet::parse(&snippet_source).log_err() {
22273 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22274 None => (None, completion.new_text.clone()),
22275 }
22276 } else {
22277 (None, completion.new_text.clone())
22278 };
22279
22280 let mut range_to_replace = {
22281 let replace_range = &completion.replace_range;
22282 if let CompletionSource::Lsp {
22283 insert_range: Some(insert_range),
22284 ..
22285 } = &completion.source
22286 {
22287 debug_assert_eq!(
22288 insert_range.start, replace_range.start,
22289 "insert_range and replace_range should start at the same position"
22290 );
22291 debug_assert!(
22292 insert_range
22293 .start
22294 .cmp(cursor_position, &buffer_snapshot)
22295 .is_le(),
22296 "insert_range should start before or at cursor position"
22297 );
22298 debug_assert!(
22299 replace_range
22300 .start
22301 .cmp(cursor_position, &buffer_snapshot)
22302 .is_le(),
22303 "replace_range should start before or at cursor position"
22304 );
22305
22306 let should_replace = match intent {
22307 CompletionIntent::CompleteWithInsert => false,
22308 CompletionIntent::CompleteWithReplace => true,
22309 CompletionIntent::Complete | CompletionIntent::Compose => {
22310 let insert_mode =
22311 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22312 .completions
22313 .lsp_insert_mode;
22314 match insert_mode {
22315 LspInsertMode::Insert => false,
22316 LspInsertMode::Replace => true,
22317 LspInsertMode::ReplaceSubsequence => {
22318 let mut text_to_replace = buffer.chars_for_range(
22319 buffer.anchor_before(replace_range.start)
22320 ..buffer.anchor_after(replace_range.end),
22321 );
22322 let mut current_needle = text_to_replace.next();
22323 for haystack_ch in completion.label.text.chars() {
22324 if let Some(needle_ch) = current_needle
22325 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22326 {
22327 current_needle = text_to_replace.next();
22328 }
22329 }
22330 current_needle.is_none()
22331 }
22332 LspInsertMode::ReplaceSuffix => {
22333 if replace_range
22334 .end
22335 .cmp(cursor_position, &buffer_snapshot)
22336 .is_gt()
22337 {
22338 let range_after_cursor = *cursor_position..replace_range.end;
22339 let text_after_cursor = buffer
22340 .text_for_range(
22341 buffer.anchor_before(range_after_cursor.start)
22342 ..buffer.anchor_after(range_after_cursor.end),
22343 )
22344 .collect::<String>()
22345 .to_ascii_lowercase();
22346 completion
22347 .label
22348 .text
22349 .to_ascii_lowercase()
22350 .ends_with(&text_after_cursor)
22351 } else {
22352 true
22353 }
22354 }
22355 }
22356 }
22357 };
22358
22359 if should_replace {
22360 replace_range.clone()
22361 } else {
22362 insert_range.clone()
22363 }
22364 } else {
22365 replace_range.clone()
22366 }
22367 };
22368
22369 if range_to_replace
22370 .end
22371 .cmp(cursor_position, &buffer_snapshot)
22372 .is_lt()
22373 {
22374 range_to_replace.end = *cursor_position;
22375 }
22376
22377 CompletionEdit {
22378 new_text,
22379 replace_range: range_to_replace.to_offset(buffer),
22380 snippet,
22381 }
22382}
22383
22384struct CompletionEdit {
22385 new_text: String,
22386 replace_range: Range<usize>,
22387 snippet: Option<Snippet>,
22388}
22389
22390fn insert_extra_newline_brackets(
22391 buffer: &MultiBufferSnapshot,
22392 range: Range<usize>,
22393 language: &language::LanguageScope,
22394) -> bool {
22395 let leading_whitespace_len = buffer
22396 .reversed_chars_at(range.start)
22397 .take_while(|c| c.is_whitespace() && *c != '\n')
22398 .map(|c| c.len_utf8())
22399 .sum::<usize>();
22400 let trailing_whitespace_len = buffer
22401 .chars_at(range.end)
22402 .take_while(|c| c.is_whitespace() && *c != '\n')
22403 .map(|c| c.len_utf8())
22404 .sum::<usize>();
22405 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22406
22407 language.brackets().any(|(pair, enabled)| {
22408 let pair_start = pair.start.trim_end();
22409 let pair_end = pair.end.trim_start();
22410
22411 enabled
22412 && pair.newline
22413 && buffer.contains_str_at(range.end, pair_end)
22414 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22415 })
22416}
22417
22418fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22419 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22420 [(buffer, range, _)] => (*buffer, range.clone()),
22421 _ => return false,
22422 };
22423 let pair = {
22424 let mut result: Option<BracketMatch> = None;
22425
22426 for pair in buffer
22427 .all_bracket_ranges(range.clone())
22428 .filter(move |pair| {
22429 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22430 })
22431 {
22432 let len = pair.close_range.end - pair.open_range.start;
22433
22434 if let Some(existing) = &result {
22435 let existing_len = existing.close_range.end - existing.open_range.start;
22436 if len > existing_len {
22437 continue;
22438 }
22439 }
22440
22441 result = Some(pair);
22442 }
22443
22444 result
22445 };
22446 let Some(pair) = pair else {
22447 return false;
22448 };
22449 pair.newline_only
22450 && buffer
22451 .chars_for_range(pair.open_range.end..range.start)
22452 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22453 .all(|c| c.is_whitespace() && c != '\n')
22454}
22455
22456fn update_uncommitted_diff_for_buffer(
22457 editor: Entity<Editor>,
22458 project: &Entity<Project>,
22459 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22460 buffer: Entity<MultiBuffer>,
22461 cx: &mut App,
22462) -> Task<()> {
22463 let mut tasks = Vec::new();
22464 project.update(cx, |project, cx| {
22465 for buffer in buffers {
22466 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22467 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22468 }
22469 }
22470 });
22471 cx.spawn(async move |cx| {
22472 let diffs = future::join_all(tasks).await;
22473 if editor
22474 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22475 .unwrap_or(false)
22476 {
22477 return;
22478 }
22479
22480 buffer
22481 .update(cx, |buffer, cx| {
22482 for diff in diffs.into_iter().flatten() {
22483 buffer.add_diff(diff, cx);
22484 }
22485 })
22486 .ok();
22487 })
22488}
22489
22490fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22491 let tab_size = tab_size.get() as usize;
22492 let mut width = offset;
22493
22494 for ch in text.chars() {
22495 width += if ch == '\t' {
22496 tab_size - (width % tab_size)
22497 } else {
22498 1
22499 };
22500 }
22501
22502 width - offset
22503}
22504
22505#[cfg(test)]
22506mod tests {
22507 use super::*;
22508
22509 #[test]
22510 fn test_string_size_with_expanded_tabs() {
22511 let nz = |val| NonZeroU32::new(val).unwrap();
22512 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22513 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22514 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22515 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22516 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22517 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22518 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22519 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22520 }
22521}
22522
22523/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22524struct WordBreakingTokenizer<'a> {
22525 input: &'a str,
22526}
22527
22528impl<'a> WordBreakingTokenizer<'a> {
22529 fn new(input: &'a str) -> Self {
22530 Self { input }
22531 }
22532}
22533
22534fn is_char_ideographic(ch: char) -> bool {
22535 use unicode_script::Script::*;
22536 use unicode_script::UnicodeScript;
22537 matches!(ch.script(), Han | Tangut | Yi)
22538}
22539
22540fn is_grapheme_ideographic(text: &str) -> bool {
22541 text.chars().any(is_char_ideographic)
22542}
22543
22544fn is_grapheme_whitespace(text: &str) -> bool {
22545 text.chars().any(|x| x.is_whitespace())
22546}
22547
22548fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22549 text.chars()
22550 .next()
22551 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22552}
22553
22554#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22555enum WordBreakToken<'a> {
22556 Word { token: &'a str, grapheme_len: usize },
22557 InlineWhitespace { token: &'a str, grapheme_len: usize },
22558 Newline,
22559}
22560
22561impl<'a> Iterator for WordBreakingTokenizer<'a> {
22562 /// Yields a span, the count of graphemes in the token, and whether it was
22563 /// whitespace. Note that it also breaks at word boundaries.
22564 type Item = WordBreakToken<'a>;
22565
22566 fn next(&mut self) -> Option<Self::Item> {
22567 use unicode_segmentation::UnicodeSegmentation;
22568 if self.input.is_empty() {
22569 return None;
22570 }
22571
22572 let mut iter = self.input.graphemes(true).peekable();
22573 let mut offset = 0;
22574 let mut grapheme_len = 0;
22575 if let Some(first_grapheme) = iter.next() {
22576 let is_newline = first_grapheme == "\n";
22577 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22578 offset += first_grapheme.len();
22579 grapheme_len += 1;
22580 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22581 if let Some(grapheme) = iter.peek().copied()
22582 && should_stay_with_preceding_ideograph(grapheme)
22583 {
22584 offset += grapheme.len();
22585 grapheme_len += 1;
22586 }
22587 } else {
22588 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22589 let mut next_word_bound = words.peek().copied();
22590 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22591 next_word_bound = words.next();
22592 }
22593 while let Some(grapheme) = iter.peek().copied() {
22594 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22595 break;
22596 };
22597 if is_grapheme_whitespace(grapheme) != is_whitespace
22598 || (grapheme == "\n") != is_newline
22599 {
22600 break;
22601 };
22602 offset += grapheme.len();
22603 grapheme_len += 1;
22604 iter.next();
22605 }
22606 }
22607 let token = &self.input[..offset];
22608 self.input = &self.input[offset..];
22609 if token == "\n" {
22610 Some(WordBreakToken::Newline)
22611 } else if is_whitespace {
22612 Some(WordBreakToken::InlineWhitespace {
22613 token,
22614 grapheme_len,
22615 })
22616 } else {
22617 Some(WordBreakToken::Word {
22618 token,
22619 grapheme_len,
22620 })
22621 }
22622 } else {
22623 None
22624 }
22625 }
22626}
22627
22628#[test]
22629fn test_word_breaking_tokenizer() {
22630 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22631 ("", &[]),
22632 (" ", &[whitespace(" ", 2)]),
22633 ("Ʒ", &[word("Ʒ", 1)]),
22634 ("Ǽ", &[word("Ǽ", 1)]),
22635 ("⋑", &[word("⋑", 1)]),
22636 ("⋑⋑", &[word("⋑⋑", 2)]),
22637 (
22638 "原理,进而",
22639 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22640 ),
22641 (
22642 "hello world",
22643 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22644 ),
22645 (
22646 "hello, world",
22647 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22648 ),
22649 (
22650 " hello world",
22651 &[
22652 whitespace(" ", 2),
22653 word("hello", 5),
22654 whitespace(" ", 1),
22655 word("world", 5),
22656 ],
22657 ),
22658 (
22659 "这是什么 \n 钢笔",
22660 &[
22661 word("这", 1),
22662 word("是", 1),
22663 word("什", 1),
22664 word("么", 1),
22665 whitespace(" ", 1),
22666 newline(),
22667 whitespace(" ", 1),
22668 word("钢", 1),
22669 word("笔", 1),
22670 ],
22671 ),
22672 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22673 ];
22674
22675 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22676 WordBreakToken::Word {
22677 token,
22678 grapheme_len,
22679 }
22680 }
22681
22682 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22683 WordBreakToken::InlineWhitespace {
22684 token,
22685 grapheme_len,
22686 }
22687 }
22688
22689 fn newline() -> WordBreakToken<'static> {
22690 WordBreakToken::Newline
22691 }
22692
22693 for (input, result) in tests {
22694 assert_eq!(
22695 WordBreakingTokenizer::new(input)
22696 .collect::<Vec<_>>()
22697 .as_slice(),
22698 *result,
22699 );
22700 }
22701}
22702
22703fn wrap_with_prefix(
22704 first_line_prefix: String,
22705 subsequent_lines_prefix: String,
22706 unwrapped_text: String,
22707 wrap_column: usize,
22708 tab_size: NonZeroU32,
22709 preserve_existing_whitespace: bool,
22710) -> String {
22711 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22712 let subsequent_lines_prefix_len =
22713 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22714 let mut wrapped_text = String::new();
22715 let mut current_line = first_line_prefix;
22716 let mut is_first_line = true;
22717
22718 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22719 let mut current_line_len = first_line_prefix_len;
22720 let mut in_whitespace = false;
22721 for token in tokenizer {
22722 let have_preceding_whitespace = in_whitespace;
22723 match token {
22724 WordBreakToken::Word {
22725 token,
22726 grapheme_len,
22727 } => {
22728 in_whitespace = false;
22729 let current_prefix_len = if is_first_line {
22730 first_line_prefix_len
22731 } else {
22732 subsequent_lines_prefix_len
22733 };
22734 if current_line_len + grapheme_len > wrap_column
22735 && current_line_len != current_prefix_len
22736 {
22737 wrapped_text.push_str(current_line.trim_end());
22738 wrapped_text.push('\n');
22739 is_first_line = false;
22740 current_line = subsequent_lines_prefix.clone();
22741 current_line_len = subsequent_lines_prefix_len;
22742 }
22743 current_line.push_str(token);
22744 current_line_len += grapheme_len;
22745 }
22746 WordBreakToken::InlineWhitespace {
22747 mut token,
22748 mut grapheme_len,
22749 } => {
22750 in_whitespace = true;
22751 if have_preceding_whitespace && !preserve_existing_whitespace {
22752 continue;
22753 }
22754 if !preserve_existing_whitespace {
22755 // Keep a single whitespace grapheme as-is
22756 if let Some(first) =
22757 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22758 {
22759 token = first;
22760 } else {
22761 token = " ";
22762 }
22763 grapheme_len = 1;
22764 }
22765 let current_prefix_len = if is_first_line {
22766 first_line_prefix_len
22767 } else {
22768 subsequent_lines_prefix_len
22769 };
22770 if current_line_len + grapheme_len > wrap_column {
22771 wrapped_text.push_str(current_line.trim_end());
22772 wrapped_text.push('\n');
22773 is_first_line = false;
22774 current_line = subsequent_lines_prefix.clone();
22775 current_line_len = subsequent_lines_prefix_len;
22776 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22777 current_line.push_str(token);
22778 current_line_len += grapheme_len;
22779 }
22780 }
22781 WordBreakToken::Newline => {
22782 in_whitespace = true;
22783 let current_prefix_len = if is_first_line {
22784 first_line_prefix_len
22785 } else {
22786 subsequent_lines_prefix_len
22787 };
22788 if preserve_existing_whitespace {
22789 wrapped_text.push_str(current_line.trim_end());
22790 wrapped_text.push('\n');
22791 is_first_line = false;
22792 current_line = subsequent_lines_prefix.clone();
22793 current_line_len = subsequent_lines_prefix_len;
22794 } else if have_preceding_whitespace {
22795 continue;
22796 } else if current_line_len + 1 > wrap_column
22797 && current_line_len != current_prefix_len
22798 {
22799 wrapped_text.push_str(current_line.trim_end());
22800 wrapped_text.push('\n');
22801 is_first_line = false;
22802 current_line = subsequent_lines_prefix.clone();
22803 current_line_len = subsequent_lines_prefix_len;
22804 } else if current_line_len != current_prefix_len {
22805 current_line.push(' ');
22806 current_line_len += 1;
22807 }
22808 }
22809 }
22810 }
22811
22812 if !current_line.is_empty() {
22813 wrapped_text.push_str(¤t_line);
22814 }
22815 wrapped_text
22816}
22817
22818#[test]
22819fn test_wrap_with_prefix() {
22820 assert_eq!(
22821 wrap_with_prefix(
22822 "# ".to_string(),
22823 "# ".to_string(),
22824 "abcdefg".to_string(),
22825 4,
22826 NonZeroU32::new(4).unwrap(),
22827 false,
22828 ),
22829 "# abcdefg"
22830 );
22831 assert_eq!(
22832 wrap_with_prefix(
22833 "".to_string(),
22834 "".to_string(),
22835 "\thello world".to_string(),
22836 8,
22837 NonZeroU32::new(4).unwrap(),
22838 false,
22839 ),
22840 "hello\nworld"
22841 );
22842 assert_eq!(
22843 wrap_with_prefix(
22844 "// ".to_string(),
22845 "// ".to_string(),
22846 "xx \nyy zz aa bb cc".to_string(),
22847 12,
22848 NonZeroU32::new(4).unwrap(),
22849 false,
22850 ),
22851 "// xx yy zz\n// aa bb cc"
22852 );
22853 assert_eq!(
22854 wrap_with_prefix(
22855 String::new(),
22856 String::new(),
22857 "这是什么 \n 钢笔".to_string(),
22858 3,
22859 NonZeroU32::new(4).unwrap(),
22860 false,
22861 ),
22862 "这是什\n么 钢\n笔"
22863 );
22864 assert_eq!(
22865 wrap_with_prefix(
22866 String::new(),
22867 String::new(),
22868 format!("foo{}bar", '\u{2009}'), // thin space
22869 80,
22870 NonZeroU32::new(4).unwrap(),
22871 false,
22872 ),
22873 format!("foo{}bar", '\u{2009}')
22874 );
22875}
22876
22877pub trait CollaborationHub {
22878 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22879 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22880 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22881}
22882
22883impl CollaborationHub for Entity<Project> {
22884 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22885 self.read(cx).collaborators()
22886 }
22887
22888 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22889 self.read(cx).user_store().read(cx).participant_indices()
22890 }
22891
22892 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22893 let this = self.read(cx);
22894 let user_ids = this.collaborators().values().map(|c| c.user_id);
22895 this.user_store().read(cx).participant_names(user_ids, cx)
22896 }
22897}
22898
22899pub trait SemanticsProvider {
22900 fn hover(
22901 &self,
22902 buffer: &Entity<Buffer>,
22903 position: text::Anchor,
22904 cx: &mut App,
22905 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22906
22907 fn inline_values(
22908 &self,
22909 buffer_handle: Entity<Buffer>,
22910 range: Range<text::Anchor>,
22911 cx: &mut App,
22912 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22913
22914 fn applicable_inlay_chunks(
22915 &self,
22916 buffer: &Entity<Buffer>,
22917 ranges: &[Range<text::Anchor>],
22918 cx: &mut App,
22919 ) -> Vec<Range<BufferRow>>;
22920
22921 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22922
22923 fn inlay_hints(
22924 &self,
22925 invalidate: InvalidationStrategy,
22926 buffer: Entity<Buffer>,
22927 ranges: Vec<Range<text::Anchor>>,
22928 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22929 cx: &mut App,
22930 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22931
22932 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22933
22934 fn document_highlights(
22935 &self,
22936 buffer: &Entity<Buffer>,
22937 position: text::Anchor,
22938 cx: &mut App,
22939 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22940
22941 fn definitions(
22942 &self,
22943 buffer: &Entity<Buffer>,
22944 position: text::Anchor,
22945 kind: GotoDefinitionKind,
22946 cx: &mut App,
22947 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22948
22949 fn range_for_rename(
22950 &self,
22951 buffer: &Entity<Buffer>,
22952 position: text::Anchor,
22953 cx: &mut App,
22954 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22955
22956 fn perform_rename(
22957 &self,
22958 buffer: &Entity<Buffer>,
22959 position: text::Anchor,
22960 new_name: String,
22961 cx: &mut App,
22962 ) -> Option<Task<Result<ProjectTransaction>>>;
22963}
22964
22965pub trait CompletionProvider {
22966 fn completions(
22967 &self,
22968 excerpt_id: ExcerptId,
22969 buffer: &Entity<Buffer>,
22970 buffer_position: text::Anchor,
22971 trigger: CompletionContext,
22972 window: &mut Window,
22973 cx: &mut Context<Editor>,
22974 ) -> Task<Result<Vec<CompletionResponse>>>;
22975
22976 fn resolve_completions(
22977 &self,
22978 _buffer: Entity<Buffer>,
22979 _completion_indices: Vec<usize>,
22980 _completions: Rc<RefCell<Box<[Completion]>>>,
22981 _cx: &mut Context<Editor>,
22982 ) -> Task<Result<bool>> {
22983 Task::ready(Ok(false))
22984 }
22985
22986 fn apply_additional_edits_for_completion(
22987 &self,
22988 _buffer: Entity<Buffer>,
22989 _completions: Rc<RefCell<Box<[Completion]>>>,
22990 _completion_index: usize,
22991 _push_to_history: bool,
22992 _cx: &mut Context<Editor>,
22993 ) -> Task<Result<Option<language::Transaction>>> {
22994 Task::ready(Ok(None))
22995 }
22996
22997 fn is_completion_trigger(
22998 &self,
22999 buffer: &Entity<Buffer>,
23000 position: language::Anchor,
23001 text: &str,
23002 trigger_in_words: bool,
23003 menu_is_open: bool,
23004 cx: &mut Context<Editor>,
23005 ) -> bool;
23006
23007 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23008
23009 fn sort_completions(&self) -> bool {
23010 true
23011 }
23012
23013 fn filter_completions(&self) -> bool {
23014 true
23015 }
23016}
23017
23018pub trait CodeActionProvider {
23019 fn id(&self) -> Arc<str>;
23020
23021 fn code_actions(
23022 &self,
23023 buffer: &Entity<Buffer>,
23024 range: Range<text::Anchor>,
23025 window: &mut Window,
23026 cx: &mut App,
23027 ) -> Task<Result<Vec<CodeAction>>>;
23028
23029 fn apply_code_action(
23030 &self,
23031 buffer_handle: Entity<Buffer>,
23032 action: CodeAction,
23033 excerpt_id: ExcerptId,
23034 push_to_history: bool,
23035 window: &mut Window,
23036 cx: &mut App,
23037 ) -> Task<Result<ProjectTransaction>>;
23038}
23039
23040impl CodeActionProvider for Entity<Project> {
23041 fn id(&self) -> Arc<str> {
23042 "project".into()
23043 }
23044
23045 fn code_actions(
23046 &self,
23047 buffer: &Entity<Buffer>,
23048 range: Range<text::Anchor>,
23049 _window: &mut Window,
23050 cx: &mut App,
23051 ) -> Task<Result<Vec<CodeAction>>> {
23052 self.update(cx, |project, cx| {
23053 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23054 let code_actions = project.code_actions(buffer, range, None, cx);
23055 cx.background_spawn(async move {
23056 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23057 Ok(code_lens_actions
23058 .context("code lens fetch")?
23059 .into_iter()
23060 .flatten()
23061 .chain(
23062 code_actions
23063 .context("code action fetch")?
23064 .into_iter()
23065 .flatten(),
23066 )
23067 .collect())
23068 })
23069 })
23070 }
23071
23072 fn apply_code_action(
23073 &self,
23074 buffer_handle: Entity<Buffer>,
23075 action: CodeAction,
23076 _excerpt_id: ExcerptId,
23077 push_to_history: bool,
23078 _window: &mut Window,
23079 cx: &mut App,
23080 ) -> Task<Result<ProjectTransaction>> {
23081 self.update(cx, |project, cx| {
23082 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23083 })
23084 }
23085}
23086
23087fn snippet_completions(
23088 project: &Project,
23089 buffer: &Entity<Buffer>,
23090 buffer_position: text::Anchor,
23091 cx: &mut App,
23092) -> Task<Result<CompletionResponse>> {
23093 let languages = buffer.read(cx).languages_at(buffer_position);
23094 let snippet_store = project.snippets().read(cx);
23095
23096 let scopes: Vec<_> = languages
23097 .iter()
23098 .filter_map(|language| {
23099 let language_name = language.lsp_id();
23100 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23101
23102 if snippets.is_empty() {
23103 None
23104 } else {
23105 Some((language.default_scope(), snippets))
23106 }
23107 })
23108 .collect();
23109
23110 if scopes.is_empty() {
23111 return Task::ready(Ok(CompletionResponse {
23112 completions: vec![],
23113 display_options: CompletionDisplayOptions::default(),
23114 is_incomplete: false,
23115 }));
23116 }
23117
23118 let snapshot = buffer.read(cx).text_snapshot();
23119 let executor = cx.background_executor().clone();
23120
23121 cx.background_spawn(async move {
23122 let mut is_incomplete = false;
23123 let mut completions: Vec<Completion> = Vec::new();
23124 for (scope, snippets) in scopes.into_iter() {
23125 let classifier =
23126 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23127
23128 const MAX_WORD_PREFIX_LEN: usize = 128;
23129 let last_word: String = snapshot
23130 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23131 .take(MAX_WORD_PREFIX_LEN)
23132 .take_while(|c| classifier.is_word(*c))
23133 .collect::<String>()
23134 .chars()
23135 .rev()
23136 .collect();
23137
23138 if last_word.is_empty() {
23139 return Ok(CompletionResponse {
23140 completions: vec![],
23141 display_options: CompletionDisplayOptions::default(),
23142 is_incomplete: true,
23143 });
23144 }
23145
23146 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23147 let to_lsp = |point: &text::Anchor| {
23148 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23149 point_to_lsp(end)
23150 };
23151 let lsp_end = to_lsp(&buffer_position);
23152
23153 let candidates = snippets
23154 .iter()
23155 .enumerate()
23156 .flat_map(|(ix, snippet)| {
23157 snippet
23158 .prefix
23159 .iter()
23160 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23161 })
23162 .collect::<Vec<StringMatchCandidate>>();
23163
23164 const MAX_RESULTS: usize = 100;
23165 let mut matches = fuzzy::match_strings(
23166 &candidates,
23167 &last_word,
23168 last_word.chars().any(|c| c.is_uppercase()),
23169 true,
23170 MAX_RESULTS,
23171 &Default::default(),
23172 executor.clone(),
23173 )
23174 .await;
23175
23176 if matches.len() >= MAX_RESULTS {
23177 is_incomplete = true;
23178 }
23179
23180 // Remove all candidates where the query's start does not match the start of any word in the candidate
23181 if let Some(query_start) = last_word.chars().next() {
23182 matches.retain(|string_match| {
23183 split_words(&string_match.string).any(|word| {
23184 // Check that the first codepoint of the word as lowercase matches the first
23185 // codepoint of the query as lowercase
23186 word.chars()
23187 .flat_map(|codepoint| codepoint.to_lowercase())
23188 .zip(query_start.to_lowercase())
23189 .all(|(word_cp, query_cp)| word_cp == query_cp)
23190 })
23191 });
23192 }
23193
23194 let matched_strings = matches
23195 .into_iter()
23196 .map(|m| m.string)
23197 .collect::<HashSet<_>>();
23198
23199 completions.extend(snippets.iter().filter_map(|snippet| {
23200 let matching_prefix = snippet
23201 .prefix
23202 .iter()
23203 .find(|prefix| matched_strings.contains(*prefix))?;
23204 let start = as_offset - last_word.len();
23205 let start = snapshot.anchor_before(start);
23206 let range = start..buffer_position;
23207 let lsp_start = to_lsp(&start);
23208 let lsp_range = lsp::Range {
23209 start: lsp_start,
23210 end: lsp_end,
23211 };
23212 Some(Completion {
23213 replace_range: range,
23214 new_text: snippet.body.clone(),
23215 source: CompletionSource::Lsp {
23216 insert_range: None,
23217 server_id: LanguageServerId(usize::MAX),
23218 resolved: true,
23219 lsp_completion: Box::new(lsp::CompletionItem {
23220 label: snippet.prefix.first().unwrap().clone(),
23221 kind: Some(CompletionItemKind::SNIPPET),
23222 label_details: snippet.description.as_ref().map(|description| {
23223 lsp::CompletionItemLabelDetails {
23224 detail: Some(description.clone()),
23225 description: None,
23226 }
23227 }),
23228 insert_text_format: Some(InsertTextFormat::SNIPPET),
23229 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23230 lsp::InsertReplaceEdit {
23231 new_text: snippet.body.clone(),
23232 insert: lsp_range,
23233 replace: lsp_range,
23234 },
23235 )),
23236 filter_text: Some(snippet.body.clone()),
23237 sort_text: Some(char::MAX.to_string()),
23238 ..lsp::CompletionItem::default()
23239 }),
23240 lsp_defaults: None,
23241 },
23242 label: CodeLabel::plain(matching_prefix.clone(), None),
23243 icon_path: None,
23244 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23245 single_line: snippet.name.clone().into(),
23246 plain_text: snippet
23247 .description
23248 .clone()
23249 .map(|description| description.into()),
23250 }),
23251 insert_text_mode: None,
23252 confirm: None,
23253 })
23254 }))
23255 }
23256
23257 Ok(CompletionResponse {
23258 completions,
23259 display_options: CompletionDisplayOptions::default(),
23260 is_incomplete,
23261 })
23262 })
23263}
23264
23265impl CompletionProvider for Entity<Project> {
23266 fn completions(
23267 &self,
23268 _excerpt_id: ExcerptId,
23269 buffer: &Entity<Buffer>,
23270 buffer_position: text::Anchor,
23271 options: CompletionContext,
23272 _window: &mut Window,
23273 cx: &mut Context<Editor>,
23274 ) -> Task<Result<Vec<CompletionResponse>>> {
23275 self.update(cx, |project, cx| {
23276 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23277 let project_completions = project.completions(buffer, buffer_position, options, cx);
23278 cx.background_spawn(async move {
23279 let mut responses = project_completions.await?;
23280 let snippets = snippets.await?;
23281 if !snippets.completions.is_empty() {
23282 responses.push(snippets);
23283 }
23284 Ok(responses)
23285 })
23286 })
23287 }
23288
23289 fn resolve_completions(
23290 &self,
23291 buffer: Entity<Buffer>,
23292 completion_indices: Vec<usize>,
23293 completions: Rc<RefCell<Box<[Completion]>>>,
23294 cx: &mut Context<Editor>,
23295 ) -> Task<Result<bool>> {
23296 self.update(cx, |project, cx| {
23297 project.lsp_store().update(cx, |lsp_store, cx| {
23298 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23299 })
23300 })
23301 }
23302
23303 fn apply_additional_edits_for_completion(
23304 &self,
23305 buffer: Entity<Buffer>,
23306 completions: Rc<RefCell<Box<[Completion]>>>,
23307 completion_index: usize,
23308 push_to_history: bool,
23309 cx: &mut Context<Editor>,
23310 ) -> Task<Result<Option<language::Transaction>>> {
23311 self.update(cx, |project, cx| {
23312 project.lsp_store().update(cx, |lsp_store, cx| {
23313 lsp_store.apply_additional_edits_for_completion(
23314 buffer,
23315 completions,
23316 completion_index,
23317 push_to_history,
23318 cx,
23319 )
23320 })
23321 })
23322 }
23323
23324 fn is_completion_trigger(
23325 &self,
23326 buffer: &Entity<Buffer>,
23327 position: language::Anchor,
23328 text: &str,
23329 trigger_in_words: bool,
23330 menu_is_open: bool,
23331 cx: &mut Context<Editor>,
23332 ) -> bool {
23333 let mut chars = text.chars();
23334 let char = if let Some(char) = chars.next() {
23335 char
23336 } else {
23337 return false;
23338 };
23339 if chars.next().is_some() {
23340 return false;
23341 }
23342
23343 let buffer = buffer.read(cx);
23344 let snapshot = buffer.snapshot();
23345 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23346 return false;
23347 }
23348 let classifier = snapshot
23349 .char_classifier_at(position)
23350 .scope_context(Some(CharScopeContext::Completion));
23351 if trigger_in_words && classifier.is_word(char) {
23352 return true;
23353 }
23354
23355 buffer.completion_triggers().contains(text)
23356 }
23357}
23358
23359impl SemanticsProvider for Entity<Project> {
23360 fn hover(
23361 &self,
23362 buffer: &Entity<Buffer>,
23363 position: text::Anchor,
23364 cx: &mut App,
23365 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23366 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23367 }
23368
23369 fn document_highlights(
23370 &self,
23371 buffer: &Entity<Buffer>,
23372 position: text::Anchor,
23373 cx: &mut App,
23374 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23375 Some(self.update(cx, |project, cx| {
23376 project.document_highlights(buffer, position, cx)
23377 }))
23378 }
23379
23380 fn definitions(
23381 &self,
23382 buffer: &Entity<Buffer>,
23383 position: text::Anchor,
23384 kind: GotoDefinitionKind,
23385 cx: &mut App,
23386 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23387 Some(self.update(cx, |project, cx| match kind {
23388 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23389 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23390 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23391 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23392 }))
23393 }
23394
23395 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23396 self.update(cx, |project, cx| {
23397 if project
23398 .active_debug_session(cx)
23399 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23400 {
23401 return true;
23402 }
23403
23404 buffer.update(cx, |buffer, cx| {
23405 project.any_language_server_supports_inlay_hints(buffer, cx)
23406 })
23407 })
23408 }
23409
23410 fn inline_values(
23411 &self,
23412 buffer_handle: Entity<Buffer>,
23413 range: Range<text::Anchor>,
23414 cx: &mut App,
23415 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23416 self.update(cx, |project, cx| {
23417 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23418
23419 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23420 })
23421 }
23422
23423 fn applicable_inlay_chunks(
23424 &self,
23425 buffer: &Entity<Buffer>,
23426 ranges: &[Range<text::Anchor>],
23427 cx: &mut App,
23428 ) -> Vec<Range<BufferRow>> {
23429 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23430 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23431 })
23432 }
23433
23434 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23435 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23436 lsp_store.invalidate_inlay_hints(for_buffers)
23437 });
23438 }
23439
23440 fn inlay_hints(
23441 &self,
23442 invalidate: InvalidationStrategy,
23443 buffer: Entity<Buffer>,
23444 ranges: Vec<Range<text::Anchor>>,
23445 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23446 cx: &mut App,
23447 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23448 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23449 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23450 }))
23451 }
23452
23453 fn range_for_rename(
23454 &self,
23455 buffer: &Entity<Buffer>,
23456 position: text::Anchor,
23457 cx: &mut App,
23458 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23459 Some(self.update(cx, |project, cx| {
23460 let buffer = buffer.clone();
23461 let task = project.prepare_rename(buffer.clone(), position, cx);
23462 cx.spawn(async move |_, cx| {
23463 Ok(match task.await? {
23464 PrepareRenameResponse::Success(range) => Some(range),
23465 PrepareRenameResponse::InvalidPosition => None,
23466 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23467 // Fallback on using TreeSitter info to determine identifier range
23468 buffer.read_with(cx, |buffer, _| {
23469 let snapshot = buffer.snapshot();
23470 let (range, kind) = snapshot.surrounding_word(position, None);
23471 if kind != Some(CharKind::Word) {
23472 return None;
23473 }
23474 Some(
23475 snapshot.anchor_before(range.start)
23476 ..snapshot.anchor_after(range.end),
23477 )
23478 })?
23479 }
23480 })
23481 })
23482 }))
23483 }
23484
23485 fn perform_rename(
23486 &self,
23487 buffer: &Entity<Buffer>,
23488 position: text::Anchor,
23489 new_name: String,
23490 cx: &mut App,
23491 ) -> Option<Task<Result<ProjectTransaction>>> {
23492 Some(self.update(cx, |project, cx| {
23493 project.perform_rename(buffer.clone(), position, new_name, cx)
23494 }))
23495 }
23496}
23497
23498fn consume_contiguous_rows(
23499 contiguous_row_selections: &mut Vec<Selection<Point>>,
23500 selection: &Selection<Point>,
23501 display_map: &DisplaySnapshot,
23502 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23503) -> (MultiBufferRow, MultiBufferRow) {
23504 contiguous_row_selections.push(selection.clone());
23505 let start_row = starting_row(selection, display_map);
23506 let mut end_row = ending_row(selection, display_map);
23507
23508 while let Some(next_selection) = selections.peek() {
23509 if next_selection.start.row <= end_row.0 {
23510 end_row = ending_row(next_selection, display_map);
23511 contiguous_row_selections.push(selections.next().unwrap().clone());
23512 } else {
23513 break;
23514 }
23515 }
23516 (start_row, end_row)
23517}
23518
23519fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23520 if selection.start.column > 0 {
23521 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23522 } else {
23523 MultiBufferRow(selection.start.row)
23524 }
23525}
23526
23527fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23528 if next_selection.end.column > 0 || next_selection.is_empty() {
23529 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23530 } else {
23531 MultiBufferRow(next_selection.end.row)
23532 }
23533}
23534
23535impl EditorSnapshot {
23536 pub fn remote_selections_in_range<'a>(
23537 &'a self,
23538 range: &'a Range<Anchor>,
23539 collaboration_hub: &dyn CollaborationHub,
23540 cx: &'a App,
23541 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23542 let participant_names = collaboration_hub.user_names(cx);
23543 let participant_indices = collaboration_hub.user_participant_indices(cx);
23544 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23545 let collaborators_by_replica_id = collaborators_by_peer_id
23546 .values()
23547 .map(|collaborator| (collaborator.replica_id, collaborator))
23548 .collect::<HashMap<_, _>>();
23549 self.buffer_snapshot()
23550 .selections_in_range(range, false)
23551 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23552 if replica_id == ReplicaId::AGENT {
23553 Some(RemoteSelection {
23554 replica_id,
23555 selection,
23556 cursor_shape,
23557 line_mode,
23558 collaborator_id: CollaboratorId::Agent,
23559 user_name: Some("Agent".into()),
23560 color: cx.theme().players().agent(),
23561 })
23562 } else {
23563 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23564 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23565 let user_name = participant_names.get(&collaborator.user_id).cloned();
23566 Some(RemoteSelection {
23567 replica_id,
23568 selection,
23569 cursor_shape,
23570 line_mode,
23571 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23572 user_name,
23573 color: if let Some(index) = participant_index {
23574 cx.theme().players().color_for_participant(index.0)
23575 } else {
23576 cx.theme().players().absent()
23577 },
23578 })
23579 }
23580 })
23581 }
23582
23583 pub fn hunks_for_ranges(
23584 &self,
23585 ranges: impl IntoIterator<Item = Range<Point>>,
23586 ) -> Vec<MultiBufferDiffHunk> {
23587 let mut hunks = Vec::new();
23588 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23589 HashMap::default();
23590 for query_range in ranges {
23591 let query_rows =
23592 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23593 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23594 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23595 ) {
23596 // Include deleted hunks that are adjacent to the query range, because
23597 // otherwise they would be missed.
23598 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23599 if hunk.status().is_deleted() {
23600 intersects_range |= hunk.row_range.start == query_rows.end;
23601 intersects_range |= hunk.row_range.end == query_rows.start;
23602 }
23603 if intersects_range {
23604 if !processed_buffer_rows
23605 .entry(hunk.buffer_id)
23606 .or_default()
23607 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23608 {
23609 continue;
23610 }
23611 hunks.push(hunk);
23612 }
23613 }
23614 }
23615
23616 hunks
23617 }
23618
23619 fn display_diff_hunks_for_rows<'a>(
23620 &'a self,
23621 display_rows: Range<DisplayRow>,
23622 folded_buffers: &'a HashSet<BufferId>,
23623 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23624 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23625 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23626
23627 self.buffer_snapshot()
23628 .diff_hunks_in_range(buffer_start..buffer_end)
23629 .filter_map(|hunk| {
23630 if folded_buffers.contains(&hunk.buffer_id) {
23631 return None;
23632 }
23633
23634 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23635 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23636
23637 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23638 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23639
23640 let display_hunk = if hunk_display_start.column() != 0 {
23641 DisplayDiffHunk::Folded {
23642 display_row: hunk_display_start.row(),
23643 }
23644 } else {
23645 let mut end_row = hunk_display_end.row();
23646 if hunk_display_end.column() > 0 {
23647 end_row.0 += 1;
23648 }
23649 let is_created_file = hunk.is_created_file();
23650 DisplayDiffHunk::Unfolded {
23651 status: hunk.status(),
23652 diff_base_byte_range: hunk.diff_base_byte_range,
23653 display_row_range: hunk_display_start.row()..end_row,
23654 multi_buffer_range: Anchor::range_in_buffer(
23655 hunk.excerpt_id,
23656 hunk.buffer_id,
23657 hunk.buffer_range,
23658 ),
23659 is_created_file,
23660 }
23661 };
23662
23663 Some(display_hunk)
23664 })
23665 }
23666
23667 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23668 self.display_snapshot
23669 .buffer_snapshot()
23670 .language_at(position)
23671 }
23672
23673 pub fn is_focused(&self) -> bool {
23674 self.is_focused
23675 }
23676
23677 pub fn placeholder_text(&self) -> Option<String> {
23678 self.placeholder_display_snapshot
23679 .as_ref()
23680 .map(|display_map| display_map.text())
23681 }
23682
23683 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23684 self.scroll_anchor.scroll_position(&self.display_snapshot)
23685 }
23686
23687 fn gutter_dimensions(
23688 &self,
23689 font_id: FontId,
23690 font_size: Pixels,
23691 max_line_number_width: Pixels,
23692 cx: &App,
23693 ) -> Option<GutterDimensions> {
23694 if !self.show_gutter {
23695 return None;
23696 }
23697
23698 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23699 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23700
23701 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23702 matches!(
23703 ProjectSettings::get_global(cx).git.git_gutter,
23704 GitGutterSetting::TrackedFiles
23705 )
23706 });
23707 let gutter_settings = EditorSettings::get_global(cx).gutter;
23708 let show_line_numbers = self
23709 .show_line_numbers
23710 .unwrap_or(gutter_settings.line_numbers);
23711 let line_gutter_width = if show_line_numbers {
23712 // Avoid flicker-like gutter resizes when the line number gains another digit by
23713 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23714 let min_width_for_number_on_gutter =
23715 ch_advance * gutter_settings.min_line_number_digits as f32;
23716 max_line_number_width.max(min_width_for_number_on_gutter)
23717 } else {
23718 0.0.into()
23719 };
23720
23721 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23722 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23723
23724 let git_blame_entries_width =
23725 self.git_blame_gutter_max_author_length
23726 .map(|max_author_length| {
23727 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23728 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23729
23730 /// The number of characters to dedicate to gaps and margins.
23731 const SPACING_WIDTH: usize = 4;
23732
23733 let max_char_count = max_author_length.min(renderer.max_author_length())
23734 + ::git::SHORT_SHA_LENGTH
23735 + MAX_RELATIVE_TIMESTAMP.len()
23736 + SPACING_WIDTH;
23737
23738 ch_advance * max_char_count
23739 });
23740
23741 let is_singleton = self.buffer_snapshot().is_singleton();
23742
23743 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23744 left_padding += if !is_singleton {
23745 ch_width * 4.0
23746 } else if show_runnables || show_breakpoints {
23747 ch_width * 3.0
23748 } else if show_git_gutter && show_line_numbers {
23749 ch_width * 2.0
23750 } else if show_git_gutter || show_line_numbers {
23751 ch_width
23752 } else {
23753 px(0.)
23754 };
23755
23756 let shows_folds = is_singleton && gutter_settings.folds;
23757
23758 let right_padding = if shows_folds && show_line_numbers {
23759 ch_width * 4.0
23760 } else if shows_folds || (!is_singleton && show_line_numbers) {
23761 ch_width * 3.0
23762 } else if show_line_numbers {
23763 ch_width
23764 } else {
23765 px(0.)
23766 };
23767
23768 Some(GutterDimensions {
23769 left_padding,
23770 right_padding,
23771 width: line_gutter_width + left_padding + right_padding,
23772 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23773 git_blame_entries_width,
23774 })
23775 }
23776
23777 pub fn render_crease_toggle(
23778 &self,
23779 buffer_row: MultiBufferRow,
23780 row_contains_cursor: bool,
23781 editor: Entity<Editor>,
23782 window: &mut Window,
23783 cx: &mut App,
23784 ) -> Option<AnyElement> {
23785 let folded = self.is_line_folded(buffer_row);
23786 let mut is_foldable = false;
23787
23788 if let Some(crease) = self
23789 .crease_snapshot
23790 .query_row(buffer_row, self.buffer_snapshot())
23791 {
23792 is_foldable = true;
23793 match crease {
23794 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23795 if let Some(render_toggle) = render_toggle {
23796 let toggle_callback =
23797 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23798 if folded {
23799 editor.update(cx, |editor, cx| {
23800 editor.fold_at(buffer_row, window, cx)
23801 });
23802 } else {
23803 editor.update(cx, |editor, cx| {
23804 editor.unfold_at(buffer_row, window, cx)
23805 });
23806 }
23807 });
23808 return Some((render_toggle)(
23809 buffer_row,
23810 folded,
23811 toggle_callback,
23812 window,
23813 cx,
23814 ));
23815 }
23816 }
23817 }
23818 }
23819
23820 is_foldable |= self.starts_indent(buffer_row);
23821
23822 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23823 Some(
23824 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23825 .toggle_state(folded)
23826 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23827 if folded {
23828 this.unfold_at(buffer_row, window, cx);
23829 } else {
23830 this.fold_at(buffer_row, window, cx);
23831 }
23832 }))
23833 .into_any_element(),
23834 )
23835 } else {
23836 None
23837 }
23838 }
23839
23840 pub fn render_crease_trailer(
23841 &self,
23842 buffer_row: MultiBufferRow,
23843 window: &mut Window,
23844 cx: &mut App,
23845 ) -> Option<AnyElement> {
23846 let folded = self.is_line_folded(buffer_row);
23847 if let Crease::Inline { render_trailer, .. } = self
23848 .crease_snapshot
23849 .query_row(buffer_row, self.buffer_snapshot())?
23850 {
23851 let render_trailer = render_trailer.as_ref()?;
23852 Some(render_trailer(buffer_row, folded, window, cx))
23853 } else {
23854 None
23855 }
23856 }
23857}
23858
23859impl Deref for EditorSnapshot {
23860 type Target = DisplaySnapshot;
23861
23862 fn deref(&self) -> &Self::Target {
23863 &self.display_snapshot
23864 }
23865}
23866
23867#[derive(Clone, Debug, PartialEq, Eq)]
23868pub enum EditorEvent {
23869 InputIgnored {
23870 text: Arc<str>,
23871 },
23872 InputHandled {
23873 utf16_range_to_replace: Option<Range<isize>>,
23874 text: Arc<str>,
23875 },
23876 ExcerptsAdded {
23877 buffer: Entity<Buffer>,
23878 predecessor: ExcerptId,
23879 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23880 },
23881 ExcerptsRemoved {
23882 ids: Vec<ExcerptId>,
23883 removed_buffer_ids: Vec<BufferId>,
23884 },
23885 BufferFoldToggled {
23886 ids: Vec<ExcerptId>,
23887 folded: bool,
23888 },
23889 ExcerptsEdited {
23890 ids: Vec<ExcerptId>,
23891 },
23892 ExcerptsExpanded {
23893 ids: Vec<ExcerptId>,
23894 },
23895 BufferEdited,
23896 Edited {
23897 transaction_id: clock::Lamport,
23898 },
23899 Reparsed(BufferId),
23900 Focused,
23901 FocusedIn,
23902 Blurred,
23903 DirtyChanged,
23904 Saved,
23905 TitleChanged,
23906 SelectionsChanged {
23907 local: bool,
23908 },
23909 ScrollPositionChanged {
23910 local: bool,
23911 autoscroll: bool,
23912 },
23913 TransactionUndone {
23914 transaction_id: clock::Lamport,
23915 },
23916 TransactionBegun {
23917 transaction_id: clock::Lamport,
23918 },
23919 CursorShapeChanged,
23920 BreadcrumbsChanged,
23921 PushedToNavHistory {
23922 anchor: Anchor,
23923 is_deactivate: bool,
23924 },
23925}
23926
23927impl EventEmitter<EditorEvent> for Editor {}
23928
23929impl Focusable for Editor {
23930 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23931 self.focus_handle.clone()
23932 }
23933}
23934
23935impl Render for Editor {
23936 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23937 let settings = ThemeSettings::get_global(cx);
23938
23939 let mut text_style = match self.mode {
23940 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23941 color: cx.theme().colors().editor_foreground,
23942 font_family: settings.ui_font.family.clone(),
23943 font_features: settings.ui_font.features.clone(),
23944 font_fallbacks: settings.ui_font.fallbacks.clone(),
23945 font_size: rems(0.875).into(),
23946 font_weight: settings.ui_font.weight,
23947 line_height: relative(settings.buffer_line_height.value()),
23948 ..Default::default()
23949 },
23950 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23951 color: cx.theme().colors().editor_foreground,
23952 font_family: settings.buffer_font.family.clone(),
23953 font_features: settings.buffer_font.features.clone(),
23954 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23955 font_size: settings.buffer_font_size(cx).into(),
23956 font_weight: settings.buffer_font.weight,
23957 line_height: relative(settings.buffer_line_height.value()),
23958 ..Default::default()
23959 },
23960 };
23961 if let Some(text_style_refinement) = &self.text_style_refinement {
23962 text_style.refine(text_style_refinement)
23963 }
23964
23965 let background = match self.mode {
23966 EditorMode::SingleLine => cx.theme().system().transparent,
23967 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23968 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23969 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23970 };
23971
23972 EditorElement::new(
23973 &cx.entity(),
23974 EditorStyle {
23975 background,
23976 border: cx.theme().colors().border,
23977 local_player: cx.theme().players().local(),
23978 text: text_style,
23979 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23980 syntax: cx.theme().syntax().clone(),
23981 status: cx.theme().status().clone(),
23982 inlay_hints_style: make_inlay_hints_style(cx),
23983 edit_prediction_styles: make_suggestion_styles(cx),
23984 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23985 show_underlines: self.diagnostics_enabled(),
23986 },
23987 )
23988 }
23989}
23990
23991impl EntityInputHandler for Editor {
23992 fn text_for_range(
23993 &mut self,
23994 range_utf16: Range<usize>,
23995 adjusted_range: &mut Option<Range<usize>>,
23996 _: &mut Window,
23997 cx: &mut Context<Self>,
23998 ) -> Option<String> {
23999 let snapshot = self.buffer.read(cx).read(cx);
24000 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24001 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24002 if (start.0..end.0) != range_utf16 {
24003 adjusted_range.replace(start.0..end.0);
24004 }
24005 Some(snapshot.text_for_range(start..end).collect())
24006 }
24007
24008 fn selected_text_range(
24009 &mut self,
24010 ignore_disabled_input: bool,
24011 _: &mut Window,
24012 cx: &mut Context<Self>,
24013 ) -> Option<UTF16Selection> {
24014 // Prevent the IME menu from appearing when holding down an alphabetic key
24015 // while input is disabled.
24016 if !ignore_disabled_input && !self.input_enabled {
24017 return None;
24018 }
24019
24020 let selection = self
24021 .selections
24022 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24023 let range = selection.range();
24024
24025 Some(UTF16Selection {
24026 range: range.start.0..range.end.0,
24027 reversed: selection.reversed,
24028 })
24029 }
24030
24031 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24032 let snapshot = self.buffer.read(cx).read(cx);
24033 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24034 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24035 }
24036
24037 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24038 self.clear_highlights::<InputComposition>(cx);
24039 self.ime_transaction.take();
24040 }
24041
24042 fn replace_text_in_range(
24043 &mut self,
24044 range_utf16: Option<Range<usize>>,
24045 text: &str,
24046 window: &mut Window,
24047 cx: &mut Context<Self>,
24048 ) {
24049 if !self.input_enabled {
24050 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24051 return;
24052 }
24053
24054 self.transact(window, cx, |this, window, cx| {
24055 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24056 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24057 Some(this.selection_replacement_ranges(range_utf16, cx))
24058 } else {
24059 this.marked_text_ranges(cx)
24060 };
24061
24062 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24063 let newest_selection_id = this.selections.newest_anchor().id;
24064 this.selections
24065 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24066 .iter()
24067 .zip(ranges_to_replace.iter())
24068 .find_map(|(selection, range)| {
24069 if selection.id == newest_selection_id {
24070 Some(
24071 (range.start.0 as isize - selection.head().0 as isize)
24072 ..(range.end.0 as isize - selection.head().0 as isize),
24073 )
24074 } else {
24075 None
24076 }
24077 })
24078 });
24079
24080 cx.emit(EditorEvent::InputHandled {
24081 utf16_range_to_replace: range_to_replace,
24082 text: text.into(),
24083 });
24084
24085 if let Some(new_selected_ranges) = new_selected_ranges {
24086 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24087 selections.select_ranges(new_selected_ranges)
24088 });
24089 this.backspace(&Default::default(), window, cx);
24090 }
24091
24092 this.handle_input(text, window, cx);
24093 });
24094
24095 if let Some(transaction) = self.ime_transaction {
24096 self.buffer.update(cx, |buffer, cx| {
24097 buffer.group_until_transaction(transaction, cx);
24098 });
24099 }
24100
24101 self.unmark_text(window, cx);
24102 }
24103
24104 fn replace_and_mark_text_in_range(
24105 &mut self,
24106 range_utf16: Option<Range<usize>>,
24107 text: &str,
24108 new_selected_range_utf16: Option<Range<usize>>,
24109 window: &mut Window,
24110 cx: &mut Context<Self>,
24111 ) {
24112 if !self.input_enabled {
24113 return;
24114 }
24115
24116 let transaction = self.transact(window, cx, |this, window, cx| {
24117 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24118 let snapshot = this.buffer.read(cx).read(cx);
24119 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24120 for marked_range in &mut marked_ranges {
24121 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24122 marked_range.start.0 += relative_range_utf16.start;
24123 marked_range.start =
24124 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24125 marked_range.end =
24126 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24127 }
24128 }
24129 Some(marked_ranges)
24130 } else if let Some(range_utf16) = range_utf16 {
24131 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24132 Some(this.selection_replacement_ranges(range_utf16, cx))
24133 } else {
24134 None
24135 };
24136
24137 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24138 let newest_selection_id = this.selections.newest_anchor().id;
24139 this.selections
24140 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24141 .iter()
24142 .zip(ranges_to_replace.iter())
24143 .find_map(|(selection, range)| {
24144 if selection.id == newest_selection_id {
24145 Some(
24146 (range.start.0 as isize - selection.head().0 as isize)
24147 ..(range.end.0 as isize - selection.head().0 as isize),
24148 )
24149 } else {
24150 None
24151 }
24152 })
24153 });
24154
24155 cx.emit(EditorEvent::InputHandled {
24156 utf16_range_to_replace: range_to_replace,
24157 text: text.into(),
24158 });
24159
24160 if let Some(ranges) = ranges_to_replace {
24161 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24162 s.select_ranges(ranges)
24163 });
24164 }
24165
24166 let marked_ranges = {
24167 let snapshot = this.buffer.read(cx).read(cx);
24168 this.selections
24169 .disjoint_anchors_arc()
24170 .iter()
24171 .map(|selection| {
24172 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24173 })
24174 .collect::<Vec<_>>()
24175 };
24176
24177 if text.is_empty() {
24178 this.unmark_text(window, cx);
24179 } else {
24180 this.highlight_text::<InputComposition>(
24181 marked_ranges.clone(),
24182 HighlightStyle {
24183 underline: Some(UnderlineStyle {
24184 thickness: px(1.),
24185 color: None,
24186 wavy: false,
24187 }),
24188 ..Default::default()
24189 },
24190 cx,
24191 );
24192 }
24193
24194 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24195 let use_autoclose = this.use_autoclose;
24196 let use_auto_surround = this.use_auto_surround;
24197 this.set_use_autoclose(false);
24198 this.set_use_auto_surround(false);
24199 this.handle_input(text, window, cx);
24200 this.set_use_autoclose(use_autoclose);
24201 this.set_use_auto_surround(use_auto_surround);
24202
24203 if let Some(new_selected_range) = new_selected_range_utf16 {
24204 let snapshot = this.buffer.read(cx).read(cx);
24205 let new_selected_ranges = marked_ranges
24206 .into_iter()
24207 .map(|marked_range| {
24208 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24209 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24210 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24211 snapshot.clip_offset_utf16(new_start, Bias::Left)
24212 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24213 })
24214 .collect::<Vec<_>>();
24215
24216 drop(snapshot);
24217 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24218 selections.select_ranges(new_selected_ranges)
24219 });
24220 }
24221 });
24222
24223 self.ime_transaction = self.ime_transaction.or(transaction);
24224 if let Some(transaction) = self.ime_transaction {
24225 self.buffer.update(cx, |buffer, cx| {
24226 buffer.group_until_transaction(transaction, cx);
24227 });
24228 }
24229
24230 if self.text_highlights::<InputComposition>(cx).is_none() {
24231 self.ime_transaction.take();
24232 }
24233 }
24234
24235 fn bounds_for_range(
24236 &mut self,
24237 range_utf16: Range<usize>,
24238 element_bounds: gpui::Bounds<Pixels>,
24239 window: &mut Window,
24240 cx: &mut Context<Self>,
24241 ) -> Option<gpui::Bounds<Pixels>> {
24242 let text_layout_details = self.text_layout_details(window);
24243 let CharacterDimensions {
24244 em_width,
24245 em_advance,
24246 line_height,
24247 } = self.character_dimensions(window);
24248
24249 let snapshot = self.snapshot(window, cx);
24250 let scroll_position = snapshot.scroll_position();
24251 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24252
24253 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24254 let x = Pixels::from(
24255 ScrollOffset::from(
24256 snapshot.x_for_display_point(start, &text_layout_details)
24257 + self.gutter_dimensions.full_width(),
24258 ) - scroll_left,
24259 );
24260 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24261
24262 Some(Bounds {
24263 origin: element_bounds.origin + point(x, y),
24264 size: size(em_width, line_height),
24265 })
24266 }
24267
24268 fn character_index_for_point(
24269 &mut self,
24270 point: gpui::Point<Pixels>,
24271 _window: &mut Window,
24272 _cx: &mut Context<Self>,
24273 ) -> Option<usize> {
24274 let position_map = self.last_position_map.as_ref()?;
24275 if !position_map.text_hitbox.contains(&point) {
24276 return None;
24277 }
24278 let display_point = position_map.point_for_position(point).previous_valid;
24279 let anchor = position_map
24280 .snapshot
24281 .display_point_to_anchor(display_point, Bias::Left);
24282 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24283 Some(utf16_offset.0)
24284 }
24285
24286 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24287 self.input_enabled
24288 }
24289}
24290
24291trait SelectionExt {
24292 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24293 fn spanned_rows(
24294 &self,
24295 include_end_if_at_line_start: bool,
24296 map: &DisplaySnapshot,
24297 ) -> Range<MultiBufferRow>;
24298}
24299
24300impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24301 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24302 let start = self
24303 .start
24304 .to_point(map.buffer_snapshot())
24305 .to_display_point(map);
24306 let end = self
24307 .end
24308 .to_point(map.buffer_snapshot())
24309 .to_display_point(map);
24310 if self.reversed {
24311 end..start
24312 } else {
24313 start..end
24314 }
24315 }
24316
24317 fn spanned_rows(
24318 &self,
24319 include_end_if_at_line_start: bool,
24320 map: &DisplaySnapshot,
24321 ) -> Range<MultiBufferRow> {
24322 let start = self.start.to_point(map.buffer_snapshot());
24323 let mut end = self.end.to_point(map.buffer_snapshot());
24324 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24325 end.row -= 1;
24326 }
24327
24328 let buffer_start = map.prev_line_boundary(start).0;
24329 let buffer_end = map.next_line_boundary(end).0;
24330 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24331 }
24332}
24333
24334impl<T: InvalidationRegion> InvalidationStack<T> {
24335 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24336 where
24337 S: Clone + ToOffset,
24338 {
24339 while let Some(region) = self.last() {
24340 let all_selections_inside_invalidation_ranges =
24341 if selections.len() == region.ranges().len() {
24342 selections
24343 .iter()
24344 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24345 .all(|(selection, invalidation_range)| {
24346 let head = selection.head().to_offset(buffer);
24347 invalidation_range.start <= head && invalidation_range.end >= head
24348 })
24349 } else {
24350 false
24351 };
24352
24353 if all_selections_inside_invalidation_ranges {
24354 break;
24355 } else {
24356 self.pop();
24357 }
24358 }
24359 }
24360}
24361
24362impl<T> Default for InvalidationStack<T> {
24363 fn default() -> Self {
24364 Self(Default::default())
24365 }
24366}
24367
24368impl<T> Deref for InvalidationStack<T> {
24369 type Target = Vec<T>;
24370
24371 fn deref(&self) -> &Self::Target {
24372 &self.0
24373 }
24374}
24375
24376impl<T> DerefMut for InvalidationStack<T> {
24377 fn deref_mut(&mut self) -> &mut Self::Target {
24378 &mut self.0
24379 }
24380}
24381
24382impl InvalidationRegion for SnippetState {
24383 fn ranges(&self) -> &[Range<Anchor>] {
24384 &self.ranges[self.active_index]
24385 }
24386}
24387
24388fn edit_prediction_edit_text(
24389 current_snapshot: &BufferSnapshot,
24390 edits: &[(Range<Anchor>, impl AsRef<str>)],
24391 edit_preview: &EditPreview,
24392 include_deletions: bool,
24393 cx: &App,
24394) -> HighlightedText {
24395 let edits = edits
24396 .iter()
24397 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24398 .collect::<Vec<_>>();
24399
24400 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24401}
24402
24403fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24404 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24405 // Just show the raw edit text with basic styling
24406 let mut text = String::new();
24407 let mut highlights = Vec::new();
24408
24409 let insertion_highlight_style = HighlightStyle {
24410 color: Some(cx.theme().colors().text),
24411 ..Default::default()
24412 };
24413
24414 for (_, edit_text) in edits {
24415 let start_offset = text.len();
24416 text.push_str(edit_text);
24417 let end_offset = text.len();
24418
24419 if start_offset < end_offset {
24420 highlights.push((start_offset..end_offset, insertion_highlight_style));
24421 }
24422 }
24423
24424 HighlightedText {
24425 text: text.into(),
24426 highlights,
24427 }
24428}
24429
24430pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24431 match severity {
24432 lsp::DiagnosticSeverity::ERROR => colors.error,
24433 lsp::DiagnosticSeverity::WARNING => colors.warning,
24434 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24435 lsp::DiagnosticSeverity::HINT => colors.info,
24436 _ => colors.ignored,
24437 }
24438}
24439
24440pub fn styled_runs_for_code_label<'a>(
24441 label: &'a CodeLabel,
24442 syntax_theme: &'a theme::SyntaxTheme,
24443) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24444 let fade_out = HighlightStyle {
24445 fade_out: Some(0.35),
24446 ..Default::default()
24447 };
24448
24449 let mut prev_end = label.filter_range.end;
24450 label
24451 .runs
24452 .iter()
24453 .enumerate()
24454 .flat_map(move |(ix, (range, highlight_id))| {
24455 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24456 style
24457 } else {
24458 return Default::default();
24459 };
24460 let muted_style = style.highlight(fade_out);
24461
24462 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24463 if range.start >= label.filter_range.end {
24464 if range.start > prev_end {
24465 runs.push((prev_end..range.start, fade_out));
24466 }
24467 runs.push((range.clone(), muted_style));
24468 } else if range.end <= label.filter_range.end {
24469 runs.push((range.clone(), style));
24470 } else {
24471 runs.push((range.start..label.filter_range.end, style));
24472 runs.push((label.filter_range.end..range.end, muted_style));
24473 }
24474 prev_end = cmp::max(prev_end, range.end);
24475
24476 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24477 runs.push((prev_end..label.text.len(), fade_out));
24478 }
24479
24480 runs
24481 })
24482}
24483
24484pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24485 let mut prev_index = 0;
24486 let mut prev_codepoint: Option<char> = None;
24487 text.char_indices()
24488 .chain([(text.len(), '\0')])
24489 .filter_map(move |(index, codepoint)| {
24490 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24491 let is_boundary = index == text.len()
24492 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24493 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24494 if is_boundary {
24495 let chunk = &text[prev_index..index];
24496 prev_index = index;
24497 Some(chunk)
24498 } else {
24499 None
24500 }
24501 })
24502}
24503
24504pub trait RangeToAnchorExt: Sized {
24505 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24506
24507 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24508 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24509 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24510 }
24511}
24512
24513impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24514 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24515 let start_offset = self.start.to_offset(snapshot);
24516 let end_offset = self.end.to_offset(snapshot);
24517 if start_offset == end_offset {
24518 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24519 } else {
24520 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24521 }
24522 }
24523}
24524
24525pub trait RowExt {
24526 fn as_f64(&self) -> f64;
24527
24528 fn next_row(&self) -> Self;
24529
24530 fn previous_row(&self) -> Self;
24531
24532 fn minus(&self, other: Self) -> u32;
24533}
24534
24535impl RowExt for DisplayRow {
24536 fn as_f64(&self) -> f64 {
24537 self.0 as _
24538 }
24539
24540 fn next_row(&self) -> Self {
24541 Self(self.0 + 1)
24542 }
24543
24544 fn previous_row(&self) -> Self {
24545 Self(self.0.saturating_sub(1))
24546 }
24547
24548 fn minus(&self, other: Self) -> u32 {
24549 self.0 - other.0
24550 }
24551}
24552
24553impl RowExt for MultiBufferRow {
24554 fn as_f64(&self) -> f64 {
24555 self.0 as _
24556 }
24557
24558 fn next_row(&self) -> Self {
24559 Self(self.0 + 1)
24560 }
24561
24562 fn previous_row(&self) -> Self {
24563 Self(self.0.saturating_sub(1))
24564 }
24565
24566 fn minus(&self, other: Self) -> u32 {
24567 self.0 - other.0
24568 }
24569}
24570
24571trait RowRangeExt {
24572 type Row;
24573
24574 fn len(&self) -> usize;
24575
24576 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24577}
24578
24579impl RowRangeExt for Range<MultiBufferRow> {
24580 type Row = MultiBufferRow;
24581
24582 fn len(&self) -> usize {
24583 (self.end.0 - self.start.0) as usize
24584 }
24585
24586 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24587 (self.start.0..self.end.0).map(MultiBufferRow)
24588 }
24589}
24590
24591impl RowRangeExt for Range<DisplayRow> {
24592 type Row = DisplayRow;
24593
24594 fn len(&self) -> usize {
24595 (self.end.0 - self.start.0) as usize
24596 }
24597
24598 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24599 (self.start.0..self.end.0).map(DisplayRow)
24600 }
24601}
24602
24603/// If select range has more than one line, we
24604/// just point the cursor to range.start.
24605fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24606 if range.start.row == range.end.row {
24607 range
24608 } else {
24609 range.start..range.start
24610 }
24611}
24612pub struct KillRing(ClipboardItem);
24613impl Global for KillRing {}
24614
24615const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24616
24617enum BreakpointPromptEditAction {
24618 Log,
24619 Condition,
24620 HitCondition,
24621}
24622
24623struct BreakpointPromptEditor {
24624 pub(crate) prompt: Entity<Editor>,
24625 editor: WeakEntity<Editor>,
24626 breakpoint_anchor: Anchor,
24627 breakpoint: Breakpoint,
24628 edit_action: BreakpointPromptEditAction,
24629 block_ids: HashSet<CustomBlockId>,
24630 editor_margins: Arc<Mutex<EditorMargins>>,
24631 _subscriptions: Vec<Subscription>,
24632}
24633
24634impl BreakpointPromptEditor {
24635 const MAX_LINES: u8 = 4;
24636
24637 fn new(
24638 editor: WeakEntity<Editor>,
24639 breakpoint_anchor: Anchor,
24640 breakpoint: Breakpoint,
24641 edit_action: BreakpointPromptEditAction,
24642 window: &mut Window,
24643 cx: &mut Context<Self>,
24644 ) -> Self {
24645 let base_text = match edit_action {
24646 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24647 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24648 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24649 }
24650 .map(|msg| msg.to_string())
24651 .unwrap_or_default();
24652
24653 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24654 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24655
24656 let prompt = cx.new(|cx| {
24657 let mut prompt = Editor::new(
24658 EditorMode::AutoHeight {
24659 min_lines: 1,
24660 max_lines: Some(Self::MAX_LINES as usize),
24661 },
24662 buffer,
24663 None,
24664 window,
24665 cx,
24666 );
24667 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24668 prompt.set_show_cursor_when_unfocused(false, cx);
24669 prompt.set_placeholder_text(
24670 match edit_action {
24671 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24672 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24673 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24674 },
24675 window,
24676 cx,
24677 );
24678
24679 prompt
24680 });
24681
24682 Self {
24683 prompt,
24684 editor,
24685 breakpoint_anchor,
24686 breakpoint,
24687 edit_action,
24688 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24689 block_ids: Default::default(),
24690 _subscriptions: vec![],
24691 }
24692 }
24693
24694 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24695 self.block_ids.extend(block_ids)
24696 }
24697
24698 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24699 if let Some(editor) = self.editor.upgrade() {
24700 let message = self
24701 .prompt
24702 .read(cx)
24703 .buffer
24704 .read(cx)
24705 .as_singleton()
24706 .expect("A multi buffer in breakpoint prompt isn't possible")
24707 .read(cx)
24708 .as_rope()
24709 .to_string();
24710
24711 editor.update(cx, |editor, cx| {
24712 editor.edit_breakpoint_at_anchor(
24713 self.breakpoint_anchor,
24714 self.breakpoint.clone(),
24715 match self.edit_action {
24716 BreakpointPromptEditAction::Log => {
24717 BreakpointEditAction::EditLogMessage(message.into())
24718 }
24719 BreakpointPromptEditAction::Condition => {
24720 BreakpointEditAction::EditCondition(message.into())
24721 }
24722 BreakpointPromptEditAction::HitCondition => {
24723 BreakpointEditAction::EditHitCondition(message.into())
24724 }
24725 },
24726 cx,
24727 );
24728
24729 editor.remove_blocks(self.block_ids.clone(), None, cx);
24730 cx.focus_self(window);
24731 });
24732 }
24733 }
24734
24735 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24736 self.editor
24737 .update(cx, |editor, cx| {
24738 editor.remove_blocks(self.block_ids.clone(), None, cx);
24739 window.focus(&editor.focus_handle);
24740 })
24741 .log_err();
24742 }
24743
24744 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24745 let settings = ThemeSettings::get_global(cx);
24746 let text_style = TextStyle {
24747 color: if self.prompt.read(cx).read_only(cx) {
24748 cx.theme().colors().text_disabled
24749 } else {
24750 cx.theme().colors().text
24751 },
24752 font_family: settings.buffer_font.family.clone(),
24753 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24754 font_size: settings.buffer_font_size(cx).into(),
24755 font_weight: settings.buffer_font.weight,
24756 line_height: relative(settings.buffer_line_height.value()),
24757 ..Default::default()
24758 };
24759 EditorElement::new(
24760 &self.prompt,
24761 EditorStyle {
24762 background: cx.theme().colors().editor_background,
24763 local_player: cx.theme().players().local(),
24764 text: text_style,
24765 ..Default::default()
24766 },
24767 )
24768 }
24769}
24770
24771impl Render for BreakpointPromptEditor {
24772 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24773 let editor_margins = *self.editor_margins.lock();
24774 let gutter_dimensions = editor_margins.gutter;
24775 h_flex()
24776 .key_context("Editor")
24777 .bg(cx.theme().colors().editor_background)
24778 .border_y_1()
24779 .border_color(cx.theme().status().info_border)
24780 .size_full()
24781 .py(window.line_height() / 2.5)
24782 .on_action(cx.listener(Self::confirm))
24783 .on_action(cx.listener(Self::cancel))
24784 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24785 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24786 }
24787}
24788
24789impl Focusable for BreakpointPromptEditor {
24790 fn focus_handle(&self, cx: &App) -> FocusHandle {
24791 self.prompt.focus_handle(cx)
24792 }
24793}
24794
24795fn all_edits_insertions_or_deletions(
24796 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24797 snapshot: &MultiBufferSnapshot,
24798) -> bool {
24799 let mut all_insertions = true;
24800 let mut all_deletions = true;
24801
24802 for (range, new_text) in edits.iter() {
24803 let range_is_empty = range.to_offset(snapshot).is_empty();
24804 let text_is_empty = new_text.is_empty();
24805
24806 if range_is_empty != text_is_empty {
24807 if range_is_empty {
24808 all_deletions = false;
24809 } else {
24810 all_insertions = false;
24811 }
24812 } else {
24813 return false;
24814 }
24815
24816 if !all_insertions && !all_deletions {
24817 return false;
24818 }
24819 }
24820 all_insertions || all_deletions
24821}
24822
24823struct MissingEditPredictionKeybindingTooltip;
24824
24825impl Render for MissingEditPredictionKeybindingTooltip {
24826 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24827 ui::tooltip_container(cx, |container, cx| {
24828 container
24829 .flex_shrink_0()
24830 .max_w_80()
24831 .min_h(rems_from_px(124.))
24832 .justify_between()
24833 .child(
24834 v_flex()
24835 .flex_1()
24836 .text_ui_sm(cx)
24837 .child(Label::new("Conflict with Accept Keybinding"))
24838 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24839 )
24840 .child(
24841 h_flex()
24842 .pb_1()
24843 .gap_1()
24844 .items_end()
24845 .w_full()
24846 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24847 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24848 }))
24849 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24850 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24851 })),
24852 )
24853 })
24854 }
24855}
24856
24857#[derive(Debug, Clone, Copy, PartialEq)]
24858pub struct LineHighlight {
24859 pub background: Background,
24860 pub border: Option<gpui::Hsla>,
24861 pub include_gutter: bool,
24862 pub type_id: Option<TypeId>,
24863}
24864
24865struct LineManipulationResult {
24866 pub new_text: String,
24867 pub line_count_before: usize,
24868 pub line_count_after: usize,
24869}
24870
24871fn render_diff_hunk_controls(
24872 row: u32,
24873 status: &DiffHunkStatus,
24874 hunk_range: Range<Anchor>,
24875 is_created_file: bool,
24876 line_height: Pixels,
24877 editor: &Entity<Editor>,
24878 _window: &mut Window,
24879 cx: &mut App,
24880) -> AnyElement {
24881 h_flex()
24882 .h(line_height)
24883 .mr_1()
24884 .gap_1()
24885 .px_0p5()
24886 .pb_1()
24887 .border_x_1()
24888 .border_b_1()
24889 .border_color(cx.theme().colors().border_variant)
24890 .rounded_b_lg()
24891 .bg(cx.theme().colors().editor_background)
24892 .gap_1()
24893 .block_mouse_except_scroll()
24894 .shadow_md()
24895 .child(if status.has_secondary_hunk() {
24896 Button::new(("stage", row as u64), "Stage")
24897 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24898 .tooltip({
24899 let focus_handle = editor.focus_handle(cx);
24900 move |_window, cx| {
24901 Tooltip::for_action_in(
24902 "Stage Hunk",
24903 &::git::ToggleStaged,
24904 &focus_handle,
24905 cx,
24906 )
24907 }
24908 })
24909 .on_click({
24910 let editor = editor.clone();
24911 move |_event, _window, cx| {
24912 editor.update(cx, |editor, cx| {
24913 editor.stage_or_unstage_diff_hunks(
24914 true,
24915 vec![hunk_range.start..hunk_range.start],
24916 cx,
24917 );
24918 });
24919 }
24920 })
24921 } else {
24922 Button::new(("unstage", row as u64), "Unstage")
24923 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24924 .tooltip({
24925 let focus_handle = editor.focus_handle(cx);
24926 move |_window, cx| {
24927 Tooltip::for_action_in(
24928 "Unstage Hunk",
24929 &::git::ToggleStaged,
24930 &focus_handle,
24931 cx,
24932 )
24933 }
24934 })
24935 .on_click({
24936 let editor = editor.clone();
24937 move |_event, _window, cx| {
24938 editor.update(cx, |editor, cx| {
24939 editor.stage_or_unstage_diff_hunks(
24940 false,
24941 vec![hunk_range.start..hunk_range.start],
24942 cx,
24943 );
24944 });
24945 }
24946 })
24947 })
24948 .child(
24949 Button::new(("restore", row as u64), "Restore")
24950 .tooltip({
24951 let focus_handle = editor.focus_handle(cx);
24952 move |_window, cx| {
24953 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24954 }
24955 })
24956 .on_click({
24957 let editor = editor.clone();
24958 move |_event, window, cx| {
24959 editor.update(cx, |editor, cx| {
24960 let snapshot = editor.snapshot(window, cx);
24961 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24962 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24963 });
24964 }
24965 })
24966 .disabled(is_created_file),
24967 )
24968 .when(
24969 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24970 |el| {
24971 el.child(
24972 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24973 .shape(IconButtonShape::Square)
24974 .icon_size(IconSize::Small)
24975 // .disabled(!has_multiple_hunks)
24976 .tooltip({
24977 let focus_handle = editor.focus_handle(cx);
24978 move |_window, cx| {
24979 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24980 }
24981 })
24982 .on_click({
24983 let editor = editor.clone();
24984 move |_event, window, cx| {
24985 editor.update(cx, |editor, cx| {
24986 let snapshot = editor.snapshot(window, cx);
24987 let position =
24988 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24989 editor.go_to_hunk_before_or_after_position(
24990 &snapshot,
24991 position,
24992 Direction::Next,
24993 window,
24994 cx,
24995 );
24996 editor.expand_selected_diff_hunks(cx);
24997 });
24998 }
24999 }),
25000 )
25001 .child(
25002 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25003 .shape(IconButtonShape::Square)
25004 .icon_size(IconSize::Small)
25005 // .disabled(!has_multiple_hunks)
25006 .tooltip({
25007 let focus_handle = editor.focus_handle(cx);
25008 move |_window, cx| {
25009 Tooltip::for_action_in(
25010 "Previous Hunk",
25011 &GoToPreviousHunk,
25012 &focus_handle,
25013 cx,
25014 )
25015 }
25016 })
25017 .on_click({
25018 let editor = editor.clone();
25019 move |_event, window, cx| {
25020 editor.update(cx, |editor, cx| {
25021 let snapshot = editor.snapshot(window, cx);
25022 let point =
25023 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25024 editor.go_to_hunk_before_or_after_position(
25025 &snapshot,
25026 point,
25027 Direction::Prev,
25028 window,
25029 cx,
25030 );
25031 editor.expand_selected_diff_hunks(cx);
25032 });
25033 }
25034 }),
25035 )
25036 },
25037 )
25038 .into_any_element()
25039}
25040
25041pub fn multibuffer_context_lines(cx: &App) -> u32 {
25042 EditorSettings::try_get(cx)
25043 .map(|settings| settings.excerpt_context_lines)
25044 .unwrap_or(2)
25045 .min(32)
25046}