1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init_settings(cx: &mut App) {
314 EditorSettings::register(cx);
315}
316
317pub fn init(cx: &mut App) {
318 init_settings(cx);
319
320 cx.set_global(GlobalBlameRenderer(Arc::new(())));
321
322 workspace::register_project_item::<Editor>(cx);
323 workspace::FollowableViewRegistry::register::<Editor>(cx);
324 workspace::register_serializable_item::<Editor>(cx);
325
326 cx.observe_new(
327 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
328 workspace.register_action(Editor::new_file);
329 workspace.register_action(Editor::new_file_split);
330 workspace.register_action(Editor::new_file_vertical);
331 workspace.register_action(Editor::new_file_horizontal);
332 workspace.register_action(Editor::cancel_language_server_work);
333 workspace.register_action(Editor::toggle_focus);
334 },
335 )
336 .detach();
337
338 cx.on_action(move |_: &workspace::NewFile, cx| {
339 let app_state = workspace::AppState::global(cx);
340 if let Some(app_state) = app_state.upgrade() {
341 workspace::open_new(
342 Default::default(),
343 app_state,
344 cx,
345 |workspace, window, cx| {
346 Editor::new_file(workspace, &Default::default(), window, cx)
347 },
348 )
349 .detach();
350 }
351 });
352 cx.on_action(move |_: &workspace::NewWindow, cx| {
353 let app_state = workspace::AppState::global(cx);
354 if let Some(app_state) = app_state.upgrade() {
355 workspace::open_new(
356 Default::default(),
357 app_state,
358 cx,
359 |workspace, window, cx| {
360 cx.activate(true);
361 Editor::new_file(workspace, &Default::default(), window, cx)
362 },
363 )
364 .detach();
365 }
366 });
367}
368
369pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
370 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
371}
372
373pub trait DiagnosticRenderer {
374 fn render_group(
375 &self,
376 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
377 buffer_id: BufferId,
378 snapshot: EditorSnapshot,
379 editor: WeakEntity<Editor>,
380 cx: &mut App,
381 ) -> Vec<BlockProperties<Anchor>>;
382
383 fn render_hover(
384 &self,
385 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
386 range: Range<Point>,
387 buffer_id: BufferId,
388 cx: &mut App,
389 ) -> Option<Entity<markdown::Markdown>>;
390
391 fn open_link(
392 &self,
393 editor: &mut Editor,
394 link: SharedString,
395 window: &mut Window,
396 cx: &mut Context<Editor>,
397 );
398}
399
400pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
401
402impl GlobalDiagnosticRenderer {
403 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
404 cx.try_global::<Self>().map(|g| g.0.clone())
405 }
406}
407
408impl gpui::Global for GlobalDiagnosticRenderer {}
409pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
410 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
411}
412
413pub struct SearchWithinRange;
414
415trait InvalidationRegion {
416 fn ranges(&self) -> &[Range<Anchor>];
417}
418
419#[derive(Clone, Debug, PartialEq)]
420pub enum SelectPhase {
421 Begin {
422 position: DisplayPoint,
423 add: bool,
424 click_count: usize,
425 },
426 BeginColumnar {
427 position: DisplayPoint,
428 reset: bool,
429 mode: ColumnarMode,
430 goal_column: u32,
431 },
432 Extend {
433 position: DisplayPoint,
434 click_count: usize,
435 },
436 Update {
437 position: DisplayPoint,
438 goal_column: u32,
439 scroll_delta: gpui::Point<f32>,
440 },
441 End,
442}
443
444#[derive(Clone, Debug, PartialEq)]
445pub enum ColumnarMode {
446 FromMouse,
447 FromSelection,
448}
449
450#[derive(Clone, Debug)]
451pub enum SelectMode {
452 Character,
453 Word(Range<Anchor>),
454 Line(Range<Anchor>),
455 All,
456}
457
458#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
459pub enum SizingBehavior {
460 /// The editor will layout itself using `size_full` and will include the vertical
461 /// scroll margin as requested by user settings.
462 #[default]
463 Default,
464 /// The editor will layout itself using `size_full`, but will not have any
465 /// vertical overscroll.
466 ExcludeOverscrollMargin,
467 /// The editor will request a vertical size according to its content and will be
468 /// layouted without a vertical scroll margin.
469 SizeByContent,
470}
471
472#[derive(Clone, PartialEq, Eq, Debug)]
473pub enum EditorMode {
474 SingleLine,
475 AutoHeight {
476 min_lines: usize,
477 max_lines: Option<usize>,
478 },
479 Full {
480 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
481 scale_ui_elements_with_buffer_font_size: bool,
482 /// When set to `true`, the editor will render a background for the active line.
483 show_active_line_background: bool,
484 /// Determines the sizing behavior for this editor
485 sizing_behavior: SizingBehavior,
486 },
487 Minimap {
488 parent: WeakEntity<Editor>,
489 },
490}
491
492impl EditorMode {
493 pub fn full() -> Self {
494 Self::Full {
495 scale_ui_elements_with_buffer_font_size: true,
496 show_active_line_background: true,
497 sizing_behavior: SizingBehavior::Default,
498 }
499 }
500
501 #[inline]
502 pub fn is_full(&self) -> bool {
503 matches!(self, Self::Full { .. })
504 }
505
506 #[inline]
507 pub fn is_single_line(&self) -> bool {
508 matches!(self, Self::SingleLine { .. })
509 }
510
511 #[inline]
512 fn is_minimap(&self) -> bool {
513 matches!(self, Self::Minimap { .. })
514 }
515}
516
517#[derive(Copy, Clone, Debug)]
518pub enum SoftWrap {
519 /// Prefer not to wrap at all.
520 ///
521 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
522 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
523 GitDiff,
524 /// Prefer a single line generally, unless an overly long line is encountered.
525 None,
526 /// Soft wrap lines that exceed the editor width.
527 EditorWidth,
528 /// Soft wrap lines at the preferred line length.
529 Column(u32),
530 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
531 Bounded(u32),
532}
533
534#[derive(Clone)]
535pub struct EditorStyle {
536 pub background: Hsla,
537 pub border: Hsla,
538 pub local_player: PlayerColor,
539 pub text: TextStyle,
540 pub scrollbar_width: Pixels,
541 pub syntax: Arc<SyntaxTheme>,
542 pub status: StatusColors,
543 pub inlay_hints_style: HighlightStyle,
544 pub edit_prediction_styles: EditPredictionStyles,
545 pub unnecessary_code_fade: f32,
546 pub show_underlines: bool,
547}
548
549impl Default for EditorStyle {
550 fn default() -> Self {
551 Self {
552 background: Hsla::default(),
553 border: Hsla::default(),
554 local_player: PlayerColor::default(),
555 text: TextStyle::default(),
556 scrollbar_width: Pixels::default(),
557 syntax: Default::default(),
558 // HACK: Status colors don't have a real default.
559 // We should look into removing the status colors from the editor
560 // style and retrieve them directly from the theme.
561 status: StatusColors::dark(),
562 inlay_hints_style: HighlightStyle::default(),
563 edit_prediction_styles: EditPredictionStyles {
564 insertion: HighlightStyle::default(),
565 whitespace: HighlightStyle::default(),
566 },
567 unnecessary_code_fade: Default::default(),
568 show_underlines: true,
569 }
570 }
571}
572
573pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
574 let show_background = language_settings::language_settings(None, None, cx)
575 .inlay_hints
576 .show_background;
577
578 let mut style = cx.theme().syntax().get("hint");
579
580 if style.color.is_none() {
581 style.color = Some(cx.theme().status().hint);
582 }
583
584 if !show_background {
585 style.background_color = None;
586 return style;
587 }
588
589 if style.background_color.is_none() {
590 style.background_color = Some(cx.theme().status().hint_background);
591 }
592
593 style
594}
595
596pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
597 EditPredictionStyles {
598 insertion: HighlightStyle {
599 color: Some(cx.theme().status().predictive),
600 ..HighlightStyle::default()
601 },
602 whitespace: HighlightStyle {
603 background_color: Some(cx.theme().status().created_background),
604 ..HighlightStyle::default()
605 },
606 }
607}
608
609type CompletionId = usize;
610
611pub(crate) enum EditDisplayMode {
612 TabAccept,
613 DiffPopover,
614 Inline,
615}
616
617enum EditPrediction {
618 Edit {
619 edits: Vec<(Range<Anchor>, String)>,
620 edit_preview: Option<EditPreview>,
621 display_mode: EditDisplayMode,
622 snapshot: BufferSnapshot,
623 },
624 /// Move to a specific location in the active editor
625 MoveWithin {
626 target: Anchor,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in a different editor (not the active one)
630 MoveOutside {
631 target: language::Anchor,
632 snapshot: BufferSnapshot,
633 },
634}
635
636struct EditPredictionState {
637 inlay_ids: Vec<InlayId>,
638 completion: EditPrediction,
639 completion_id: Option<SharedString>,
640 invalidation_range: Option<Range<Anchor>>,
641}
642
643enum EditPredictionSettings {
644 Disabled,
645 Enabled {
646 show_in_menu: bool,
647 preview_requires_modifier: bool,
648 },
649}
650
651enum EditPredictionHighlight {}
652
653#[derive(Debug, Clone)]
654struct InlineDiagnostic {
655 message: SharedString,
656 group_id: usize,
657 is_primary: bool,
658 start: Point,
659 severity: lsp::DiagnosticSeverity,
660}
661
662pub enum MenuEditPredictionsPolicy {
663 Never,
664 ByProvider,
665}
666
667pub enum EditPredictionPreview {
668 /// Modifier is not pressed
669 Inactive { released_too_fast: bool },
670 /// Modifier pressed
671 Active {
672 since: Instant,
673 previous_scroll_position: Option<ScrollAnchor>,
674 },
675}
676
677impl EditPredictionPreview {
678 pub fn released_too_fast(&self) -> bool {
679 match self {
680 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
681 EditPredictionPreview::Active { .. } => false,
682 }
683 }
684
685 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
686 if let EditPredictionPreview::Active {
687 previous_scroll_position,
688 ..
689 } = self
690 {
691 *previous_scroll_position = scroll_position;
692 }
693 }
694}
695
696pub struct ContextMenuOptions {
697 pub min_entries_visible: usize,
698 pub max_entries_visible: usize,
699 pub placement: Option<ContextMenuPlacement>,
700}
701
702#[derive(Debug, Clone, PartialEq, Eq)]
703pub enum ContextMenuPlacement {
704 Above,
705 Below,
706}
707
708#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
709struct EditorActionId(usize);
710
711impl EditorActionId {
712 pub fn post_inc(&mut self) -> Self {
713 let answer = self.0;
714
715 *self = Self(answer + 1);
716
717 Self(answer)
718 }
719}
720
721// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
722// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
723
724type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
725type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
726
727#[derive(Default)]
728struct ScrollbarMarkerState {
729 scrollbar_size: Size<Pixels>,
730 dirty: bool,
731 markers: Arc<[PaintQuad]>,
732 pending_refresh: Option<Task<Result<()>>>,
733}
734
735impl ScrollbarMarkerState {
736 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
737 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
738 }
739}
740
741#[derive(Clone, Copy, PartialEq, Eq)]
742pub enum MinimapVisibility {
743 Disabled,
744 Enabled {
745 /// The configuration currently present in the users settings.
746 setting_configuration: bool,
747 /// Whether to override the currently set visibility from the users setting.
748 toggle_override: bool,
749 },
750}
751
752impl MinimapVisibility {
753 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
754 if mode.is_full() {
755 Self::Enabled {
756 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
757 toggle_override: false,
758 }
759 } else {
760 Self::Disabled
761 }
762 }
763
764 fn hidden(&self) -> Self {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => Self::Enabled {
770 setting_configuration,
771 toggle_override: setting_configuration,
772 },
773 Self::Disabled => Self::Disabled,
774 }
775 }
776
777 fn disabled(&self) -> bool {
778 matches!(*self, Self::Disabled)
779 }
780
781 fn settings_visibility(&self) -> bool {
782 match *self {
783 Self::Enabled {
784 setting_configuration,
785 ..
786 } => setting_configuration,
787 _ => false,
788 }
789 }
790
791 fn visible(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 toggle_override,
796 } => setting_configuration ^ toggle_override,
797 _ => false,
798 }
799 }
800
801 fn toggle_visibility(&self) -> Self {
802 match *self {
803 Self::Enabled {
804 toggle_override,
805 setting_configuration,
806 } => Self::Enabled {
807 setting_configuration,
808 toggle_override: !toggle_override,
809 },
810 Self::Disabled => Self::Disabled,
811 }
812 }
813}
814
815#[derive(Clone, Debug)]
816struct RunnableTasks {
817 templates: Vec<(TaskSourceKind, TaskTemplate)>,
818 offset: multi_buffer::Anchor,
819 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
820 column: u32,
821 // Values of all named captures, including those starting with '_'
822 extra_variables: HashMap<String, String>,
823 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
824 context_range: Range<BufferOffset>,
825}
826
827impl RunnableTasks {
828 fn resolve<'a>(
829 &'a self,
830 cx: &'a task::TaskContext,
831 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
832 self.templates.iter().filter_map(|(kind, template)| {
833 template
834 .resolve_task(&kind.to_id_base(), cx)
835 .map(|task| (kind.clone(), task))
836 })
837 }
838}
839
840#[derive(Clone)]
841pub struct ResolvedTasks {
842 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
843 position: Anchor,
844}
845
846#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
847struct BufferOffset(usize);
848
849/// Addons allow storing per-editor state in other crates (e.g. Vim)
850pub trait Addon: 'static {
851 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
852
853 fn render_buffer_header_controls(
854 &self,
855 _: &ExcerptInfo,
856 _: &Window,
857 _: &App,
858 ) -> Option<AnyElement> {
859 None
860 }
861
862 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
863 None
864 }
865
866 fn to_any(&self) -> &dyn std::any::Any;
867
868 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
869 None
870 }
871}
872
873struct ChangeLocation {
874 current: Option<Vec<Anchor>>,
875 original: Vec<Anchor>,
876}
877impl ChangeLocation {
878 fn locations(&self) -> &[Anchor] {
879 self.current.as_ref().unwrap_or(&self.original)
880 }
881}
882
883/// A set of caret positions, registered when the editor was edited.
884pub struct ChangeList {
885 changes: Vec<ChangeLocation>,
886 /// Currently "selected" change.
887 position: Option<usize>,
888}
889
890impl ChangeList {
891 pub fn new() -> Self {
892 Self {
893 changes: Vec::new(),
894 position: None,
895 }
896 }
897
898 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
899 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
900 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
901 if self.changes.is_empty() {
902 return None;
903 }
904
905 let prev = self.position.unwrap_or(self.changes.len());
906 let next = if direction == Direction::Prev {
907 prev.saturating_sub(count)
908 } else {
909 (prev + count).min(self.changes.len() - 1)
910 };
911 self.position = Some(next);
912 self.changes.get(next).map(|change| change.locations())
913 }
914
915 /// Adds a new change to the list, resetting the change list position.
916 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
917 self.position.take();
918 if let Some(last) = self.changes.last_mut()
919 && group
920 {
921 last.current = Some(new_positions)
922 } else {
923 self.changes.push(ChangeLocation {
924 original: new_positions,
925 current: None,
926 });
927 }
928 }
929
930 pub fn last(&self) -> Option<&[Anchor]> {
931 self.changes.last().map(|change| change.locations())
932 }
933
934 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
935 self.changes.last().map(|change| change.original.as_slice())
936 }
937
938 pub fn invert_last_group(&mut self) {
939 if let Some(last) = self.changes.last_mut()
940 && let Some(current) = last.current.as_mut()
941 {
942 mem::swap(&mut last.original, current);
943 }
944 }
945}
946
947#[derive(Clone)]
948struct InlineBlamePopoverState {
949 scroll_handle: ScrollHandle,
950 commit_message: Option<ParsedCommitMessage>,
951 markdown: Entity<Markdown>,
952}
953
954struct InlineBlamePopover {
955 position: gpui::Point<Pixels>,
956 hide_task: Option<Task<()>>,
957 popover_bounds: Option<Bounds<Pixels>>,
958 popover_state: InlineBlamePopoverState,
959 keyboard_grace: bool,
960}
961
962enum SelectionDragState {
963 /// State when no drag related activity is detected.
964 None,
965 /// State when the mouse is down on a selection that is about to be dragged.
966 ReadyToDrag {
967 selection: Selection<Anchor>,
968 click_position: gpui::Point<Pixels>,
969 mouse_down_time: Instant,
970 },
971 /// State when the mouse is dragging the selection in the editor.
972 Dragging {
973 selection: Selection<Anchor>,
974 drop_cursor: Selection<Anchor>,
975 hide_drop_cursor: bool,
976 },
977}
978
979enum ColumnarSelectionState {
980 FromMouse {
981 selection_tail: Anchor,
982 display_point: Option<DisplayPoint>,
983 },
984 FromSelection {
985 selection_tail: Anchor,
986 },
987}
988
989/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
990/// a breakpoint on them.
991#[derive(Clone, Copy, Debug, PartialEq, Eq)]
992struct PhantomBreakpointIndicator {
993 display_row: DisplayRow,
994 /// There's a small debounce between hovering over the line and showing the indicator.
995 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
996 is_active: bool,
997 collides_with_existing_breakpoint: bool,
998}
999
1000/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1001///
1002/// See the [module level documentation](self) for more information.
1003pub struct Editor {
1004 focus_handle: FocusHandle,
1005 last_focused_descendant: Option<WeakFocusHandle>,
1006 /// The text buffer being edited
1007 buffer: Entity<MultiBuffer>,
1008 /// Map of how text in the buffer should be displayed.
1009 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1010 pub display_map: Entity<DisplayMap>,
1011 placeholder_display_map: Option<Entity<DisplayMap>>,
1012 pub selections: SelectionsCollection,
1013 pub scroll_manager: ScrollManager,
1014 /// When inline assist editors are linked, they all render cursors because
1015 /// typing enters text into each of them, even the ones that aren't focused.
1016 pub(crate) show_cursor_when_unfocused: bool,
1017 columnar_selection_state: Option<ColumnarSelectionState>,
1018 add_selections_state: Option<AddSelectionsState>,
1019 select_next_state: Option<SelectNextState>,
1020 select_prev_state: Option<SelectNextState>,
1021 selection_history: SelectionHistory,
1022 defer_selection_effects: bool,
1023 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1024 autoclose_regions: Vec<AutocloseRegion>,
1025 snippet_stack: InvalidationStack<SnippetState>,
1026 select_syntax_node_history: SelectSyntaxNodeHistory,
1027 ime_transaction: Option<TransactionId>,
1028 pub diagnostics_max_severity: DiagnosticSeverity,
1029 active_diagnostics: ActiveDiagnostic,
1030 show_inline_diagnostics: bool,
1031 inline_diagnostics_update: Task<()>,
1032 inline_diagnostics_enabled: bool,
1033 diagnostics_enabled: bool,
1034 word_completions_enabled: bool,
1035 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1036 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1037 hard_wrap: Option<usize>,
1038 project: Option<Entity<Project>>,
1039 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1040 completion_provider: Option<Rc<dyn CompletionProvider>>,
1041 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1042 blink_manager: Entity<BlinkManager>,
1043 show_cursor_names: bool,
1044 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1045 pub show_local_selections: bool,
1046 mode: EditorMode,
1047 show_breadcrumbs: bool,
1048 show_gutter: bool,
1049 show_scrollbars: ScrollbarAxes,
1050 minimap_visibility: MinimapVisibility,
1051 offset_content: bool,
1052 disable_expand_excerpt_buttons: bool,
1053 show_line_numbers: Option<bool>,
1054 use_relative_line_numbers: Option<bool>,
1055 show_git_diff_gutter: Option<bool>,
1056 show_code_actions: Option<bool>,
1057 show_runnables: Option<bool>,
1058 show_breakpoints: Option<bool>,
1059 show_wrap_guides: Option<bool>,
1060 show_indent_guides: Option<bool>,
1061 highlight_order: usize,
1062 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1063 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1064 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1065 scrollbar_marker_state: ScrollbarMarkerState,
1066 active_indent_guides_state: ActiveIndentGuidesState,
1067 nav_history: Option<ItemNavHistory>,
1068 context_menu: RefCell<Option<CodeContextMenu>>,
1069 context_menu_options: Option<ContextMenuOptions>,
1070 mouse_context_menu: Option<MouseContextMenu>,
1071 completion_tasks: Vec<(CompletionId, Task<()>)>,
1072 inline_blame_popover: Option<InlineBlamePopover>,
1073 inline_blame_popover_show_task: Option<Task<()>>,
1074 signature_help_state: SignatureHelpState,
1075 auto_signature_help: Option<bool>,
1076 find_all_references_task_sources: Vec<Anchor>,
1077 next_completion_id: CompletionId,
1078 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1079 code_actions_task: Option<Task<Result<()>>>,
1080 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1081 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1082 document_highlights_task: Option<Task<()>>,
1083 linked_editing_range_task: Option<Task<Option<()>>>,
1084 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1085 pending_rename: Option<RenameState>,
1086 searchable: bool,
1087 cursor_shape: CursorShape,
1088 current_line_highlight: Option<CurrentLineHighlight>,
1089 autoindent_mode: Option<AutoindentMode>,
1090 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1091 input_enabled: bool,
1092 use_modal_editing: bool,
1093 read_only: bool,
1094 leader_id: Option<CollaboratorId>,
1095 remote_id: Option<ViewId>,
1096 pub hover_state: HoverState,
1097 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1098 gutter_hovered: bool,
1099 hovered_link_state: Option<HoveredLinkState>,
1100 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1101 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1102 active_edit_prediction: Option<EditPredictionState>,
1103 /// Used to prevent flickering as the user types while the menu is open
1104 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1105 edit_prediction_settings: EditPredictionSettings,
1106 edit_predictions_hidden_for_vim_mode: bool,
1107 show_edit_predictions_override: Option<bool>,
1108 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1109 edit_prediction_preview: EditPredictionPreview,
1110 edit_prediction_indent_conflict: bool,
1111 edit_prediction_requires_modifier_in_indent_conflict: bool,
1112 next_inlay_id: usize,
1113 next_color_inlay_id: usize,
1114 _subscriptions: Vec<Subscription>,
1115 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1116 gutter_dimensions: GutterDimensions,
1117 style: Option<EditorStyle>,
1118 text_style_refinement: Option<TextStyleRefinement>,
1119 next_editor_action_id: EditorActionId,
1120 editor_actions: Rc<
1121 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1122 >,
1123 use_autoclose: bool,
1124 use_auto_surround: bool,
1125 auto_replace_emoji_shortcode: bool,
1126 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1127 show_git_blame_gutter: bool,
1128 show_git_blame_inline: bool,
1129 show_git_blame_inline_delay_task: Option<Task<()>>,
1130 git_blame_inline_enabled: bool,
1131 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1132 serialize_dirty_buffers: bool,
1133 show_selection_menu: Option<bool>,
1134 blame: Option<Entity<GitBlame>>,
1135 blame_subscription: Option<Subscription>,
1136 custom_context_menu: Option<
1137 Box<
1138 dyn 'static
1139 + Fn(
1140 &mut Self,
1141 DisplayPoint,
1142 &mut Window,
1143 &mut Context<Self>,
1144 ) -> Option<Entity<ui::ContextMenu>>,
1145 >,
1146 >,
1147 last_bounds: Option<Bounds<Pixels>>,
1148 last_position_map: Option<Rc<PositionMap>>,
1149 expect_bounds_change: Option<Bounds<Pixels>>,
1150 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1151 tasks_update_task: Option<Task<()>>,
1152 breakpoint_store: Option<Entity<BreakpointStore>>,
1153 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1154 hovered_diff_hunk_row: Option<DisplayRow>,
1155 pull_diagnostics_task: Task<()>,
1156 in_project_search: bool,
1157 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1158 breadcrumb_header: Option<String>,
1159 focused_block: Option<FocusedBlock>,
1160 next_scroll_position: NextScrollCursorCenterTopBottom,
1161 addons: HashMap<TypeId, Box<dyn Addon>>,
1162 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1163 load_diff_task: Option<Shared<Task<()>>>,
1164 /// Whether we are temporarily displaying a diff other than git's
1165 temporary_diff_override: bool,
1166 selection_mark_mode: bool,
1167 toggle_fold_multiple_buffers: Task<()>,
1168 _scroll_cursor_center_top_bottom_task: Task<()>,
1169 serialize_selections: Task<()>,
1170 serialize_folds: Task<()>,
1171 mouse_cursor_hidden: bool,
1172 minimap: Option<Entity<Self>>,
1173 hide_mouse_mode: HideMouseMode,
1174 pub change_list: ChangeList,
1175 inline_value_cache: InlineValueCache,
1176 selection_drag_state: SelectionDragState,
1177 colors: Option<LspColorData>,
1178 post_scroll_update: Task<()>,
1179 refresh_colors_task: Task<()>,
1180 inlay_hints: Option<LspInlayHintData>,
1181 folding_newlines: Task<()>,
1182 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1183}
1184
1185fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1186 if debounce_ms > 0 {
1187 Some(Duration::from_millis(debounce_ms))
1188 } else {
1189 None
1190 }
1191}
1192
1193#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1194enum NextScrollCursorCenterTopBottom {
1195 #[default]
1196 Center,
1197 Top,
1198 Bottom,
1199}
1200
1201impl NextScrollCursorCenterTopBottom {
1202 fn next(&self) -> Self {
1203 match self {
1204 Self::Center => Self::Top,
1205 Self::Top => Self::Bottom,
1206 Self::Bottom => Self::Center,
1207 }
1208 }
1209}
1210
1211#[derive(Clone)]
1212pub struct EditorSnapshot {
1213 pub mode: EditorMode,
1214 show_gutter: bool,
1215 show_line_numbers: Option<bool>,
1216 show_git_diff_gutter: Option<bool>,
1217 show_code_actions: Option<bool>,
1218 show_runnables: Option<bool>,
1219 show_breakpoints: Option<bool>,
1220 git_blame_gutter_max_author_length: Option<usize>,
1221 pub display_snapshot: DisplaySnapshot,
1222 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1223 is_focused: bool,
1224 scroll_anchor: ScrollAnchor,
1225 ongoing_scroll: OngoingScroll,
1226 current_line_highlight: CurrentLineHighlight,
1227 gutter_hovered: bool,
1228}
1229
1230#[derive(Default, Debug, Clone, Copy)]
1231pub struct GutterDimensions {
1232 pub left_padding: Pixels,
1233 pub right_padding: Pixels,
1234 pub width: Pixels,
1235 pub margin: Pixels,
1236 pub git_blame_entries_width: Option<Pixels>,
1237}
1238
1239impl GutterDimensions {
1240 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1241 Self {
1242 margin: Self::default_gutter_margin(font_id, font_size, cx),
1243 ..Default::default()
1244 }
1245 }
1246
1247 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1248 -cx.text_system().descent(font_id, font_size)
1249 }
1250 /// The full width of the space taken up by the gutter.
1251 pub fn full_width(&self) -> Pixels {
1252 self.margin + self.width
1253 }
1254
1255 /// The width of the space reserved for the fold indicators,
1256 /// use alongside 'justify_end' and `gutter_width` to
1257 /// right align content with the line numbers
1258 pub fn fold_area_width(&self) -> Pixels {
1259 self.margin + self.right_padding
1260 }
1261}
1262
1263struct CharacterDimensions {
1264 em_width: Pixels,
1265 em_advance: Pixels,
1266 line_height: Pixels,
1267}
1268
1269#[derive(Debug)]
1270pub struct RemoteSelection {
1271 pub replica_id: ReplicaId,
1272 pub selection: Selection<Anchor>,
1273 pub cursor_shape: CursorShape,
1274 pub collaborator_id: CollaboratorId,
1275 pub line_mode: bool,
1276 pub user_name: Option<SharedString>,
1277 pub color: PlayerColor,
1278}
1279
1280#[derive(Clone, Debug)]
1281struct SelectionHistoryEntry {
1282 selections: Arc<[Selection<Anchor>]>,
1283 select_next_state: Option<SelectNextState>,
1284 select_prev_state: Option<SelectNextState>,
1285 add_selections_state: Option<AddSelectionsState>,
1286}
1287
1288#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1289enum SelectionHistoryMode {
1290 Normal,
1291 Undoing,
1292 Redoing,
1293 Skipping,
1294}
1295
1296#[derive(Clone, PartialEq, Eq, Hash)]
1297struct HoveredCursor {
1298 replica_id: ReplicaId,
1299 selection_id: usize,
1300}
1301
1302impl Default for SelectionHistoryMode {
1303 fn default() -> Self {
1304 Self::Normal
1305 }
1306}
1307
1308#[derive(Debug)]
1309/// SelectionEffects controls the side-effects of updating the selection.
1310///
1311/// The default behaviour does "what you mostly want":
1312/// - it pushes to the nav history if the cursor moved by >10 lines
1313/// - it re-triggers completion requests
1314/// - it scrolls to fit
1315///
1316/// You might want to modify these behaviours. For example when doing a "jump"
1317/// like go to definition, we always want to add to nav history; but when scrolling
1318/// in vim mode we never do.
1319///
1320/// Similarly, you might want to disable scrolling if you don't want the viewport to
1321/// move.
1322#[derive(Clone)]
1323pub struct SelectionEffects {
1324 nav_history: Option<bool>,
1325 completions: bool,
1326 scroll: Option<Autoscroll>,
1327}
1328
1329impl Default for SelectionEffects {
1330 fn default() -> Self {
1331 Self {
1332 nav_history: None,
1333 completions: true,
1334 scroll: Some(Autoscroll::fit()),
1335 }
1336 }
1337}
1338impl SelectionEffects {
1339 pub fn scroll(scroll: Autoscroll) -> Self {
1340 Self {
1341 scroll: Some(scroll),
1342 ..Default::default()
1343 }
1344 }
1345
1346 pub fn no_scroll() -> Self {
1347 Self {
1348 scroll: None,
1349 ..Default::default()
1350 }
1351 }
1352
1353 pub fn completions(self, completions: bool) -> Self {
1354 Self {
1355 completions,
1356 ..self
1357 }
1358 }
1359
1360 pub fn nav_history(self, nav_history: bool) -> Self {
1361 Self {
1362 nav_history: Some(nav_history),
1363 ..self
1364 }
1365 }
1366}
1367
1368struct DeferredSelectionEffectsState {
1369 changed: bool,
1370 effects: SelectionEffects,
1371 old_cursor_position: Anchor,
1372 history_entry: SelectionHistoryEntry,
1373}
1374
1375#[derive(Default)]
1376struct SelectionHistory {
1377 #[allow(clippy::type_complexity)]
1378 selections_by_transaction:
1379 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1380 mode: SelectionHistoryMode,
1381 undo_stack: VecDeque<SelectionHistoryEntry>,
1382 redo_stack: VecDeque<SelectionHistoryEntry>,
1383}
1384
1385impl SelectionHistory {
1386 #[track_caller]
1387 fn insert_transaction(
1388 &mut self,
1389 transaction_id: TransactionId,
1390 selections: Arc<[Selection<Anchor>]>,
1391 ) {
1392 if selections.is_empty() {
1393 log::error!(
1394 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1395 std::panic::Location::caller()
1396 );
1397 return;
1398 }
1399 self.selections_by_transaction
1400 .insert(transaction_id, (selections, None));
1401 }
1402
1403 #[allow(clippy::type_complexity)]
1404 fn transaction(
1405 &self,
1406 transaction_id: TransactionId,
1407 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1408 self.selections_by_transaction.get(&transaction_id)
1409 }
1410
1411 #[allow(clippy::type_complexity)]
1412 fn transaction_mut(
1413 &mut self,
1414 transaction_id: TransactionId,
1415 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1416 self.selections_by_transaction.get_mut(&transaction_id)
1417 }
1418
1419 fn push(&mut self, entry: SelectionHistoryEntry) {
1420 if !entry.selections.is_empty() {
1421 match self.mode {
1422 SelectionHistoryMode::Normal => {
1423 self.push_undo(entry);
1424 self.redo_stack.clear();
1425 }
1426 SelectionHistoryMode::Undoing => self.push_redo(entry),
1427 SelectionHistoryMode::Redoing => self.push_undo(entry),
1428 SelectionHistoryMode::Skipping => {}
1429 }
1430 }
1431 }
1432
1433 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1434 if self
1435 .undo_stack
1436 .back()
1437 .is_none_or(|e| e.selections != entry.selections)
1438 {
1439 self.undo_stack.push_back(entry);
1440 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1441 self.undo_stack.pop_front();
1442 }
1443 }
1444 }
1445
1446 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1447 if self
1448 .redo_stack
1449 .back()
1450 .is_none_or(|e| e.selections != entry.selections)
1451 {
1452 self.redo_stack.push_back(entry);
1453 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1454 self.redo_stack.pop_front();
1455 }
1456 }
1457 }
1458}
1459
1460#[derive(Clone, Copy)]
1461pub struct RowHighlightOptions {
1462 pub autoscroll: bool,
1463 pub include_gutter: bool,
1464}
1465
1466impl Default for RowHighlightOptions {
1467 fn default() -> Self {
1468 Self {
1469 autoscroll: Default::default(),
1470 include_gutter: true,
1471 }
1472 }
1473}
1474
1475struct RowHighlight {
1476 index: usize,
1477 range: Range<Anchor>,
1478 color: Hsla,
1479 options: RowHighlightOptions,
1480 type_id: TypeId,
1481}
1482
1483#[derive(Clone, Debug)]
1484struct AddSelectionsState {
1485 groups: Vec<AddSelectionsGroup>,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsGroup {
1490 above: bool,
1491 stack: Vec<usize>,
1492}
1493
1494#[derive(Clone)]
1495struct SelectNextState {
1496 query: AhoCorasick,
1497 wordwise: bool,
1498 done: bool,
1499}
1500
1501impl std::fmt::Debug for SelectNextState {
1502 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1503 f.debug_struct(std::any::type_name::<Self>())
1504 .field("wordwise", &self.wordwise)
1505 .field("done", &self.done)
1506 .finish()
1507 }
1508}
1509
1510#[derive(Debug)]
1511struct AutocloseRegion {
1512 selection_id: usize,
1513 range: Range<Anchor>,
1514 pair: BracketPair,
1515}
1516
1517#[derive(Debug)]
1518struct SnippetState {
1519 ranges: Vec<Vec<Range<Anchor>>>,
1520 active_index: usize,
1521 choices: Vec<Option<Vec<String>>>,
1522}
1523
1524#[doc(hidden)]
1525pub struct RenameState {
1526 pub range: Range<Anchor>,
1527 pub old_name: Arc<str>,
1528 pub editor: Entity<Editor>,
1529 block_id: CustomBlockId,
1530}
1531
1532struct InvalidationStack<T>(Vec<T>);
1533
1534struct RegisteredEditPredictionProvider {
1535 provider: Arc<dyn EditPredictionProviderHandle>,
1536 _subscription: Subscription,
1537}
1538
1539#[derive(Debug, PartialEq, Eq)]
1540pub struct ActiveDiagnosticGroup {
1541 pub active_range: Range<Anchor>,
1542 pub active_message: String,
1543 pub group_id: usize,
1544 pub blocks: HashSet<CustomBlockId>,
1545}
1546
1547#[derive(Debug, PartialEq, Eq)]
1548
1549pub(crate) enum ActiveDiagnostic {
1550 None,
1551 All,
1552 Group(ActiveDiagnosticGroup),
1553}
1554
1555#[derive(Serialize, Deserialize, Clone, Debug)]
1556pub struct ClipboardSelection {
1557 /// The number of bytes in this selection.
1558 pub len: usize,
1559 /// Whether this was a full-line selection.
1560 pub is_entire_line: bool,
1561 /// The indentation of the first line when this content was originally copied.
1562 pub first_line_indent: u32,
1563}
1564
1565// selections, scroll behavior, was newest selection reversed
1566type SelectSyntaxNodeHistoryState = (
1567 Box<[Selection<usize>]>,
1568 SelectSyntaxNodeScrollBehavior,
1569 bool,
1570);
1571
1572#[derive(Default)]
1573struct SelectSyntaxNodeHistory {
1574 stack: Vec<SelectSyntaxNodeHistoryState>,
1575 // disable temporarily to allow changing selections without losing the stack
1576 pub disable_clearing: bool,
1577}
1578
1579impl SelectSyntaxNodeHistory {
1580 pub fn try_clear(&mut self) {
1581 if !self.disable_clearing {
1582 self.stack.clear();
1583 }
1584 }
1585
1586 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1587 self.stack.push(selection);
1588 }
1589
1590 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1591 self.stack.pop()
1592 }
1593}
1594
1595enum SelectSyntaxNodeScrollBehavior {
1596 CursorTop,
1597 FitSelection,
1598 CursorBottom,
1599}
1600
1601#[derive(Debug)]
1602pub(crate) struct NavigationData {
1603 cursor_anchor: Anchor,
1604 cursor_position: Point,
1605 scroll_anchor: ScrollAnchor,
1606 scroll_top_row: u32,
1607}
1608
1609#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1610pub enum GotoDefinitionKind {
1611 Symbol,
1612 Declaration,
1613 Type,
1614 Implementation,
1615}
1616
1617pub enum FormatTarget {
1618 Buffers(HashSet<Entity<Buffer>>),
1619 Ranges(Vec<Range<MultiBufferPoint>>),
1620}
1621
1622pub(crate) struct FocusedBlock {
1623 id: BlockId,
1624 focus_handle: WeakFocusHandle,
1625}
1626
1627#[derive(Clone)]
1628enum JumpData {
1629 MultiBufferRow {
1630 row: MultiBufferRow,
1631 line_offset_from_top: u32,
1632 },
1633 MultiBufferPoint {
1634 excerpt_id: ExcerptId,
1635 position: Point,
1636 anchor: text::Anchor,
1637 line_offset_from_top: u32,
1638 },
1639}
1640
1641pub enum MultibufferSelectionMode {
1642 First,
1643 All,
1644}
1645
1646#[derive(Clone, Copy, Debug, Default)]
1647pub struct RewrapOptions {
1648 pub override_language_settings: bool,
1649 pub preserve_existing_whitespace: bool,
1650}
1651
1652impl Editor {
1653 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1654 let buffer = cx.new(|cx| Buffer::local("", cx));
1655 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1656 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1657 }
1658
1659 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1660 let buffer = cx.new(|cx| Buffer::local("", cx));
1661 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1662 Self::new(EditorMode::full(), buffer, None, window, cx)
1663 }
1664
1665 pub fn auto_height(
1666 min_lines: usize,
1667 max_lines: usize,
1668 window: &mut Window,
1669 cx: &mut Context<Self>,
1670 ) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(
1674 EditorMode::AutoHeight {
1675 min_lines,
1676 max_lines: Some(max_lines),
1677 },
1678 buffer,
1679 None,
1680 window,
1681 cx,
1682 )
1683 }
1684
1685 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1686 /// The editor grows as tall as needed to fit its content.
1687 pub fn auto_height_unbounded(
1688 min_lines: usize,
1689 window: &mut Window,
1690 cx: &mut Context<Self>,
1691 ) -> Self {
1692 let buffer = cx.new(|cx| Buffer::local("", cx));
1693 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1694 Self::new(
1695 EditorMode::AutoHeight {
1696 min_lines,
1697 max_lines: None,
1698 },
1699 buffer,
1700 None,
1701 window,
1702 cx,
1703 )
1704 }
1705
1706 pub fn for_buffer(
1707 buffer: Entity<Buffer>,
1708 project: Option<Entity<Project>>,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1713 Self::new(EditorMode::full(), buffer, project, window, cx)
1714 }
1715
1716 pub fn for_multibuffer(
1717 buffer: Entity<MultiBuffer>,
1718 project: Option<Entity<Project>>,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 Self::new(EditorMode::full(), buffer, project, window, cx)
1723 }
1724
1725 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let mut clone = Self::new(
1727 self.mode.clone(),
1728 self.buffer.clone(),
1729 self.project.clone(),
1730 window,
1731 cx,
1732 );
1733 self.display_map.update(cx, |display_map, cx| {
1734 let snapshot = display_map.snapshot(cx);
1735 clone.display_map.update(cx, |display_map, cx| {
1736 display_map.set_state(&snapshot, cx);
1737 });
1738 });
1739 clone.folds_did_change(cx);
1740 clone.selections.clone_state(&self.selections);
1741 clone.scroll_manager.clone_state(&self.scroll_manager);
1742 clone.searchable = self.searchable;
1743 clone.read_only = self.read_only;
1744 clone
1745 }
1746
1747 pub fn new(
1748 mode: EditorMode,
1749 buffer: Entity<MultiBuffer>,
1750 project: Option<Entity<Project>>,
1751 window: &mut Window,
1752 cx: &mut Context<Self>,
1753 ) -> Self {
1754 Editor::new_internal(mode, buffer, project, None, window, cx)
1755 }
1756
1757 fn new_internal(
1758 mode: EditorMode,
1759 multi_buffer: Entity<MultiBuffer>,
1760 project: Option<Entity<Project>>,
1761 display_map: Option<Entity<DisplayMap>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 debug_assert!(
1766 display_map.is_none() || mode.is_minimap(),
1767 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1768 );
1769
1770 let full_mode = mode.is_full();
1771 let is_minimap = mode.is_minimap();
1772 let diagnostics_max_severity = if full_mode {
1773 EditorSettings::get_global(cx)
1774 .diagnostics_max_severity
1775 .unwrap_or(DiagnosticSeverity::Hint)
1776 } else {
1777 DiagnosticSeverity::Off
1778 };
1779 let style = window.text_style();
1780 let font_size = style.font_size.to_pixels(window.rem_size());
1781 let editor = cx.entity().downgrade();
1782 let fold_placeholder = FoldPlaceholder {
1783 constrain_width: false,
1784 render: Arc::new(move |fold_id, fold_range, cx| {
1785 let editor = editor.clone();
1786 div()
1787 .id(fold_id)
1788 .bg(cx.theme().colors().ghost_element_background)
1789 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1790 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1791 .rounded_xs()
1792 .size_full()
1793 .cursor_pointer()
1794 .child("⋯")
1795 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1796 .on_click(move |_, _window, cx| {
1797 editor
1798 .update(cx, |editor, cx| {
1799 editor.unfold_ranges(
1800 &[fold_range.start..fold_range.end],
1801 true,
1802 false,
1803 cx,
1804 );
1805 cx.stop_propagation();
1806 })
1807 .ok();
1808 })
1809 .into_any()
1810 }),
1811 merge_adjacent: true,
1812 ..FoldPlaceholder::default()
1813 };
1814 let display_map = display_map.unwrap_or_else(|| {
1815 cx.new(|cx| {
1816 DisplayMap::new(
1817 multi_buffer.clone(),
1818 style.font(),
1819 font_size,
1820 None,
1821 FILE_HEADER_HEIGHT,
1822 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1823 fold_placeholder,
1824 diagnostics_max_severity,
1825 cx,
1826 )
1827 })
1828 });
1829
1830 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1831
1832 let blink_manager = cx.new(|cx| {
1833 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1834 if is_minimap {
1835 blink_manager.disable(cx);
1836 }
1837 blink_manager
1838 });
1839
1840 let soft_wrap_mode_override =
1841 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1842
1843 let mut project_subscriptions = Vec::new();
1844 if full_mode && let Some(project) = project.as_ref() {
1845 project_subscriptions.push(cx.subscribe_in(
1846 project,
1847 window,
1848 |editor, _, event, window, cx| match event {
1849 project::Event::RefreshCodeLens => {
1850 // we always query lens with actions, without storing them, always refreshing them
1851 }
1852 project::Event::RefreshInlayHints {
1853 server_id,
1854 request_id,
1855 } => {
1856 editor.refresh_inlay_hints(
1857 InlayHintRefreshReason::RefreshRequested {
1858 server_id: *server_id,
1859 request_id: *request_id,
1860 },
1861 cx,
1862 );
1863 }
1864 project::Event::LanguageServerRemoved(..) => {
1865 if editor.tasks_update_task.is_none() {
1866 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1867 }
1868 editor.registered_buffers.clear();
1869 editor.register_visible_buffers(cx);
1870 }
1871 project::Event::LanguageServerAdded(..) => {
1872 if editor.tasks_update_task.is_none() {
1873 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1874 }
1875 }
1876 project::Event::SnippetEdit(id, snippet_edits) => {
1877 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1878 let focus_handle = editor.focus_handle(cx);
1879 if focus_handle.is_focused(window) {
1880 let snapshot = buffer.read(cx).snapshot();
1881 for (range, snippet) in snippet_edits {
1882 let editor_range =
1883 language::range_from_lsp(*range).to_offset(&snapshot);
1884 editor
1885 .insert_snippet(
1886 &[editor_range],
1887 snippet.clone(),
1888 window,
1889 cx,
1890 )
1891 .ok();
1892 }
1893 }
1894 }
1895 }
1896 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1897 let buffer_id = *buffer_id;
1898 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1899 editor.register_buffer(buffer_id, cx);
1900 editor.update_lsp_data(Some(buffer_id), window, cx);
1901 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1902 refresh_linked_ranges(editor, window, cx);
1903 editor.refresh_code_actions(window, cx);
1904 editor.refresh_document_highlights(cx);
1905 }
1906 }
1907
1908 project::Event::EntryRenamed(transaction) => {
1909 let Some(workspace) = editor.workspace() else {
1910 return;
1911 };
1912 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1913 else {
1914 return;
1915 };
1916 if active_editor.entity_id() == cx.entity_id() {
1917 let edited_buffers_already_open = {
1918 let other_editors: Vec<Entity<Editor>> = workspace
1919 .read(cx)
1920 .panes()
1921 .iter()
1922 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1923 .filter(|editor| editor.entity_id() != cx.entity_id())
1924 .collect();
1925
1926 transaction.0.keys().all(|buffer| {
1927 other_editors.iter().any(|editor| {
1928 let multi_buffer = editor.read(cx).buffer();
1929 multi_buffer.read(cx).is_singleton()
1930 && multi_buffer.read(cx).as_singleton().map_or(
1931 false,
1932 |singleton| {
1933 singleton.entity_id() == buffer.entity_id()
1934 },
1935 )
1936 })
1937 })
1938 };
1939
1940 if !edited_buffers_already_open {
1941 let workspace = workspace.downgrade();
1942 let transaction = transaction.clone();
1943 cx.defer_in(window, move |_, window, cx| {
1944 cx.spawn_in(window, async move |editor, cx| {
1945 Self::open_project_transaction(
1946 &editor,
1947 workspace,
1948 transaction,
1949 "Rename".to_string(),
1950 cx,
1951 )
1952 .await
1953 .ok()
1954 })
1955 .detach();
1956 });
1957 }
1958 }
1959 }
1960
1961 _ => {}
1962 },
1963 ));
1964 if let Some(task_inventory) = project
1965 .read(cx)
1966 .task_store()
1967 .read(cx)
1968 .task_inventory()
1969 .cloned()
1970 {
1971 project_subscriptions.push(cx.observe_in(
1972 &task_inventory,
1973 window,
1974 |editor, _, window, cx| {
1975 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1976 },
1977 ));
1978 };
1979
1980 project_subscriptions.push(cx.subscribe_in(
1981 &project.read(cx).breakpoint_store(),
1982 window,
1983 |editor, _, event, window, cx| match event {
1984 BreakpointStoreEvent::ClearDebugLines => {
1985 editor.clear_row_highlights::<ActiveDebugLine>();
1986 editor.refresh_inline_values(cx);
1987 }
1988 BreakpointStoreEvent::SetDebugLine => {
1989 if editor.go_to_active_debug_line(window, cx) {
1990 cx.stop_propagation();
1991 }
1992
1993 editor.refresh_inline_values(cx);
1994 }
1995 _ => {}
1996 },
1997 ));
1998 let git_store = project.read(cx).git_store().clone();
1999 let project = project.clone();
2000 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2001 if let GitStoreEvent::RepositoryAdded = event {
2002 this.load_diff_task = Some(
2003 update_uncommitted_diff_for_buffer(
2004 cx.entity(),
2005 &project,
2006 this.buffer.read(cx).all_buffers(),
2007 this.buffer.clone(),
2008 cx,
2009 )
2010 .shared(),
2011 );
2012 }
2013 }));
2014 }
2015
2016 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2017
2018 let inlay_hint_settings =
2019 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2020 let focus_handle = cx.focus_handle();
2021 if !is_minimap {
2022 cx.on_focus(&focus_handle, window, Self::handle_focus)
2023 .detach();
2024 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2025 .detach();
2026 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2027 .detach();
2028 cx.on_blur(&focus_handle, window, Self::handle_blur)
2029 .detach();
2030 cx.observe_pending_input(window, Self::observe_pending_input)
2031 .detach();
2032 }
2033
2034 let show_indent_guides =
2035 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2036 Some(false)
2037 } else {
2038 None
2039 };
2040
2041 let breakpoint_store = match (&mode, project.as_ref()) {
2042 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2043 _ => None,
2044 };
2045
2046 let mut code_action_providers = Vec::new();
2047 let mut load_uncommitted_diff = None;
2048 if let Some(project) = project.clone() {
2049 load_uncommitted_diff = Some(
2050 update_uncommitted_diff_for_buffer(
2051 cx.entity(),
2052 &project,
2053 multi_buffer.read(cx).all_buffers(),
2054 multi_buffer.clone(),
2055 cx,
2056 )
2057 .shared(),
2058 );
2059 code_action_providers.push(Rc::new(project) as Rc<_>);
2060 }
2061
2062 let mut editor = Self {
2063 focus_handle,
2064 show_cursor_when_unfocused: false,
2065 last_focused_descendant: None,
2066 buffer: multi_buffer.clone(),
2067 display_map: display_map.clone(),
2068 placeholder_display_map: None,
2069 selections,
2070 scroll_manager: ScrollManager::new(cx),
2071 columnar_selection_state: None,
2072 add_selections_state: None,
2073 select_next_state: None,
2074 select_prev_state: None,
2075 selection_history: SelectionHistory::default(),
2076 defer_selection_effects: false,
2077 deferred_selection_effects_state: None,
2078 autoclose_regions: Vec::new(),
2079 snippet_stack: InvalidationStack::default(),
2080 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2081 ime_transaction: None,
2082 active_diagnostics: ActiveDiagnostic::None,
2083 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2084 inline_diagnostics_update: Task::ready(()),
2085 inline_diagnostics: Vec::new(),
2086 soft_wrap_mode_override,
2087 diagnostics_max_severity,
2088 hard_wrap: None,
2089 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2090 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2091 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2092 project,
2093 blink_manager: blink_manager.clone(),
2094 show_local_selections: true,
2095 show_scrollbars: ScrollbarAxes {
2096 horizontal: full_mode,
2097 vertical: full_mode,
2098 },
2099 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2100 offset_content: !matches!(mode, EditorMode::SingleLine),
2101 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2102 show_gutter: full_mode,
2103 show_line_numbers: (!full_mode).then_some(false),
2104 use_relative_line_numbers: None,
2105 disable_expand_excerpt_buttons: !full_mode,
2106 show_git_diff_gutter: None,
2107 show_code_actions: None,
2108 show_runnables: None,
2109 show_breakpoints: None,
2110 show_wrap_guides: None,
2111 show_indent_guides,
2112 highlight_order: 0,
2113 highlighted_rows: HashMap::default(),
2114 background_highlights: HashMap::default(),
2115 gutter_highlights: HashMap::default(),
2116 scrollbar_marker_state: ScrollbarMarkerState::default(),
2117 active_indent_guides_state: ActiveIndentGuidesState::default(),
2118 nav_history: None,
2119 context_menu: RefCell::new(None),
2120 context_menu_options: None,
2121 mouse_context_menu: None,
2122 completion_tasks: Vec::new(),
2123 inline_blame_popover: None,
2124 inline_blame_popover_show_task: None,
2125 signature_help_state: SignatureHelpState::default(),
2126 auto_signature_help: None,
2127 find_all_references_task_sources: Vec::new(),
2128 next_completion_id: 0,
2129 next_inlay_id: 0,
2130 code_action_providers,
2131 available_code_actions: None,
2132 code_actions_task: None,
2133 quick_selection_highlight_task: None,
2134 debounced_selection_highlight_task: None,
2135 document_highlights_task: None,
2136 linked_editing_range_task: None,
2137 pending_rename: None,
2138 searchable: !is_minimap,
2139 cursor_shape: EditorSettings::get_global(cx)
2140 .cursor_shape
2141 .unwrap_or_default(),
2142 current_line_highlight: None,
2143 autoindent_mode: Some(AutoindentMode::EachLine),
2144
2145 workspace: None,
2146 input_enabled: !is_minimap,
2147 use_modal_editing: full_mode,
2148 read_only: is_minimap,
2149 use_autoclose: true,
2150 use_auto_surround: true,
2151 auto_replace_emoji_shortcode: false,
2152 jsx_tag_auto_close_enabled_in_any_buffer: false,
2153 leader_id: None,
2154 remote_id: None,
2155 hover_state: HoverState::default(),
2156 pending_mouse_down: None,
2157 hovered_link_state: None,
2158 edit_prediction_provider: None,
2159 active_edit_prediction: None,
2160 stale_edit_prediction_in_menu: None,
2161 edit_prediction_preview: EditPredictionPreview::Inactive {
2162 released_too_fast: false,
2163 },
2164 inline_diagnostics_enabled: full_mode,
2165 diagnostics_enabled: full_mode,
2166 word_completions_enabled: full_mode,
2167 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2168 gutter_hovered: false,
2169 pixel_position_of_newest_cursor: None,
2170 last_bounds: None,
2171 last_position_map: None,
2172 expect_bounds_change: None,
2173 gutter_dimensions: GutterDimensions::default(),
2174 style: None,
2175 show_cursor_names: false,
2176 hovered_cursors: HashMap::default(),
2177 next_editor_action_id: EditorActionId::default(),
2178 editor_actions: Rc::default(),
2179 edit_predictions_hidden_for_vim_mode: false,
2180 show_edit_predictions_override: None,
2181 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2182 edit_prediction_settings: EditPredictionSettings::Disabled,
2183 edit_prediction_indent_conflict: false,
2184 edit_prediction_requires_modifier_in_indent_conflict: true,
2185 custom_context_menu: None,
2186 show_git_blame_gutter: false,
2187 show_git_blame_inline: false,
2188 show_selection_menu: None,
2189 show_git_blame_inline_delay_task: None,
2190 git_blame_inline_enabled: full_mode
2191 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2192 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2193 serialize_dirty_buffers: !is_minimap
2194 && ProjectSettings::get_global(cx)
2195 .session
2196 .restore_unsaved_buffers,
2197 blame: None,
2198 blame_subscription: None,
2199 tasks: BTreeMap::default(),
2200
2201 breakpoint_store,
2202 gutter_breakpoint_indicator: (None, None),
2203 hovered_diff_hunk_row: None,
2204 _subscriptions: (!is_minimap)
2205 .then(|| {
2206 vec![
2207 cx.observe(&multi_buffer, Self::on_buffer_changed),
2208 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2209 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2210 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2211 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2212 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2213 cx.observe_window_activation(window, |editor, window, cx| {
2214 let active = window.is_window_active();
2215 editor.blink_manager.update(cx, |blink_manager, cx| {
2216 if active {
2217 blink_manager.enable(cx);
2218 } else {
2219 blink_manager.disable(cx);
2220 }
2221 });
2222 if active {
2223 editor.show_mouse_cursor(cx);
2224 }
2225 }),
2226 ]
2227 })
2228 .unwrap_or_default(),
2229 tasks_update_task: None,
2230 pull_diagnostics_task: Task::ready(()),
2231 colors: None,
2232 refresh_colors_task: Task::ready(()),
2233 inlay_hints: None,
2234 next_color_inlay_id: 0,
2235 post_scroll_update: Task::ready(()),
2236 linked_edit_ranges: Default::default(),
2237 in_project_search: false,
2238 previous_search_ranges: None,
2239 breadcrumb_header: None,
2240 focused_block: None,
2241 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2242 addons: HashMap::default(),
2243 registered_buffers: HashMap::default(),
2244 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2245 selection_mark_mode: false,
2246 toggle_fold_multiple_buffers: Task::ready(()),
2247 serialize_selections: Task::ready(()),
2248 serialize_folds: Task::ready(()),
2249 text_style_refinement: None,
2250 load_diff_task: load_uncommitted_diff,
2251 temporary_diff_override: false,
2252 mouse_cursor_hidden: false,
2253 minimap: None,
2254 hide_mouse_mode: EditorSettings::get_global(cx)
2255 .hide_mouse
2256 .unwrap_or_default(),
2257 change_list: ChangeList::new(),
2258 mode,
2259 selection_drag_state: SelectionDragState::None,
2260 folding_newlines: Task::ready(()),
2261 lookup_key: None,
2262 };
2263
2264 if is_minimap {
2265 return editor;
2266 }
2267
2268 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2269 editor
2270 ._subscriptions
2271 .push(cx.observe(breakpoints, |_, _, cx| {
2272 cx.notify();
2273 }));
2274 }
2275 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2276 editor._subscriptions.extend(project_subscriptions);
2277
2278 editor._subscriptions.push(cx.subscribe_in(
2279 &cx.entity(),
2280 window,
2281 |editor, _, e: &EditorEvent, window, cx| match e {
2282 EditorEvent::ScrollPositionChanged { local, .. } => {
2283 if *local {
2284 let new_anchor = editor.scroll_manager.anchor();
2285 let snapshot = editor.snapshot(window, cx);
2286 editor.update_restoration_data(cx, move |data| {
2287 data.scroll_position = (
2288 new_anchor.top_row(snapshot.buffer_snapshot()),
2289 new_anchor.offset,
2290 );
2291 });
2292 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2293 editor.inline_blame_popover.take();
2294 }
2295 }
2296 EditorEvent::Edited { .. } => {
2297 if vim_flavor(cx).is_none() {
2298 let display_map = editor.display_snapshot(cx);
2299 let selections = editor.selections.all_adjusted_display(&display_map);
2300 let pop_state = editor
2301 .change_list
2302 .last()
2303 .map(|previous| {
2304 previous.len() == selections.len()
2305 && previous.iter().enumerate().all(|(ix, p)| {
2306 p.to_display_point(&display_map).row()
2307 == selections[ix].head().row()
2308 })
2309 })
2310 .unwrap_or(false);
2311 let new_positions = selections
2312 .into_iter()
2313 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2314 .collect();
2315 editor
2316 .change_list
2317 .push_to_change_list(pop_state, new_positions);
2318 }
2319 }
2320 _ => (),
2321 },
2322 ));
2323
2324 if let Some(dap_store) = editor
2325 .project
2326 .as_ref()
2327 .map(|project| project.read(cx).dap_store())
2328 {
2329 let weak_editor = cx.weak_entity();
2330
2331 editor
2332 ._subscriptions
2333 .push(
2334 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2335 let session_entity = cx.entity();
2336 weak_editor
2337 .update(cx, |editor, cx| {
2338 editor._subscriptions.push(
2339 cx.subscribe(&session_entity, Self::on_debug_session_event),
2340 );
2341 })
2342 .ok();
2343 }),
2344 );
2345
2346 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2347 editor
2348 ._subscriptions
2349 .push(cx.subscribe(&session, Self::on_debug_session_event));
2350 }
2351 }
2352
2353 // skip adding the initial selection to selection history
2354 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2355 editor.end_selection(window, cx);
2356 editor.selection_history.mode = SelectionHistoryMode::Normal;
2357
2358 editor.scroll_manager.show_scrollbars(window, cx);
2359 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2360
2361 if full_mode {
2362 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2363 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2364
2365 if editor.git_blame_inline_enabled {
2366 editor.start_git_blame_inline(false, window, cx);
2367 }
2368
2369 editor.go_to_active_debug_line(window, cx);
2370
2371 editor.minimap =
2372 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2373 editor.colors = Some(LspColorData::new(cx));
2374 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2375
2376 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2377 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2378 }
2379 editor.update_lsp_data(None, window, cx);
2380 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2381 }
2382
2383 editor
2384 }
2385
2386 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2387 self.selections.display_map(cx)
2388 }
2389
2390 pub fn deploy_mouse_context_menu(
2391 &mut self,
2392 position: gpui::Point<Pixels>,
2393 context_menu: Entity<ContextMenu>,
2394 window: &mut Window,
2395 cx: &mut Context<Self>,
2396 ) {
2397 self.mouse_context_menu = Some(MouseContextMenu::new(
2398 self,
2399 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2400 context_menu,
2401 window,
2402 cx,
2403 ));
2404 }
2405
2406 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2407 self.mouse_context_menu
2408 .as_ref()
2409 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2410 }
2411
2412 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2413 if self
2414 .selections
2415 .pending_anchor()
2416 .is_some_and(|pending_selection| {
2417 let snapshot = self.buffer().read(cx).snapshot(cx);
2418 pending_selection.range().includes(range, &snapshot)
2419 })
2420 {
2421 return true;
2422 }
2423
2424 self.selections
2425 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2426 .into_iter()
2427 .any(|selection| {
2428 // This is needed to cover a corner case, if we just check for an existing
2429 // selection in the fold range, having a cursor at the start of the fold
2430 // marks it as selected. Non-empty selections don't cause this.
2431 let length = selection.end - selection.start;
2432 length > 0
2433 })
2434 }
2435
2436 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2437 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2438 }
2439
2440 fn key_context_internal(
2441 &self,
2442 has_active_edit_prediction: bool,
2443 window: &mut Window,
2444 cx: &mut App,
2445 ) -> KeyContext {
2446 let mut key_context = KeyContext::new_with_defaults();
2447 key_context.add("Editor");
2448 let mode = match self.mode {
2449 EditorMode::SingleLine => "single_line",
2450 EditorMode::AutoHeight { .. } => "auto_height",
2451 EditorMode::Minimap { .. } => "minimap",
2452 EditorMode::Full { .. } => "full",
2453 };
2454
2455 if EditorSettings::jupyter_enabled(cx) {
2456 key_context.add("jupyter");
2457 }
2458
2459 key_context.set("mode", mode);
2460 if self.pending_rename.is_some() {
2461 key_context.add("renaming");
2462 }
2463
2464 if !self.snippet_stack.is_empty() {
2465 key_context.add("in_snippet");
2466 }
2467
2468 match self.context_menu.borrow().as_ref() {
2469 Some(CodeContextMenu::Completions(menu)) => {
2470 if menu.visible() {
2471 key_context.add("menu");
2472 key_context.add("showing_completions");
2473 }
2474 }
2475 Some(CodeContextMenu::CodeActions(menu)) => {
2476 if menu.visible() {
2477 key_context.add("menu");
2478 key_context.add("showing_code_actions")
2479 }
2480 }
2481 None => {}
2482 }
2483
2484 if self.signature_help_state.has_multiple_signatures() {
2485 key_context.add("showing_signature_help");
2486 }
2487
2488 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2489 if !self.focus_handle(cx).contains_focused(window, cx)
2490 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2491 {
2492 for addon in self.addons.values() {
2493 addon.extend_key_context(&mut key_context, cx)
2494 }
2495 }
2496
2497 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2498 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2499 Some(
2500 file.full_path(cx)
2501 .extension()?
2502 .to_string_lossy()
2503 .into_owned(),
2504 )
2505 }) {
2506 key_context.set("extension", extension);
2507 }
2508 } else {
2509 key_context.add("multibuffer");
2510 }
2511
2512 if has_active_edit_prediction {
2513 if self.edit_prediction_in_conflict() {
2514 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2515 } else {
2516 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2517 key_context.add("copilot_suggestion");
2518 }
2519 }
2520
2521 if self.selection_mark_mode {
2522 key_context.add("selection_mode");
2523 }
2524
2525 let disjoint = self.selections.disjoint_anchors();
2526 let snapshot = self.snapshot(window, cx);
2527 let snapshot = snapshot.buffer_snapshot();
2528 if self.mode == EditorMode::SingleLine
2529 && let [selection] = disjoint
2530 && selection.start == selection.end
2531 && selection.end.to_offset(snapshot) == snapshot.len()
2532 {
2533 key_context.add("end_of_input");
2534 }
2535
2536 key_context
2537 }
2538
2539 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2540 self.last_bounds.as_ref()
2541 }
2542
2543 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2544 if self.mouse_cursor_hidden {
2545 self.mouse_cursor_hidden = false;
2546 cx.notify();
2547 }
2548 }
2549
2550 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2551 let hide_mouse_cursor = match origin {
2552 HideMouseCursorOrigin::TypingAction => {
2553 matches!(
2554 self.hide_mouse_mode,
2555 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2556 )
2557 }
2558 HideMouseCursorOrigin::MovementAction => {
2559 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2560 }
2561 };
2562 if self.mouse_cursor_hidden != hide_mouse_cursor {
2563 self.mouse_cursor_hidden = hide_mouse_cursor;
2564 cx.notify();
2565 }
2566 }
2567
2568 pub fn edit_prediction_in_conflict(&self) -> bool {
2569 if !self.show_edit_predictions_in_menu() {
2570 return false;
2571 }
2572
2573 let showing_completions = self
2574 .context_menu
2575 .borrow()
2576 .as_ref()
2577 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2578
2579 showing_completions
2580 || self.edit_prediction_requires_modifier()
2581 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2582 // bindings to insert tab characters.
2583 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2584 }
2585
2586 pub fn accept_edit_prediction_keybind(
2587 &self,
2588 accept_partial: bool,
2589 window: &mut Window,
2590 cx: &mut App,
2591 ) -> AcceptEditPredictionBinding {
2592 let key_context = self.key_context_internal(true, window, cx);
2593 let in_conflict = self.edit_prediction_in_conflict();
2594
2595 let bindings = if accept_partial {
2596 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2597 } else {
2598 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2599 };
2600
2601 // TODO: if the binding contains multiple keystrokes, display all of them, not
2602 // just the first one.
2603 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2604 !in_conflict
2605 || binding
2606 .keystrokes()
2607 .first()
2608 .is_some_and(|keystroke| keystroke.modifiers().modified())
2609 }))
2610 }
2611
2612 pub fn new_file(
2613 workspace: &mut Workspace,
2614 _: &workspace::NewFile,
2615 window: &mut Window,
2616 cx: &mut Context<Workspace>,
2617 ) {
2618 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2619 "Failed to create buffer",
2620 window,
2621 cx,
2622 |e, _, _| match e.error_code() {
2623 ErrorCode::RemoteUpgradeRequired => Some(format!(
2624 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2625 e.error_tag("required").unwrap_or("the latest version")
2626 )),
2627 _ => None,
2628 },
2629 );
2630 }
2631
2632 pub fn new_in_workspace(
2633 workspace: &mut Workspace,
2634 window: &mut Window,
2635 cx: &mut Context<Workspace>,
2636 ) -> Task<Result<Entity<Editor>>> {
2637 let project = workspace.project().clone();
2638 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2639
2640 cx.spawn_in(window, async move |workspace, cx| {
2641 let buffer = create.await?;
2642 workspace.update_in(cx, |workspace, window, cx| {
2643 let editor =
2644 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2645 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2646 editor
2647 })
2648 })
2649 }
2650
2651 fn new_file_vertical(
2652 workspace: &mut Workspace,
2653 _: &workspace::NewFileSplitVertical,
2654 window: &mut Window,
2655 cx: &mut Context<Workspace>,
2656 ) {
2657 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2658 }
2659
2660 fn new_file_horizontal(
2661 workspace: &mut Workspace,
2662 _: &workspace::NewFileSplitHorizontal,
2663 window: &mut Window,
2664 cx: &mut Context<Workspace>,
2665 ) {
2666 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2667 }
2668
2669 fn new_file_split(
2670 workspace: &mut Workspace,
2671 action: &workspace::NewFileSplit,
2672 window: &mut Window,
2673 cx: &mut Context<Workspace>,
2674 ) {
2675 Self::new_file_in_direction(workspace, action.0, window, cx)
2676 }
2677
2678 fn new_file_in_direction(
2679 workspace: &mut Workspace,
2680 direction: SplitDirection,
2681 window: &mut Window,
2682 cx: &mut Context<Workspace>,
2683 ) {
2684 let project = workspace.project().clone();
2685 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2686
2687 cx.spawn_in(window, async move |workspace, cx| {
2688 let buffer = create.await?;
2689 workspace.update_in(cx, move |workspace, window, cx| {
2690 workspace.split_item(
2691 direction,
2692 Box::new(
2693 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2694 ),
2695 window,
2696 cx,
2697 )
2698 })?;
2699 anyhow::Ok(())
2700 })
2701 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2702 match e.error_code() {
2703 ErrorCode::RemoteUpgradeRequired => Some(format!(
2704 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2705 e.error_tag("required").unwrap_or("the latest version")
2706 )),
2707 _ => None,
2708 }
2709 });
2710 }
2711
2712 pub fn leader_id(&self) -> Option<CollaboratorId> {
2713 self.leader_id
2714 }
2715
2716 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2717 &self.buffer
2718 }
2719
2720 pub fn project(&self) -> Option<&Entity<Project>> {
2721 self.project.as_ref()
2722 }
2723
2724 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2725 self.workspace.as_ref()?.0.upgrade()
2726 }
2727
2728 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2729 self.buffer().read(cx).title(cx)
2730 }
2731
2732 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2733 let git_blame_gutter_max_author_length = self
2734 .render_git_blame_gutter(cx)
2735 .then(|| {
2736 if let Some(blame) = self.blame.as_ref() {
2737 let max_author_length =
2738 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2739 Some(max_author_length)
2740 } else {
2741 None
2742 }
2743 })
2744 .flatten();
2745
2746 EditorSnapshot {
2747 mode: self.mode.clone(),
2748 show_gutter: self.show_gutter,
2749 show_line_numbers: self.show_line_numbers,
2750 show_git_diff_gutter: self.show_git_diff_gutter,
2751 show_code_actions: self.show_code_actions,
2752 show_runnables: self.show_runnables,
2753 show_breakpoints: self.show_breakpoints,
2754 git_blame_gutter_max_author_length,
2755 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2756 placeholder_display_snapshot: self
2757 .placeholder_display_map
2758 .as_ref()
2759 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2760 scroll_anchor: self.scroll_manager.anchor(),
2761 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2762 is_focused: self.focus_handle.is_focused(window),
2763 current_line_highlight: self
2764 .current_line_highlight
2765 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2766 gutter_hovered: self.gutter_hovered,
2767 }
2768 }
2769
2770 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2771 self.buffer.read(cx).language_at(point, cx)
2772 }
2773
2774 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2775 self.buffer.read(cx).read(cx).file_at(point).cloned()
2776 }
2777
2778 pub fn active_excerpt(
2779 &self,
2780 cx: &App,
2781 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2782 self.buffer
2783 .read(cx)
2784 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2785 }
2786
2787 pub fn mode(&self) -> &EditorMode {
2788 &self.mode
2789 }
2790
2791 pub fn set_mode(&mut self, mode: EditorMode) {
2792 self.mode = mode;
2793 }
2794
2795 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2796 self.collaboration_hub.as_deref()
2797 }
2798
2799 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2800 self.collaboration_hub = Some(hub);
2801 }
2802
2803 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2804 self.in_project_search = in_project_search;
2805 }
2806
2807 pub fn set_custom_context_menu(
2808 &mut self,
2809 f: impl 'static
2810 + Fn(
2811 &mut Self,
2812 DisplayPoint,
2813 &mut Window,
2814 &mut Context<Self>,
2815 ) -> Option<Entity<ui::ContextMenu>>,
2816 ) {
2817 self.custom_context_menu = Some(Box::new(f))
2818 }
2819
2820 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2821 self.completion_provider = provider;
2822 }
2823
2824 #[cfg(any(test, feature = "test-support"))]
2825 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2826 self.completion_provider.clone()
2827 }
2828
2829 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2830 self.semantics_provider.clone()
2831 }
2832
2833 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2834 self.semantics_provider = provider;
2835 }
2836
2837 pub fn set_edit_prediction_provider<T>(
2838 &mut self,
2839 provider: Option<Entity<T>>,
2840 window: &mut Window,
2841 cx: &mut Context<Self>,
2842 ) where
2843 T: EditPredictionProvider,
2844 {
2845 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2846 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2847 if this.focus_handle.is_focused(window) {
2848 this.update_visible_edit_prediction(window, cx);
2849 }
2850 }),
2851 provider: Arc::new(provider),
2852 });
2853 self.update_edit_prediction_settings(cx);
2854 self.refresh_edit_prediction(false, false, window, cx);
2855 }
2856
2857 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2858 self.placeholder_display_map
2859 .as_ref()
2860 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2861 }
2862
2863 pub fn set_placeholder_text(
2864 &mut self,
2865 placeholder_text: &str,
2866 window: &mut Window,
2867 cx: &mut Context<Self>,
2868 ) {
2869 let multibuffer = cx
2870 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2871
2872 let style = window.text_style();
2873
2874 self.placeholder_display_map = Some(cx.new(|cx| {
2875 DisplayMap::new(
2876 multibuffer,
2877 style.font(),
2878 style.font_size.to_pixels(window.rem_size()),
2879 None,
2880 FILE_HEADER_HEIGHT,
2881 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2882 Default::default(),
2883 DiagnosticSeverity::Off,
2884 cx,
2885 )
2886 }));
2887 cx.notify();
2888 }
2889
2890 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2891 self.cursor_shape = cursor_shape;
2892
2893 // Disrupt blink for immediate user feedback that the cursor shape has changed
2894 self.blink_manager.update(cx, BlinkManager::show_cursor);
2895
2896 cx.notify();
2897 }
2898
2899 pub fn set_current_line_highlight(
2900 &mut self,
2901 current_line_highlight: Option<CurrentLineHighlight>,
2902 ) {
2903 self.current_line_highlight = current_line_highlight;
2904 }
2905
2906 pub fn range_for_match<T: std::marker::Copy>(
2907 &self,
2908 range: &Range<T>,
2909 collapse: bool,
2910 ) -> Range<T> {
2911 if collapse {
2912 return range.start..range.start;
2913 }
2914 range.clone()
2915 }
2916
2917 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2918 if self.display_map.read(cx).clip_at_line_ends != clip {
2919 self.display_map
2920 .update(cx, |map, _| map.clip_at_line_ends = clip);
2921 }
2922 }
2923
2924 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2925 self.input_enabled = input_enabled;
2926 }
2927
2928 pub fn set_edit_predictions_hidden_for_vim_mode(
2929 &mut self,
2930 hidden: bool,
2931 window: &mut Window,
2932 cx: &mut Context<Self>,
2933 ) {
2934 if hidden != self.edit_predictions_hidden_for_vim_mode {
2935 self.edit_predictions_hidden_for_vim_mode = hidden;
2936 if hidden {
2937 self.update_visible_edit_prediction(window, cx);
2938 } else {
2939 self.refresh_edit_prediction(true, false, window, cx);
2940 }
2941 }
2942 }
2943
2944 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2945 self.menu_edit_predictions_policy = value;
2946 }
2947
2948 pub fn set_autoindent(&mut self, autoindent: bool) {
2949 if autoindent {
2950 self.autoindent_mode = Some(AutoindentMode::EachLine);
2951 } else {
2952 self.autoindent_mode = None;
2953 }
2954 }
2955
2956 pub fn read_only(&self, cx: &App) -> bool {
2957 self.read_only || self.buffer.read(cx).read_only()
2958 }
2959
2960 pub fn set_read_only(&mut self, read_only: bool) {
2961 self.read_only = read_only;
2962 }
2963
2964 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2965 self.use_autoclose = autoclose;
2966 }
2967
2968 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2969 self.use_auto_surround = auto_surround;
2970 }
2971
2972 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2973 self.auto_replace_emoji_shortcode = auto_replace;
2974 }
2975
2976 pub fn toggle_edit_predictions(
2977 &mut self,
2978 _: &ToggleEditPrediction,
2979 window: &mut Window,
2980 cx: &mut Context<Self>,
2981 ) {
2982 if self.show_edit_predictions_override.is_some() {
2983 self.set_show_edit_predictions(None, window, cx);
2984 } else {
2985 let show_edit_predictions = !self.edit_predictions_enabled();
2986 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2987 }
2988 }
2989
2990 pub fn set_show_edit_predictions(
2991 &mut self,
2992 show_edit_predictions: Option<bool>,
2993 window: &mut Window,
2994 cx: &mut Context<Self>,
2995 ) {
2996 self.show_edit_predictions_override = show_edit_predictions;
2997 self.update_edit_prediction_settings(cx);
2998
2999 if let Some(false) = show_edit_predictions {
3000 self.discard_edit_prediction(false, cx);
3001 } else {
3002 self.refresh_edit_prediction(false, true, window, cx);
3003 }
3004 }
3005
3006 fn edit_predictions_disabled_in_scope(
3007 &self,
3008 buffer: &Entity<Buffer>,
3009 buffer_position: language::Anchor,
3010 cx: &App,
3011 ) -> bool {
3012 let snapshot = buffer.read(cx).snapshot();
3013 let settings = snapshot.settings_at(buffer_position, cx);
3014
3015 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3016 return false;
3017 };
3018
3019 scope.override_name().is_some_and(|scope_name| {
3020 settings
3021 .edit_predictions_disabled_in
3022 .iter()
3023 .any(|s| s == scope_name)
3024 })
3025 }
3026
3027 pub fn set_use_modal_editing(&mut self, to: bool) {
3028 self.use_modal_editing = to;
3029 }
3030
3031 pub fn use_modal_editing(&self) -> bool {
3032 self.use_modal_editing
3033 }
3034
3035 fn selections_did_change(
3036 &mut self,
3037 local: bool,
3038 old_cursor_position: &Anchor,
3039 effects: SelectionEffects,
3040 window: &mut Window,
3041 cx: &mut Context<Self>,
3042 ) {
3043 window.invalidate_character_coordinates();
3044
3045 // Copy selections to primary selection buffer
3046 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3047 if local {
3048 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3049 let buffer_handle = self.buffer.read(cx).read(cx);
3050
3051 let mut text = String::new();
3052 for (index, selection) in selections.iter().enumerate() {
3053 let text_for_selection = buffer_handle
3054 .text_for_range(selection.start..selection.end)
3055 .collect::<String>();
3056
3057 text.push_str(&text_for_selection);
3058 if index != selections.len() - 1 {
3059 text.push('\n');
3060 }
3061 }
3062
3063 if !text.is_empty() {
3064 cx.write_to_primary(ClipboardItem::new_string(text));
3065 }
3066 }
3067
3068 let selection_anchors = self.selections.disjoint_anchors_arc();
3069
3070 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3071 self.buffer.update(cx, |buffer, cx| {
3072 buffer.set_active_selections(
3073 &selection_anchors,
3074 self.selections.line_mode(),
3075 self.cursor_shape,
3076 cx,
3077 )
3078 });
3079 }
3080 let display_map = self
3081 .display_map
3082 .update(cx, |display_map, cx| display_map.snapshot(cx));
3083 let buffer = display_map.buffer_snapshot();
3084 if self.selections.count() == 1 {
3085 self.add_selections_state = None;
3086 }
3087 self.select_next_state = None;
3088 self.select_prev_state = None;
3089 self.select_syntax_node_history.try_clear();
3090 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3091 self.snippet_stack.invalidate(&selection_anchors, buffer);
3092 self.take_rename(false, window, cx);
3093
3094 let newest_selection = self.selections.newest_anchor();
3095 let new_cursor_position = newest_selection.head();
3096 let selection_start = newest_selection.start;
3097
3098 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3099 self.push_to_nav_history(
3100 *old_cursor_position,
3101 Some(new_cursor_position.to_point(buffer)),
3102 false,
3103 effects.nav_history == Some(true),
3104 cx,
3105 );
3106 }
3107
3108 if local {
3109 if let Some(buffer_id) = new_cursor_position.buffer_id {
3110 self.register_buffer(buffer_id, cx);
3111 }
3112
3113 let mut context_menu = self.context_menu.borrow_mut();
3114 let completion_menu = match context_menu.as_ref() {
3115 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3116 Some(CodeContextMenu::CodeActions(_)) => {
3117 *context_menu = None;
3118 None
3119 }
3120 None => None,
3121 };
3122 let completion_position = completion_menu.map(|menu| menu.initial_position);
3123 drop(context_menu);
3124
3125 if effects.completions
3126 && let Some(completion_position) = completion_position
3127 {
3128 let start_offset = selection_start.to_offset(buffer);
3129 let position_matches = start_offset == completion_position.to_offset(buffer);
3130 let continue_showing = if position_matches {
3131 if self.snippet_stack.is_empty() {
3132 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3133 == Some(CharKind::Word)
3134 } else {
3135 // Snippet choices can be shown even when the cursor is in whitespace.
3136 // Dismissing the menu with actions like backspace is handled by
3137 // invalidation regions.
3138 true
3139 }
3140 } else {
3141 false
3142 };
3143
3144 if continue_showing {
3145 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3146 } else {
3147 self.hide_context_menu(window, cx);
3148 }
3149 }
3150
3151 hide_hover(self, cx);
3152
3153 if old_cursor_position.to_display_point(&display_map).row()
3154 != new_cursor_position.to_display_point(&display_map).row()
3155 {
3156 self.available_code_actions.take();
3157 }
3158 self.refresh_code_actions(window, cx);
3159 self.refresh_document_highlights(cx);
3160 refresh_linked_ranges(self, window, cx);
3161
3162 self.refresh_selected_text_highlights(false, window, cx);
3163 self.refresh_matching_bracket_highlights(window, cx);
3164 self.update_visible_edit_prediction(window, cx);
3165 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3166 self.inline_blame_popover.take();
3167 if self.git_blame_inline_enabled {
3168 self.start_inline_blame_timer(window, cx);
3169 }
3170 }
3171
3172 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3173 cx.emit(EditorEvent::SelectionsChanged { local });
3174
3175 let selections = &self.selections.disjoint_anchors_arc();
3176 if selections.len() == 1 {
3177 cx.emit(SearchEvent::ActiveMatchChanged)
3178 }
3179 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3180 let inmemory_selections = selections
3181 .iter()
3182 .map(|s| {
3183 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3184 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3185 })
3186 .collect();
3187 self.update_restoration_data(cx, |data| {
3188 data.selections = inmemory_selections;
3189 });
3190
3191 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3192 && let Some(workspace_id) =
3193 self.workspace.as_ref().and_then(|workspace| workspace.1)
3194 {
3195 let snapshot = self.buffer().read(cx).snapshot(cx);
3196 let selections = selections.clone();
3197 let background_executor = cx.background_executor().clone();
3198 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3199 self.serialize_selections = cx.background_spawn(async move {
3200 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3201 let db_selections = selections
3202 .iter()
3203 .map(|selection| {
3204 (
3205 selection.start.to_offset(&snapshot),
3206 selection.end.to_offset(&snapshot),
3207 )
3208 })
3209 .collect();
3210
3211 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3212 .await
3213 .with_context(|| {
3214 format!(
3215 "persisting editor selections for editor {editor_id}, \
3216 workspace {workspace_id:?}"
3217 )
3218 })
3219 .log_err();
3220 });
3221 }
3222 }
3223
3224 cx.notify();
3225 }
3226
3227 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3228 use text::ToOffset as _;
3229 use text::ToPoint as _;
3230
3231 if self.mode.is_minimap()
3232 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3233 {
3234 return;
3235 }
3236
3237 if !self.buffer().read(cx).is_singleton() {
3238 return;
3239 }
3240
3241 let display_snapshot = self
3242 .display_map
3243 .update(cx, |display_map, cx| display_map.snapshot(cx));
3244 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3245 return;
3246 };
3247 let inmemory_folds = display_snapshot
3248 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3249 .map(|fold| {
3250 fold.range.start.text_anchor.to_point(&snapshot)
3251 ..fold.range.end.text_anchor.to_point(&snapshot)
3252 })
3253 .collect();
3254 self.update_restoration_data(cx, |data| {
3255 data.folds = inmemory_folds;
3256 });
3257
3258 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3259 return;
3260 };
3261 let background_executor = cx.background_executor().clone();
3262 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3263 let db_folds = display_snapshot
3264 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3265 .map(|fold| {
3266 (
3267 fold.range.start.text_anchor.to_offset(&snapshot),
3268 fold.range.end.text_anchor.to_offset(&snapshot),
3269 )
3270 })
3271 .collect();
3272 self.serialize_folds = cx.background_spawn(async move {
3273 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3274 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3275 .await
3276 .with_context(|| {
3277 format!(
3278 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3279 )
3280 })
3281 .log_err();
3282 });
3283 }
3284
3285 pub fn sync_selections(
3286 &mut self,
3287 other: Entity<Editor>,
3288 cx: &mut Context<Self>,
3289 ) -> gpui::Subscription {
3290 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3291 if !other_selections.is_empty() {
3292 self.selections.change_with(cx, |selections| {
3293 selections.select_anchors(other_selections);
3294 });
3295 }
3296
3297 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3298 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3299 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3300 if other_selections.is_empty() {
3301 return;
3302 }
3303 this.selections.change_with(cx, |selections| {
3304 selections.select_anchors(other_selections);
3305 });
3306 }
3307 });
3308
3309 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3310 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3311 let these_selections = this.selections.disjoint_anchors().to_vec();
3312 if these_selections.is_empty() {
3313 return;
3314 }
3315 other.update(cx, |other_editor, cx| {
3316 other_editor.selections.change_with(cx, |selections| {
3317 selections.select_anchors(these_selections);
3318 })
3319 });
3320 }
3321 });
3322
3323 Subscription::join(other_subscription, this_subscription)
3324 }
3325
3326 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3327 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3328 /// effects of selection change occur at the end of the transaction.
3329 pub fn change_selections<R>(
3330 &mut self,
3331 effects: SelectionEffects,
3332 window: &mut Window,
3333 cx: &mut Context<Self>,
3334 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3335 ) -> R {
3336 if let Some(state) = &mut self.deferred_selection_effects_state {
3337 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3338 state.effects.completions = effects.completions;
3339 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3340 let (changed, result) = self.selections.change_with(cx, change);
3341 state.changed |= changed;
3342 return result;
3343 }
3344 let mut state = DeferredSelectionEffectsState {
3345 changed: false,
3346 effects,
3347 old_cursor_position: self.selections.newest_anchor().head(),
3348 history_entry: SelectionHistoryEntry {
3349 selections: self.selections.disjoint_anchors_arc(),
3350 select_next_state: self.select_next_state.clone(),
3351 select_prev_state: self.select_prev_state.clone(),
3352 add_selections_state: self.add_selections_state.clone(),
3353 },
3354 };
3355 let (changed, result) = self.selections.change_with(cx, change);
3356 state.changed = state.changed || changed;
3357 if self.defer_selection_effects {
3358 self.deferred_selection_effects_state = Some(state);
3359 } else {
3360 self.apply_selection_effects(state, window, cx);
3361 }
3362 result
3363 }
3364
3365 /// Defers the effects of selection change, so that the effects of multiple calls to
3366 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3367 /// to selection history and the state of popovers based on selection position aren't
3368 /// erroneously updated.
3369 pub fn with_selection_effects_deferred<R>(
3370 &mut self,
3371 window: &mut Window,
3372 cx: &mut Context<Self>,
3373 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3374 ) -> R {
3375 let already_deferred = self.defer_selection_effects;
3376 self.defer_selection_effects = true;
3377 let result = update(self, window, cx);
3378 if !already_deferred {
3379 self.defer_selection_effects = false;
3380 if let Some(state) = self.deferred_selection_effects_state.take() {
3381 self.apply_selection_effects(state, window, cx);
3382 }
3383 }
3384 result
3385 }
3386
3387 fn apply_selection_effects(
3388 &mut self,
3389 state: DeferredSelectionEffectsState,
3390 window: &mut Window,
3391 cx: &mut Context<Self>,
3392 ) {
3393 if state.changed {
3394 self.selection_history.push(state.history_entry);
3395
3396 if let Some(autoscroll) = state.effects.scroll {
3397 self.request_autoscroll(autoscroll, cx);
3398 }
3399
3400 let old_cursor_position = &state.old_cursor_position;
3401
3402 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3403
3404 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3405 self.show_signature_help(&ShowSignatureHelp, window, cx);
3406 }
3407 }
3408 }
3409
3410 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3411 where
3412 I: IntoIterator<Item = (Range<S>, T)>,
3413 S: ToOffset,
3414 T: Into<Arc<str>>,
3415 {
3416 if self.read_only(cx) {
3417 return;
3418 }
3419
3420 self.buffer
3421 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3422 }
3423
3424 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3425 where
3426 I: IntoIterator<Item = (Range<S>, T)>,
3427 S: ToOffset,
3428 T: Into<Arc<str>>,
3429 {
3430 if self.read_only(cx) {
3431 return;
3432 }
3433
3434 self.buffer.update(cx, |buffer, cx| {
3435 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3436 });
3437 }
3438
3439 pub fn edit_with_block_indent<I, S, T>(
3440 &mut self,
3441 edits: I,
3442 original_indent_columns: Vec<Option<u32>>,
3443 cx: &mut Context<Self>,
3444 ) where
3445 I: IntoIterator<Item = (Range<S>, T)>,
3446 S: ToOffset,
3447 T: Into<Arc<str>>,
3448 {
3449 if self.read_only(cx) {
3450 return;
3451 }
3452
3453 self.buffer.update(cx, |buffer, cx| {
3454 buffer.edit(
3455 edits,
3456 Some(AutoindentMode::Block {
3457 original_indent_columns,
3458 }),
3459 cx,
3460 )
3461 });
3462 }
3463
3464 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3465 self.hide_context_menu(window, cx);
3466
3467 match phase {
3468 SelectPhase::Begin {
3469 position,
3470 add,
3471 click_count,
3472 } => self.begin_selection(position, add, click_count, window, cx),
3473 SelectPhase::BeginColumnar {
3474 position,
3475 goal_column,
3476 reset,
3477 mode,
3478 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3479 SelectPhase::Extend {
3480 position,
3481 click_count,
3482 } => self.extend_selection(position, click_count, window, cx),
3483 SelectPhase::Update {
3484 position,
3485 goal_column,
3486 scroll_delta,
3487 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3488 SelectPhase::End => self.end_selection(window, cx),
3489 }
3490 }
3491
3492 fn extend_selection(
3493 &mut self,
3494 position: DisplayPoint,
3495 click_count: usize,
3496 window: &mut Window,
3497 cx: &mut Context<Self>,
3498 ) {
3499 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3500 let tail = self.selections.newest::<usize>(&display_map).tail();
3501 let click_count = click_count.max(match self.selections.select_mode() {
3502 SelectMode::Character => 1,
3503 SelectMode::Word(_) => 2,
3504 SelectMode::Line(_) => 3,
3505 SelectMode::All => 4,
3506 });
3507 self.begin_selection(position, false, click_count, window, cx);
3508
3509 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3510
3511 let current_selection = match self.selections.select_mode() {
3512 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3513 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3514 };
3515
3516 let mut pending_selection = self
3517 .selections
3518 .pending_anchor()
3519 .cloned()
3520 .expect("extend_selection not called with pending selection");
3521
3522 if pending_selection
3523 .start
3524 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3525 == Ordering::Greater
3526 {
3527 pending_selection.start = current_selection.start;
3528 }
3529 if pending_selection
3530 .end
3531 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3532 == Ordering::Less
3533 {
3534 pending_selection.end = current_selection.end;
3535 pending_selection.reversed = true;
3536 }
3537
3538 let mut pending_mode = self.selections.pending_mode().unwrap();
3539 match &mut pending_mode {
3540 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3541 _ => {}
3542 }
3543
3544 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3545 SelectionEffects::scroll(Autoscroll::fit())
3546 } else {
3547 SelectionEffects::no_scroll()
3548 };
3549
3550 self.change_selections(effects, window, cx, |s| {
3551 s.set_pending(pending_selection.clone(), pending_mode);
3552 s.set_is_extending(true);
3553 });
3554 }
3555
3556 fn begin_selection(
3557 &mut self,
3558 position: DisplayPoint,
3559 add: bool,
3560 click_count: usize,
3561 window: &mut Window,
3562 cx: &mut Context<Self>,
3563 ) {
3564 if !self.focus_handle.is_focused(window) {
3565 self.last_focused_descendant = None;
3566 window.focus(&self.focus_handle);
3567 }
3568
3569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3570 let buffer = display_map.buffer_snapshot();
3571 let position = display_map.clip_point(position, Bias::Left);
3572
3573 let start;
3574 let end;
3575 let mode;
3576 let mut auto_scroll;
3577 match click_count {
3578 1 => {
3579 start = buffer.anchor_before(position.to_point(&display_map));
3580 end = start;
3581 mode = SelectMode::Character;
3582 auto_scroll = true;
3583 }
3584 2 => {
3585 let position = display_map
3586 .clip_point(position, Bias::Left)
3587 .to_offset(&display_map, Bias::Left);
3588 let (range, _) = buffer.surrounding_word(position, None);
3589 start = buffer.anchor_before(range.start);
3590 end = buffer.anchor_before(range.end);
3591 mode = SelectMode::Word(start..end);
3592 auto_scroll = true;
3593 }
3594 3 => {
3595 let position = display_map
3596 .clip_point(position, Bias::Left)
3597 .to_point(&display_map);
3598 let line_start = display_map.prev_line_boundary(position).0;
3599 let next_line_start = buffer.clip_point(
3600 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3601 Bias::Left,
3602 );
3603 start = buffer.anchor_before(line_start);
3604 end = buffer.anchor_before(next_line_start);
3605 mode = SelectMode::Line(start..end);
3606 auto_scroll = true;
3607 }
3608 _ => {
3609 start = buffer.anchor_before(0);
3610 end = buffer.anchor_before(buffer.len());
3611 mode = SelectMode::All;
3612 auto_scroll = false;
3613 }
3614 }
3615 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3616
3617 let point_to_delete: Option<usize> = {
3618 let selected_points: Vec<Selection<Point>> =
3619 self.selections.disjoint_in_range(start..end, &display_map);
3620
3621 if !add || click_count > 1 {
3622 None
3623 } else if !selected_points.is_empty() {
3624 Some(selected_points[0].id)
3625 } else {
3626 let clicked_point_already_selected =
3627 self.selections.disjoint_anchors().iter().find(|selection| {
3628 selection.start.to_point(buffer) == start.to_point(buffer)
3629 || selection.end.to_point(buffer) == end.to_point(buffer)
3630 });
3631
3632 clicked_point_already_selected.map(|selection| selection.id)
3633 }
3634 };
3635
3636 let selections_count = self.selections.count();
3637 let effects = if auto_scroll {
3638 SelectionEffects::default()
3639 } else {
3640 SelectionEffects::no_scroll()
3641 };
3642
3643 self.change_selections(effects, window, cx, |s| {
3644 if let Some(point_to_delete) = point_to_delete {
3645 s.delete(point_to_delete);
3646
3647 if selections_count == 1 {
3648 s.set_pending_anchor_range(start..end, mode);
3649 }
3650 } else {
3651 if !add {
3652 s.clear_disjoint();
3653 }
3654
3655 s.set_pending_anchor_range(start..end, mode);
3656 }
3657 });
3658 }
3659
3660 fn begin_columnar_selection(
3661 &mut self,
3662 position: DisplayPoint,
3663 goal_column: u32,
3664 reset: bool,
3665 mode: ColumnarMode,
3666 window: &mut Window,
3667 cx: &mut Context<Self>,
3668 ) {
3669 if !self.focus_handle.is_focused(window) {
3670 self.last_focused_descendant = None;
3671 window.focus(&self.focus_handle);
3672 }
3673
3674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3675
3676 if reset {
3677 let pointer_position = display_map
3678 .buffer_snapshot()
3679 .anchor_before(position.to_point(&display_map));
3680
3681 self.change_selections(
3682 SelectionEffects::scroll(Autoscroll::newest()),
3683 window,
3684 cx,
3685 |s| {
3686 s.clear_disjoint();
3687 s.set_pending_anchor_range(
3688 pointer_position..pointer_position,
3689 SelectMode::Character,
3690 );
3691 },
3692 );
3693 };
3694
3695 let tail = self.selections.newest::<Point>(&display_map).tail();
3696 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3697 self.columnar_selection_state = match mode {
3698 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3699 selection_tail: selection_anchor,
3700 display_point: if reset {
3701 if position.column() != goal_column {
3702 Some(DisplayPoint::new(position.row(), goal_column))
3703 } else {
3704 None
3705 }
3706 } else {
3707 None
3708 },
3709 }),
3710 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3711 selection_tail: selection_anchor,
3712 }),
3713 };
3714
3715 if !reset {
3716 self.select_columns(position, goal_column, &display_map, window, cx);
3717 }
3718 }
3719
3720 fn update_selection(
3721 &mut self,
3722 position: DisplayPoint,
3723 goal_column: u32,
3724 scroll_delta: gpui::Point<f32>,
3725 window: &mut Window,
3726 cx: &mut Context<Self>,
3727 ) {
3728 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3729
3730 if self.columnar_selection_state.is_some() {
3731 self.select_columns(position, goal_column, &display_map, window, cx);
3732 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3733 let buffer = display_map.buffer_snapshot();
3734 let head;
3735 let tail;
3736 let mode = self.selections.pending_mode().unwrap();
3737 match &mode {
3738 SelectMode::Character => {
3739 head = position.to_point(&display_map);
3740 tail = pending.tail().to_point(buffer);
3741 }
3742 SelectMode::Word(original_range) => {
3743 let offset = display_map
3744 .clip_point(position, Bias::Left)
3745 .to_offset(&display_map, Bias::Left);
3746 let original_range = original_range.to_offset(buffer);
3747
3748 let head_offset = if buffer.is_inside_word(offset, None)
3749 || original_range.contains(&offset)
3750 {
3751 let (word_range, _) = buffer.surrounding_word(offset, None);
3752 if word_range.start < original_range.start {
3753 word_range.start
3754 } else {
3755 word_range.end
3756 }
3757 } else {
3758 offset
3759 };
3760
3761 head = head_offset.to_point(buffer);
3762 if head_offset <= original_range.start {
3763 tail = original_range.end.to_point(buffer);
3764 } else {
3765 tail = original_range.start.to_point(buffer);
3766 }
3767 }
3768 SelectMode::Line(original_range) => {
3769 let original_range = original_range.to_point(display_map.buffer_snapshot());
3770
3771 let position = display_map
3772 .clip_point(position, Bias::Left)
3773 .to_point(&display_map);
3774 let line_start = display_map.prev_line_boundary(position).0;
3775 let next_line_start = buffer.clip_point(
3776 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3777 Bias::Left,
3778 );
3779
3780 if line_start < original_range.start {
3781 head = line_start
3782 } else {
3783 head = next_line_start
3784 }
3785
3786 if head <= original_range.start {
3787 tail = original_range.end;
3788 } else {
3789 tail = original_range.start;
3790 }
3791 }
3792 SelectMode::All => {
3793 return;
3794 }
3795 };
3796
3797 if head < tail {
3798 pending.start = buffer.anchor_before(head);
3799 pending.end = buffer.anchor_before(tail);
3800 pending.reversed = true;
3801 } else {
3802 pending.start = buffer.anchor_before(tail);
3803 pending.end = buffer.anchor_before(head);
3804 pending.reversed = false;
3805 }
3806
3807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3808 s.set_pending(pending.clone(), mode);
3809 });
3810 } else {
3811 log::error!("update_selection dispatched with no pending selection");
3812 return;
3813 }
3814
3815 self.apply_scroll_delta(scroll_delta, window, cx);
3816 cx.notify();
3817 }
3818
3819 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3820 self.columnar_selection_state.take();
3821 if let Some(pending_mode) = self.selections.pending_mode() {
3822 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3823 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3824 s.select(selections);
3825 s.clear_pending();
3826 if s.is_extending() {
3827 s.set_is_extending(false);
3828 } else {
3829 s.set_select_mode(pending_mode);
3830 }
3831 });
3832 }
3833 }
3834
3835 fn select_columns(
3836 &mut self,
3837 head: DisplayPoint,
3838 goal_column: u32,
3839 display_map: &DisplaySnapshot,
3840 window: &mut Window,
3841 cx: &mut Context<Self>,
3842 ) {
3843 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3844 return;
3845 };
3846
3847 let tail = match columnar_state {
3848 ColumnarSelectionState::FromMouse {
3849 selection_tail,
3850 display_point,
3851 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3852 ColumnarSelectionState::FromSelection { selection_tail } => {
3853 selection_tail.to_display_point(display_map)
3854 }
3855 };
3856
3857 let start_row = cmp::min(tail.row(), head.row());
3858 let end_row = cmp::max(tail.row(), head.row());
3859 let start_column = cmp::min(tail.column(), goal_column);
3860 let end_column = cmp::max(tail.column(), goal_column);
3861 let reversed = start_column < tail.column();
3862
3863 let selection_ranges = (start_row.0..=end_row.0)
3864 .map(DisplayRow)
3865 .filter_map(|row| {
3866 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3867 || start_column <= display_map.line_len(row))
3868 && !display_map.is_block_line(row)
3869 {
3870 let start = display_map
3871 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3872 .to_point(display_map);
3873 let end = display_map
3874 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3875 .to_point(display_map);
3876 if reversed {
3877 Some(end..start)
3878 } else {
3879 Some(start..end)
3880 }
3881 } else {
3882 None
3883 }
3884 })
3885 .collect::<Vec<_>>();
3886 if selection_ranges.is_empty() {
3887 return;
3888 }
3889
3890 let ranges = match columnar_state {
3891 ColumnarSelectionState::FromMouse { .. } => {
3892 let mut non_empty_ranges = selection_ranges
3893 .iter()
3894 .filter(|selection_range| selection_range.start != selection_range.end)
3895 .peekable();
3896 if non_empty_ranges.peek().is_some() {
3897 non_empty_ranges.cloned().collect()
3898 } else {
3899 selection_ranges
3900 }
3901 }
3902 _ => selection_ranges,
3903 };
3904
3905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3906 s.select_ranges(ranges);
3907 });
3908 cx.notify();
3909 }
3910
3911 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3912 self.selections
3913 .all_adjusted(snapshot)
3914 .iter()
3915 .any(|selection| !selection.is_empty())
3916 }
3917
3918 pub fn has_pending_nonempty_selection(&self) -> bool {
3919 let pending_nonempty_selection = match self.selections.pending_anchor() {
3920 Some(Selection { start, end, .. }) => start != end,
3921 None => false,
3922 };
3923
3924 pending_nonempty_selection
3925 || (self.columnar_selection_state.is_some()
3926 && self.selections.disjoint_anchors().len() > 1)
3927 }
3928
3929 pub fn has_pending_selection(&self) -> bool {
3930 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3931 }
3932
3933 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3934 self.selection_mark_mode = false;
3935 self.selection_drag_state = SelectionDragState::None;
3936
3937 if self.clear_expanded_diff_hunks(cx) {
3938 cx.notify();
3939 return;
3940 }
3941 if self.dismiss_menus_and_popups(true, window, cx) {
3942 return;
3943 }
3944
3945 if self.mode.is_full()
3946 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3947 {
3948 return;
3949 }
3950
3951 cx.propagate();
3952 }
3953
3954 pub fn dismiss_menus_and_popups(
3955 &mut self,
3956 is_user_requested: bool,
3957 window: &mut Window,
3958 cx: &mut Context<Self>,
3959 ) -> bool {
3960 if self.take_rename(false, window, cx).is_some() {
3961 return true;
3962 }
3963
3964 if self.hide_blame_popover(true, cx) {
3965 return true;
3966 }
3967
3968 if hide_hover(self, cx) {
3969 return true;
3970 }
3971
3972 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3973 return true;
3974 }
3975
3976 if self.hide_context_menu(window, cx).is_some() {
3977 return true;
3978 }
3979
3980 if self.mouse_context_menu.take().is_some() {
3981 return true;
3982 }
3983
3984 if is_user_requested && self.discard_edit_prediction(true, cx) {
3985 return true;
3986 }
3987
3988 if self.snippet_stack.pop().is_some() {
3989 return true;
3990 }
3991
3992 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3993 self.dismiss_diagnostics(cx);
3994 return true;
3995 }
3996
3997 false
3998 }
3999
4000 fn linked_editing_ranges_for(
4001 &self,
4002 selection: Range<text::Anchor>,
4003 cx: &App,
4004 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4005 if self.linked_edit_ranges.is_empty() {
4006 return None;
4007 }
4008 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4009 selection.end.buffer_id.and_then(|end_buffer_id| {
4010 if selection.start.buffer_id != Some(end_buffer_id) {
4011 return None;
4012 }
4013 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4014 let snapshot = buffer.read(cx).snapshot();
4015 self.linked_edit_ranges
4016 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4017 .map(|ranges| (ranges, snapshot, buffer))
4018 })?;
4019 use text::ToOffset as TO;
4020 // find offset from the start of current range to current cursor position
4021 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4022
4023 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4024 let start_difference = start_offset - start_byte_offset;
4025 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4026 let end_difference = end_offset - start_byte_offset;
4027 // Current range has associated linked ranges.
4028 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4029 for range in linked_ranges.iter() {
4030 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4031 let end_offset = start_offset + end_difference;
4032 let start_offset = start_offset + start_difference;
4033 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4034 continue;
4035 }
4036 if self.selections.disjoint_anchor_ranges().any(|s| {
4037 if s.start.buffer_id != selection.start.buffer_id
4038 || s.end.buffer_id != selection.end.buffer_id
4039 {
4040 return false;
4041 }
4042 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4043 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4044 }) {
4045 continue;
4046 }
4047 let start = buffer_snapshot.anchor_after(start_offset);
4048 let end = buffer_snapshot.anchor_after(end_offset);
4049 linked_edits
4050 .entry(buffer.clone())
4051 .or_default()
4052 .push(start..end);
4053 }
4054 Some(linked_edits)
4055 }
4056
4057 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4058 let text: Arc<str> = text.into();
4059
4060 if self.read_only(cx) {
4061 return;
4062 }
4063
4064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4065
4066 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4067 let mut bracket_inserted = false;
4068 let mut edits = Vec::new();
4069 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4070 let mut new_selections = Vec::with_capacity(selections.len());
4071 let mut new_autoclose_regions = Vec::new();
4072 let snapshot = self.buffer.read(cx).read(cx);
4073 let mut clear_linked_edit_ranges = false;
4074
4075 for (selection, autoclose_region) in
4076 self.selections_with_autoclose_regions(selections, &snapshot)
4077 {
4078 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4079 // Determine if the inserted text matches the opening or closing
4080 // bracket of any of this language's bracket pairs.
4081 let mut bracket_pair = None;
4082 let mut is_bracket_pair_start = false;
4083 let mut is_bracket_pair_end = false;
4084 if !text.is_empty() {
4085 let mut bracket_pair_matching_end = None;
4086 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4087 // and they are removing the character that triggered IME popup.
4088 for (pair, enabled) in scope.brackets() {
4089 if !pair.close && !pair.surround {
4090 continue;
4091 }
4092
4093 if enabled && pair.start.ends_with(text.as_ref()) {
4094 let prefix_len = pair.start.len() - text.len();
4095 let preceding_text_matches_prefix = prefix_len == 0
4096 || (selection.start.column >= (prefix_len as u32)
4097 && snapshot.contains_str_at(
4098 Point::new(
4099 selection.start.row,
4100 selection.start.column - (prefix_len as u32),
4101 ),
4102 &pair.start[..prefix_len],
4103 ));
4104 if preceding_text_matches_prefix {
4105 bracket_pair = Some(pair.clone());
4106 is_bracket_pair_start = true;
4107 break;
4108 }
4109 }
4110 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4111 {
4112 // take first bracket pair matching end, but don't break in case a later bracket
4113 // pair matches start
4114 bracket_pair_matching_end = Some(pair.clone());
4115 }
4116 }
4117 if let Some(end) = bracket_pair_matching_end
4118 && bracket_pair.is_none()
4119 {
4120 bracket_pair = Some(end);
4121 is_bracket_pair_end = true;
4122 }
4123 }
4124
4125 if let Some(bracket_pair) = bracket_pair {
4126 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4127 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4128 let auto_surround =
4129 self.use_auto_surround && snapshot_settings.use_auto_surround;
4130 if selection.is_empty() {
4131 if is_bracket_pair_start {
4132 // If the inserted text is a suffix of an opening bracket and the
4133 // selection is preceded by the rest of the opening bracket, then
4134 // insert the closing bracket.
4135 let following_text_allows_autoclose = snapshot
4136 .chars_at(selection.start)
4137 .next()
4138 .is_none_or(|c| scope.should_autoclose_before(c));
4139
4140 let preceding_text_allows_autoclose = selection.start.column == 0
4141 || snapshot
4142 .reversed_chars_at(selection.start)
4143 .next()
4144 .is_none_or(|c| {
4145 bracket_pair.start != bracket_pair.end
4146 || !snapshot
4147 .char_classifier_at(selection.start)
4148 .is_word(c)
4149 });
4150
4151 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4152 && bracket_pair.start.len() == 1
4153 {
4154 let target = bracket_pair.start.chars().next().unwrap();
4155 let current_line_count = snapshot
4156 .reversed_chars_at(selection.start)
4157 .take_while(|&c| c != '\n')
4158 .filter(|&c| c == target)
4159 .count();
4160 current_line_count % 2 == 1
4161 } else {
4162 false
4163 };
4164
4165 if autoclose
4166 && bracket_pair.close
4167 && following_text_allows_autoclose
4168 && preceding_text_allows_autoclose
4169 && !is_closing_quote
4170 {
4171 let anchor = snapshot.anchor_before(selection.end);
4172 new_selections.push((selection.map(|_| anchor), text.len()));
4173 new_autoclose_regions.push((
4174 anchor,
4175 text.len(),
4176 selection.id,
4177 bracket_pair.clone(),
4178 ));
4179 edits.push((
4180 selection.range(),
4181 format!("{}{}", text, bracket_pair.end).into(),
4182 ));
4183 bracket_inserted = true;
4184 continue;
4185 }
4186 }
4187
4188 if let Some(region) = autoclose_region {
4189 // If the selection is followed by an auto-inserted closing bracket,
4190 // then don't insert that closing bracket again; just move the selection
4191 // past the closing bracket.
4192 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4193 && text.as_ref() == region.pair.end.as_str()
4194 && snapshot.contains_str_at(region.range.end, text.as_ref());
4195 if should_skip {
4196 let anchor = snapshot.anchor_after(selection.end);
4197 new_selections
4198 .push((selection.map(|_| anchor), region.pair.end.len()));
4199 continue;
4200 }
4201 }
4202
4203 let always_treat_brackets_as_autoclosed = snapshot
4204 .language_settings_at(selection.start, cx)
4205 .always_treat_brackets_as_autoclosed;
4206 if always_treat_brackets_as_autoclosed
4207 && is_bracket_pair_end
4208 && snapshot.contains_str_at(selection.end, text.as_ref())
4209 {
4210 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4211 // and the inserted text is a closing bracket and the selection is followed
4212 // by the closing bracket then move the selection past the closing bracket.
4213 let anchor = snapshot.anchor_after(selection.end);
4214 new_selections.push((selection.map(|_| anchor), text.len()));
4215 continue;
4216 }
4217 }
4218 // If an opening bracket is 1 character long and is typed while
4219 // text is selected, then surround that text with the bracket pair.
4220 else if auto_surround
4221 && bracket_pair.surround
4222 && is_bracket_pair_start
4223 && bracket_pair.start.chars().count() == 1
4224 {
4225 edits.push((selection.start..selection.start, text.clone()));
4226 edits.push((
4227 selection.end..selection.end,
4228 bracket_pair.end.as_str().into(),
4229 ));
4230 bracket_inserted = true;
4231 new_selections.push((
4232 Selection {
4233 id: selection.id,
4234 start: snapshot.anchor_after(selection.start),
4235 end: snapshot.anchor_before(selection.end),
4236 reversed: selection.reversed,
4237 goal: selection.goal,
4238 },
4239 0,
4240 ));
4241 continue;
4242 }
4243 }
4244 }
4245
4246 if self.auto_replace_emoji_shortcode
4247 && selection.is_empty()
4248 && text.as_ref().ends_with(':')
4249 && let Some(possible_emoji_short_code) =
4250 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4251 && !possible_emoji_short_code.is_empty()
4252 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4253 {
4254 let emoji_shortcode_start = Point::new(
4255 selection.start.row,
4256 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4257 );
4258
4259 // Remove shortcode from buffer
4260 edits.push((
4261 emoji_shortcode_start..selection.start,
4262 "".to_string().into(),
4263 ));
4264 new_selections.push((
4265 Selection {
4266 id: selection.id,
4267 start: snapshot.anchor_after(emoji_shortcode_start),
4268 end: snapshot.anchor_before(selection.start),
4269 reversed: selection.reversed,
4270 goal: selection.goal,
4271 },
4272 0,
4273 ));
4274
4275 // Insert emoji
4276 let selection_start_anchor = snapshot.anchor_after(selection.start);
4277 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4278 edits.push((selection.start..selection.end, emoji.to_string().into()));
4279
4280 continue;
4281 }
4282
4283 // If not handling any auto-close operation, then just replace the selected
4284 // text with the given input and move the selection to the end of the
4285 // newly inserted text.
4286 let anchor = snapshot.anchor_after(selection.end);
4287 if !self.linked_edit_ranges.is_empty() {
4288 let start_anchor = snapshot.anchor_before(selection.start);
4289
4290 let is_word_char = text.chars().next().is_none_or(|char| {
4291 let classifier = snapshot
4292 .char_classifier_at(start_anchor.to_offset(&snapshot))
4293 .scope_context(Some(CharScopeContext::LinkedEdit));
4294 classifier.is_word(char)
4295 });
4296
4297 if is_word_char {
4298 if let Some(ranges) = self
4299 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4300 {
4301 for (buffer, edits) in ranges {
4302 linked_edits
4303 .entry(buffer.clone())
4304 .or_default()
4305 .extend(edits.into_iter().map(|range| (range, text.clone())));
4306 }
4307 }
4308 } else {
4309 clear_linked_edit_ranges = true;
4310 }
4311 }
4312
4313 new_selections.push((selection.map(|_| anchor), 0));
4314 edits.push((selection.start..selection.end, text.clone()));
4315 }
4316
4317 drop(snapshot);
4318
4319 self.transact(window, cx, |this, window, cx| {
4320 if clear_linked_edit_ranges {
4321 this.linked_edit_ranges.clear();
4322 }
4323 let initial_buffer_versions =
4324 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4325
4326 this.buffer.update(cx, |buffer, cx| {
4327 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4328 });
4329 for (buffer, edits) in linked_edits {
4330 buffer.update(cx, |buffer, cx| {
4331 let snapshot = buffer.snapshot();
4332 let edits = edits
4333 .into_iter()
4334 .map(|(range, text)| {
4335 use text::ToPoint as TP;
4336 let end_point = TP::to_point(&range.end, &snapshot);
4337 let start_point = TP::to_point(&range.start, &snapshot);
4338 (start_point..end_point, text)
4339 })
4340 .sorted_by_key(|(range, _)| range.start);
4341 buffer.edit(edits, None, cx);
4342 })
4343 }
4344 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4345 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4346 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4347 let new_selections =
4348 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4349 .zip(new_selection_deltas)
4350 .map(|(selection, delta)| Selection {
4351 id: selection.id,
4352 start: selection.start + delta,
4353 end: selection.end + delta,
4354 reversed: selection.reversed,
4355 goal: SelectionGoal::None,
4356 })
4357 .collect::<Vec<_>>();
4358
4359 let mut i = 0;
4360 for (position, delta, selection_id, pair) in new_autoclose_regions {
4361 let position = position.to_offset(map.buffer_snapshot()) + delta;
4362 let start = map.buffer_snapshot().anchor_before(position);
4363 let end = map.buffer_snapshot().anchor_after(position);
4364 while let Some(existing_state) = this.autoclose_regions.get(i) {
4365 match existing_state
4366 .range
4367 .start
4368 .cmp(&start, map.buffer_snapshot())
4369 {
4370 Ordering::Less => i += 1,
4371 Ordering::Greater => break,
4372 Ordering::Equal => {
4373 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4374 Ordering::Less => i += 1,
4375 Ordering::Equal => break,
4376 Ordering::Greater => break,
4377 }
4378 }
4379 }
4380 }
4381 this.autoclose_regions.insert(
4382 i,
4383 AutocloseRegion {
4384 selection_id,
4385 range: start..end,
4386 pair,
4387 },
4388 );
4389 }
4390
4391 let had_active_edit_prediction = this.has_active_edit_prediction();
4392 this.change_selections(
4393 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4394 window,
4395 cx,
4396 |s| s.select(new_selections),
4397 );
4398
4399 if !bracket_inserted
4400 && let Some(on_type_format_task) =
4401 this.trigger_on_type_formatting(text.to_string(), window, cx)
4402 {
4403 on_type_format_task.detach_and_log_err(cx);
4404 }
4405
4406 let editor_settings = EditorSettings::get_global(cx);
4407 if bracket_inserted
4408 && (editor_settings.auto_signature_help
4409 || editor_settings.show_signature_help_after_edits)
4410 {
4411 this.show_signature_help(&ShowSignatureHelp, window, cx);
4412 }
4413
4414 let trigger_in_words =
4415 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4416 if this.hard_wrap.is_some() {
4417 let latest: Range<Point> = this.selections.newest(&map).range();
4418 if latest.is_empty()
4419 && this
4420 .buffer()
4421 .read(cx)
4422 .snapshot(cx)
4423 .line_len(MultiBufferRow(latest.start.row))
4424 == latest.start.column
4425 {
4426 this.rewrap_impl(
4427 RewrapOptions {
4428 override_language_settings: true,
4429 preserve_existing_whitespace: true,
4430 },
4431 cx,
4432 )
4433 }
4434 }
4435 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4436 refresh_linked_ranges(this, window, cx);
4437 this.refresh_edit_prediction(true, false, window, cx);
4438 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4439 });
4440 }
4441
4442 fn find_possible_emoji_shortcode_at_position(
4443 snapshot: &MultiBufferSnapshot,
4444 position: Point,
4445 ) -> Option<String> {
4446 let mut chars = Vec::new();
4447 let mut found_colon = false;
4448 for char in snapshot.reversed_chars_at(position).take(100) {
4449 // Found a possible emoji shortcode in the middle of the buffer
4450 if found_colon {
4451 if char.is_whitespace() {
4452 chars.reverse();
4453 return Some(chars.iter().collect());
4454 }
4455 // If the previous character is not a whitespace, we are in the middle of a word
4456 // and we only want to complete the shortcode if the word is made up of other emojis
4457 let mut containing_word = String::new();
4458 for ch in snapshot
4459 .reversed_chars_at(position)
4460 .skip(chars.len() + 1)
4461 .take(100)
4462 {
4463 if ch.is_whitespace() {
4464 break;
4465 }
4466 containing_word.push(ch);
4467 }
4468 let containing_word = containing_word.chars().rev().collect::<String>();
4469 if util::word_consists_of_emojis(containing_word.as_str()) {
4470 chars.reverse();
4471 return Some(chars.iter().collect());
4472 }
4473 }
4474
4475 if char.is_whitespace() || !char.is_ascii() {
4476 return None;
4477 }
4478 if char == ':' {
4479 found_colon = true;
4480 } else {
4481 chars.push(char);
4482 }
4483 }
4484 // Found a possible emoji shortcode at the beginning of the buffer
4485 chars.reverse();
4486 Some(chars.iter().collect())
4487 }
4488
4489 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4491 self.transact(window, cx, |this, window, cx| {
4492 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4493 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4494 let multi_buffer = this.buffer.read(cx);
4495 let buffer = multi_buffer.snapshot(cx);
4496 selections
4497 .iter()
4498 .map(|selection| {
4499 let start_point = selection.start.to_point(&buffer);
4500 let mut existing_indent =
4501 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4502 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4503 let start = selection.start;
4504 let end = selection.end;
4505 let selection_is_empty = start == end;
4506 let language_scope = buffer.language_scope_at(start);
4507 let (
4508 comment_delimiter,
4509 doc_delimiter,
4510 insert_extra_newline,
4511 indent_on_newline,
4512 indent_on_extra_newline,
4513 ) = if let Some(language) = &language_scope {
4514 let mut insert_extra_newline =
4515 insert_extra_newline_brackets(&buffer, start..end, language)
4516 || insert_extra_newline_tree_sitter(&buffer, start..end);
4517
4518 // Comment extension on newline is allowed only for cursor selections
4519 let comment_delimiter = maybe!({
4520 if !selection_is_empty {
4521 return None;
4522 }
4523
4524 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4525 return None;
4526 }
4527
4528 let delimiters = language.line_comment_prefixes();
4529 let max_len_of_delimiter =
4530 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4531 let (snapshot, range) =
4532 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4533
4534 let num_of_whitespaces = snapshot
4535 .chars_for_range(range.clone())
4536 .take_while(|c| c.is_whitespace())
4537 .count();
4538 let comment_candidate = snapshot
4539 .chars_for_range(range.clone())
4540 .skip(num_of_whitespaces)
4541 .take(max_len_of_delimiter)
4542 .collect::<String>();
4543 let (delimiter, trimmed_len) = delimiters
4544 .iter()
4545 .filter_map(|delimiter| {
4546 let prefix = delimiter.trim_end();
4547 if comment_candidate.starts_with(prefix) {
4548 Some((delimiter, prefix.len()))
4549 } else {
4550 None
4551 }
4552 })
4553 .max_by_key(|(_, len)| *len)?;
4554
4555 if let Some(BlockCommentConfig {
4556 start: block_start, ..
4557 }) = language.block_comment()
4558 {
4559 let block_start_trimmed = block_start.trim_end();
4560 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4561 let line_content = snapshot
4562 .chars_for_range(range)
4563 .skip(num_of_whitespaces)
4564 .take(block_start_trimmed.len())
4565 .collect::<String>();
4566
4567 if line_content.starts_with(block_start_trimmed) {
4568 return None;
4569 }
4570 }
4571 }
4572
4573 let cursor_is_placed_after_comment_marker =
4574 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4575 if cursor_is_placed_after_comment_marker {
4576 Some(delimiter.clone())
4577 } else {
4578 None
4579 }
4580 });
4581
4582 let mut indent_on_newline = IndentSize::spaces(0);
4583 let mut indent_on_extra_newline = IndentSize::spaces(0);
4584
4585 let doc_delimiter = maybe!({
4586 if !selection_is_empty {
4587 return None;
4588 }
4589
4590 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4591 return None;
4592 }
4593
4594 let BlockCommentConfig {
4595 start: start_tag,
4596 end: end_tag,
4597 prefix: delimiter,
4598 tab_size: len,
4599 } = language.documentation_comment()?;
4600 let is_within_block_comment = buffer
4601 .language_scope_at(start_point)
4602 .is_some_and(|scope| scope.override_name() == Some("comment"));
4603 if !is_within_block_comment {
4604 return None;
4605 }
4606
4607 let (snapshot, range) =
4608 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4609
4610 let num_of_whitespaces = snapshot
4611 .chars_for_range(range.clone())
4612 .take_while(|c| c.is_whitespace())
4613 .count();
4614
4615 // 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.
4616 let column = start_point.column;
4617 let cursor_is_after_start_tag = {
4618 let start_tag_len = start_tag.len();
4619 let start_tag_line = snapshot
4620 .chars_for_range(range.clone())
4621 .skip(num_of_whitespaces)
4622 .take(start_tag_len)
4623 .collect::<String>();
4624 if start_tag_line.starts_with(start_tag.as_ref()) {
4625 num_of_whitespaces + start_tag_len <= column as usize
4626 } else {
4627 false
4628 }
4629 };
4630
4631 let cursor_is_after_delimiter = {
4632 let delimiter_trim = delimiter.trim_end();
4633 let delimiter_line = snapshot
4634 .chars_for_range(range.clone())
4635 .skip(num_of_whitespaces)
4636 .take(delimiter_trim.len())
4637 .collect::<String>();
4638 if delimiter_line.starts_with(delimiter_trim) {
4639 num_of_whitespaces + delimiter_trim.len() <= column as usize
4640 } else {
4641 false
4642 }
4643 };
4644
4645 let cursor_is_before_end_tag_if_exists = {
4646 let mut char_position = 0u32;
4647 let mut end_tag_offset = None;
4648
4649 'outer: for chunk in snapshot.text_for_range(range) {
4650 if let Some(byte_pos) = chunk.find(&**end_tag) {
4651 let chars_before_match =
4652 chunk[..byte_pos].chars().count() as u32;
4653 end_tag_offset =
4654 Some(char_position + chars_before_match);
4655 break 'outer;
4656 }
4657 char_position += chunk.chars().count() as u32;
4658 }
4659
4660 if let Some(end_tag_offset) = end_tag_offset {
4661 let cursor_is_before_end_tag = column <= end_tag_offset;
4662 if cursor_is_after_start_tag {
4663 if cursor_is_before_end_tag {
4664 insert_extra_newline = true;
4665 }
4666 let cursor_is_at_start_of_end_tag =
4667 column == end_tag_offset;
4668 if cursor_is_at_start_of_end_tag {
4669 indent_on_extra_newline.len = *len;
4670 }
4671 }
4672 cursor_is_before_end_tag
4673 } else {
4674 true
4675 }
4676 };
4677
4678 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4679 && cursor_is_before_end_tag_if_exists
4680 {
4681 if cursor_is_after_start_tag {
4682 indent_on_newline.len = *len;
4683 }
4684 Some(delimiter.clone())
4685 } else {
4686 None
4687 }
4688 });
4689
4690 (
4691 comment_delimiter,
4692 doc_delimiter,
4693 insert_extra_newline,
4694 indent_on_newline,
4695 indent_on_extra_newline,
4696 )
4697 } else {
4698 (
4699 None,
4700 None,
4701 false,
4702 IndentSize::default(),
4703 IndentSize::default(),
4704 )
4705 };
4706
4707 let prevent_auto_indent = doc_delimiter.is_some();
4708 let delimiter = comment_delimiter.or(doc_delimiter);
4709
4710 let capacity_for_delimiter =
4711 delimiter.as_deref().map(str::len).unwrap_or_default();
4712 let mut new_text = String::with_capacity(
4713 1 + capacity_for_delimiter
4714 + existing_indent.len as usize
4715 + indent_on_newline.len as usize
4716 + indent_on_extra_newline.len as usize,
4717 );
4718 new_text.push('\n');
4719 new_text.extend(existing_indent.chars());
4720 new_text.extend(indent_on_newline.chars());
4721
4722 if let Some(delimiter) = &delimiter {
4723 new_text.push_str(delimiter);
4724 }
4725
4726 if insert_extra_newline {
4727 new_text.push('\n');
4728 new_text.extend(existing_indent.chars());
4729 new_text.extend(indent_on_extra_newline.chars());
4730 }
4731
4732 let anchor = buffer.anchor_after(end);
4733 let new_selection = selection.map(|_| anchor);
4734 (
4735 ((start..end, new_text), prevent_auto_indent),
4736 (insert_extra_newline, new_selection),
4737 )
4738 })
4739 .unzip()
4740 };
4741
4742 let mut auto_indent_edits = Vec::new();
4743 let mut edits = Vec::new();
4744 for (edit, prevent_auto_indent) in edits_with_flags {
4745 if prevent_auto_indent {
4746 edits.push(edit);
4747 } else {
4748 auto_indent_edits.push(edit);
4749 }
4750 }
4751 if !edits.is_empty() {
4752 this.edit(edits, cx);
4753 }
4754 if !auto_indent_edits.is_empty() {
4755 this.edit_with_autoindent(auto_indent_edits, cx);
4756 }
4757
4758 let buffer = this.buffer.read(cx).snapshot(cx);
4759 let new_selections = selection_info
4760 .into_iter()
4761 .map(|(extra_newline_inserted, new_selection)| {
4762 let mut cursor = new_selection.end.to_point(&buffer);
4763 if extra_newline_inserted {
4764 cursor.row -= 1;
4765 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4766 }
4767 new_selection.map(|_| cursor)
4768 })
4769 .collect();
4770
4771 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4772 this.refresh_edit_prediction(true, false, window, cx);
4773 });
4774 }
4775
4776 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4778
4779 let buffer = self.buffer.read(cx);
4780 let snapshot = buffer.snapshot(cx);
4781
4782 let mut edits = Vec::new();
4783 let mut rows = Vec::new();
4784
4785 for (rows_inserted, selection) in self
4786 .selections
4787 .all_adjusted(&self.display_snapshot(cx))
4788 .into_iter()
4789 .enumerate()
4790 {
4791 let cursor = selection.head();
4792 let row = cursor.row;
4793
4794 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4795
4796 let newline = "\n".to_string();
4797 edits.push((start_of_line..start_of_line, newline));
4798
4799 rows.push(row + rows_inserted as u32);
4800 }
4801
4802 self.transact(window, cx, |editor, window, cx| {
4803 editor.edit(edits, cx);
4804
4805 editor.change_selections(Default::default(), window, cx, |s| {
4806 let mut index = 0;
4807 s.move_cursors_with(|map, _, _| {
4808 let row = rows[index];
4809 index += 1;
4810
4811 let point = Point::new(row, 0);
4812 let boundary = map.next_line_boundary(point).1;
4813 let clipped = map.clip_point(boundary, Bias::Left);
4814
4815 (clipped, SelectionGoal::None)
4816 });
4817 });
4818
4819 let mut indent_edits = Vec::new();
4820 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4821 for row in rows {
4822 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4823 for (row, indent) in indents {
4824 if indent.len == 0 {
4825 continue;
4826 }
4827
4828 let text = match indent.kind {
4829 IndentKind::Space => " ".repeat(indent.len as usize),
4830 IndentKind::Tab => "\t".repeat(indent.len as usize),
4831 };
4832 let point = Point::new(row.0, 0);
4833 indent_edits.push((point..point, text));
4834 }
4835 }
4836 editor.edit(indent_edits, cx);
4837 });
4838 }
4839
4840 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4841 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4842
4843 let buffer = self.buffer.read(cx);
4844 let snapshot = buffer.snapshot(cx);
4845
4846 let mut edits = Vec::new();
4847 let mut rows = Vec::new();
4848 let mut rows_inserted = 0;
4849
4850 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4851 let cursor = selection.head();
4852 let row = cursor.row;
4853
4854 let point = Point::new(row + 1, 0);
4855 let start_of_line = snapshot.clip_point(point, Bias::Left);
4856
4857 let newline = "\n".to_string();
4858 edits.push((start_of_line..start_of_line, newline));
4859
4860 rows_inserted += 1;
4861 rows.push(row + rows_inserted);
4862 }
4863
4864 self.transact(window, cx, |editor, window, cx| {
4865 editor.edit(edits, cx);
4866
4867 editor.change_selections(Default::default(), window, cx, |s| {
4868 let mut index = 0;
4869 s.move_cursors_with(|map, _, _| {
4870 let row = rows[index];
4871 index += 1;
4872
4873 let point = Point::new(row, 0);
4874 let boundary = map.next_line_boundary(point).1;
4875 let clipped = map.clip_point(boundary, Bias::Left);
4876
4877 (clipped, SelectionGoal::None)
4878 });
4879 });
4880
4881 let mut indent_edits = Vec::new();
4882 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4883 for row in rows {
4884 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4885 for (row, indent) in indents {
4886 if indent.len == 0 {
4887 continue;
4888 }
4889
4890 let text = match indent.kind {
4891 IndentKind::Space => " ".repeat(indent.len as usize),
4892 IndentKind::Tab => "\t".repeat(indent.len as usize),
4893 };
4894 let point = Point::new(row.0, 0);
4895 indent_edits.push((point..point, text));
4896 }
4897 }
4898 editor.edit(indent_edits, cx);
4899 });
4900 }
4901
4902 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4903 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4904 original_indent_columns: Vec::new(),
4905 });
4906 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4907 }
4908
4909 fn insert_with_autoindent_mode(
4910 &mut self,
4911 text: &str,
4912 autoindent_mode: Option<AutoindentMode>,
4913 window: &mut Window,
4914 cx: &mut Context<Self>,
4915 ) {
4916 if self.read_only(cx) {
4917 return;
4918 }
4919
4920 let text: Arc<str> = text.into();
4921 self.transact(window, cx, |this, window, cx| {
4922 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4923 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4924 let anchors = {
4925 let snapshot = buffer.read(cx);
4926 old_selections
4927 .iter()
4928 .map(|s| {
4929 let anchor = snapshot.anchor_after(s.head());
4930 s.map(|_| anchor)
4931 })
4932 .collect::<Vec<_>>()
4933 };
4934 buffer.edit(
4935 old_selections
4936 .iter()
4937 .map(|s| (s.start..s.end, text.clone())),
4938 autoindent_mode,
4939 cx,
4940 );
4941 anchors
4942 });
4943
4944 this.change_selections(Default::default(), window, cx, |s| {
4945 s.select_anchors(selection_anchors);
4946 });
4947
4948 cx.notify();
4949 });
4950 }
4951
4952 fn trigger_completion_on_input(
4953 &mut self,
4954 text: &str,
4955 trigger_in_words: bool,
4956 window: &mut Window,
4957 cx: &mut Context<Self>,
4958 ) {
4959 let completions_source = self
4960 .context_menu
4961 .borrow()
4962 .as_ref()
4963 .and_then(|menu| match menu {
4964 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4965 CodeContextMenu::CodeActions(_) => None,
4966 });
4967
4968 match completions_source {
4969 Some(CompletionsMenuSource::Words { .. }) => {
4970 self.open_or_update_completions_menu(
4971 Some(CompletionsMenuSource::Words {
4972 ignore_threshold: false,
4973 }),
4974 None,
4975 window,
4976 cx,
4977 );
4978 }
4979 Some(CompletionsMenuSource::Normal)
4980 | Some(CompletionsMenuSource::SnippetChoices)
4981 | None
4982 if self.is_completion_trigger(
4983 text,
4984 trigger_in_words,
4985 completions_source.is_some(),
4986 cx,
4987 ) =>
4988 {
4989 self.show_completions(
4990 &ShowCompletions {
4991 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4992 },
4993 window,
4994 cx,
4995 )
4996 }
4997 _ => {
4998 self.hide_context_menu(window, cx);
4999 }
5000 }
5001 }
5002
5003 fn is_completion_trigger(
5004 &self,
5005 text: &str,
5006 trigger_in_words: bool,
5007 menu_is_open: bool,
5008 cx: &mut Context<Self>,
5009 ) -> bool {
5010 let position = self.selections.newest_anchor().head();
5011 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5012 return false;
5013 };
5014
5015 if let Some(completion_provider) = &self.completion_provider {
5016 completion_provider.is_completion_trigger(
5017 &buffer,
5018 position.text_anchor,
5019 text,
5020 trigger_in_words,
5021 menu_is_open,
5022 cx,
5023 )
5024 } else {
5025 false
5026 }
5027 }
5028
5029 /// If any empty selections is touching the start of its innermost containing autoclose
5030 /// region, expand it to select the brackets.
5031 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5032 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5033 let buffer = self.buffer.read(cx).read(cx);
5034 let new_selections = self
5035 .selections_with_autoclose_regions(selections, &buffer)
5036 .map(|(mut selection, region)| {
5037 if !selection.is_empty() {
5038 return selection;
5039 }
5040
5041 if let Some(region) = region {
5042 let mut range = region.range.to_offset(&buffer);
5043 if selection.start == range.start && range.start >= region.pair.start.len() {
5044 range.start -= region.pair.start.len();
5045 if buffer.contains_str_at(range.start, ®ion.pair.start)
5046 && buffer.contains_str_at(range.end, ®ion.pair.end)
5047 {
5048 range.end += region.pair.end.len();
5049 selection.start = range.start;
5050 selection.end = range.end;
5051
5052 return selection;
5053 }
5054 }
5055 }
5056
5057 let always_treat_brackets_as_autoclosed = buffer
5058 .language_settings_at(selection.start, cx)
5059 .always_treat_brackets_as_autoclosed;
5060
5061 if !always_treat_brackets_as_autoclosed {
5062 return selection;
5063 }
5064
5065 if let Some(scope) = buffer.language_scope_at(selection.start) {
5066 for (pair, enabled) in scope.brackets() {
5067 if !enabled || !pair.close {
5068 continue;
5069 }
5070
5071 if buffer.contains_str_at(selection.start, &pair.end) {
5072 let pair_start_len = pair.start.len();
5073 if buffer.contains_str_at(
5074 selection.start.saturating_sub(pair_start_len),
5075 &pair.start,
5076 ) {
5077 selection.start -= pair_start_len;
5078 selection.end += pair.end.len();
5079
5080 return selection;
5081 }
5082 }
5083 }
5084 }
5085
5086 selection
5087 })
5088 .collect();
5089
5090 drop(buffer);
5091 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5092 selections.select(new_selections)
5093 });
5094 }
5095
5096 /// Iterate the given selections, and for each one, find the smallest surrounding
5097 /// autoclose region. This uses the ordering of the selections and the autoclose
5098 /// regions to avoid repeated comparisons.
5099 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5100 &'a self,
5101 selections: impl IntoIterator<Item = Selection<D>>,
5102 buffer: &'a MultiBufferSnapshot,
5103 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5104 let mut i = 0;
5105 let mut regions = self.autoclose_regions.as_slice();
5106 selections.into_iter().map(move |selection| {
5107 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5108
5109 let mut enclosing = None;
5110 while let Some(pair_state) = regions.get(i) {
5111 if pair_state.range.end.to_offset(buffer) < range.start {
5112 regions = ®ions[i + 1..];
5113 i = 0;
5114 } else if pair_state.range.start.to_offset(buffer) > range.end {
5115 break;
5116 } else {
5117 if pair_state.selection_id == selection.id {
5118 enclosing = Some(pair_state);
5119 }
5120 i += 1;
5121 }
5122 }
5123
5124 (selection, enclosing)
5125 })
5126 }
5127
5128 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5129 fn invalidate_autoclose_regions(
5130 &mut self,
5131 mut selections: &[Selection<Anchor>],
5132 buffer: &MultiBufferSnapshot,
5133 ) {
5134 self.autoclose_regions.retain(|state| {
5135 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5136 return false;
5137 }
5138
5139 let mut i = 0;
5140 while let Some(selection) = selections.get(i) {
5141 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5142 selections = &selections[1..];
5143 continue;
5144 }
5145 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5146 break;
5147 }
5148 if selection.id == state.selection_id {
5149 return true;
5150 } else {
5151 i += 1;
5152 }
5153 }
5154 false
5155 });
5156 }
5157
5158 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5159 let offset = position.to_offset(buffer);
5160 let (word_range, kind) =
5161 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5162 if offset > word_range.start && kind == Some(CharKind::Word) {
5163 Some(
5164 buffer
5165 .text_for_range(word_range.start..offset)
5166 .collect::<String>(),
5167 )
5168 } else {
5169 None
5170 }
5171 }
5172
5173 pub fn visible_excerpts(
5174 &self,
5175 cx: &mut Context<Editor>,
5176 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5177 let Some(project) = self.project() else {
5178 return HashMap::default();
5179 };
5180 let project = project.read(cx);
5181 let multi_buffer = self.buffer().read(cx);
5182 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5183 let multi_buffer_visible_start = self
5184 .scroll_manager
5185 .anchor()
5186 .anchor
5187 .to_point(&multi_buffer_snapshot);
5188 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5189 multi_buffer_visible_start
5190 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5191 Bias::Left,
5192 );
5193 multi_buffer_snapshot
5194 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5195 .into_iter()
5196 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5197 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5198 let buffer_file = project::File::from_dyn(buffer.file())?;
5199 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5200 let worktree_entry = buffer_worktree
5201 .read(cx)
5202 .entry_for_id(buffer_file.project_entry_id()?)?;
5203 if worktree_entry.is_ignored {
5204 None
5205 } else {
5206 Some((
5207 excerpt_id,
5208 (
5209 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5210 buffer.version().clone(),
5211 excerpt_visible_range,
5212 ),
5213 ))
5214 }
5215 })
5216 .collect()
5217 }
5218
5219 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5220 TextLayoutDetails {
5221 text_system: window.text_system().clone(),
5222 editor_style: self.style.clone().unwrap(),
5223 rem_size: window.rem_size(),
5224 scroll_anchor: self.scroll_manager.anchor(),
5225 visible_rows: self.visible_line_count(),
5226 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5227 }
5228 }
5229
5230 fn trigger_on_type_formatting(
5231 &self,
5232 input: String,
5233 window: &mut Window,
5234 cx: &mut Context<Self>,
5235 ) -> Option<Task<Result<()>>> {
5236 if input.len() != 1 {
5237 return None;
5238 }
5239
5240 let project = self.project()?;
5241 let position = self.selections.newest_anchor().head();
5242 let (buffer, buffer_position) = self
5243 .buffer
5244 .read(cx)
5245 .text_anchor_for_position(position, cx)?;
5246
5247 let settings = language_settings::language_settings(
5248 buffer
5249 .read(cx)
5250 .language_at(buffer_position)
5251 .map(|l| l.name()),
5252 buffer.read(cx).file(),
5253 cx,
5254 );
5255 if !settings.use_on_type_format {
5256 return None;
5257 }
5258
5259 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5260 // hence we do LSP request & edit on host side only — add formats to host's history.
5261 let push_to_lsp_host_history = true;
5262 // If this is not the host, append its history with new edits.
5263 let push_to_client_history = project.read(cx).is_via_collab();
5264
5265 let on_type_formatting = project.update(cx, |project, cx| {
5266 project.on_type_format(
5267 buffer.clone(),
5268 buffer_position,
5269 input,
5270 push_to_lsp_host_history,
5271 cx,
5272 )
5273 });
5274 Some(cx.spawn_in(window, async move |editor, cx| {
5275 if let Some(transaction) = on_type_formatting.await? {
5276 if push_to_client_history {
5277 buffer
5278 .update(cx, |buffer, _| {
5279 buffer.push_transaction(transaction, Instant::now());
5280 buffer.finalize_last_transaction();
5281 })
5282 .ok();
5283 }
5284 editor.update(cx, |editor, cx| {
5285 editor.refresh_document_highlights(cx);
5286 })?;
5287 }
5288 Ok(())
5289 }))
5290 }
5291
5292 pub fn show_word_completions(
5293 &mut self,
5294 _: &ShowWordCompletions,
5295 window: &mut Window,
5296 cx: &mut Context<Self>,
5297 ) {
5298 self.open_or_update_completions_menu(
5299 Some(CompletionsMenuSource::Words {
5300 ignore_threshold: true,
5301 }),
5302 None,
5303 window,
5304 cx,
5305 );
5306 }
5307
5308 pub fn show_completions(
5309 &mut self,
5310 options: &ShowCompletions,
5311 window: &mut Window,
5312 cx: &mut Context<Self>,
5313 ) {
5314 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5315 }
5316
5317 fn open_or_update_completions_menu(
5318 &mut self,
5319 requested_source: Option<CompletionsMenuSource>,
5320 trigger: Option<&str>,
5321 window: &mut Window,
5322 cx: &mut Context<Self>,
5323 ) {
5324 if self.pending_rename.is_some() {
5325 return;
5326 }
5327
5328 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5329
5330 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5331 // inserted and selected. To handle that case, the start of the selection is used so that
5332 // the menu starts with all choices.
5333 let position = self
5334 .selections
5335 .newest_anchor()
5336 .start
5337 .bias_right(&multibuffer_snapshot);
5338 if position.diff_base_anchor.is_some() {
5339 return;
5340 }
5341 let buffer_position = multibuffer_snapshot.anchor_before(position);
5342 let Some(buffer) = buffer_position
5343 .buffer_id
5344 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5345 else {
5346 return;
5347 };
5348 let buffer_snapshot = buffer.read(cx).snapshot();
5349
5350 let query: Option<Arc<String>> =
5351 Self::completion_query(&multibuffer_snapshot, buffer_position)
5352 .map(|query| query.into());
5353
5354 drop(multibuffer_snapshot);
5355
5356 // Hide the current completions menu when query is empty. Without this, cached
5357 // completions from before the trigger char may be reused (#32774).
5358 if query.is_none() {
5359 let menu_is_open = matches!(
5360 self.context_menu.borrow().as_ref(),
5361 Some(CodeContextMenu::Completions(_))
5362 );
5363 if menu_is_open {
5364 self.hide_context_menu(window, cx);
5365 }
5366 }
5367
5368 let mut ignore_word_threshold = false;
5369 let provider = match requested_source {
5370 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5371 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5372 ignore_word_threshold = ignore_threshold;
5373 None
5374 }
5375 Some(CompletionsMenuSource::SnippetChoices) => {
5376 log::error!("bug: SnippetChoices requested_source is not handled");
5377 None
5378 }
5379 };
5380
5381 let sort_completions = provider
5382 .as_ref()
5383 .is_some_and(|provider| provider.sort_completions());
5384
5385 let filter_completions = provider
5386 .as_ref()
5387 .is_none_or(|provider| provider.filter_completions());
5388
5389 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5390 if filter_completions {
5391 menu.filter(query.clone(), provider.clone(), window, cx);
5392 }
5393 // When `is_incomplete` is false, no need to re-query completions when the current query
5394 // is a suffix of the initial query.
5395 if !menu.is_incomplete {
5396 // If the new query is a suffix of the old query (typing more characters) and
5397 // the previous result was complete, the existing completions can be filtered.
5398 //
5399 // Note that this is always true for snippet completions.
5400 let query_matches = match (&menu.initial_query, &query) {
5401 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5402 (None, _) => true,
5403 _ => false,
5404 };
5405 if query_matches {
5406 let position_matches = if menu.initial_position == position {
5407 true
5408 } else {
5409 let snapshot = self.buffer.read(cx).read(cx);
5410 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5411 };
5412 if position_matches {
5413 return;
5414 }
5415 }
5416 }
5417 };
5418
5419 let trigger_kind = match trigger {
5420 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5421 CompletionTriggerKind::TRIGGER_CHARACTER
5422 }
5423 _ => CompletionTriggerKind::INVOKED,
5424 };
5425 let completion_context = CompletionContext {
5426 trigger_character: trigger.and_then(|trigger| {
5427 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5428 Some(String::from(trigger))
5429 } else {
5430 None
5431 }
5432 }),
5433 trigger_kind,
5434 };
5435
5436 let Anchor {
5437 excerpt_id: buffer_excerpt_id,
5438 text_anchor: buffer_position,
5439 ..
5440 } = buffer_position;
5441
5442 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5443 buffer_snapshot.surrounding_word(buffer_position, None)
5444 {
5445 let word_to_exclude = buffer_snapshot
5446 .text_for_range(word_range.clone())
5447 .collect::<String>();
5448 (
5449 buffer_snapshot.anchor_before(word_range.start)
5450 ..buffer_snapshot.anchor_after(buffer_position),
5451 Some(word_to_exclude),
5452 )
5453 } else {
5454 (buffer_position..buffer_position, None)
5455 };
5456
5457 let language = buffer_snapshot
5458 .language_at(buffer_position)
5459 .map(|language| language.name());
5460
5461 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5462 .completions
5463 .clone();
5464
5465 let show_completion_documentation = buffer_snapshot
5466 .settings_at(buffer_position, cx)
5467 .show_completion_documentation;
5468
5469 // The document can be large, so stay in reasonable bounds when searching for words,
5470 // otherwise completion pop-up might be slow to appear.
5471 const WORD_LOOKUP_ROWS: u32 = 5_000;
5472 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5473 let min_word_search = buffer_snapshot.clip_point(
5474 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5475 Bias::Left,
5476 );
5477 let max_word_search = buffer_snapshot.clip_point(
5478 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5479 Bias::Right,
5480 );
5481 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5482 ..buffer_snapshot.point_to_offset(max_word_search);
5483
5484 let skip_digits = query
5485 .as_ref()
5486 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5487
5488 let omit_word_completions = !self.word_completions_enabled
5489 || (!ignore_word_threshold
5490 && match &query {
5491 Some(query) => query.chars().count() < completion_settings.words_min_length,
5492 None => completion_settings.words_min_length != 0,
5493 });
5494
5495 let (mut words, provider_responses) = match &provider {
5496 Some(provider) => {
5497 let provider_responses = provider.completions(
5498 buffer_excerpt_id,
5499 &buffer,
5500 buffer_position,
5501 completion_context,
5502 window,
5503 cx,
5504 );
5505
5506 let words = match (omit_word_completions, completion_settings.words) {
5507 (true, _) | (_, WordsCompletionMode::Disabled) => {
5508 Task::ready(BTreeMap::default())
5509 }
5510 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5511 .background_spawn(async move {
5512 buffer_snapshot.words_in_range(WordsQuery {
5513 fuzzy_contents: None,
5514 range: word_search_range,
5515 skip_digits,
5516 })
5517 }),
5518 };
5519
5520 (words, provider_responses)
5521 }
5522 None => {
5523 let words = if omit_word_completions {
5524 Task::ready(BTreeMap::default())
5525 } else {
5526 cx.background_spawn(async move {
5527 buffer_snapshot.words_in_range(WordsQuery {
5528 fuzzy_contents: None,
5529 range: word_search_range,
5530 skip_digits,
5531 })
5532 })
5533 };
5534 (words, Task::ready(Ok(Vec::new())))
5535 }
5536 };
5537
5538 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5539
5540 let id = post_inc(&mut self.next_completion_id);
5541 let task = cx.spawn_in(window, async move |editor, cx| {
5542 let Ok(()) = editor.update(cx, |this, _| {
5543 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5544 }) else {
5545 return;
5546 };
5547
5548 // TODO: Ideally completions from different sources would be selectively re-queried, so
5549 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5550 let mut completions = Vec::new();
5551 let mut is_incomplete = false;
5552 let mut display_options: Option<CompletionDisplayOptions> = None;
5553 if let Some(provider_responses) = provider_responses.await.log_err()
5554 && !provider_responses.is_empty()
5555 {
5556 for response in provider_responses {
5557 completions.extend(response.completions);
5558 is_incomplete = is_incomplete || response.is_incomplete;
5559 match display_options.as_mut() {
5560 None => {
5561 display_options = Some(response.display_options);
5562 }
5563 Some(options) => options.merge(&response.display_options),
5564 }
5565 }
5566 if completion_settings.words == WordsCompletionMode::Fallback {
5567 words = Task::ready(BTreeMap::default());
5568 }
5569 }
5570 let display_options = display_options.unwrap_or_default();
5571
5572 let mut words = words.await;
5573 if let Some(word_to_exclude) = &word_to_exclude {
5574 words.remove(word_to_exclude);
5575 }
5576 for lsp_completion in &completions {
5577 words.remove(&lsp_completion.new_text);
5578 }
5579 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5580 replace_range: word_replace_range.clone(),
5581 new_text: word.clone(),
5582 label: CodeLabel::plain(word, None),
5583 icon_path: None,
5584 documentation: None,
5585 source: CompletionSource::BufferWord {
5586 word_range,
5587 resolved: false,
5588 },
5589 insert_text_mode: Some(InsertTextMode::AS_IS),
5590 confirm: None,
5591 }));
5592
5593 let menu = if completions.is_empty() {
5594 None
5595 } else {
5596 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5597 let languages = editor
5598 .workspace
5599 .as_ref()
5600 .and_then(|(workspace, _)| workspace.upgrade())
5601 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5602 let menu = CompletionsMenu::new(
5603 id,
5604 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5605 sort_completions,
5606 show_completion_documentation,
5607 position,
5608 query.clone(),
5609 is_incomplete,
5610 buffer.clone(),
5611 completions.into(),
5612 display_options,
5613 snippet_sort_order,
5614 languages,
5615 language,
5616 cx,
5617 );
5618
5619 let query = if filter_completions { query } else { None };
5620 let matches_task = if let Some(query) = query {
5621 menu.do_async_filtering(query, cx)
5622 } else {
5623 Task::ready(menu.unfiltered_matches())
5624 };
5625 (menu, matches_task)
5626 }) else {
5627 return;
5628 };
5629
5630 let matches = matches_task.await;
5631
5632 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5633 // Newer menu already set, so exit.
5634 if let Some(CodeContextMenu::Completions(prev_menu)) =
5635 editor.context_menu.borrow().as_ref()
5636 && prev_menu.id > id
5637 {
5638 return;
5639 };
5640
5641 // Only valid to take prev_menu because it the new menu is immediately set
5642 // below, or the menu is hidden.
5643 if let Some(CodeContextMenu::Completions(prev_menu)) =
5644 editor.context_menu.borrow_mut().take()
5645 {
5646 let position_matches =
5647 if prev_menu.initial_position == menu.initial_position {
5648 true
5649 } else {
5650 let snapshot = editor.buffer.read(cx).read(cx);
5651 prev_menu.initial_position.to_offset(&snapshot)
5652 == menu.initial_position.to_offset(&snapshot)
5653 };
5654 if position_matches {
5655 // Preserve markdown cache before `set_filter_results` because it will
5656 // try to populate the documentation cache.
5657 menu.preserve_markdown_cache(prev_menu);
5658 }
5659 };
5660
5661 menu.set_filter_results(matches, provider, window, cx);
5662 }) else {
5663 return;
5664 };
5665
5666 menu.visible().then_some(menu)
5667 };
5668
5669 editor
5670 .update_in(cx, |editor, window, cx| {
5671 if editor.focus_handle.is_focused(window)
5672 && let Some(menu) = menu
5673 {
5674 *editor.context_menu.borrow_mut() =
5675 Some(CodeContextMenu::Completions(menu));
5676
5677 crate::hover_popover::hide_hover(editor, cx);
5678 if editor.show_edit_predictions_in_menu() {
5679 editor.update_visible_edit_prediction(window, cx);
5680 } else {
5681 editor.discard_edit_prediction(false, cx);
5682 }
5683
5684 cx.notify();
5685 return;
5686 }
5687
5688 if editor.completion_tasks.len() <= 1 {
5689 // If there are no more completion tasks and the last menu was empty, we should hide it.
5690 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5691 // If it was already hidden and we don't show edit predictions in the menu,
5692 // we should also show the edit prediction when available.
5693 if was_hidden && editor.show_edit_predictions_in_menu() {
5694 editor.update_visible_edit_prediction(window, cx);
5695 }
5696 }
5697 })
5698 .ok();
5699 });
5700
5701 self.completion_tasks.push((id, task));
5702 }
5703
5704 #[cfg(feature = "test-support")]
5705 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5706 let menu = self.context_menu.borrow();
5707 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5708 let completions = menu.completions.borrow();
5709 Some(completions.to_vec())
5710 } else {
5711 None
5712 }
5713 }
5714
5715 pub fn with_completions_menu_matching_id<R>(
5716 &self,
5717 id: CompletionId,
5718 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5719 ) -> R {
5720 let mut context_menu = self.context_menu.borrow_mut();
5721 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5722 return f(None);
5723 };
5724 if completions_menu.id != id {
5725 return f(None);
5726 }
5727 f(Some(completions_menu))
5728 }
5729
5730 pub fn confirm_completion(
5731 &mut self,
5732 action: &ConfirmCompletion,
5733 window: &mut Window,
5734 cx: &mut Context<Self>,
5735 ) -> Option<Task<Result<()>>> {
5736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5737 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5738 }
5739
5740 pub fn confirm_completion_insert(
5741 &mut self,
5742 _: &ConfirmCompletionInsert,
5743 window: &mut Window,
5744 cx: &mut Context<Self>,
5745 ) -> Option<Task<Result<()>>> {
5746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5747 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5748 }
5749
5750 pub fn confirm_completion_replace(
5751 &mut self,
5752 _: &ConfirmCompletionReplace,
5753 window: &mut Window,
5754 cx: &mut Context<Self>,
5755 ) -> Option<Task<Result<()>>> {
5756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5757 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5758 }
5759
5760 pub fn compose_completion(
5761 &mut self,
5762 action: &ComposeCompletion,
5763 window: &mut Window,
5764 cx: &mut Context<Self>,
5765 ) -> Option<Task<Result<()>>> {
5766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5767 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5768 }
5769
5770 fn do_completion(
5771 &mut self,
5772 item_ix: Option<usize>,
5773 intent: CompletionIntent,
5774 window: &mut Window,
5775 cx: &mut Context<Editor>,
5776 ) -> Option<Task<Result<()>>> {
5777 use language::ToOffset as _;
5778
5779 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5780 else {
5781 return None;
5782 };
5783
5784 let candidate_id = {
5785 let entries = completions_menu.entries.borrow();
5786 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5787 if self.show_edit_predictions_in_menu() {
5788 self.discard_edit_prediction(true, cx);
5789 }
5790 mat.candidate_id
5791 };
5792
5793 let completion = completions_menu
5794 .completions
5795 .borrow()
5796 .get(candidate_id)?
5797 .clone();
5798 cx.stop_propagation();
5799
5800 let buffer_handle = completions_menu.buffer.clone();
5801
5802 let CompletionEdit {
5803 new_text,
5804 snippet,
5805 replace_range,
5806 } = process_completion_for_edit(
5807 &completion,
5808 intent,
5809 &buffer_handle,
5810 &completions_menu.initial_position.text_anchor,
5811 cx,
5812 );
5813
5814 let buffer = buffer_handle.read(cx);
5815 let snapshot = self.buffer.read(cx).snapshot(cx);
5816 let newest_anchor = self.selections.newest_anchor();
5817 let replace_range_multibuffer = {
5818 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5819 excerpt.map_range_from_buffer(replace_range.clone())
5820 };
5821 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5822 return None;
5823 }
5824
5825 let old_text = buffer
5826 .text_for_range(replace_range.clone())
5827 .collect::<String>();
5828 let lookbehind = newest_anchor
5829 .start
5830 .text_anchor
5831 .to_offset(buffer)
5832 .saturating_sub(replace_range.start);
5833 let lookahead = replace_range
5834 .end
5835 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5836 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5837 let suffix = &old_text[lookbehind.min(old_text.len())..];
5838
5839 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5840 let mut ranges = Vec::new();
5841 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5842
5843 for selection in &selections {
5844 let range = if selection.id == newest_anchor.id {
5845 replace_range_multibuffer.clone()
5846 } else {
5847 let mut range = selection.range();
5848
5849 // if prefix is present, don't duplicate it
5850 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5851 range.start = range.start.saturating_sub(lookbehind);
5852
5853 // if suffix is also present, mimic the newest cursor and replace it
5854 if selection.id != newest_anchor.id
5855 && snapshot.contains_str_at(range.end, suffix)
5856 {
5857 range.end += lookahead;
5858 }
5859 }
5860 range
5861 };
5862
5863 ranges.push(range.clone());
5864
5865 if !self.linked_edit_ranges.is_empty() {
5866 let start_anchor = snapshot.anchor_before(range.start);
5867 let end_anchor = snapshot.anchor_after(range.end);
5868 if let Some(ranges) = self
5869 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5870 {
5871 for (buffer, edits) in ranges {
5872 linked_edits
5873 .entry(buffer.clone())
5874 .or_default()
5875 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5876 }
5877 }
5878 }
5879 }
5880
5881 let common_prefix_len = old_text
5882 .chars()
5883 .zip(new_text.chars())
5884 .take_while(|(a, b)| a == b)
5885 .map(|(a, _)| a.len_utf8())
5886 .sum::<usize>();
5887
5888 cx.emit(EditorEvent::InputHandled {
5889 utf16_range_to_replace: None,
5890 text: new_text[common_prefix_len..].into(),
5891 });
5892
5893 self.transact(window, cx, |editor, window, cx| {
5894 if let Some(mut snippet) = snippet {
5895 snippet.text = new_text.to_string();
5896 editor
5897 .insert_snippet(&ranges, snippet, window, cx)
5898 .log_err();
5899 } else {
5900 editor.buffer.update(cx, |multi_buffer, cx| {
5901 let auto_indent = match completion.insert_text_mode {
5902 Some(InsertTextMode::AS_IS) => None,
5903 _ => editor.autoindent_mode.clone(),
5904 };
5905 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5906 multi_buffer.edit(edits, auto_indent, cx);
5907 });
5908 }
5909 for (buffer, edits) in linked_edits {
5910 buffer.update(cx, |buffer, cx| {
5911 let snapshot = buffer.snapshot();
5912 let edits = edits
5913 .into_iter()
5914 .map(|(range, text)| {
5915 use text::ToPoint as TP;
5916 let end_point = TP::to_point(&range.end, &snapshot);
5917 let start_point = TP::to_point(&range.start, &snapshot);
5918 (start_point..end_point, text)
5919 })
5920 .sorted_by_key(|(range, _)| range.start);
5921 buffer.edit(edits, None, cx);
5922 })
5923 }
5924
5925 editor.refresh_edit_prediction(true, false, window, cx);
5926 });
5927 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5928
5929 let show_new_completions_on_confirm = completion
5930 .confirm
5931 .as_ref()
5932 .is_some_and(|confirm| confirm(intent, window, cx));
5933 if show_new_completions_on_confirm {
5934 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5935 }
5936
5937 let provider = self.completion_provider.as_ref()?;
5938 drop(completion);
5939 let apply_edits = provider.apply_additional_edits_for_completion(
5940 buffer_handle,
5941 completions_menu.completions.clone(),
5942 candidate_id,
5943 true,
5944 cx,
5945 );
5946
5947 let editor_settings = EditorSettings::get_global(cx);
5948 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5949 // After the code completion is finished, users often want to know what signatures are needed.
5950 // so we should automatically call signature_help
5951 self.show_signature_help(&ShowSignatureHelp, window, cx);
5952 }
5953
5954 Some(cx.foreground_executor().spawn(async move {
5955 apply_edits.await?;
5956 Ok(())
5957 }))
5958 }
5959
5960 pub fn toggle_code_actions(
5961 &mut self,
5962 action: &ToggleCodeActions,
5963 window: &mut Window,
5964 cx: &mut Context<Self>,
5965 ) {
5966 let quick_launch = action.quick_launch;
5967 let mut context_menu = self.context_menu.borrow_mut();
5968 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5969 if code_actions.deployed_from == action.deployed_from {
5970 // Toggle if we're selecting the same one
5971 *context_menu = None;
5972 cx.notify();
5973 return;
5974 } else {
5975 // Otherwise, clear it and start a new one
5976 *context_menu = None;
5977 cx.notify();
5978 }
5979 }
5980 drop(context_menu);
5981 let snapshot = self.snapshot(window, cx);
5982 let deployed_from = action.deployed_from.clone();
5983 let action = action.clone();
5984 self.completion_tasks.clear();
5985 self.discard_edit_prediction(false, cx);
5986
5987 let multibuffer_point = match &action.deployed_from {
5988 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5989 DisplayPoint::new(*row, 0).to_point(&snapshot)
5990 }
5991 _ => self
5992 .selections
5993 .newest::<Point>(&snapshot.display_snapshot)
5994 .head(),
5995 };
5996 let Some((buffer, buffer_row)) = snapshot
5997 .buffer_snapshot()
5998 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5999 .and_then(|(buffer_snapshot, range)| {
6000 self.buffer()
6001 .read(cx)
6002 .buffer(buffer_snapshot.remote_id())
6003 .map(|buffer| (buffer, range.start.row))
6004 })
6005 else {
6006 return;
6007 };
6008 let buffer_id = buffer.read(cx).remote_id();
6009 let tasks = self
6010 .tasks
6011 .get(&(buffer_id, buffer_row))
6012 .map(|t| Arc::new(t.to_owned()));
6013
6014 if !self.focus_handle.is_focused(window) {
6015 return;
6016 }
6017 let project = self.project.clone();
6018
6019 let code_actions_task = match deployed_from {
6020 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6021 _ => self.code_actions(buffer_row, window, cx),
6022 };
6023
6024 let runnable_task = match deployed_from {
6025 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6026 _ => {
6027 let mut task_context_task = Task::ready(None);
6028 if let Some(tasks) = &tasks
6029 && let Some(project) = project
6030 {
6031 task_context_task =
6032 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6033 }
6034
6035 cx.spawn_in(window, {
6036 let buffer = buffer.clone();
6037 async move |editor, cx| {
6038 let task_context = task_context_task.await;
6039
6040 let resolved_tasks =
6041 tasks
6042 .zip(task_context.clone())
6043 .map(|(tasks, task_context)| ResolvedTasks {
6044 templates: tasks.resolve(&task_context).collect(),
6045 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6046 multibuffer_point.row,
6047 tasks.column,
6048 )),
6049 });
6050 let debug_scenarios = editor
6051 .update(cx, |editor, cx| {
6052 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6053 })?
6054 .await;
6055 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6056 }
6057 })
6058 }
6059 };
6060
6061 cx.spawn_in(window, async move |editor, cx| {
6062 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6063 let code_actions = code_actions_task.await;
6064 let spawn_straight_away = quick_launch
6065 && resolved_tasks
6066 .as_ref()
6067 .is_some_and(|tasks| tasks.templates.len() == 1)
6068 && code_actions
6069 .as_ref()
6070 .is_none_or(|actions| actions.is_empty())
6071 && debug_scenarios.is_empty();
6072
6073 editor.update_in(cx, |editor, window, cx| {
6074 crate::hover_popover::hide_hover(editor, cx);
6075 let actions = CodeActionContents::new(
6076 resolved_tasks,
6077 code_actions,
6078 debug_scenarios,
6079 task_context.unwrap_or_default(),
6080 );
6081
6082 // Don't show the menu if there are no actions available
6083 if actions.is_empty() {
6084 cx.notify();
6085 return Task::ready(Ok(()));
6086 }
6087
6088 *editor.context_menu.borrow_mut() =
6089 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6090 buffer,
6091 actions,
6092 selected_item: Default::default(),
6093 scroll_handle: UniformListScrollHandle::default(),
6094 deployed_from,
6095 }));
6096 cx.notify();
6097 if spawn_straight_away
6098 && let Some(task) = editor.confirm_code_action(
6099 &ConfirmCodeAction { item_ix: Some(0) },
6100 window,
6101 cx,
6102 )
6103 {
6104 return task;
6105 }
6106
6107 Task::ready(Ok(()))
6108 })
6109 })
6110 .detach_and_log_err(cx);
6111 }
6112
6113 fn debug_scenarios(
6114 &mut self,
6115 resolved_tasks: &Option<ResolvedTasks>,
6116 buffer: &Entity<Buffer>,
6117 cx: &mut App,
6118 ) -> Task<Vec<task::DebugScenario>> {
6119 maybe!({
6120 let project = self.project()?;
6121 let dap_store = project.read(cx).dap_store();
6122 let mut scenarios = vec![];
6123 let resolved_tasks = resolved_tasks.as_ref()?;
6124 let buffer = buffer.read(cx);
6125 let language = buffer.language()?;
6126 let file = buffer.file();
6127 let debug_adapter = language_settings(language.name().into(), file, cx)
6128 .debuggers
6129 .first()
6130 .map(SharedString::from)
6131 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6132
6133 dap_store.update(cx, |dap_store, cx| {
6134 for (_, task) in &resolved_tasks.templates {
6135 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6136 task.original_task().clone(),
6137 debug_adapter.clone().into(),
6138 task.display_label().to_owned().into(),
6139 cx,
6140 );
6141 scenarios.push(maybe_scenario);
6142 }
6143 });
6144 Some(cx.background_spawn(async move {
6145 futures::future::join_all(scenarios)
6146 .await
6147 .into_iter()
6148 .flatten()
6149 .collect::<Vec<_>>()
6150 }))
6151 })
6152 .unwrap_or_else(|| Task::ready(vec![]))
6153 }
6154
6155 fn code_actions(
6156 &mut self,
6157 buffer_row: u32,
6158 window: &mut Window,
6159 cx: &mut Context<Self>,
6160 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6161 let mut task = self.code_actions_task.take();
6162 cx.spawn_in(window, async move |editor, cx| {
6163 while let Some(prev_task) = task {
6164 prev_task.await.log_err();
6165 task = editor
6166 .update(cx, |this, _| this.code_actions_task.take())
6167 .ok()?;
6168 }
6169
6170 editor
6171 .update(cx, |editor, cx| {
6172 editor
6173 .available_code_actions
6174 .clone()
6175 .and_then(|(location, code_actions)| {
6176 let snapshot = location.buffer.read(cx).snapshot();
6177 let point_range = location.range.to_point(&snapshot);
6178 let point_range = point_range.start.row..=point_range.end.row;
6179 if point_range.contains(&buffer_row) {
6180 Some(code_actions)
6181 } else {
6182 None
6183 }
6184 })
6185 })
6186 .ok()
6187 .flatten()
6188 })
6189 }
6190
6191 pub fn confirm_code_action(
6192 &mut self,
6193 action: &ConfirmCodeAction,
6194 window: &mut Window,
6195 cx: &mut Context<Self>,
6196 ) -> Option<Task<Result<()>>> {
6197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6198
6199 let actions_menu =
6200 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6201 menu
6202 } else {
6203 return None;
6204 };
6205
6206 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6207 let action = actions_menu.actions.get(action_ix)?;
6208 let title = action.label();
6209 let buffer = actions_menu.buffer;
6210 let workspace = self.workspace()?;
6211
6212 match action {
6213 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6214 workspace.update(cx, |workspace, cx| {
6215 workspace.schedule_resolved_task(
6216 task_source_kind,
6217 resolved_task,
6218 false,
6219 window,
6220 cx,
6221 );
6222
6223 Some(Task::ready(Ok(())))
6224 })
6225 }
6226 CodeActionsItem::CodeAction {
6227 excerpt_id,
6228 action,
6229 provider,
6230 } => {
6231 let apply_code_action =
6232 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6233 let workspace = workspace.downgrade();
6234 Some(cx.spawn_in(window, async move |editor, cx| {
6235 let project_transaction = apply_code_action.await?;
6236 Self::open_project_transaction(
6237 &editor,
6238 workspace,
6239 project_transaction,
6240 title,
6241 cx,
6242 )
6243 .await
6244 }))
6245 }
6246 CodeActionsItem::DebugScenario(scenario) => {
6247 let context = actions_menu.actions.context;
6248
6249 workspace.update(cx, |workspace, cx| {
6250 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6251 workspace.start_debug_session(
6252 scenario,
6253 context,
6254 Some(buffer),
6255 None,
6256 window,
6257 cx,
6258 );
6259 });
6260 Some(Task::ready(Ok(())))
6261 }
6262 }
6263 }
6264
6265 pub async fn open_project_transaction(
6266 editor: &WeakEntity<Editor>,
6267 workspace: WeakEntity<Workspace>,
6268 transaction: ProjectTransaction,
6269 title: String,
6270 cx: &mut AsyncWindowContext,
6271 ) -> Result<()> {
6272 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6273 cx.update(|_, cx| {
6274 entries.sort_unstable_by_key(|(buffer, _)| {
6275 buffer.read(cx).file().map(|f| f.path().clone())
6276 });
6277 })?;
6278 if entries.is_empty() {
6279 return Ok(());
6280 }
6281
6282 // If the project transaction's edits are all contained within this editor, then
6283 // avoid opening a new editor to display them.
6284
6285 if let [(buffer, transaction)] = &*entries {
6286 let excerpt = editor.update(cx, |editor, cx| {
6287 editor
6288 .buffer()
6289 .read(cx)
6290 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6291 })?;
6292 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6293 && excerpted_buffer == *buffer
6294 {
6295 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6296 let excerpt_range = excerpt_range.to_offset(buffer);
6297 buffer
6298 .edited_ranges_for_transaction::<usize>(transaction)
6299 .all(|range| {
6300 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6301 })
6302 })?;
6303
6304 if all_edits_within_excerpt {
6305 return Ok(());
6306 }
6307 }
6308 }
6309
6310 let mut ranges_to_highlight = Vec::new();
6311 let excerpt_buffer = cx.new(|cx| {
6312 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6313 for (buffer_handle, transaction) in &entries {
6314 let edited_ranges = buffer_handle
6315 .read(cx)
6316 .edited_ranges_for_transaction::<Point>(transaction)
6317 .collect::<Vec<_>>();
6318 let (ranges, _) = multibuffer.set_excerpts_for_path(
6319 PathKey::for_buffer(buffer_handle, cx),
6320 buffer_handle.clone(),
6321 edited_ranges,
6322 multibuffer_context_lines(cx),
6323 cx,
6324 );
6325
6326 ranges_to_highlight.extend(ranges);
6327 }
6328 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6329 multibuffer
6330 })?;
6331
6332 workspace.update_in(cx, |workspace, window, cx| {
6333 let project = workspace.project().clone();
6334 let editor =
6335 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6336 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6337 editor.update(cx, |editor, cx| {
6338 editor.highlight_background::<Self>(
6339 &ranges_to_highlight,
6340 |theme| theme.colors().editor_highlighted_line_background,
6341 cx,
6342 );
6343 });
6344 })?;
6345
6346 Ok(())
6347 }
6348
6349 pub fn clear_code_action_providers(&mut self) {
6350 self.code_action_providers.clear();
6351 self.available_code_actions.take();
6352 }
6353
6354 pub fn add_code_action_provider(
6355 &mut self,
6356 provider: Rc<dyn CodeActionProvider>,
6357 window: &mut Window,
6358 cx: &mut Context<Self>,
6359 ) {
6360 if self
6361 .code_action_providers
6362 .iter()
6363 .any(|existing_provider| existing_provider.id() == provider.id())
6364 {
6365 return;
6366 }
6367
6368 self.code_action_providers.push(provider);
6369 self.refresh_code_actions(window, cx);
6370 }
6371
6372 pub fn remove_code_action_provider(
6373 &mut self,
6374 id: Arc<str>,
6375 window: &mut Window,
6376 cx: &mut Context<Self>,
6377 ) {
6378 self.code_action_providers
6379 .retain(|provider| provider.id() != id);
6380 self.refresh_code_actions(window, cx);
6381 }
6382
6383 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6384 !self.code_action_providers.is_empty()
6385 && EditorSettings::get_global(cx).toolbar.code_actions
6386 }
6387
6388 pub fn has_available_code_actions(&self) -> bool {
6389 self.available_code_actions
6390 .as_ref()
6391 .is_some_and(|(_, actions)| !actions.is_empty())
6392 }
6393
6394 fn render_inline_code_actions(
6395 &self,
6396 icon_size: ui::IconSize,
6397 display_row: DisplayRow,
6398 is_active: bool,
6399 cx: &mut Context<Self>,
6400 ) -> AnyElement {
6401 let show_tooltip = !self.context_menu_visible();
6402 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6403 .icon_size(icon_size)
6404 .shape(ui::IconButtonShape::Square)
6405 .icon_color(ui::Color::Hidden)
6406 .toggle_state(is_active)
6407 .when(show_tooltip, |this| {
6408 this.tooltip({
6409 let focus_handle = self.focus_handle.clone();
6410 move |_window, cx| {
6411 Tooltip::for_action_in(
6412 "Toggle Code Actions",
6413 &ToggleCodeActions {
6414 deployed_from: None,
6415 quick_launch: false,
6416 },
6417 &focus_handle,
6418 cx,
6419 )
6420 }
6421 })
6422 })
6423 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6424 window.focus(&editor.focus_handle(cx));
6425 editor.toggle_code_actions(
6426 &crate::actions::ToggleCodeActions {
6427 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6428 display_row,
6429 )),
6430 quick_launch: false,
6431 },
6432 window,
6433 cx,
6434 );
6435 }))
6436 .into_any_element()
6437 }
6438
6439 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6440 &self.context_menu
6441 }
6442
6443 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6444 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6445 cx.background_executor()
6446 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6447 .await;
6448
6449 let (start_buffer, start, _, end, newest_selection) = this
6450 .update(cx, |this, cx| {
6451 let newest_selection = this.selections.newest_anchor().clone();
6452 if newest_selection.head().diff_base_anchor.is_some() {
6453 return None;
6454 }
6455 let display_snapshot = this.display_snapshot(cx);
6456 let newest_selection_adjusted =
6457 this.selections.newest_adjusted(&display_snapshot);
6458 let buffer = this.buffer.read(cx);
6459
6460 let (start_buffer, start) =
6461 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6462 let (end_buffer, end) =
6463 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6464
6465 Some((start_buffer, start, end_buffer, end, newest_selection))
6466 })?
6467 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6468 .context(
6469 "Expected selection to lie in a single buffer when refreshing code actions",
6470 )?;
6471 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6472 let providers = this.code_action_providers.clone();
6473 let tasks = this
6474 .code_action_providers
6475 .iter()
6476 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6477 .collect::<Vec<_>>();
6478 (providers, tasks)
6479 })?;
6480
6481 let mut actions = Vec::new();
6482 for (provider, provider_actions) in
6483 providers.into_iter().zip(future::join_all(tasks).await)
6484 {
6485 if let Some(provider_actions) = provider_actions.log_err() {
6486 actions.extend(provider_actions.into_iter().map(|action| {
6487 AvailableCodeAction {
6488 excerpt_id: newest_selection.start.excerpt_id,
6489 action,
6490 provider: provider.clone(),
6491 }
6492 }));
6493 }
6494 }
6495
6496 this.update(cx, |this, cx| {
6497 this.available_code_actions = if actions.is_empty() {
6498 None
6499 } else {
6500 Some((
6501 Location {
6502 buffer: start_buffer,
6503 range: start..end,
6504 },
6505 actions.into(),
6506 ))
6507 };
6508 cx.notify();
6509 })
6510 }));
6511 }
6512
6513 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6514 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6515 self.show_git_blame_inline = false;
6516
6517 self.show_git_blame_inline_delay_task =
6518 Some(cx.spawn_in(window, async move |this, cx| {
6519 cx.background_executor().timer(delay).await;
6520
6521 this.update(cx, |this, cx| {
6522 this.show_git_blame_inline = true;
6523 cx.notify();
6524 })
6525 .log_err();
6526 }));
6527 }
6528 }
6529
6530 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6531 let snapshot = self.snapshot(window, cx);
6532 let cursor = self
6533 .selections
6534 .newest::<Point>(&snapshot.display_snapshot)
6535 .head();
6536 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6537 else {
6538 return;
6539 };
6540
6541 let Some(blame) = self.blame.as_ref() else {
6542 return;
6543 };
6544
6545 let row_info = RowInfo {
6546 buffer_id: Some(buffer.remote_id()),
6547 buffer_row: Some(point.row),
6548 ..Default::default()
6549 };
6550 let Some((buffer, blame_entry)) = blame
6551 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6552 .flatten()
6553 else {
6554 return;
6555 };
6556
6557 let anchor = self.selections.newest_anchor().head();
6558 let position = self.to_pixel_point(anchor, &snapshot, window);
6559 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6560 self.show_blame_popover(
6561 buffer,
6562 &blame_entry,
6563 position + last_bounds.origin,
6564 true,
6565 cx,
6566 );
6567 };
6568 }
6569
6570 fn show_blame_popover(
6571 &mut self,
6572 buffer: BufferId,
6573 blame_entry: &BlameEntry,
6574 position: gpui::Point<Pixels>,
6575 ignore_timeout: bool,
6576 cx: &mut Context<Self>,
6577 ) {
6578 if let Some(state) = &mut self.inline_blame_popover {
6579 state.hide_task.take();
6580 } else {
6581 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6582 let blame_entry = blame_entry.clone();
6583 let show_task = cx.spawn(async move |editor, cx| {
6584 if !ignore_timeout {
6585 cx.background_executor()
6586 .timer(std::time::Duration::from_millis(blame_popover_delay))
6587 .await;
6588 }
6589 editor
6590 .update(cx, |editor, cx| {
6591 editor.inline_blame_popover_show_task.take();
6592 let Some(blame) = editor.blame.as_ref() else {
6593 return;
6594 };
6595 let blame = blame.read(cx);
6596 let details = blame.details_for_entry(buffer, &blame_entry);
6597 let markdown = cx.new(|cx| {
6598 Markdown::new(
6599 details
6600 .as_ref()
6601 .map(|message| message.message.clone())
6602 .unwrap_or_default(),
6603 None,
6604 None,
6605 cx,
6606 )
6607 });
6608 editor.inline_blame_popover = Some(InlineBlamePopover {
6609 position,
6610 hide_task: None,
6611 popover_bounds: None,
6612 popover_state: InlineBlamePopoverState {
6613 scroll_handle: ScrollHandle::new(),
6614 commit_message: details,
6615 markdown,
6616 },
6617 keyboard_grace: ignore_timeout,
6618 });
6619 cx.notify();
6620 })
6621 .ok();
6622 });
6623 self.inline_blame_popover_show_task = Some(show_task);
6624 }
6625 }
6626
6627 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6628 self.inline_blame_popover_show_task.take();
6629 if let Some(state) = &mut self.inline_blame_popover {
6630 let hide_task = cx.spawn(async move |editor, cx| {
6631 if !ignore_timeout {
6632 cx.background_executor()
6633 .timer(std::time::Duration::from_millis(100))
6634 .await;
6635 }
6636 editor
6637 .update(cx, |editor, cx| {
6638 editor.inline_blame_popover.take();
6639 cx.notify();
6640 })
6641 .ok();
6642 });
6643 state.hide_task = Some(hide_task);
6644 true
6645 } else {
6646 false
6647 }
6648 }
6649
6650 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6651 if self.pending_rename.is_some() {
6652 return None;
6653 }
6654
6655 let provider = self.semantics_provider.clone()?;
6656 let buffer = self.buffer.read(cx);
6657 let newest_selection = self.selections.newest_anchor().clone();
6658 let cursor_position = newest_selection.head();
6659 let (cursor_buffer, cursor_buffer_position) =
6660 buffer.text_anchor_for_position(cursor_position, cx)?;
6661 let (tail_buffer, tail_buffer_position) =
6662 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6663 if cursor_buffer != tail_buffer {
6664 return None;
6665 }
6666
6667 let snapshot = cursor_buffer.read(cx).snapshot();
6668 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6669 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6670 if start_word_range != end_word_range {
6671 self.document_highlights_task.take();
6672 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6673 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6674 return None;
6675 }
6676
6677 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6678 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6679 cx.background_executor()
6680 .timer(Duration::from_millis(debounce))
6681 .await;
6682
6683 let highlights = if let Some(highlights) = cx
6684 .update(|cx| {
6685 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6686 })
6687 .ok()
6688 .flatten()
6689 {
6690 highlights.await.log_err()
6691 } else {
6692 None
6693 };
6694
6695 if let Some(highlights) = highlights {
6696 this.update(cx, |this, cx| {
6697 if this.pending_rename.is_some() {
6698 return;
6699 }
6700
6701 let buffer = this.buffer.read(cx);
6702 if buffer
6703 .text_anchor_for_position(cursor_position, cx)
6704 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6705 {
6706 return;
6707 }
6708
6709 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6710 let mut write_ranges = Vec::new();
6711 let mut read_ranges = Vec::new();
6712 for highlight in highlights {
6713 let buffer_id = cursor_buffer.read(cx).remote_id();
6714 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6715 {
6716 let start = highlight
6717 .range
6718 .start
6719 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6720 let end = highlight
6721 .range
6722 .end
6723 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6724 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6725 continue;
6726 }
6727
6728 let range =
6729 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6730 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6731 write_ranges.push(range);
6732 } else {
6733 read_ranges.push(range);
6734 }
6735 }
6736 }
6737
6738 this.highlight_background::<DocumentHighlightRead>(
6739 &read_ranges,
6740 |theme| theme.colors().editor_document_highlight_read_background,
6741 cx,
6742 );
6743 this.highlight_background::<DocumentHighlightWrite>(
6744 &write_ranges,
6745 |theme| theme.colors().editor_document_highlight_write_background,
6746 cx,
6747 );
6748 cx.notify();
6749 })
6750 .log_err();
6751 }
6752 }));
6753 None
6754 }
6755
6756 fn prepare_highlight_query_from_selection(
6757 &mut self,
6758 window: &Window,
6759 cx: &mut Context<Editor>,
6760 ) -> Option<(String, Range<Anchor>)> {
6761 if matches!(self.mode, EditorMode::SingleLine) {
6762 return None;
6763 }
6764 if !EditorSettings::get_global(cx).selection_highlight {
6765 return None;
6766 }
6767 if self.selections.count() != 1 || self.selections.line_mode() {
6768 return None;
6769 }
6770 let snapshot = self.snapshot(window, cx);
6771 let selection = self.selections.newest::<Point>(&snapshot);
6772 // If the selection spans multiple rows OR it is empty
6773 if selection.start.row != selection.end.row
6774 || selection.start.column == selection.end.column
6775 {
6776 return None;
6777 }
6778 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6779 let query = snapshot
6780 .buffer_snapshot()
6781 .text_for_range(selection_anchor_range.clone())
6782 .collect::<String>();
6783 if query.trim().is_empty() {
6784 return None;
6785 }
6786 Some((query, selection_anchor_range))
6787 }
6788
6789 fn update_selection_occurrence_highlights(
6790 &mut self,
6791 query_text: String,
6792 query_range: Range<Anchor>,
6793 multi_buffer_range_to_query: Range<Point>,
6794 use_debounce: bool,
6795 window: &mut Window,
6796 cx: &mut Context<Editor>,
6797 ) -> Task<()> {
6798 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6799 cx.spawn_in(window, async move |editor, cx| {
6800 if use_debounce {
6801 cx.background_executor()
6802 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6803 .await;
6804 }
6805 let match_task = cx.background_spawn(async move {
6806 let buffer_ranges = multi_buffer_snapshot
6807 .range_to_buffer_ranges(multi_buffer_range_to_query)
6808 .into_iter()
6809 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6810 let mut match_ranges = Vec::new();
6811 let Ok(regex) = project::search::SearchQuery::text(
6812 query_text.clone(),
6813 false,
6814 false,
6815 false,
6816 Default::default(),
6817 Default::default(),
6818 false,
6819 None,
6820 ) else {
6821 return Vec::default();
6822 };
6823 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6824 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6825 match_ranges.extend(
6826 regex
6827 .search(buffer_snapshot, Some(search_range.clone()))
6828 .await
6829 .into_iter()
6830 .filter_map(|match_range| {
6831 let match_start = buffer_snapshot
6832 .anchor_after(search_range.start + match_range.start);
6833 let match_end = buffer_snapshot
6834 .anchor_before(search_range.start + match_range.end);
6835 let match_anchor_range = Anchor::range_in_buffer(
6836 excerpt_id,
6837 buffer_snapshot.remote_id(),
6838 match_start..match_end,
6839 );
6840 (match_anchor_range != query_range).then_some(match_anchor_range)
6841 }),
6842 );
6843 }
6844 match_ranges
6845 });
6846 let match_ranges = match_task.await;
6847 editor
6848 .update_in(cx, |editor, _, cx| {
6849 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6850 if !match_ranges.is_empty() {
6851 editor.highlight_background::<SelectedTextHighlight>(
6852 &match_ranges,
6853 |theme| theme.colors().editor_document_highlight_bracket_background,
6854 cx,
6855 )
6856 }
6857 })
6858 .log_err();
6859 })
6860 }
6861
6862 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6863 struct NewlineFold;
6864 let type_id = std::any::TypeId::of::<NewlineFold>();
6865 if !self.mode.is_single_line() {
6866 return;
6867 }
6868 let snapshot = self.snapshot(window, cx);
6869 if snapshot.buffer_snapshot().max_point().row == 0 {
6870 return;
6871 }
6872 let task = cx.background_spawn(async move {
6873 let new_newlines = snapshot
6874 .buffer_chars_at(0)
6875 .filter_map(|(c, i)| {
6876 if c == '\n' {
6877 Some(
6878 snapshot.buffer_snapshot().anchor_after(i)
6879 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6880 )
6881 } else {
6882 None
6883 }
6884 })
6885 .collect::<Vec<_>>();
6886 let existing_newlines = snapshot
6887 .folds_in_range(0..snapshot.buffer_snapshot().len())
6888 .filter_map(|fold| {
6889 if fold.placeholder.type_tag == Some(type_id) {
6890 Some(fold.range.start..fold.range.end)
6891 } else {
6892 None
6893 }
6894 })
6895 .collect::<Vec<_>>();
6896
6897 (new_newlines, existing_newlines)
6898 });
6899 self.folding_newlines = cx.spawn(async move |this, cx| {
6900 let (new_newlines, existing_newlines) = task.await;
6901 if new_newlines == existing_newlines {
6902 return;
6903 }
6904 let placeholder = FoldPlaceholder {
6905 render: Arc::new(move |_, _, cx| {
6906 div()
6907 .bg(cx.theme().status().hint_background)
6908 .border_b_1()
6909 .size_full()
6910 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6911 .border_color(cx.theme().status().hint)
6912 .child("\\n")
6913 .into_any()
6914 }),
6915 constrain_width: false,
6916 merge_adjacent: false,
6917 type_tag: Some(type_id),
6918 };
6919 let creases = new_newlines
6920 .into_iter()
6921 .map(|range| Crease::simple(range, placeholder.clone()))
6922 .collect();
6923 this.update(cx, |this, cx| {
6924 this.display_map.update(cx, |display_map, cx| {
6925 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6926 display_map.fold(creases, cx);
6927 });
6928 })
6929 .ok();
6930 });
6931 }
6932
6933 fn refresh_selected_text_highlights(
6934 &mut self,
6935 on_buffer_edit: bool,
6936 window: &mut Window,
6937 cx: &mut Context<Editor>,
6938 ) {
6939 let Some((query_text, query_range)) =
6940 self.prepare_highlight_query_from_selection(window, cx)
6941 else {
6942 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6943 self.quick_selection_highlight_task.take();
6944 self.debounced_selection_highlight_task.take();
6945 return;
6946 };
6947 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6948 if on_buffer_edit
6949 || self
6950 .quick_selection_highlight_task
6951 .as_ref()
6952 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6953 {
6954 let multi_buffer_visible_start = self
6955 .scroll_manager
6956 .anchor()
6957 .anchor
6958 .to_point(&multi_buffer_snapshot);
6959 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6960 multi_buffer_visible_start
6961 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6962 Bias::Left,
6963 );
6964 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6965 self.quick_selection_highlight_task = Some((
6966 query_range.clone(),
6967 self.update_selection_occurrence_highlights(
6968 query_text.clone(),
6969 query_range.clone(),
6970 multi_buffer_visible_range,
6971 false,
6972 window,
6973 cx,
6974 ),
6975 ));
6976 }
6977 if on_buffer_edit
6978 || self
6979 .debounced_selection_highlight_task
6980 .as_ref()
6981 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6982 {
6983 let multi_buffer_start = multi_buffer_snapshot
6984 .anchor_before(0)
6985 .to_point(&multi_buffer_snapshot);
6986 let multi_buffer_end = multi_buffer_snapshot
6987 .anchor_after(multi_buffer_snapshot.len())
6988 .to_point(&multi_buffer_snapshot);
6989 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6990 self.debounced_selection_highlight_task = Some((
6991 query_range.clone(),
6992 self.update_selection_occurrence_highlights(
6993 query_text,
6994 query_range,
6995 multi_buffer_full_range,
6996 true,
6997 window,
6998 cx,
6999 ),
7000 ));
7001 }
7002 }
7003
7004 pub fn refresh_edit_prediction(
7005 &mut self,
7006 debounce: bool,
7007 user_requested: bool,
7008 window: &mut Window,
7009 cx: &mut Context<Self>,
7010 ) -> Option<()> {
7011 if DisableAiSettings::get_global(cx).disable_ai {
7012 return None;
7013 }
7014
7015 let provider = self.edit_prediction_provider()?;
7016 let cursor = self.selections.newest_anchor().head();
7017 let (buffer, cursor_buffer_position) =
7018 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7019
7020 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7021 self.discard_edit_prediction(false, cx);
7022 return None;
7023 }
7024
7025 self.update_visible_edit_prediction(window, cx);
7026
7027 if !user_requested
7028 && (!self.should_show_edit_predictions()
7029 || !self.is_focused(window)
7030 || buffer.read(cx).is_empty())
7031 {
7032 self.discard_edit_prediction(false, cx);
7033 return None;
7034 }
7035
7036 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7037 Some(())
7038 }
7039
7040 fn show_edit_predictions_in_menu(&self) -> bool {
7041 match self.edit_prediction_settings {
7042 EditPredictionSettings::Disabled => false,
7043 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7044 }
7045 }
7046
7047 pub fn edit_predictions_enabled(&self) -> bool {
7048 match self.edit_prediction_settings {
7049 EditPredictionSettings::Disabled => false,
7050 EditPredictionSettings::Enabled { .. } => true,
7051 }
7052 }
7053
7054 fn edit_prediction_requires_modifier(&self) -> bool {
7055 match self.edit_prediction_settings {
7056 EditPredictionSettings::Disabled => false,
7057 EditPredictionSettings::Enabled {
7058 preview_requires_modifier,
7059 ..
7060 } => preview_requires_modifier,
7061 }
7062 }
7063
7064 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7065 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7066 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7067 self.discard_edit_prediction(false, cx);
7068 } else {
7069 let selection = self.selections.newest_anchor();
7070 let cursor = selection.head();
7071
7072 if let Some((buffer, cursor_buffer_position)) =
7073 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7074 {
7075 self.edit_prediction_settings =
7076 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7077 }
7078 }
7079 }
7080
7081 fn edit_prediction_settings_at_position(
7082 &self,
7083 buffer: &Entity<Buffer>,
7084 buffer_position: language::Anchor,
7085 cx: &App,
7086 ) -> EditPredictionSettings {
7087 if !self.mode.is_full()
7088 || !self.show_edit_predictions_override.unwrap_or(true)
7089 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7090 {
7091 return EditPredictionSettings::Disabled;
7092 }
7093
7094 let buffer = buffer.read(cx);
7095
7096 let file = buffer.file();
7097
7098 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7099 return EditPredictionSettings::Disabled;
7100 };
7101
7102 let by_provider = matches!(
7103 self.menu_edit_predictions_policy,
7104 MenuEditPredictionsPolicy::ByProvider
7105 );
7106
7107 let show_in_menu = by_provider
7108 && self
7109 .edit_prediction_provider
7110 .as_ref()
7111 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7112
7113 let preview_requires_modifier =
7114 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7115
7116 EditPredictionSettings::Enabled {
7117 show_in_menu,
7118 preview_requires_modifier,
7119 }
7120 }
7121
7122 fn should_show_edit_predictions(&self) -> bool {
7123 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7124 }
7125
7126 pub fn edit_prediction_preview_is_active(&self) -> bool {
7127 matches!(
7128 self.edit_prediction_preview,
7129 EditPredictionPreview::Active { .. }
7130 )
7131 }
7132
7133 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7134 let cursor = self.selections.newest_anchor().head();
7135 if let Some((buffer, cursor_position)) =
7136 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7137 {
7138 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7139 } else {
7140 false
7141 }
7142 }
7143
7144 pub fn supports_minimap(&self, cx: &App) -> bool {
7145 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7146 }
7147
7148 fn edit_predictions_enabled_in_buffer(
7149 &self,
7150 buffer: &Entity<Buffer>,
7151 buffer_position: language::Anchor,
7152 cx: &App,
7153 ) -> bool {
7154 maybe!({
7155 if self.read_only(cx) {
7156 return Some(false);
7157 }
7158 let provider = self.edit_prediction_provider()?;
7159 if !provider.is_enabled(buffer, buffer_position, cx) {
7160 return Some(false);
7161 }
7162 let buffer = buffer.read(cx);
7163 let Some(file) = buffer.file() else {
7164 return Some(true);
7165 };
7166 let settings = all_language_settings(Some(file), cx);
7167 Some(settings.edit_predictions_enabled_for_file(file, cx))
7168 })
7169 .unwrap_or(false)
7170 }
7171
7172 fn cycle_edit_prediction(
7173 &mut self,
7174 direction: Direction,
7175 window: &mut Window,
7176 cx: &mut Context<Self>,
7177 ) -> Option<()> {
7178 let provider = self.edit_prediction_provider()?;
7179 let cursor = self.selections.newest_anchor().head();
7180 let (buffer, cursor_buffer_position) =
7181 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7182 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7183 return None;
7184 }
7185
7186 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7187 self.update_visible_edit_prediction(window, cx);
7188
7189 Some(())
7190 }
7191
7192 pub fn show_edit_prediction(
7193 &mut self,
7194 _: &ShowEditPrediction,
7195 window: &mut Window,
7196 cx: &mut Context<Self>,
7197 ) {
7198 if !self.has_active_edit_prediction() {
7199 self.refresh_edit_prediction(false, true, window, cx);
7200 return;
7201 }
7202
7203 self.update_visible_edit_prediction(window, cx);
7204 }
7205
7206 pub fn display_cursor_names(
7207 &mut self,
7208 _: &DisplayCursorNames,
7209 window: &mut Window,
7210 cx: &mut Context<Self>,
7211 ) {
7212 self.show_cursor_names(window, cx);
7213 }
7214
7215 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7216 self.show_cursor_names = true;
7217 cx.notify();
7218 cx.spawn_in(window, async move |this, cx| {
7219 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7220 this.update(cx, |this, cx| {
7221 this.show_cursor_names = false;
7222 cx.notify()
7223 })
7224 .ok()
7225 })
7226 .detach();
7227 }
7228
7229 pub fn next_edit_prediction(
7230 &mut self,
7231 _: &NextEditPrediction,
7232 window: &mut Window,
7233 cx: &mut Context<Self>,
7234 ) {
7235 if self.has_active_edit_prediction() {
7236 self.cycle_edit_prediction(Direction::Next, window, cx);
7237 } else {
7238 let is_copilot_disabled = self
7239 .refresh_edit_prediction(false, true, window, cx)
7240 .is_none();
7241 if is_copilot_disabled {
7242 cx.propagate();
7243 }
7244 }
7245 }
7246
7247 pub fn previous_edit_prediction(
7248 &mut self,
7249 _: &PreviousEditPrediction,
7250 window: &mut Window,
7251 cx: &mut Context<Self>,
7252 ) {
7253 if self.has_active_edit_prediction() {
7254 self.cycle_edit_prediction(Direction::Prev, window, cx);
7255 } else {
7256 let is_copilot_disabled = self
7257 .refresh_edit_prediction(false, true, window, cx)
7258 .is_none();
7259 if is_copilot_disabled {
7260 cx.propagate();
7261 }
7262 }
7263 }
7264
7265 pub fn accept_edit_prediction(
7266 &mut self,
7267 _: &AcceptEditPrediction,
7268 window: &mut Window,
7269 cx: &mut Context<Self>,
7270 ) {
7271 if self.show_edit_predictions_in_menu() {
7272 self.hide_context_menu(window, cx);
7273 }
7274
7275 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7276 return;
7277 };
7278
7279 match &active_edit_prediction.completion {
7280 EditPrediction::MoveWithin { target, .. } => {
7281 let target = *target;
7282
7283 if let Some(position_map) = &self.last_position_map {
7284 if position_map
7285 .visible_row_range
7286 .contains(&target.to_display_point(&position_map.snapshot).row())
7287 || !self.edit_prediction_requires_modifier()
7288 {
7289 self.unfold_ranges(&[target..target], true, false, cx);
7290 // Note that this is also done in vim's handler of the Tab action.
7291 self.change_selections(
7292 SelectionEffects::scroll(Autoscroll::newest()),
7293 window,
7294 cx,
7295 |selections| {
7296 selections.select_anchor_ranges([target..target]);
7297 },
7298 );
7299 self.clear_row_highlights::<EditPredictionPreview>();
7300
7301 self.edit_prediction_preview
7302 .set_previous_scroll_position(None);
7303 } else {
7304 self.edit_prediction_preview
7305 .set_previous_scroll_position(Some(
7306 position_map.snapshot.scroll_anchor,
7307 ));
7308
7309 self.highlight_rows::<EditPredictionPreview>(
7310 target..target,
7311 cx.theme().colors().editor_highlighted_line_background,
7312 RowHighlightOptions {
7313 autoscroll: true,
7314 ..Default::default()
7315 },
7316 cx,
7317 );
7318 self.request_autoscroll(Autoscroll::fit(), cx);
7319 }
7320 }
7321 }
7322 EditPrediction::MoveOutside { snapshot, target } => {
7323 if let Some(workspace) = self.workspace() {
7324 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7325 .detach_and_log_err(cx);
7326 }
7327 }
7328 EditPrediction::Edit { edits, .. } => {
7329 self.report_edit_prediction_event(
7330 active_edit_prediction.completion_id.clone(),
7331 true,
7332 cx,
7333 );
7334
7335 if let Some(provider) = self.edit_prediction_provider() {
7336 provider.accept(cx);
7337 }
7338
7339 // Store the transaction ID and selections before applying the edit
7340 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7341
7342 let snapshot = self.buffer.read(cx).snapshot(cx);
7343 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7344
7345 self.buffer.update(cx, |buffer, cx| {
7346 buffer.edit(edits.iter().cloned(), None, cx)
7347 });
7348
7349 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7350 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7351 });
7352
7353 let selections = self.selections.disjoint_anchors_arc();
7354 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7355 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7356 if has_new_transaction {
7357 self.selection_history
7358 .insert_transaction(transaction_id_now, selections);
7359 }
7360 }
7361
7362 self.update_visible_edit_prediction(window, cx);
7363 if self.active_edit_prediction.is_none() {
7364 self.refresh_edit_prediction(true, true, window, cx);
7365 }
7366
7367 cx.notify();
7368 }
7369 }
7370
7371 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7372 }
7373
7374 pub fn accept_partial_edit_prediction(
7375 &mut self,
7376 _: &AcceptPartialEditPrediction,
7377 window: &mut Window,
7378 cx: &mut Context<Self>,
7379 ) {
7380 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7381 return;
7382 };
7383 if self.selections.count() != 1 {
7384 return;
7385 }
7386
7387 match &active_edit_prediction.completion {
7388 EditPrediction::MoveWithin { target, .. } => {
7389 let target = *target;
7390 self.change_selections(
7391 SelectionEffects::scroll(Autoscroll::newest()),
7392 window,
7393 cx,
7394 |selections| {
7395 selections.select_anchor_ranges([target..target]);
7396 },
7397 );
7398 }
7399 EditPrediction::MoveOutside { snapshot, target } => {
7400 if let Some(workspace) = self.workspace() {
7401 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7402 .detach_and_log_err(cx);
7403 }
7404 }
7405 EditPrediction::Edit { edits, .. } => {
7406 self.report_edit_prediction_event(
7407 active_edit_prediction.completion_id.clone(),
7408 true,
7409 cx,
7410 );
7411
7412 // Find an insertion that starts at the cursor position.
7413 let snapshot = self.buffer.read(cx).snapshot(cx);
7414 let cursor_offset = self
7415 .selections
7416 .newest::<usize>(&self.display_snapshot(cx))
7417 .head();
7418 let insertion = edits.iter().find_map(|(range, text)| {
7419 let range = range.to_offset(&snapshot);
7420 if range.is_empty() && range.start == cursor_offset {
7421 Some(text)
7422 } else {
7423 None
7424 }
7425 });
7426
7427 if let Some(text) = insertion {
7428 let mut partial_completion = text
7429 .chars()
7430 .by_ref()
7431 .take_while(|c| c.is_alphabetic())
7432 .collect::<String>();
7433 if partial_completion.is_empty() {
7434 partial_completion = text
7435 .chars()
7436 .by_ref()
7437 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7438 .collect::<String>();
7439 }
7440
7441 cx.emit(EditorEvent::InputHandled {
7442 utf16_range_to_replace: None,
7443 text: partial_completion.clone().into(),
7444 });
7445
7446 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7447
7448 self.refresh_edit_prediction(true, true, window, cx);
7449 cx.notify();
7450 } else {
7451 self.accept_edit_prediction(&Default::default(), window, cx);
7452 }
7453 }
7454 }
7455 }
7456
7457 fn discard_edit_prediction(
7458 &mut self,
7459 should_report_edit_prediction_event: bool,
7460 cx: &mut Context<Self>,
7461 ) -> bool {
7462 if should_report_edit_prediction_event {
7463 let completion_id = self
7464 .active_edit_prediction
7465 .as_ref()
7466 .and_then(|active_completion| active_completion.completion_id.clone());
7467
7468 self.report_edit_prediction_event(completion_id, false, cx);
7469 }
7470
7471 if let Some(provider) = self.edit_prediction_provider() {
7472 provider.discard(cx);
7473 }
7474
7475 self.take_active_edit_prediction(cx)
7476 }
7477
7478 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7479 let Some(provider) = self.edit_prediction_provider() else {
7480 return;
7481 };
7482
7483 let Some((_, buffer, _)) = self
7484 .buffer
7485 .read(cx)
7486 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7487 else {
7488 return;
7489 };
7490
7491 let extension = buffer
7492 .read(cx)
7493 .file()
7494 .and_then(|file| Some(file.path().extension()?.to_string()));
7495
7496 let event_type = match accepted {
7497 true => "Edit Prediction Accepted",
7498 false => "Edit Prediction Discarded",
7499 };
7500 telemetry::event!(
7501 event_type,
7502 provider = provider.name(),
7503 prediction_id = id,
7504 suggestion_accepted = accepted,
7505 file_extension = extension,
7506 );
7507 }
7508
7509 fn open_editor_at_anchor(
7510 snapshot: &language::BufferSnapshot,
7511 target: language::Anchor,
7512 workspace: &Entity<Workspace>,
7513 window: &mut Window,
7514 cx: &mut App,
7515 ) -> Task<Result<()>> {
7516 workspace.update(cx, |workspace, cx| {
7517 let path = snapshot.file().map(|file| file.full_path(cx));
7518 let Some(path) =
7519 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7520 else {
7521 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7522 };
7523 let target = text::ToPoint::to_point(&target, snapshot);
7524 let item = workspace.open_path(path, None, true, window, cx);
7525 window.spawn(cx, async move |cx| {
7526 let Some(editor) = item.await?.downcast::<Editor>() else {
7527 return Ok(());
7528 };
7529 editor
7530 .update_in(cx, |editor, window, cx| {
7531 editor.go_to_singleton_buffer_point(target, window, cx);
7532 })
7533 .ok();
7534 anyhow::Ok(())
7535 })
7536 })
7537 }
7538
7539 pub fn has_active_edit_prediction(&self) -> bool {
7540 self.active_edit_prediction.is_some()
7541 }
7542
7543 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7544 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7545 return false;
7546 };
7547
7548 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7549 self.clear_highlights::<EditPredictionHighlight>(cx);
7550 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7551 true
7552 }
7553
7554 /// Returns true when we're displaying the edit prediction popover below the cursor
7555 /// like we are not previewing and the LSP autocomplete menu is visible
7556 /// or we are in `when_holding_modifier` mode.
7557 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7558 if self.edit_prediction_preview_is_active()
7559 || !self.show_edit_predictions_in_menu()
7560 || !self.edit_predictions_enabled()
7561 {
7562 return false;
7563 }
7564
7565 if self.has_visible_completions_menu() {
7566 return true;
7567 }
7568
7569 has_completion && self.edit_prediction_requires_modifier()
7570 }
7571
7572 fn handle_modifiers_changed(
7573 &mut self,
7574 modifiers: Modifiers,
7575 position_map: &PositionMap,
7576 window: &mut Window,
7577 cx: &mut Context<Self>,
7578 ) {
7579 // Ensure that the edit prediction preview is updated, even when not
7580 // enabled, if there's an active edit prediction preview.
7581 if self.show_edit_predictions_in_menu()
7582 || matches!(
7583 self.edit_prediction_preview,
7584 EditPredictionPreview::Active { .. }
7585 )
7586 {
7587 self.update_edit_prediction_preview(&modifiers, window, cx);
7588 }
7589
7590 self.update_selection_mode(&modifiers, position_map, window, cx);
7591
7592 let mouse_position = window.mouse_position();
7593 if !position_map.text_hitbox.is_hovered(window) {
7594 return;
7595 }
7596
7597 self.update_hovered_link(
7598 position_map.point_for_position(mouse_position),
7599 &position_map.snapshot,
7600 modifiers,
7601 window,
7602 cx,
7603 )
7604 }
7605
7606 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7607 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7608 if invert {
7609 match multi_cursor_setting {
7610 MultiCursorModifier::Alt => modifiers.alt,
7611 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7612 }
7613 } else {
7614 match multi_cursor_setting {
7615 MultiCursorModifier::Alt => modifiers.secondary(),
7616 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7617 }
7618 }
7619 }
7620
7621 fn columnar_selection_mode(
7622 modifiers: &Modifiers,
7623 cx: &mut Context<Self>,
7624 ) -> Option<ColumnarMode> {
7625 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7626 if Self::multi_cursor_modifier(false, modifiers, cx) {
7627 Some(ColumnarMode::FromMouse)
7628 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7629 Some(ColumnarMode::FromSelection)
7630 } else {
7631 None
7632 }
7633 } else {
7634 None
7635 }
7636 }
7637
7638 fn update_selection_mode(
7639 &mut self,
7640 modifiers: &Modifiers,
7641 position_map: &PositionMap,
7642 window: &mut Window,
7643 cx: &mut Context<Self>,
7644 ) {
7645 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7646 return;
7647 };
7648 if self.selections.pending_anchor().is_none() {
7649 return;
7650 }
7651
7652 let mouse_position = window.mouse_position();
7653 let point_for_position = position_map.point_for_position(mouse_position);
7654 let position = point_for_position.previous_valid;
7655
7656 self.select(
7657 SelectPhase::BeginColumnar {
7658 position,
7659 reset: false,
7660 mode,
7661 goal_column: point_for_position.exact_unclipped.column(),
7662 },
7663 window,
7664 cx,
7665 );
7666 }
7667
7668 fn update_edit_prediction_preview(
7669 &mut self,
7670 modifiers: &Modifiers,
7671 window: &mut Window,
7672 cx: &mut Context<Self>,
7673 ) {
7674 let mut modifiers_held = false;
7675 if let Some(accept_keystroke) = self
7676 .accept_edit_prediction_keybind(false, window, cx)
7677 .keystroke()
7678 {
7679 modifiers_held = modifiers_held
7680 || (accept_keystroke.modifiers() == modifiers
7681 && accept_keystroke.modifiers().modified());
7682 };
7683 if let Some(accept_partial_keystroke) = self
7684 .accept_edit_prediction_keybind(true, window, cx)
7685 .keystroke()
7686 {
7687 modifiers_held = modifiers_held
7688 || (accept_partial_keystroke.modifiers() == modifiers
7689 && accept_partial_keystroke.modifiers().modified());
7690 }
7691
7692 if modifiers_held {
7693 if matches!(
7694 self.edit_prediction_preview,
7695 EditPredictionPreview::Inactive { .. }
7696 ) {
7697 self.edit_prediction_preview = EditPredictionPreview::Active {
7698 previous_scroll_position: None,
7699 since: Instant::now(),
7700 };
7701
7702 self.update_visible_edit_prediction(window, cx);
7703 cx.notify();
7704 }
7705 } else if let EditPredictionPreview::Active {
7706 previous_scroll_position,
7707 since,
7708 } = self.edit_prediction_preview
7709 {
7710 if let (Some(previous_scroll_position), Some(position_map)) =
7711 (previous_scroll_position, self.last_position_map.as_ref())
7712 {
7713 self.set_scroll_position(
7714 previous_scroll_position
7715 .scroll_position(&position_map.snapshot.display_snapshot),
7716 window,
7717 cx,
7718 );
7719 }
7720
7721 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7722 released_too_fast: since.elapsed() < Duration::from_millis(200),
7723 };
7724 self.clear_row_highlights::<EditPredictionPreview>();
7725 self.update_visible_edit_prediction(window, cx);
7726 cx.notify();
7727 }
7728 }
7729
7730 fn update_visible_edit_prediction(
7731 &mut self,
7732 _window: &mut Window,
7733 cx: &mut Context<Self>,
7734 ) -> Option<()> {
7735 if DisableAiSettings::get_global(cx).disable_ai {
7736 return None;
7737 }
7738
7739 if self.ime_transaction.is_some() {
7740 self.discard_edit_prediction(false, cx);
7741 return None;
7742 }
7743
7744 let selection = self.selections.newest_anchor();
7745 let cursor = selection.head();
7746 let multibuffer = self.buffer.read(cx).snapshot(cx);
7747 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7748 let excerpt_id = cursor.excerpt_id;
7749
7750 let show_in_menu = self.show_edit_predictions_in_menu();
7751 let completions_menu_has_precedence = !show_in_menu
7752 && (self.context_menu.borrow().is_some()
7753 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7754
7755 if completions_menu_has_precedence
7756 || !offset_selection.is_empty()
7757 || self
7758 .active_edit_prediction
7759 .as_ref()
7760 .is_some_and(|completion| {
7761 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7762 return false;
7763 };
7764 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7765 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7766 !invalidation_range.contains(&offset_selection.head())
7767 })
7768 {
7769 self.discard_edit_prediction(false, cx);
7770 return None;
7771 }
7772
7773 self.take_active_edit_prediction(cx);
7774 let Some(provider) = self.edit_prediction_provider() else {
7775 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7776 return None;
7777 };
7778
7779 let (buffer, cursor_buffer_position) =
7780 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7781
7782 self.edit_prediction_settings =
7783 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7784
7785 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7786
7787 if self.edit_prediction_indent_conflict {
7788 let cursor_point = cursor.to_point(&multibuffer);
7789
7790 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7791
7792 if let Some((_, indent)) = indents.iter().next()
7793 && indent.len == cursor_point.column
7794 {
7795 self.edit_prediction_indent_conflict = false;
7796 }
7797 }
7798
7799 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7800
7801 let (completion_id, edits, edit_preview) = match edit_prediction {
7802 edit_prediction::EditPrediction::Local {
7803 id,
7804 edits,
7805 edit_preview,
7806 } => (id, edits, edit_preview),
7807 edit_prediction::EditPrediction::Jump {
7808 id,
7809 snapshot,
7810 target,
7811 } => {
7812 self.stale_edit_prediction_in_menu = None;
7813 self.active_edit_prediction = Some(EditPredictionState {
7814 inlay_ids: vec![],
7815 completion: EditPrediction::MoveOutside { snapshot, target },
7816 completion_id: id,
7817 invalidation_range: None,
7818 });
7819 cx.notify();
7820 return Some(());
7821 }
7822 };
7823
7824 let edits = edits
7825 .into_iter()
7826 .flat_map(|(range, new_text)| {
7827 Some((
7828 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7829 new_text,
7830 ))
7831 })
7832 .collect::<Vec<_>>();
7833 if edits.is_empty() {
7834 return None;
7835 }
7836
7837 let first_edit_start = edits.first().unwrap().0.start;
7838 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7839 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7840
7841 let last_edit_end = edits.last().unwrap().0.end;
7842 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7843 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7844
7845 let cursor_row = cursor.to_point(&multibuffer).row;
7846
7847 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7848
7849 let mut inlay_ids = Vec::new();
7850 let invalidation_row_range;
7851 let move_invalidation_row_range = if cursor_row < edit_start_row {
7852 Some(cursor_row..edit_end_row)
7853 } else if cursor_row > edit_end_row {
7854 Some(edit_start_row..cursor_row)
7855 } else {
7856 None
7857 };
7858 let supports_jump = self
7859 .edit_prediction_provider
7860 .as_ref()
7861 .map(|provider| provider.provider.supports_jump_to_edit())
7862 .unwrap_or(true);
7863
7864 let is_move = supports_jump
7865 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7866 let completion = if is_move {
7867 invalidation_row_range =
7868 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7869 let target = first_edit_start;
7870 EditPrediction::MoveWithin { target, snapshot }
7871 } else {
7872 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7873 && !self.edit_predictions_hidden_for_vim_mode;
7874
7875 if show_completions_in_buffer {
7876 if edits
7877 .iter()
7878 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7879 {
7880 let mut inlays = Vec::new();
7881 for (range, new_text) in &edits {
7882 let inlay = Inlay::edit_prediction(
7883 post_inc(&mut self.next_inlay_id),
7884 range.start,
7885 new_text.as_str(),
7886 );
7887 inlay_ids.push(inlay.id);
7888 inlays.push(inlay);
7889 }
7890
7891 self.splice_inlays(&[], inlays, cx);
7892 } else {
7893 let background_color = cx.theme().status().deleted_background;
7894 self.highlight_text::<EditPredictionHighlight>(
7895 edits.iter().map(|(range, _)| range.clone()).collect(),
7896 HighlightStyle {
7897 background_color: Some(background_color),
7898 ..Default::default()
7899 },
7900 cx,
7901 );
7902 }
7903 }
7904
7905 invalidation_row_range = edit_start_row..edit_end_row;
7906
7907 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7908 if provider.show_tab_accept_marker() {
7909 EditDisplayMode::TabAccept
7910 } else {
7911 EditDisplayMode::Inline
7912 }
7913 } else {
7914 EditDisplayMode::DiffPopover
7915 };
7916
7917 EditPrediction::Edit {
7918 edits,
7919 edit_preview,
7920 display_mode,
7921 snapshot,
7922 }
7923 };
7924
7925 let invalidation_range = multibuffer
7926 .anchor_before(Point::new(invalidation_row_range.start, 0))
7927 ..multibuffer.anchor_after(Point::new(
7928 invalidation_row_range.end,
7929 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7930 ));
7931
7932 self.stale_edit_prediction_in_menu = None;
7933 self.active_edit_prediction = Some(EditPredictionState {
7934 inlay_ids,
7935 completion,
7936 completion_id,
7937 invalidation_range: Some(invalidation_range),
7938 });
7939
7940 cx.notify();
7941
7942 Some(())
7943 }
7944
7945 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7946 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7947 }
7948
7949 fn clear_tasks(&mut self) {
7950 self.tasks.clear()
7951 }
7952
7953 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7954 if self.tasks.insert(key, value).is_some() {
7955 // This case should hopefully be rare, but just in case...
7956 log::error!(
7957 "multiple different run targets found on a single line, only the last target will be rendered"
7958 )
7959 }
7960 }
7961
7962 /// Get all display points of breakpoints that will be rendered within editor
7963 ///
7964 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7965 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7966 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7967 fn active_breakpoints(
7968 &self,
7969 range: Range<DisplayRow>,
7970 window: &mut Window,
7971 cx: &mut Context<Self>,
7972 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7973 let mut breakpoint_display_points = HashMap::default();
7974
7975 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7976 return breakpoint_display_points;
7977 };
7978
7979 let snapshot = self.snapshot(window, cx);
7980
7981 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7982 let Some(project) = self.project() else {
7983 return breakpoint_display_points;
7984 };
7985
7986 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7987 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7988
7989 for (buffer_snapshot, range, excerpt_id) in
7990 multi_buffer_snapshot.range_to_buffer_ranges(range)
7991 {
7992 let Some(buffer) = project
7993 .read(cx)
7994 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7995 else {
7996 continue;
7997 };
7998 let breakpoints = breakpoint_store.read(cx).breakpoints(
7999 &buffer,
8000 Some(
8001 buffer_snapshot.anchor_before(range.start)
8002 ..buffer_snapshot.anchor_after(range.end),
8003 ),
8004 buffer_snapshot,
8005 cx,
8006 );
8007 for (breakpoint, state) in breakpoints {
8008 let multi_buffer_anchor =
8009 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8010 let position = multi_buffer_anchor
8011 .to_point(&multi_buffer_snapshot)
8012 .to_display_point(&snapshot);
8013
8014 breakpoint_display_points.insert(
8015 position.row(),
8016 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8017 );
8018 }
8019 }
8020
8021 breakpoint_display_points
8022 }
8023
8024 fn breakpoint_context_menu(
8025 &self,
8026 anchor: Anchor,
8027 window: &mut Window,
8028 cx: &mut Context<Self>,
8029 ) -> Entity<ui::ContextMenu> {
8030 let weak_editor = cx.weak_entity();
8031 let focus_handle = self.focus_handle(cx);
8032
8033 let row = self
8034 .buffer
8035 .read(cx)
8036 .snapshot(cx)
8037 .summary_for_anchor::<Point>(&anchor)
8038 .row;
8039
8040 let breakpoint = self
8041 .breakpoint_at_row(row, window, cx)
8042 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8043
8044 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8045 "Edit Log Breakpoint"
8046 } else {
8047 "Set Log Breakpoint"
8048 };
8049
8050 let condition_breakpoint_msg = if breakpoint
8051 .as_ref()
8052 .is_some_and(|bp| bp.1.condition.is_some())
8053 {
8054 "Edit Condition Breakpoint"
8055 } else {
8056 "Set Condition Breakpoint"
8057 };
8058
8059 let hit_condition_breakpoint_msg = if breakpoint
8060 .as_ref()
8061 .is_some_and(|bp| bp.1.hit_condition.is_some())
8062 {
8063 "Edit Hit Condition Breakpoint"
8064 } else {
8065 "Set Hit Condition Breakpoint"
8066 };
8067
8068 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8069 "Unset Breakpoint"
8070 } else {
8071 "Set Breakpoint"
8072 };
8073
8074 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8075
8076 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8077 BreakpointState::Enabled => Some("Disable"),
8078 BreakpointState::Disabled => Some("Enable"),
8079 });
8080
8081 let (anchor, breakpoint) =
8082 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8083
8084 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8085 menu.on_blur_subscription(Subscription::new(|| {}))
8086 .context(focus_handle)
8087 .when(run_to_cursor, |this| {
8088 let weak_editor = weak_editor.clone();
8089 this.entry("Run to cursor", None, move |window, cx| {
8090 weak_editor
8091 .update(cx, |editor, cx| {
8092 editor.change_selections(
8093 SelectionEffects::no_scroll(),
8094 window,
8095 cx,
8096 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8097 );
8098 })
8099 .ok();
8100
8101 window.dispatch_action(Box::new(RunToCursor), cx);
8102 })
8103 .separator()
8104 })
8105 .when_some(toggle_state_msg, |this, msg| {
8106 this.entry(msg, None, {
8107 let weak_editor = weak_editor.clone();
8108 let breakpoint = breakpoint.clone();
8109 move |_window, cx| {
8110 weak_editor
8111 .update(cx, |this, cx| {
8112 this.edit_breakpoint_at_anchor(
8113 anchor,
8114 breakpoint.as_ref().clone(),
8115 BreakpointEditAction::InvertState,
8116 cx,
8117 );
8118 })
8119 .log_err();
8120 }
8121 })
8122 })
8123 .entry(set_breakpoint_msg, None, {
8124 let weak_editor = weak_editor.clone();
8125 let breakpoint = breakpoint.clone();
8126 move |_window, cx| {
8127 weak_editor
8128 .update(cx, |this, cx| {
8129 this.edit_breakpoint_at_anchor(
8130 anchor,
8131 breakpoint.as_ref().clone(),
8132 BreakpointEditAction::Toggle,
8133 cx,
8134 );
8135 })
8136 .log_err();
8137 }
8138 })
8139 .entry(log_breakpoint_msg, None, {
8140 let breakpoint = breakpoint.clone();
8141 let weak_editor = weak_editor.clone();
8142 move |window, cx| {
8143 weak_editor
8144 .update(cx, |this, cx| {
8145 this.add_edit_breakpoint_block(
8146 anchor,
8147 breakpoint.as_ref(),
8148 BreakpointPromptEditAction::Log,
8149 window,
8150 cx,
8151 );
8152 })
8153 .log_err();
8154 }
8155 })
8156 .entry(condition_breakpoint_msg, None, {
8157 let breakpoint = breakpoint.clone();
8158 let weak_editor = weak_editor.clone();
8159 move |window, cx| {
8160 weak_editor
8161 .update(cx, |this, cx| {
8162 this.add_edit_breakpoint_block(
8163 anchor,
8164 breakpoint.as_ref(),
8165 BreakpointPromptEditAction::Condition,
8166 window,
8167 cx,
8168 );
8169 })
8170 .log_err();
8171 }
8172 })
8173 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8174 weak_editor
8175 .update(cx, |this, cx| {
8176 this.add_edit_breakpoint_block(
8177 anchor,
8178 breakpoint.as_ref(),
8179 BreakpointPromptEditAction::HitCondition,
8180 window,
8181 cx,
8182 );
8183 })
8184 .log_err();
8185 })
8186 })
8187 }
8188
8189 fn render_breakpoint(
8190 &self,
8191 position: Anchor,
8192 row: DisplayRow,
8193 breakpoint: &Breakpoint,
8194 state: Option<BreakpointSessionState>,
8195 cx: &mut Context<Self>,
8196 ) -> IconButton {
8197 let is_rejected = state.is_some_and(|s| !s.verified);
8198 // Is it a breakpoint that shows up when hovering over gutter?
8199 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8200 (false, false),
8201 |PhantomBreakpointIndicator {
8202 is_active,
8203 display_row,
8204 collides_with_existing_breakpoint,
8205 }| {
8206 (
8207 is_active && display_row == row,
8208 collides_with_existing_breakpoint,
8209 )
8210 },
8211 );
8212
8213 let (color, icon) = {
8214 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8215 (false, false) => ui::IconName::DebugBreakpoint,
8216 (true, false) => ui::IconName::DebugLogBreakpoint,
8217 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8218 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8219 };
8220
8221 let color = if is_phantom {
8222 Color::Hint
8223 } else if is_rejected {
8224 Color::Disabled
8225 } else {
8226 Color::Debugger
8227 };
8228
8229 (color, icon)
8230 };
8231
8232 let breakpoint = Arc::from(breakpoint.clone());
8233
8234 let alt_as_text = gpui::Keystroke {
8235 modifiers: Modifiers::secondary_key(),
8236 ..Default::default()
8237 };
8238 let primary_action_text = if breakpoint.is_disabled() {
8239 "Enable breakpoint"
8240 } else if is_phantom && !collides_with_existing {
8241 "Set breakpoint"
8242 } else {
8243 "Unset breakpoint"
8244 };
8245 let focus_handle = self.focus_handle.clone();
8246
8247 let meta = if is_rejected {
8248 SharedString::from("No executable code is associated with this line.")
8249 } else if collides_with_existing && !breakpoint.is_disabled() {
8250 SharedString::from(format!(
8251 "{alt_as_text}-click to disable,\nright-click for more options."
8252 ))
8253 } else {
8254 SharedString::from("Right-click for more options.")
8255 };
8256 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8257 .icon_size(IconSize::XSmall)
8258 .size(ui::ButtonSize::None)
8259 .when(is_rejected, |this| {
8260 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8261 })
8262 .icon_color(color)
8263 .style(ButtonStyle::Transparent)
8264 .on_click(cx.listener({
8265 move |editor, event: &ClickEvent, window, cx| {
8266 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8267 BreakpointEditAction::InvertState
8268 } else {
8269 BreakpointEditAction::Toggle
8270 };
8271
8272 window.focus(&editor.focus_handle(cx));
8273 editor.edit_breakpoint_at_anchor(
8274 position,
8275 breakpoint.as_ref().clone(),
8276 edit_action,
8277 cx,
8278 );
8279 }
8280 }))
8281 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8282 editor.set_breakpoint_context_menu(
8283 row,
8284 Some(position),
8285 event.position(),
8286 window,
8287 cx,
8288 );
8289 }))
8290 .tooltip(move |_window, cx| {
8291 Tooltip::with_meta_in(
8292 primary_action_text,
8293 Some(&ToggleBreakpoint),
8294 meta.clone(),
8295 &focus_handle,
8296 cx,
8297 )
8298 })
8299 }
8300
8301 fn build_tasks_context(
8302 project: &Entity<Project>,
8303 buffer: &Entity<Buffer>,
8304 buffer_row: u32,
8305 tasks: &Arc<RunnableTasks>,
8306 cx: &mut Context<Self>,
8307 ) -> Task<Option<task::TaskContext>> {
8308 let position = Point::new(buffer_row, tasks.column);
8309 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8310 let location = Location {
8311 buffer: buffer.clone(),
8312 range: range_start..range_start,
8313 };
8314 // Fill in the environmental variables from the tree-sitter captures
8315 let mut captured_task_variables = TaskVariables::default();
8316 for (capture_name, value) in tasks.extra_variables.clone() {
8317 captured_task_variables.insert(
8318 task::VariableName::Custom(capture_name.into()),
8319 value.clone(),
8320 );
8321 }
8322 project.update(cx, |project, cx| {
8323 project.task_store().update(cx, |task_store, cx| {
8324 task_store.task_context_for_location(captured_task_variables, location, cx)
8325 })
8326 })
8327 }
8328
8329 pub fn spawn_nearest_task(
8330 &mut self,
8331 action: &SpawnNearestTask,
8332 window: &mut Window,
8333 cx: &mut Context<Self>,
8334 ) {
8335 let Some((workspace, _)) = self.workspace.clone() else {
8336 return;
8337 };
8338 let Some(project) = self.project.clone() else {
8339 return;
8340 };
8341
8342 // Try to find a closest, enclosing node using tree-sitter that has a task
8343 let Some((buffer, buffer_row, tasks)) = self
8344 .find_enclosing_node_task(cx)
8345 // Or find the task that's closest in row-distance.
8346 .or_else(|| self.find_closest_task(cx))
8347 else {
8348 return;
8349 };
8350
8351 let reveal_strategy = action.reveal;
8352 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8353 cx.spawn_in(window, async move |_, cx| {
8354 let context = task_context.await?;
8355 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8356
8357 let resolved = &mut resolved_task.resolved;
8358 resolved.reveal = reveal_strategy;
8359
8360 workspace
8361 .update_in(cx, |workspace, window, cx| {
8362 workspace.schedule_resolved_task(
8363 task_source_kind,
8364 resolved_task,
8365 false,
8366 window,
8367 cx,
8368 );
8369 })
8370 .ok()
8371 })
8372 .detach();
8373 }
8374
8375 fn find_closest_task(
8376 &mut self,
8377 cx: &mut Context<Self>,
8378 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8379 let cursor_row = self
8380 .selections
8381 .newest_adjusted(&self.display_snapshot(cx))
8382 .head()
8383 .row;
8384
8385 let ((buffer_id, row), tasks) = self
8386 .tasks
8387 .iter()
8388 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8389
8390 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8391 let tasks = Arc::new(tasks.to_owned());
8392 Some((buffer, *row, tasks))
8393 }
8394
8395 fn find_enclosing_node_task(
8396 &mut self,
8397 cx: &mut Context<Self>,
8398 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8399 let snapshot = self.buffer.read(cx).snapshot(cx);
8400 let offset = self
8401 .selections
8402 .newest::<usize>(&self.display_snapshot(cx))
8403 .head();
8404 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8405 let buffer_id = excerpt.buffer().remote_id();
8406
8407 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8408 let mut cursor = layer.node().walk();
8409
8410 while cursor.goto_first_child_for_byte(offset).is_some() {
8411 if cursor.node().end_byte() == offset {
8412 cursor.goto_next_sibling();
8413 }
8414 }
8415
8416 // Ascend to the smallest ancestor that contains the range and has a task.
8417 loop {
8418 let node = cursor.node();
8419 let node_range = node.byte_range();
8420 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8421
8422 // Check if this node contains our offset
8423 if node_range.start <= offset && node_range.end >= offset {
8424 // If it contains offset, check for task
8425 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8426 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8427 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8428 }
8429 }
8430
8431 if !cursor.goto_parent() {
8432 break;
8433 }
8434 }
8435 None
8436 }
8437
8438 fn render_run_indicator(
8439 &self,
8440 _style: &EditorStyle,
8441 is_active: bool,
8442 row: DisplayRow,
8443 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8444 cx: &mut Context<Self>,
8445 ) -> IconButton {
8446 let color = Color::Muted;
8447 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8448
8449 IconButton::new(
8450 ("run_indicator", row.0 as usize),
8451 ui::IconName::PlayOutlined,
8452 )
8453 .shape(ui::IconButtonShape::Square)
8454 .icon_size(IconSize::XSmall)
8455 .icon_color(color)
8456 .toggle_state(is_active)
8457 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8458 let quick_launch = match e {
8459 ClickEvent::Keyboard(_) => true,
8460 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8461 };
8462
8463 window.focus(&editor.focus_handle(cx));
8464 editor.toggle_code_actions(
8465 &ToggleCodeActions {
8466 deployed_from: Some(CodeActionSource::RunMenu(row)),
8467 quick_launch,
8468 },
8469 window,
8470 cx,
8471 );
8472 }))
8473 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8474 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8475 }))
8476 }
8477
8478 pub fn context_menu_visible(&self) -> bool {
8479 !self.edit_prediction_preview_is_active()
8480 && self
8481 .context_menu
8482 .borrow()
8483 .as_ref()
8484 .is_some_and(|menu| menu.visible())
8485 }
8486
8487 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8488 self.context_menu
8489 .borrow()
8490 .as_ref()
8491 .map(|menu| menu.origin())
8492 }
8493
8494 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8495 self.context_menu_options = Some(options);
8496 }
8497
8498 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8499 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8500
8501 fn render_edit_prediction_popover(
8502 &mut self,
8503 text_bounds: &Bounds<Pixels>,
8504 content_origin: gpui::Point<Pixels>,
8505 right_margin: Pixels,
8506 editor_snapshot: &EditorSnapshot,
8507 visible_row_range: Range<DisplayRow>,
8508 scroll_top: ScrollOffset,
8509 scroll_bottom: ScrollOffset,
8510 line_layouts: &[LineWithInvisibles],
8511 line_height: Pixels,
8512 scroll_position: gpui::Point<ScrollOffset>,
8513 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8514 newest_selection_head: Option<DisplayPoint>,
8515 editor_width: Pixels,
8516 style: &EditorStyle,
8517 window: &mut Window,
8518 cx: &mut App,
8519 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8520 if self.mode().is_minimap() {
8521 return None;
8522 }
8523 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8524
8525 if self.edit_prediction_visible_in_cursor_popover(true) {
8526 return None;
8527 }
8528
8529 match &active_edit_prediction.completion {
8530 EditPrediction::MoveWithin { target, .. } => {
8531 let target_display_point = target.to_display_point(editor_snapshot);
8532
8533 if self.edit_prediction_requires_modifier() {
8534 if !self.edit_prediction_preview_is_active() {
8535 return None;
8536 }
8537
8538 self.render_edit_prediction_modifier_jump_popover(
8539 text_bounds,
8540 content_origin,
8541 visible_row_range,
8542 line_layouts,
8543 line_height,
8544 scroll_pixel_position,
8545 newest_selection_head,
8546 target_display_point,
8547 window,
8548 cx,
8549 )
8550 } else {
8551 self.render_edit_prediction_eager_jump_popover(
8552 text_bounds,
8553 content_origin,
8554 editor_snapshot,
8555 visible_row_range,
8556 scroll_top,
8557 scroll_bottom,
8558 line_height,
8559 scroll_pixel_position,
8560 target_display_point,
8561 editor_width,
8562 window,
8563 cx,
8564 )
8565 }
8566 }
8567 EditPrediction::Edit {
8568 display_mode: EditDisplayMode::Inline,
8569 ..
8570 } => None,
8571 EditPrediction::Edit {
8572 display_mode: EditDisplayMode::TabAccept,
8573 edits,
8574 ..
8575 } => {
8576 let range = &edits.first()?.0;
8577 let target_display_point = range.end.to_display_point(editor_snapshot);
8578
8579 self.render_edit_prediction_end_of_line_popover(
8580 "Accept",
8581 editor_snapshot,
8582 visible_row_range,
8583 target_display_point,
8584 line_height,
8585 scroll_pixel_position,
8586 content_origin,
8587 editor_width,
8588 window,
8589 cx,
8590 )
8591 }
8592 EditPrediction::Edit {
8593 edits,
8594 edit_preview,
8595 display_mode: EditDisplayMode::DiffPopover,
8596 snapshot,
8597 } => self.render_edit_prediction_diff_popover(
8598 text_bounds,
8599 content_origin,
8600 right_margin,
8601 editor_snapshot,
8602 visible_row_range,
8603 line_layouts,
8604 line_height,
8605 scroll_position,
8606 scroll_pixel_position,
8607 newest_selection_head,
8608 editor_width,
8609 style,
8610 edits,
8611 edit_preview,
8612 snapshot,
8613 window,
8614 cx,
8615 ),
8616 EditPrediction::MoveOutside { snapshot, .. } => {
8617 let file_name = snapshot
8618 .file()
8619 .map(|file| file.file_name(cx))
8620 .unwrap_or("untitled");
8621 let mut element = self
8622 .render_edit_prediction_line_popover(
8623 format!("Jump to {file_name}"),
8624 Some(IconName::ZedPredict),
8625 window,
8626 cx,
8627 )
8628 .into_any();
8629
8630 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8631 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8632 let origin_y = text_bounds.size.height - size.height - px(30.);
8633 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8634 element.prepaint_at(origin, window, cx);
8635
8636 Some((element, origin))
8637 }
8638 }
8639 }
8640
8641 fn render_edit_prediction_modifier_jump_popover(
8642 &mut self,
8643 text_bounds: &Bounds<Pixels>,
8644 content_origin: gpui::Point<Pixels>,
8645 visible_row_range: Range<DisplayRow>,
8646 line_layouts: &[LineWithInvisibles],
8647 line_height: Pixels,
8648 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8649 newest_selection_head: Option<DisplayPoint>,
8650 target_display_point: DisplayPoint,
8651 window: &mut Window,
8652 cx: &mut App,
8653 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8654 let scrolled_content_origin =
8655 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8656
8657 const SCROLL_PADDING_Y: Pixels = px(12.);
8658
8659 if target_display_point.row() < visible_row_range.start {
8660 return self.render_edit_prediction_scroll_popover(
8661 |_| SCROLL_PADDING_Y,
8662 IconName::ArrowUp,
8663 visible_row_range,
8664 line_layouts,
8665 newest_selection_head,
8666 scrolled_content_origin,
8667 window,
8668 cx,
8669 );
8670 } else if target_display_point.row() >= visible_row_range.end {
8671 return self.render_edit_prediction_scroll_popover(
8672 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8673 IconName::ArrowDown,
8674 visible_row_range,
8675 line_layouts,
8676 newest_selection_head,
8677 scrolled_content_origin,
8678 window,
8679 cx,
8680 );
8681 }
8682
8683 const POLE_WIDTH: Pixels = px(2.);
8684
8685 let line_layout =
8686 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8687 let target_column = target_display_point.column() as usize;
8688
8689 let target_x = line_layout.x_for_index(target_column);
8690 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8691 - scroll_pixel_position.y;
8692
8693 let flag_on_right = target_x < text_bounds.size.width / 2.;
8694
8695 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8696 border_color.l += 0.001;
8697
8698 let mut element = v_flex()
8699 .items_end()
8700 .when(flag_on_right, |el| el.items_start())
8701 .child(if flag_on_right {
8702 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8703 .rounded_bl(px(0.))
8704 .rounded_tl(px(0.))
8705 .border_l_2()
8706 .border_color(border_color)
8707 } else {
8708 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8709 .rounded_br(px(0.))
8710 .rounded_tr(px(0.))
8711 .border_r_2()
8712 .border_color(border_color)
8713 })
8714 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8715 .into_any();
8716
8717 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8718
8719 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8720 - point(
8721 if flag_on_right {
8722 POLE_WIDTH
8723 } else {
8724 size.width - POLE_WIDTH
8725 },
8726 size.height - line_height,
8727 );
8728
8729 origin.x = origin.x.max(content_origin.x);
8730
8731 element.prepaint_at(origin, window, cx);
8732
8733 Some((element, origin))
8734 }
8735
8736 fn render_edit_prediction_scroll_popover(
8737 &mut self,
8738 to_y: impl Fn(Size<Pixels>) -> Pixels,
8739 scroll_icon: IconName,
8740 visible_row_range: Range<DisplayRow>,
8741 line_layouts: &[LineWithInvisibles],
8742 newest_selection_head: Option<DisplayPoint>,
8743 scrolled_content_origin: gpui::Point<Pixels>,
8744 window: &mut Window,
8745 cx: &mut App,
8746 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8747 let mut element = self
8748 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8749 .into_any();
8750
8751 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8752
8753 let cursor = newest_selection_head?;
8754 let cursor_row_layout =
8755 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8756 let cursor_column = cursor.column() as usize;
8757
8758 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8759
8760 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8761
8762 element.prepaint_at(origin, window, cx);
8763 Some((element, origin))
8764 }
8765
8766 fn render_edit_prediction_eager_jump_popover(
8767 &mut self,
8768 text_bounds: &Bounds<Pixels>,
8769 content_origin: gpui::Point<Pixels>,
8770 editor_snapshot: &EditorSnapshot,
8771 visible_row_range: Range<DisplayRow>,
8772 scroll_top: ScrollOffset,
8773 scroll_bottom: ScrollOffset,
8774 line_height: Pixels,
8775 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8776 target_display_point: DisplayPoint,
8777 editor_width: Pixels,
8778 window: &mut Window,
8779 cx: &mut App,
8780 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8781 if target_display_point.row().as_f64() < scroll_top {
8782 let mut element = self
8783 .render_edit_prediction_line_popover(
8784 "Jump to Edit",
8785 Some(IconName::ArrowUp),
8786 window,
8787 cx,
8788 )
8789 .into_any();
8790
8791 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8792 let offset = point(
8793 (text_bounds.size.width - size.width) / 2.,
8794 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8795 );
8796
8797 let origin = text_bounds.origin + offset;
8798 element.prepaint_at(origin, window, cx);
8799 Some((element, origin))
8800 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8801 let mut element = self
8802 .render_edit_prediction_line_popover(
8803 "Jump to Edit",
8804 Some(IconName::ArrowDown),
8805 window,
8806 cx,
8807 )
8808 .into_any();
8809
8810 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8811 let offset = point(
8812 (text_bounds.size.width - size.width) / 2.,
8813 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8814 );
8815
8816 let origin = text_bounds.origin + offset;
8817 element.prepaint_at(origin, window, cx);
8818 Some((element, origin))
8819 } else {
8820 self.render_edit_prediction_end_of_line_popover(
8821 "Jump to Edit",
8822 editor_snapshot,
8823 visible_row_range,
8824 target_display_point,
8825 line_height,
8826 scroll_pixel_position,
8827 content_origin,
8828 editor_width,
8829 window,
8830 cx,
8831 )
8832 }
8833 }
8834
8835 fn render_edit_prediction_end_of_line_popover(
8836 self: &mut Editor,
8837 label: &'static str,
8838 editor_snapshot: &EditorSnapshot,
8839 visible_row_range: Range<DisplayRow>,
8840 target_display_point: DisplayPoint,
8841 line_height: Pixels,
8842 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8843 content_origin: gpui::Point<Pixels>,
8844 editor_width: Pixels,
8845 window: &mut Window,
8846 cx: &mut App,
8847 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8848 let target_line_end = DisplayPoint::new(
8849 target_display_point.row(),
8850 editor_snapshot.line_len(target_display_point.row()),
8851 );
8852
8853 let mut element = self
8854 .render_edit_prediction_line_popover(label, None, window, cx)
8855 .into_any();
8856
8857 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8858
8859 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8860
8861 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8862 let mut origin = start_point
8863 + line_origin
8864 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8865 origin.x = origin.x.max(content_origin.x);
8866
8867 let max_x = content_origin.x + editor_width - size.width;
8868
8869 if origin.x > max_x {
8870 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8871
8872 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8873 origin.y += offset;
8874 IconName::ArrowUp
8875 } else {
8876 origin.y -= offset;
8877 IconName::ArrowDown
8878 };
8879
8880 element = self
8881 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8882 .into_any();
8883
8884 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8885
8886 origin.x = content_origin.x + editor_width - size.width - px(2.);
8887 }
8888
8889 element.prepaint_at(origin, window, cx);
8890 Some((element, origin))
8891 }
8892
8893 fn render_edit_prediction_diff_popover(
8894 self: &Editor,
8895 text_bounds: &Bounds<Pixels>,
8896 content_origin: gpui::Point<Pixels>,
8897 right_margin: Pixels,
8898 editor_snapshot: &EditorSnapshot,
8899 visible_row_range: Range<DisplayRow>,
8900 line_layouts: &[LineWithInvisibles],
8901 line_height: Pixels,
8902 scroll_position: gpui::Point<ScrollOffset>,
8903 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8904 newest_selection_head: Option<DisplayPoint>,
8905 editor_width: Pixels,
8906 style: &EditorStyle,
8907 edits: &Vec<(Range<Anchor>, String)>,
8908 edit_preview: &Option<language::EditPreview>,
8909 snapshot: &language::BufferSnapshot,
8910 window: &mut Window,
8911 cx: &mut App,
8912 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8913 let edit_start = edits
8914 .first()
8915 .unwrap()
8916 .0
8917 .start
8918 .to_display_point(editor_snapshot);
8919 let edit_end = edits
8920 .last()
8921 .unwrap()
8922 .0
8923 .end
8924 .to_display_point(editor_snapshot);
8925
8926 let is_visible = visible_row_range.contains(&edit_start.row())
8927 || visible_row_range.contains(&edit_end.row());
8928 if !is_visible {
8929 return None;
8930 }
8931
8932 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8933 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8934 } else {
8935 // Fallback for providers without edit_preview
8936 crate::edit_prediction_fallback_text(edits, cx)
8937 };
8938
8939 let styled_text = highlighted_edits.to_styled_text(&style.text);
8940 let line_count = highlighted_edits.text.lines().count();
8941
8942 const BORDER_WIDTH: Pixels = px(1.);
8943
8944 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8945 let has_keybind = keybind.is_some();
8946
8947 let mut element = h_flex()
8948 .items_start()
8949 .child(
8950 h_flex()
8951 .bg(cx.theme().colors().editor_background)
8952 .border(BORDER_WIDTH)
8953 .shadow_xs()
8954 .border_color(cx.theme().colors().border)
8955 .rounded_l_lg()
8956 .when(line_count > 1, |el| el.rounded_br_lg())
8957 .pr_1()
8958 .child(styled_text),
8959 )
8960 .child(
8961 h_flex()
8962 .h(line_height + BORDER_WIDTH * 2.)
8963 .px_1p5()
8964 .gap_1()
8965 // Workaround: For some reason, there's a gap if we don't do this
8966 .ml(-BORDER_WIDTH)
8967 .shadow(vec![gpui::BoxShadow {
8968 color: gpui::black().opacity(0.05),
8969 offset: point(px(1.), px(1.)),
8970 blur_radius: px(2.),
8971 spread_radius: px(0.),
8972 }])
8973 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8974 .border(BORDER_WIDTH)
8975 .border_color(cx.theme().colors().border)
8976 .rounded_r_lg()
8977 .id("edit_prediction_diff_popover_keybind")
8978 .when(!has_keybind, |el| {
8979 let status_colors = cx.theme().status();
8980
8981 el.bg(status_colors.error_background)
8982 .border_color(status_colors.error.opacity(0.6))
8983 .child(Icon::new(IconName::Info).color(Color::Error))
8984 .cursor_default()
8985 .hoverable_tooltip(move |_window, cx| {
8986 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8987 })
8988 })
8989 .children(keybind),
8990 )
8991 .into_any();
8992
8993 let longest_row =
8994 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8995 let longest_line_width = if visible_row_range.contains(&longest_row) {
8996 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8997 } else {
8998 layout_line(
8999 longest_row,
9000 editor_snapshot,
9001 style,
9002 editor_width,
9003 |_| false,
9004 window,
9005 cx,
9006 )
9007 .width
9008 };
9009
9010 let viewport_bounds =
9011 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9012 right: -right_margin,
9013 ..Default::default()
9014 });
9015
9016 let x_after_longest = Pixels::from(
9017 ScrollPixelOffset::from(
9018 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9019 ) - scroll_pixel_position.x,
9020 );
9021
9022 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9023
9024 // Fully visible if it can be displayed within the window (allow overlapping other
9025 // panes). However, this is only allowed if the popover starts within text_bounds.
9026 let can_position_to_the_right = x_after_longest < text_bounds.right()
9027 && x_after_longest + element_bounds.width < viewport_bounds.right();
9028
9029 let mut origin = if can_position_to_the_right {
9030 point(
9031 x_after_longest,
9032 text_bounds.origin.y
9033 + Pixels::from(
9034 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9035 - scroll_pixel_position.y,
9036 ),
9037 )
9038 } else {
9039 let cursor_row = newest_selection_head.map(|head| head.row());
9040 let above_edit = edit_start
9041 .row()
9042 .0
9043 .checked_sub(line_count as u32)
9044 .map(DisplayRow);
9045 let below_edit = Some(edit_end.row() + 1);
9046 let above_cursor =
9047 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9048 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9049
9050 // Place the edit popover adjacent to the edit if there is a location
9051 // available that is onscreen and does not obscure the cursor. Otherwise,
9052 // place it adjacent to the cursor.
9053 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9054 .into_iter()
9055 .flatten()
9056 .find(|&start_row| {
9057 let end_row = start_row + line_count as u32;
9058 visible_row_range.contains(&start_row)
9059 && visible_row_range.contains(&end_row)
9060 && cursor_row
9061 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9062 })?;
9063
9064 content_origin
9065 + point(
9066 Pixels::from(-scroll_pixel_position.x),
9067 Pixels::from(
9068 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9069 ),
9070 )
9071 };
9072
9073 origin.x -= BORDER_WIDTH;
9074
9075 window.defer_draw(element, origin, 1);
9076
9077 // Do not return an element, since it will already be drawn due to defer_draw.
9078 None
9079 }
9080
9081 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9082 px(30.)
9083 }
9084
9085 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9086 if self.read_only(cx) {
9087 cx.theme().players().read_only()
9088 } else {
9089 self.style.as_ref().unwrap().local_player
9090 }
9091 }
9092
9093 fn render_edit_prediction_accept_keybind(
9094 &self,
9095 window: &mut Window,
9096 cx: &mut App,
9097 ) -> Option<AnyElement> {
9098 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9099 let accept_keystroke = accept_binding.keystroke()?;
9100
9101 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9102
9103 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9104 Color::Accent
9105 } else {
9106 Color::Muted
9107 };
9108
9109 h_flex()
9110 .px_0p5()
9111 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9112 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9113 .text_size(TextSize::XSmall.rems(cx))
9114 .child(h_flex().children(ui::render_modifiers(
9115 accept_keystroke.modifiers(),
9116 PlatformStyle::platform(),
9117 Some(modifiers_color),
9118 Some(IconSize::XSmall.rems().into()),
9119 true,
9120 )))
9121 .when(is_platform_style_mac, |parent| {
9122 parent.child(accept_keystroke.key().to_string())
9123 })
9124 .when(!is_platform_style_mac, |parent| {
9125 parent.child(
9126 Key::new(
9127 util::capitalize(accept_keystroke.key()),
9128 Some(Color::Default),
9129 )
9130 .size(Some(IconSize::XSmall.rems().into())),
9131 )
9132 })
9133 .into_any()
9134 .into()
9135 }
9136
9137 fn render_edit_prediction_line_popover(
9138 &self,
9139 label: impl Into<SharedString>,
9140 icon: Option<IconName>,
9141 window: &mut Window,
9142 cx: &mut App,
9143 ) -> Stateful<Div> {
9144 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9145
9146 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9147 let has_keybind = keybind.is_some();
9148
9149 h_flex()
9150 .id("ep-line-popover")
9151 .py_0p5()
9152 .pl_1()
9153 .pr(padding_right)
9154 .gap_1()
9155 .rounded_md()
9156 .border_1()
9157 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9158 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9159 .shadow_xs()
9160 .when(!has_keybind, |el| {
9161 let status_colors = cx.theme().status();
9162
9163 el.bg(status_colors.error_background)
9164 .border_color(status_colors.error.opacity(0.6))
9165 .pl_2()
9166 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9167 .cursor_default()
9168 .hoverable_tooltip(move |_window, cx| {
9169 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9170 })
9171 })
9172 .children(keybind)
9173 .child(
9174 Label::new(label)
9175 .size(LabelSize::Small)
9176 .when(!has_keybind, |el| {
9177 el.color(cx.theme().status().error.into()).strikethrough()
9178 }),
9179 )
9180 .when(!has_keybind, |el| {
9181 el.child(
9182 h_flex().ml_1().child(
9183 Icon::new(IconName::Info)
9184 .size(IconSize::Small)
9185 .color(cx.theme().status().error.into()),
9186 ),
9187 )
9188 })
9189 .when_some(icon, |element, icon| {
9190 element.child(
9191 div()
9192 .mt(px(1.5))
9193 .child(Icon::new(icon).size(IconSize::Small)),
9194 )
9195 })
9196 }
9197
9198 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9199 let accent_color = cx.theme().colors().text_accent;
9200 let editor_bg_color = cx.theme().colors().editor_background;
9201 editor_bg_color.blend(accent_color.opacity(0.1))
9202 }
9203
9204 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9205 let accent_color = cx.theme().colors().text_accent;
9206 let editor_bg_color = cx.theme().colors().editor_background;
9207 editor_bg_color.blend(accent_color.opacity(0.6))
9208 }
9209 fn get_prediction_provider_icon_name(
9210 provider: &Option<RegisteredEditPredictionProvider>,
9211 ) -> IconName {
9212 match provider {
9213 Some(provider) => match provider.provider.name() {
9214 "copilot" => IconName::Copilot,
9215 "supermaven" => IconName::Supermaven,
9216 _ => IconName::ZedPredict,
9217 },
9218 None => IconName::ZedPredict,
9219 }
9220 }
9221
9222 fn render_edit_prediction_cursor_popover(
9223 &self,
9224 min_width: Pixels,
9225 max_width: Pixels,
9226 cursor_point: Point,
9227 style: &EditorStyle,
9228 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9229 _window: &Window,
9230 cx: &mut Context<Editor>,
9231 ) -> Option<AnyElement> {
9232 let provider = self.edit_prediction_provider.as_ref()?;
9233 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9234
9235 let is_refreshing = provider.provider.is_refreshing(cx);
9236
9237 fn pending_completion_container(icon: IconName) -> Div {
9238 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9239 }
9240
9241 let completion = match &self.active_edit_prediction {
9242 Some(prediction) => {
9243 if !self.has_visible_completions_menu() {
9244 const RADIUS: Pixels = px(6.);
9245 const BORDER_WIDTH: Pixels = px(1.);
9246
9247 return Some(
9248 h_flex()
9249 .elevation_2(cx)
9250 .border(BORDER_WIDTH)
9251 .border_color(cx.theme().colors().border)
9252 .when(accept_keystroke.is_none(), |el| {
9253 el.border_color(cx.theme().status().error)
9254 })
9255 .rounded(RADIUS)
9256 .rounded_tl(px(0.))
9257 .overflow_hidden()
9258 .child(div().px_1p5().child(match &prediction.completion {
9259 EditPrediction::MoveWithin { target, snapshot } => {
9260 use text::ToPoint as _;
9261 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9262 {
9263 Icon::new(IconName::ZedPredictDown)
9264 } else {
9265 Icon::new(IconName::ZedPredictUp)
9266 }
9267 }
9268 EditPrediction::MoveOutside { .. } => {
9269 // TODO [zeta2] custom icon for external jump?
9270 Icon::new(provider_icon)
9271 }
9272 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9273 }))
9274 .child(
9275 h_flex()
9276 .gap_1()
9277 .py_1()
9278 .px_2()
9279 .rounded_r(RADIUS - BORDER_WIDTH)
9280 .border_l_1()
9281 .border_color(cx.theme().colors().border)
9282 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9283 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9284 el.child(
9285 Label::new("Hold")
9286 .size(LabelSize::Small)
9287 .when(accept_keystroke.is_none(), |el| {
9288 el.strikethrough()
9289 })
9290 .line_height_style(LineHeightStyle::UiLabel),
9291 )
9292 })
9293 .id("edit_prediction_cursor_popover_keybind")
9294 .when(accept_keystroke.is_none(), |el| {
9295 let status_colors = cx.theme().status();
9296
9297 el.bg(status_colors.error_background)
9298 .border_color(status_colors.error.opacity(0.6))
9299 .child(Icon::new(IconName::Info).color(Color::Error))
9300 .cursor_default()
9301 .hoverable_tooltip(move |_window, cx| {
9302 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9303 .into()
9304 })
9305 })
9306 .when_some(
9307 accept_keystroke.as_ref(),
9308 |el, accept_keystroke| {
9309 el.child(h_flex().children(ui::render_modifiers(
9310 accept_keystroke.modifiers(),
9311 PlatformStyle::platform(),
9312 Some(Color::Default),
9313 Some(IconSize::XSmall.rems().into()),
9314 false,
9315 )))
9316 },
9317 ),
9318 )
9319 .into_any(),
9320 );
9321 }
9322
9323 self.render_edit_prediction_cursor_popover_preview(
9324 prediction,
9325 cursor_point,
9326 style,
9327 cx,
9328 )?
9329 }
9330
9331 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9332 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9333 stale_completion,
9334 cursor_point,
9335 style,
9336 cx,
9337 )?,
9338
9339 None => pending_completion_container(provider_icon)
9340 .child(Label::new("...").size(LabelSize::Small)),
9341 },
9342
9343 None => pending_completion_container(provider_icon)
9344 .child(Label::new("...").size(LabelSize::Small)),
9345 };
9346
9347 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9348 completion
9349 .with_animation(
9350 "loading-completion",
9351 Animation::new(Duration::from_secs(2))
9352 .repeat()
9353 .with_easing(pulsating_between(0.4, 0.8)),
9354 |label, delta| label.opacity(delta),
9355 )
9356 .into_any_element()
9357 } else {
9358 completion.into_any_element()
9359 };
9360
9361 let has_completion = self.active_edit_prediction.is_some();
9362
9363 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9364 Some(
9365 h_flex()
9366 .min_w(min_width)
9367 .max_w(max_width)
9368 .flex_1()
9369 .elevation_2(cx)
9370 .border_color(cx.theme().colors().border)
9371 .child(
9372 div()
9373 .flex_1()
9374 .py_1()
9375 .px_2()
9376 .overflow_hidden()
9377 .child(completion),
9378 )
9379 .when_some(accept_keystroke, |el, accept_keystroke| {
9380 if !accept_keystroke.modifiers().modified() {
9381 return el;
9382 }
9383
9384 el.child(
9385 h_flex()
9386 .h_full()
9387 .border_l_1()
9388 .rounded_r_lg()
9389 .border_color(cx.theme().colors().border)
9390 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9391 .gap_1()
9392 .py_1()
9393 .px_2()
9394 .child(
9395 h_flex()
9396 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9397 .when(is_platform_style_mac, |parent| parent.gap_1())
9398 .child(h_flex().children(ui::render_modifiers(
9399 accept_keystroke.modifiers(),
9400 PlatformStyle::platform(),
9401 Some(if !has_completion {
9402 Color::Muted
9403 } else {
9404 Color::Default
9405 }),
9406 None,
9407 false,
9408 ))),
9409 )
9410 .child(Label::new("Preview").into_any_element())
9411 .opacity(if has_completion { 1.0 } else { 0.4 }),
9412 )
9413 })
9414 .into_any(),
9415 )
9416 }
9417
9418 fn render_edit_prediction_cursor_popover_preview(
9419 &self,
9420 completion: &EditPredictionState,
9421 cursor_point: Point,
9422 style: &EditorStyle,
9423 cx: &mut Context<Editor>,
9424 ) -> Option<Div> {
9425 use text::ToPoint as _;
9426
9427 fn render_relative_row_jump(
9428 prefix: impl Into<String>,
9429 current_row: u32,
9430 target_row: u32,
9431 ) -> Div {
9432 let (row_diff, arrow) = if target_row < current_row {
9433 (current_row - target_row, IconName::ArrowUp)
9434 } else {
9435 (target_row - current_row, IconName::ArrowDown)
9436 };
9437
9438 h_flex()
9439 .child(
9440 Label::new(format!("{}{}", prefix.into(), row_diff))
9441 .color(Color::Muted)
9442 .size(LabelSize::Small),
9443 )
9444 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9445 }
9446
9447 let supports_jump = self
9448 .edit_prediction_provider
9449 .as_ref()
9450 .map(|provider| provider.provider.supports_jump_to_edit())
9451 .unwrap_or(true);
9452
9453 match &completion.completion {
9454 EditPrediction::MoveWithin {
9455 target, snapshot, ..
9456 } => {
9457 if !supports_jump {
9458 return None;
9459 }
9460
9461 Some(
9462 h_flex()
9463 .px_2()
9464 .gap_2()
9465 .flex_1()
9466 .child(
9467 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9468 Icon::new(IconName::ZedPredictDown)
9469 } else {
9470 Icon::new(IconName::ZedPredictUp)
9471 },
9472 )
9473 .child(Label::new("Jump to Edit")),
9474 )
9475 }
9476 EditPrediction::MoveOutside { snapshot, .. } => {
9477 let file_name = snapshot
9478 .file()
9479 .map(|file| file.file_name(cx))
9480 .unwrap_or("untitled");
9481 Some(
9482 h_flex()
9483 .px_2()
9484 .gap_2()
9485 .flex_1()
9486 .child(Icon::new(IconName::ZedPredict))
9487 .child(Label::new(format!("Jump to {file_name}"))),
9488 )
9489 }
9490 EditPrediction::Edit {
9491 edits,
9492 edit_preview,
9493 snapshot,
9494 display_mode: _,
9495 } => {
9496 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9497
9498 let (highlighted_edits, has_more_lines) =
9499 if let Some(edit_preview) = edit_preview.as_ref() {
9500 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9501 .first_line_preview()
9502 } else {
9503 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9504 };
9505
9506 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9507 .with_default_highlights(&style.text, highlighted_edits.highlights);
9508
9509 let preview = h_flex()
9510 .gap_1()
9511 .min_w_16()
9512 .child(styled_text)
9513 .when(has_more_lines, |parent| parent.child("…"));
9514
9515 let left = if supports_jump && first_edit_row != cursor_point.row {
9516 render_relative_row_jump("", cursor_point.row, first_edit_row)
9517 .into_any_element()
9518 } else {
9519 let icon_name =
9520 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9521 Icon::new(icon_name).into_any_element()
9522 };
9523
9524 Some(
9525 h_flex()
9526 .h_full()
9527 .flex_1()
9528 .gap_2()
9529 .pr_1()
9530 .overflow_x_hidden()
9531 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9532 .child(left)
9533 .child(preview),
9534 )
9535 }
9536 }
9537 }
9538
9539 pub fn render_context_menu(
9540 &self,
9541 style: &EditorStyle,
9542 max_height_in_lines: u32,
9543 window: &mut Window,
9544 cx: &mut Context<Editor>,
9545 ) -> Option<AnyElement> {
9546 let menu = self.context_menu.borrow();
9547 let menu = menu.as_ref()?;
9548 if !menu.visible() {
9549 return None;
9550 };
9551 Some(menu.render(style, max_height_in_lines, window, cx))
9552 }
9553
9554 fn render_context_menu_aside(
9555 &mut self,
9556 max_size: Size<Pixels>,
9557 window: &mut Window,
9558 cx: &mut Context<Editor>,
9559 ) -> Option<AnyElement> {
9560 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9561 if menu.visible() {
9562 menu.render_aside(max_size, window, cx)
9563 } else {
9564 None
9565 }
9566 })
9567 }
9568
9569 fn hide_context_menu(
9570 &mut self,
9571 window: &mut Window,
9572 cx: &mut Context<Self>,
9573 ) -> Option<CodeContextMenu> {
9574 cx.notify();
9575 self.completion_tasks.clear();
9576 let context_menu = self.context_menu.borrow_mut().take();
9577 self.stale_edit_prediction_in_menu.take();
9578 self.update_visible_edit_prediction(window, cx);
9579 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9580 && let Some(completion_provider) = &self.completion_provider
9581 {
9582 completion_provider.selection_changed(None, window, cx);
9583 }
9584 context_menu
9585 }
9586
9587 fn show_snippet_choices(
9588 &mut self,
9589 choices: &Vec<String>,
9590 selection: Range<Anchor>,
9591 cx: &mut Context<Self>,
9592 ) {
9593 let Some((_, buffer, _)) = self
9594 .buffer()
9595 .read(cx)
9596 .excerpt_containing(selection.start, cx)
9597 else {
9598 return;
9599 };
9600 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9601 else {
9602 return;
9603 };
9604 if buffer != end_buffer {
9605 log::error!("expected anchor range to have matching buffer IDs");
9606 return;
9607 }
9608
9609 let id = post_inc(&mut self.next_completion_id);
9610 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9611 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9612 CompletionsMenu::new_snippet_choices(
9613 id,
9614 true,
9615 choices,
9616 selection,
9617 buffer,
9618 snippet_sort_order,
9619 ),
9620 ));
9621 }
9622
9623 pub fn insert_snippet(
9624 &mut self,
9625 insertion_ranges: &[Range<usize>],
9626 snippet: Snippet,
9627 window: &mut Window,
9628 cx: &mut Context<Self>,
9629 ) -> Result<()> {
9630 struct Tabstop<T> {
9631 is_end_tabstop: bool,
9632 ranges: Vec<Range<T>>,
9633 choices: Option<Vec<String>>,
9634 }
9635
9636 let tabstops = self.buffer.update(cx, |buffer, cx| {
9637 let snippet_text: Arc<str> = snippet.text.clone().into();
9638 let edits = insertion_ranges
9639 .iter()
9640 .cloned()
9641 .map(|range| (range, snippet_text.clone()));
9642 let autoindent_mode = AutoindentMode::Block {
9643 original_indent_columns: Vec::new(),
9644 };
9645 buffer.edit(edits, Some(autoindent_mode), cx);
9646
9647 let snapshot = &*buffer.read(cx);
9648 let snippet = &snippet;
9649 snippet
9650 .tabstops
9651 .iter()
9652 .map(|tabstop| {
9653 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9654 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9655 });
9656 let mut tabstop_ranges = tabstop
9657 .ranges
9658 .iter()
9659 .flat_map(|tabstop_range| {
9660 let mut delta = 0_isize;
9661 insertion_ranges.iter().map(move |insertion_range| {
9662 let insertion_start = insertion_range.start as isize + delta;
9663 delta +=
9664 snippet.text.len() as isize - insertion_range.len() as isize;
9665
9666 let start = ((insertion_start + tabstop_range.start) as usize)
9667 .min(snapshot.len());
9668 let end = ((insertion_start + tabstop_range.end) as usize)
9669 .min(snapshot.len());
9670 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9671 })
9672 })
9673 .collect::<Vec<_>>();
9674 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9675
9676 Tabstop {
9677 is_end_tabstop,
9678 ranges: tabstop_ranges,
9679 choices: tabstop.choices.clone(),
9680 }
9681 })
9682 .collect::<Vec<_>>()
9683 });
9684 if let Some(tabstop) = tabstops.first() {
9685 self.change_selections(Default::default(), window, cx, |s| {
9686 // Reverse order so that the first range is the newest created selection.
9687 // Completions will use it and autoscroll will prioritize it.
9688 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9689 });
9690
9691 if let Some(choices) = &tabstop.choices
9692 && let Some(selection) = tabstop.ranges.first()
9693 {
9694 self.show_snippet_choices(choices, selection.clone(), cx)
9695 }
9696
9697 // If we're already at the last tabstop and it's at the end of the snippet,
9698 // we're done, we don't need to keep the state around.
9699 if !tabstop.is_end_tabstop {
9700 let choices = tabstops
9701 .iter()
9702 .map(|tabstop| tabstop.choices.clone())
9703 .collect();
9704
9705 let ranges = tabstops
9706 .into_iter()
9707 .map(|tabstop| tabstop.ranges)
9708 .collect::<Vec<_>>();
9709
9710 self.snippet_stack.push(SnippetState {
9711 active_index: 0,
9712 ranges,
9713 choices,
9714 });
9715 }
9716
9717 // Check whether the just-entered snippet ends with an auto-closable bracket.
9718 if self.autoclose_regions.is_empty() {
9719 let snapshot = self.buffer.read(cx).snapshot(cx);
9720 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9721 let selection_head = selection.head();
9722 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9723 continue;
9724 };
9725
9726 let mut bracket_pair = None;
9727 let max_lookup_length = scope
9728 .brackets()
9729 .map(|(pair, _)| {
9730 pair.start
9731 .as_str()
9732 .chars()
9733 .count()
9734 .max(pair.end.as_str().chars().count())
9735 })
9736 .max();
9737 if let Some(max_lookup_length) = max_lookup_length {
9738 let next_text = snapshot
9739 .chars_at(selection_head)
9740 .take(max_lookup_length)
9741 .collect::<String>();
9742 let prev_text = snapshot
9743 .reversed_chars_at(selection_head)
9744 .take(max_lookup_length)
9745 .collect::<String>();
9746
9747 for (pair, enabled) in scope.brackets() {
9748 if enabled
9749 && pair.close
9750 && prev_text.starts_with(pair.start.as_str())
9751 && next_text.starts_with(pair.end.as_str())
9752 {
9753 bracket_pair = Some(pair.clone());
9754 break;
9755 }
9756 }
9757 }
9758
9759 if let Some(pair) = bracket_pair {
9760 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9761 let autoclose_enabled =
9762 self.use_autoclose && snapshot_settings.use_autoclose;
9763 if autoclose_enabled {
9764 let start = snapshot.anchor_after(selection_head);
9765 let end = snapshot.anchor_after(selection_head);
9766 self.autoclose_regions.push(AutocloseRegion {
9767 selection_id: selection.id,
9768 range: start..end,
9769 pair,
9770 });
9771 }
9772 }
9773 }
9774 }
9775 }
9776 Ok(())
9777 }
9778
9779 pub fn move_to_next_snippet_tabstop(
9780 &mut self,
9781 window: &mut Window,
9782 cx: &mut Context<Self>,
9783 ) -> bool {
9784 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9785 }
9786
9787 pub fn move_to_prev_snippet_tabstop(
9788 &mut self,
9789 window: &mut Window,
9790 cx: &mut Context<Self>,
9791 ) -> bool {
9792 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9793 }
9794
9795 pub fn move_to_snippet_tabstop(
9796 &mut self,
9797 bias: Bias,
9798 window: &mut Window,
9799 cx: &mut Context<Self>,
9800 ) -> bool {
9801 if let Some(mut snippet) = self.snippet_stack.pop() {
9802 match bias {
9803 Bias::Left => {
9804 if snippet.active_index > 0 {
9805 snippet.active_index -= 1;
9806 } else {
9807 self.snippet_stack.push(snippet);
9808 return false;
9809 }
9810 }
9811 Bias::Right => {
9812 if snippet.active_index + 1 < snippet.ranges.len() {
9813 snippet.active_index += 1;
9814 } else {
9815 self.snippet_stack.push(snippet);
9816 return false;
9817 }
9818 }
9819 }
9820 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9821 self.change_selections(Default::default(), window, cx, |s| {
9822 // Reverse order so that the first range is the newest created selection.
9823 // Completions will use it and autoscroll will prioritize it.
9824 s.select_ranges(current_ranges.iter().rev().cloned())
9825 });
9826
9827 if let Some(choices) = &snippet.choices[snippet.active_index]
9828 && let Some(selection) = current_ranges.first()
9829 {
9830 self.show_snippet_choices(choices, selection.clone(), cx);
9831 }
9832
9833 // If snippet state is not at the last tabstop, push it back on the stack
9834 if snippet.active_index + 1 < snippet.ranges.len() {
9835 self.snippet_stack.push(snippet);
9836 }
9837 return true;
9838 }
9839 }
9840
9841 false
9842 }
9843
9844 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9845 self.transact(window, cx, |this, window, cx| {
9846 this.select_all(&SelectAll, window, cx);
9847 this.insert("", window, cx);
9848 });
9849 }
9850
9851 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9852 if self.read_only(cx) {
9853 return;
9854 }
9855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9856 self.transact(window, cx, |this, window, cx| {
9857 this.select_autoclose_pair(window, cx);
9858
9859 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9860
9861 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9862 if !this.linked_edit_ranges.is_empty() {
9863 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9864 let snapshot = this.buffer.read(cx).snapshot(cx);
9865
9866 for selection in selections.iter() {
9867 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9868 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9869 if selection_start.buffer_id != selection_end.buffer_id {
9870 continue;
9871 }
9872 if let Some(ranges) =
9873 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9874 {
9875 for (buffer, entries) in ranges {
9876 linked_ranges.entry(buffer).or_default().extend(entries);
9877 }
9878 }
9879 }
9880 }
9881
9882 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9883 for selection in &mut selections {
9884 if selection.is_empty() {
9885 let old_head = selection.head();
9886 let mut new_head =
9887 movement::left(&display_map, old_head.to_display_point(&display_map))
9888 .to_point(&display_map);
9889 if let Some((buffer, line_buffer_range)) = display_map
9890 .buffer_snapshot()
9891 .buffer_line_for_row(MultiBufferRow(old_head.row))
9892 {
9893 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9894 let indent_len = match indent_size.kind {
9895 IndentKind::Space => {
9896 buffer.settings_at(line_buffer_range.start, cx).tab_size
9897 }
9898 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9899 };
9900 if old_head.column <= indent_size.len && old_head.column > 0 {
9901 let indent_len = indent_len.get();
9902 new_head = cmp::min(
9903 new_head,
9904 MultiBufferPoint::new(
9905 old_head.row,
9906 ((old_head.column - 1) / indent_len) * indent_len,
9907 ),
9908 );
9909 }
9910 }
9911
9912 selection.set_head(new_head, SelectionGoal::None);
9913 }
9914 }
9915
9916 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9917 this.insert("", window, cx);
9918 let empty_str: Arc<str> = Arc::from("");
9919 for (buffer, edits) in linked_ranges {
9920 let snapshot = buffer.read(cx).snapshot();
9921 use text::ToPoint as TP;
9922
9923 let edits = edits
9924 .into_iter()
9925 .map(|range| {
9926 let end_point = TP::to_point(&range.end, &snapshot);
9927 let mut start_point = TP::to_point(&range.start, &snapshot);
9928
9929 if end_point == start_point {
9930 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9931 .saturating_sub(1);
9932 start_point =
9933 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9934 };
9935
9936 (start_point..end_point, empty_str.clone())
9937 })
9938 .sorted_by_key(|(range, _)| range.start)
9939 .collect::<Vec<_>>();
9940 buffer.update(cx, |this, cx| {
9941 this.edit(edits, None, cx);
9942 })
9943 }
9944 this.refresh_edit_prediction(true, false, window, cx);
9945 refresh_linked_ranges(this, window, cx);
9946 });
9947 }
9948
9949 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9950 if self.read_only(cx) {
9951 return;
9952 }
9953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9954 self.transact(window, cx, |this, window, cx| {
9955 this.change_selections(Default::default(), window, cx, |s| {
9956 s.move_with(|map, selection| {
9957 if selection.is_empty() {
9958 let cursor = movement::right(map, selection.head());
9959 selection.end = cursor;
9960 selection.reversed = true;
9961 selection.goal = SelectionGoal::None;
9962 }
9963 })
9964 });
9965 this.insert("", window, cx);
9966 this.refresh_edit_prediction(true, false, window, cx);
9967 });
9968 }
9969
9970 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9971 if self.mode.is_single_line() {
9972 cx.propagate();
9973 return;
9974 }
9975
9976 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9977 if self.move_to_prev_snippet_tabstop(window, cx) {
9978 return;
9979 }
9980 self.outdent(&Outdent, window, cx);
9981 }
9982
9983 pub fn next_snippet_tabstop(
9984 &mut self,
9985 _: &NextSnippetTabstop,
9986 window: &mut Window,
9987 cx: &mut Context<Self>,
9988 ) {
9989 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
9990 return;
9991 }
9992
9993 if self.move_to_next_snippet_tabstop(window, cx) {
9994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9995 return;
9996 }
9997 }
9998
9999 pub fn previous_snippet_tabstop(
10000 &mut self,
10001 _: &PreviousSnippetTabstop,
10002 window: &mut Window,
10003 cx: &mut Context<Self>,
10004 ) {
10005 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10006 return;
10007 }
10008
10009 if self.move_to_prev_snippet_tabstop(window, cx) {
10010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10011 return;
10012 }
10013 }
10014
10015 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10016 if self.mode.is_single_line() {
10017 cx.propagate();
10018 return;
10019 }
10020
10021 if self.move_to_next_snippet_tabstop(window, cx) {
10022 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10023 return;
10024 }
10025 if self.read_only(cx) {
10026 return;
10027 }
10028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10029 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10030 let buffer = self.buffer.read(cx);
10031 let snapshot = buffer.snapshot(cx);
10032 let rows_iter = selections.iter().map(|s| s.head().row);
10033 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10034
10035 let has_some_cursor_in_whitespace = selections
10036 .iter()
10037 .filter(|selection| selection.is_empty())
10038 .any(|selection| {
10039 let cursor = selection.head();
10040 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10041 cursor.column < current_indent.len
10042 });
10043
10044 let mut edits = Vec::new();
10045 let mut prev_edited_row = 0;
10046 let mut row_delta = 0;
10047 for selection in &mut selections {
10048 if selection.start.row != prev_edited_row {
10049 row_delta = 0;
10050 }
10051 prev_edited_row = selection.end.row;
10052
10053 // If the selection is non-empty, then increase the indentation of the selected lines.
10054 if !selection.is_empty() {
10055 row_delta =
10056 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10057 continue;
10058 }
10059
10060 let cursor = selection.head();
10061 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10062 if let Some(suggested_indent) =
10063 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10064 {
10065 // Don't do anything if already at suggested indent
10066 // and there is any other cursor which is not
10067 if has_some_cursor_in_whitespace
10068 && cursor.column == current_indent.len
10069 && current_indent.len == suggested_indent.len
10070 {
10071 continue;
10072 }
10073
10074 // Adjust line and move cursor to suggested indent
10075 // if cursor is not at suggested indent
10076 if cursor.column < suggested_indent.len
10077 && cursor.column <= current_indent.len
10078 && current_indent.len <= suggested_indent.len
10079 {
10080 selection.start = Point::new(cursor.row, suggested_indent.len);
10081 selection.end = selection.start;
10082 if row_delta == 0 {
10083 edits.extend(Buffer::edit_for_indent_size_adjustment(
10084 cursor.row,
10085 current_indent,
10086 suggested_indent,
10087 ));
10088 row_delta = suggested_indent.len - current_indent.len;
10089 }
10090 continue;
10091 }
10092
10093 // If current indent is more than suggested indent
10094 // only move cursor to current indent and skip indent
10095 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10096 selection.start = Point::new(cursor.row, current_indent.len);
10097 selection.end = selection.start;
10098 continue;
10099 }
10100 }
10101
10102 // Otherwise, insert a hard or soft tab.
10103 let settings = buffer.language_settings_at(cursor, cx);
10104 let tab_size = if settings.hard_tabs {
10105 IndentSize::tab()
10106 } else {
10107 let tab_size = settings.tab_size.get();
10108 let indent_remainder = snapshot
10109 .text_for_range(Point::new(cursor.row, 0)..cursor)
10110 .flat_map(str::chars)
10111 .fold(row_delta % tab_size, |counter: u32, c| {
10112 if c == '\t' {
10113 0
10114 } else {
10115 (counter + 1) % tab_size
10116 }
10117 });
10118
10119 let chars_to_next_tab_stop = tab_size - indent_remainder;
10120 IndentSize::spaces(chars_to_next_tab_stop)
10121 };
10122 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10123 selection.end = selection.start;
10124 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10125 row_delta += tab_size.len;
10126 }
10127
10128 self.transact(window, cx, |this, window, cx| {
10129 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10130 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10131 this.refresh_edit_prediction(true, false, window, cx);
10132 });
10133 }
10134
10135 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10136 if self.read_only(cx) {
10137 return;
10138 }
10139 if self.mode.is_single_line() {
10140 cx.propagate();
10141 return;
10142 }
10143
10144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10145 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10146 let mut prev_edited_row = 0;
10147 let mut row_delta = 0;
10148 let mut edits = Vec::new();
10149 let buffer = self.buffer.read(cx);
10150 let snapshot = buffer.snapshot(cx);
10151 for selection in &mut selections {
10152 if selection.start.row != prev_edited_row {
10153 row_delta = 0;
10154 }
10155 prev_edited_row = selection.end.row;
10156
10157 row_delta =
10158 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10159 }
10160
10161 self.transact(window, cx, |this, window, cx| {
10162 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10163 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10164 });
10165 }
10166
10167 fn indent_selection(
10168 buffer: &MultiBuffer,
10169 snapshot: &MultiBufferSnapshot,
10170 selection: &mut Selection<Point>,
10171 edits: &mut Vec<(Range<Point>, String)>,
10172 delta_for_start_row: u32,
10173 cx: &App,
10174 ) -> u32 {
10175 let settings = buffer.language_settings_at(selection.start, cx);
10176 let tab_size = settings.tab_size.get();
10177 let indent_kind = if settings.hard_tabs {
10178 IndentKind::Tab
10179 } else {
10180 IndentKind::Space
10181 };
10182 let mut start_row = selection.start.row;
10183 let mut end_row = selection.end.row + 1;
10184
10185 // If a selection ends at the beginning of a line, don't indent
10186 // that last line.
10187 if selection.end.column == 0 && selection.end.row > selection.start.row {
10188 end_row -= 1;
10189 }
10190
10191 // Avoid re-indenting a row that has already been indented by a
10192 // previous selection, but still update this selection's column
10193 // to reflect that indentation.
10194 if delta_for_start_row > 0 {
10195 start_row += 1;
10196 selection.start.column += delta_for_start_row;
10197 if selection.end.row == selection.start.row {
10198 selection.end.column += delta_for_start_row;
10199 }
10200 }
10201
10202 let mut delta_for_end_row = 0;
10203 let has_multiple_rows = start_row + 1 != end_row;
10204 for row in start_row..end_row {
10205 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10206 let indent_delta = match (current_indent.kind, indent_kind) {
10207 (IndentKind::Space, IndentKind::Space) => {
10208 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10209 IndentSize::spaces(columns_to_next_tab_stop)
10210 }
10211 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10212 (_, IndentKind::Tab) => IndentSize::tab(),
10213 };
10214
10215 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10216 0
10217 } else {
10218 selection.start.column
10219 };
10220 let row_start = Point::new(row, start);
10221 edits.push((
10222 row_start..row_start,
10223 indent_delta.chars().collect::<String>(),
10224 ));
10225
10226 // Update this selection's endpoints to reflect the indentation.
10227 if row == selection.start.row {
10228 selection.start.column += indent_delta.len;
10229 }
10230 if row == selection.end.row {
10231 selection.end.column += indent_delta.len;
10232 delta_for_end_row = indent_delta.len;
10233 }
10234 }
10235
10236 if selection.start.row == selection.end.row {
10237 delta_for_start_row + delta_for_end_row
10238 } else {
10239 delta_for_end_row
10240 }
10241 }
10242
10243 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10244 if self.read_only(cx) {
10245 return;
10246 }
10247 if self.mode.is_single_line() {
10248 cx.propagate();
10249 return;
10250 }
10251
10252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10254 let selections = self.selections.all::<Point>(&display_map);
10255 let mut deletion_ranges = Vec::new();
10256 let mut last_outdent = None;
10257 {
10258 let buffer = self.buffer.read(cx);
10259 let snapshot = buffer.snapshot(cx);
10260 for selection in &selections {
10261 let settings = buffer.language_settings_at(selection.start, cx);
10262 let tab_size = settings.tab_size.get();
10263 let mut rows = selection.spanned_rows(false, &display_map);
10264
10265 // Avoid re-outdenting a row that has already been outdented by a
10266 // previous selection.
10267 if let Some(last_row) = last_outdent
10268 && last_row == rows.start
10269 {
10270 rows.start = rows.start.next_row();
10271 }
10272 let has_multiple_rows = rows.len() > 1;
10273 for row in rows.iter_rows() {
10274 let indent_size = snapshot.indent_size_for_line(row);
10275 if indent_size.len > 0 {
10276 let deletion_len = match indent_size.kind {
10277 IndentKind::Space => {
10278 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10279 if columns_to_prev_tab_stop == 0 {
10280 tab_size
10281 } else {
10282 columns_to_prev_tab_stop
10283 }
10284 }
10285 IndentKind::Tab => 1,
10286 };
10287 let start = if has_multiple_rows
10288 || deletion_len > selection.start.column
10289 || indent_size.len < selection.start.column
10290 {
10291 0
10292 } else {
10293 selection.start.column - deletion_len
10294 };
10295 deletion_ranges.push(
10296 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10297 );
10298 last_outdent = Some(row);
10299 }
10300 }
10301 }
10302 }
10303
10304 self.transact(window, cx, |this, window, cx| {
10305 this.buffer.update(cx, |buffer, cx| {
10306 let empty_str: Arc<str> = Arc::default();
10307 buffer.edit(
10308 deletion_ranges
10309 .into_iter()
10310 .map(|range| (range, empty_str.clone())),
10311 None,
10312 cx,
10313 );
10314 });
10315 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10316 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10317 });
10318 }
10319
10320 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10321 if self.read_only(cx) {
10322 return;
10323 }
10324 if self.mode.is_single_line() {
10325 cx.propagate();
10326 return;
10327 }
10328
10329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10330 let selections = self
10331 .selections
10332 .all::<usize>(&self.display_snapshot(cx))
10333 .into_iter()
10334 .map(|s| s.range());
10335
10336 self.transact(window, cx, |this, window, cx| {
10337 this.buffer.update(cx, |buffer, cx| {
10338 buffer.autoindent_ranges(selections, cx);
10339 });
10340 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10341 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10342 });
10343 }
10344
10345 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10348 let selections = self.selections.all::<Point>(&display_map);
10349
10350 let mut new_cursors = Vec::new();
10351 let mut edit_ranges = Vec::new();
10352 let mut selections = selections.iter().peekable();
10353 while let Some(selection) = selections.next() {
10354 let mut rows = selection.spanned_rows(false, &display_map);
10355
10356 // Accumulate contiguous regions of rows that we want to delete.
10357 while let Some(next_selection) = selections.peek() {
10358 let next_rows = next_selection.spanned_rows(false, &display_map);
10359 if next_rows.start <= rows.end {
10360 rows.end = next_rows.end;
10361 selections.next().unwrap();
10362 } else {
10363 break;
10364 }
10365 }
10366
10367 let buffer = display_map.buffer_snapshot();
10368 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10369 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10370 // If there's a line after the range, delete the \n from the end of the row range
10371 (
10372 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10373 rows.end,
10374 )
10375 } else {
10376 // If there isn't a line after the range, delete the \n from the line before the
10377 // start of the row range
10378 edit_start = edit_start.saturating_sub(1);
10379 (buffer.len(), rows.start.previous_row())
10380 };
10381
10382 let text_layout_details = self.text_layout_details(window);
10383 let x = display_map.x_for_display_point(
10384 selection.head().to_display_point(&display_map),
10385 &text_layout_details,
10386 );
10387 let row = Point::new(target_row.0, 0)
10388 .to_display_point(&display_map)
10389 .row();
10390 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10391
10392 new_cursors.push((
10393 selection.id,
10394 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10395 SelectionGoal::None,
10396 ));
10397 edit_ranges.push(edit_start..edit_end);
10398 }
10399
10400 self.transact(window, cx, |this, window, cx| {
10401 let buffer = this.buffer.update(cx, |buffer, cx| {
10402 let empty_str: Arc<str> = Arc::default();
10403 buffer.edit(
10404 edit_ranges
10405 .into_iter()
10406 .map(|range| (range, empty_str.clone())),
10407 None,
10408 cx,
10409 );
10410 buffer.snapshot(cx)
10411 });
10412 let new_selections = new_cursors
10413 .into_iter()
10414 .map(|(id, cursor, goal)| {
10415 let cursor = cursor.to_point(&buffer);
10416 Selection {
10417 id,
10418 start: cursor,
10419 end: cursor,
10420 reversed: false,
10421 goal,
10422 }
10423 })
10424 .collect();
10425
10426 this.change_selections(Default::default(), window, cx, |s| {
10427 s.select(new_selections);
10428 });
10429 });
10430 }
10431
10432 pub fn join_lines_impl(
10433 &mut self,
10434 insert_whitespace: bool,
10435 window: &mut Window,
10436 cx: &mut Context<Self>,
10437 ) {
10438 if self.read_only(cx) {
10439 return;
10440 }
10441 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10442 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10443 let start = MultiBufferRow(selection.start.row);
10444 // Treat single line selections as if they include the next line. Otherwise this action
10445 // would do nothing for single line selections individual cursors.
10446 let end = if selection.start.row == selection.end.row {
10447 MultiBufferRow(selection.start.row + 1)
10448 } else {
10449 MultiBufferRow(selection.end.row)
10450 };
10451
10452 if let Some(last_row_range) = row_ranges.last_mut()
10453 && start <= last_row_range.end
10454 {
10455 last_row_range.end = end;
10456 continue;
10457 }
10458 row_ranges.push(start..end);
10459 }
10460
10461 let snapshot = self.buffer.read(cx).snapshot(cx);
10462 let mut cursor_positions = Vec::new();
10463 for row_range in &row_ranges {
10464 let anchor = snapshot.anchor_before(Point::new(
10465 row_range.end.previous_row().0,
10466 snapshot.line_len(row_range.end.previous_row()),
10467 ));
10468 cursor_positions.push(anchor..anchor);
10469 }
10470
10471 self.transact(window, cx, |this, window, cx| {
10472 for row_range in row_ranges.into_iter().rev() {
10473 for row in row_range.iter_rows().rev() {
10474 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10475 let next_line_row = row.next_row();
10476 let indent = snapshot.indent_size_for_line(next_line_row);
10477 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10478
10479 let replace =
10480 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10481 " "
10482 } else {
10483 ""
10484 };
10485
10486 this.buffer.update(cx, |buffer, cx| {
10487 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10488 });
10489 }
10490 }
10491
10492 this.change_selections(Default::default(), window, cx, |s| {
10493 s.select_anchor_ranges(cursor_positions)
10494 });
10495 });
10496 }
10497
10498 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10500 self.join_lines_impl(true, window, cx);
10501 }
10502
10503 pub fn sort_lines_case_sensitive(
10504 &mut self,
10505 _: &SortLinesCaseSensitive,
10506 window: &mut Window,
10507 cx: &mut Context<Self>,
10508 ) {
10509 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10510 }
10511
10512 pub fn sort_lines_by_length(
10513 &mut self,
10514 _: &SortLinesByLength,
10515 window: &mut Window,
10516 cx: &mut Context<Self>,
10517 ) {
10518 self.manipulate_immutable_lines(window, cx, |lines| {
10519 lines.sort_by_key(|&line| line.chars().count())
10520 })
10521 }
10522
10523 pub fn sort_lines_case_insensitive(
10524 &mut self,
10525 _: &SortLinesCaseInsensitive,
10526 window: &mut Window,
10527 cx: &mut Context<Self>,
10528 ) {
10529 self.manipulate_immutable_lines(window, cx, |lines| {
10530 lines.sort_by_key(|line| line.to_lowercase())
10531 })
10532 }
10533
10534 pub fn unique_lines_case_insensitive(
10535 &mut self,
10536 _: &UniqueLinesCaseInsensitive,
10537 window: &mut Window,
10538 cx: &mut Context<Self>,
10539 ) {
10540 self.manipulate_immutable_lines(window, cx, |lines| {
10541 let mut seen = HashSet::default();
10542 lines.retain(|line| seen.insert(line.to_lowercase()));
10543 })
10544 }
10545
10546 pub fn unique_lines_case_sensitive(
10547 &mut self,
10548 _: &UniqueLinesCaseSensitive,
10549 window: &mut Window,
10550 cx: &mut Context<Self>,
10551 ) {
10552 self.manipulate_immutable_lines(window, cx, |lines| {
10553 let mut seen = HashSet::default();
10554 lines.retain(|line| seen.insert(*line));
10555 })
10556 }
10557
10558 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10559 let snapshot = self.buffer.read(cx).snapshot(cx);
10560 for selection in self.selections.disjoint_anchors_arc().iter() {
10561 if snapshot
10562 .language_at(selection.start)
10563 .and_then(|lang| lang.config().wrap_characters.as_ref())
10564 .is_some()
10565 {
10566 return true;
10567 }
10568 }
10569 false
10570 }
10571
10572 fn wrap_selections_in_tag(
10573 &mut self,
10574 _: &WrapSelectionsInTag,
10575 window: &mut Window,
10576 cx: &mut Context<Self>,
10577 ) {
10578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10579
10580 let snapshot = self.buffer.read(cx).snapshot(cx);
10581
10582 let mut edits = Vec::new();
10583 let mut boundaries = Vec::new();
10584
10585 for selection in self
10586 .selections
10587 .all_adjusted(&self.display_snapshot(cx))
10588 .iter()
10589 {
10590 let Some(wrap_config) = snapshot
10591 .language_at(selection.start)
10592 .and_then(|lang| lang.config().wrap_characters.clone())
10593 else {
10594 continue;
10595 };
10596
10597 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10598 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10599
10600 let start_before = snapshot.anchor_before(selection.start);
10601 let end_after = snapshot.anchor_after(selection.end);
10602
10603 edits.push((start_before..start_before, open_tag));
10604 edits.push((end_after..end_after, close_tag));
10605
10606 boundaries.push((
10607 start_before,
10608 end_after,
10609 wrap_config.start_prefix.len(),
10610 wrap_config.end_suffix.len(),
10611 ));
10612 }
10613
10614 if edits.is_empty() {
10615 return;
10616 }
10617
10618 self.transact(window, cx, |this, window, cx| {
10619 let buffer = this.buffer.update(cx, |buffer, cx| {
10620 buffer.edit(edits, None, cx);
10621 buffer.snapshot(cx)
10622 });
10623
10624 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10625 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10626 boundaries.into_iter()
10627 {
10628 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10629 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10630 new_selections.push(open_offset..open_offset);
10631 new_selections.push(close_offset..close_offset);
10632 }
10633
10634 this.change_selections(Default::default(), window, cx, |s| {
10635 s.select_ranges(new_selections);
10636 });
10637
10638 this.request_autoscroll(Autoscroll::fit(), cx);
10639 });
10640 }
10641
10642 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10643 let Some(project) = self.project.clone() else {
10644 return;
10645 };
10646 self.reload(project, window, cx)
10647 .detach_and_notify_err(window, cx);
10648 }
10649
10650 pub fn restore_file(
10651 &mut self,
10652 _: &::git::RestoreFile,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10657 let mut buffer_ids = HashSet::default();
10658 let snapshot = self.buffer().read(cx).snapshot(cx);
10659 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10660 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10661 }
10662
10663 let buffer = self.buffer().read(cx);
10664 let ranges = buffer_ids
10665 .into_iter()
10666 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10667 .collect::<Vec<_>>();
10668
10669 self.restore_hunks_in_ranges(ranges, window, cx);
10670 }
10671
10672 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10673 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10674 let selections = self
10675 .selections
10676 .all(&self.display_snapshot(cx))
10677 .into_iter()
10678 .map(|s| s.range())
10679 .collect();
10680 self.restore_hunks_in_ranges(selections, window, cx);
10681 }
10682
10683 pub fn restore_hunks_in_ranges(
10684 &mut self,
10685 ranges: Vec<Range<Point>>,
10686 window: &mut Window,
10687 cx: &mut Context<Editor>,
10688 ) {
10689 let mut revert_changes = HashMap::default();
10690 let chunk_by = self
10691 .snapshot(window, cx)
10692 .hunks_for_ranges(ranges)
10693 .into_iter()
10694 .chunk_by(|hunk| hunk.buffer_id);
10695 for (buffer_id, hunks) in &chunk_by {
10696 let hunks = hunks.collect::<Vec<_>>();
10697 for hunk in &hunks {
10698 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10699 }
10700 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10701 }
10702 drop(chunk_by);
10703 if !revert_changes.is_empty() {
10704 self.transact(window, cx, |editor, window, cx| {
10705 editor.restore(revert_changes, window, cx);
10706 });
10707 }
10708 }
10709
10710 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10711 if let Some(status) = self
10712 .addons
10713 .iter()
10714 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10715 {
10716 return Some(status);
10717 }
10718 self.project
10719 .as_ref()?
10720 .read(cx)
10721 .status_for_buffer_id(buffer_id, cx)
10722 }
10723
10724 pub fn open_active_item_in_terminal(
10725 &mut self,
10726 _: &OpenInTerminal,
10727 window: &mut Window,
10728 cx: &mut Context<Self>,
10729 ) {
10730 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10731 let project_path = buffer.read(cx).project_path(cx)?;
10732 let project = self.project()?.read(cx);
10733 let entry = project.entry_for_path(&project_path, cx)?;
10734 let parent = match &entry.canonical_path {
10735 Some(canonical_path) => canonical_path.to_path_buf(),
10736 None => project.absolute_path(&project_path, cx)?,
10737 }
10738 .parent()?
10739 .to_path_buf();
10740 Some(parent)
10741 }) {
10742 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10743 }
10744 }
10745
10746 fn set_breakpoint_context_menu(
10747 &mut self,
10748 display_row: DisplayRow,
10749 position: Option<Anchor>,
10750 clicked_point: gpui::Point<Pixels>,
10751 window: &mut Window,
10752 cx: &mut Context<Self>,
10753 ) {
10754 let source = self
10755 .buffer
10756 .read(cx)
10757 .snapshot(cx)
10758 .anchor_before(Point::new(display_row.0, 0u32));
10759
10760 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10761
10762 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10763 self,
10764 source,
10765 clicked_point,
10766 context_menu,
10767 window,
10768 cx,
10769 );
10770 }
10771
10772 fn add_edit_breakpoint_block(
10773 &mut self,
10774 anchor: Anchor,
10775 breakpoint: &Breakpoint,
10776 edit_action: BreakpointPromptEditAction,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 let weak_editor = cx.weak_entity();
10781 let bp_prompt = cx.new(|cx| {
10782 BreakpointPromptEditor::new(
10783 weak_editor,
10784 anchor,
10785 breakpoint.clone(),
10786 edit_action,
10787 window,
10788 cx,
10789 )
10790 });
10791
10792 let height = bp_prompt.update(cx, |this, cx| {
10793 this.prompt
10794 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10795 });
10796 let cloned_prompt = bp_prompt.clone();
10797 let blocks = vec![BlockProperties {
10798 style: BlockStyle::Sticky,
10799 placement: BlockPlacement::Above(anchor),
10800 height: Some(height),
10801 render: Arc::new(move |cx| {
10802 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10803 cloned_prompt.clone().into_any_element()
10804 }),
10805 priority: 0,
10806 }];
10807
10808 let focus_handle = bp_prompt.focus_handle(cx);
10809 window.focus(&focus_handle);
10810
10811 let block_ids = self.insert_blocks(blocks, None, cx);
10812 bp_prompt.update(cx, |prompt, _| {
10813 prompt.add_block_ids(block_ids);
10814 });
10815 }
10816
10817 pub(crate) fn breakpoint_at_row(
10818 &self,
10819 row: u32,
10820 window: &mut Window,
10821 cx: &mut Context<Self>,
10822 ) -> Option<(Anchor, Breakpoint)> {
10823 let snapshot = self.snapshot(window, cx);
10824 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10825
10826 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10827 }
10828
10829 pub(crate) fn breakpoint_at_anchor(
10830 &self,
10831 breakpoint_position: Anchor,
10832 snapshot: &EditorSnapshot,
10833 cx: &mut Context<Self>,
10834 ) -> Option<(Anchor, Breakpoint)> {
10835 let buffer = self
10836 .buffer
10837 .read(cx)
10838 .buffer_for_anchor(breakpoint_position, cx)?;
10839
10840 let enclosing_excerpt = breakpoint_position.excerpt_id;
10841 let buffer_snapshot = buffer.read(cx).snapshot();
10842
10843 let row = buffer_snapshot
10844 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10845 .row;
10846
10847 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10848 let anchor_end = snapshot
10849 .buffer_snapshot()
10850 .anchor_after(Point::new(row, line_len));
10851
10852 self.breakpoint_store
10853 .as_ref()?
10854 .read_with(cx, |breakpoint_store, cx| {
10855 breakpoint_store
10856 .breakpoints(
10857 &buffer,
10858 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10859 &buffer_snapshot,
10860 cx,
10861 )
10862 .next()
10863 .and_then(|(bp, _)| {
10864 let breakpoint_row = buffer_snapshot
10865 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10866 .row;
10867
10868 if breakpoint_row == row {
10869 snapshot
10870 .buffer_snapshot()
10871 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10872 .map(|position| (position, bp.bp.clone()))
10873 } else {
10874 None
10875 }
10876 })
10877 })
10878 }
10879
10880 pub fn edit_log_breakpoint(
10881 &mut self,
10882 _: &EditLogBreakpoint,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10887 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10888 message: None,
10889 state: BreakpointState::Enabled,
10890 condition: None,
10891 hit_condition: None,
10892 });
10893
10894 self.add_edit_breakpoint_block(
10895 anchor,
10896 &breakpoint,
10897 BreakpointPromptEditAction::Log,
10898 window,
10899 cx,
10900 );
10901 }
10902 }
10903
10904 fn breakpoints_at_cursors(
10905 &self,
10906 window: &mut Window,
10907 cx: &mut Context<Self>,
10908 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10909 let snapshot = self.snapshot(window, cx);
10910 let cursors = self
10911 .selections
10912 .disjoint_anchors_arc()
10913 .iter()
10914 .map(|selection| {
10915 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10916
10917 let breakpoint_position = self
10918 .breakpoint_at_row(cursor_position.row, window, cx)
10919 .map(|bp| bp.0)
10920 .unwrap_or_else(|| {
10921 snapshot
10922 .display_snapshot
10923 .buffer_snapshot()
10924 .anchor_after(Point::new(cursor_position.row, 0))
10925 });
10926
10927 let breakpoint = self
10928 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10929 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10930
10931 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10932 })
10933 // 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.
10934 .collect::<HashMap<Anchor, _>>();
10935
10936 cursors.into_iter().collect()
10937 }
10938
10939 pub fn enable_breakpoint(
10940 &mut self,
10941 _: &crate::actions::EnableBreakpoint,
10942 window: &mut Window,
10943 cx: &mut Context<Self>,
10944 ) {
10945 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10946 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10947 continue;
10948 };
10949 self.edit_breakpoint_at_anchor(
10950 anchor,
10951 breakpoint,
10952 BreakpointEditAction::InvertState,
10953 cx,
10954 );
10955 }
10956 }
10957
10958 pub fn disable_breakpoint(
10959 &mut self,
10960 _: &crate::actions::DisableBreakpoint,
10961 window: &mut Window,
10962 cx: &mut Context<Self>,
10963 ) {
10964 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10965 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10966 continue;
10967 };
10968 self.edit_breakpoint_at_anchor(
10969 anchor,
10970 breakpoint,
10971 BreakpointEditAction::InvertState,
10972 cx,
10973 );
10974 }
10975 }
10976
10977 pub fn toggle_breakpoint(
10978 &mut self,
10979 _: &crate::actions::ToggleBreakpoint,
10980 window: &mut Window,
10981 cx: &mut Context<Self>,
10982 ) {
10983 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10984 if let Some(breakpoint) = breakpoint {
10985 self.edit_breakpoint_at_anchor(
10986 anchor,
10987 breakpoint,
10988 BreakpointEditAction::Toggle,
10989 cx,
10990 );
10991 } else {
10992 self.edit_breakpoint_at_anchor(
10993 anchor,
10994 Breakpoint::new_standard(),
10995 BreakpointEditAction::Toggle,
10996 cx,
10997 );
10998 }
10999 }
11000 }
11001
11002 pub fn edit_breakpoint_at_anchor(
11003 &mut self,
11004 breakpoint_position: Anchor,
11005 breakpoint: Breakpoint,
11006 edit_action: BreakpointEditAction,
11007 cx: &mut Context<Self>,
11008 ) {
11009 let Some(breakpoint_store) = &self.breakpoint_store else {
11010 return;
11011 };
11012
11013 let Some(buffer) = self
11014 .buffer
11015 .read(cx)
11016 .buffer_for_anchor(breakpoint_position, cx)
11017 else {
11018 return;
11019 };
11020
11021 breakpoint_store.update(cx, |breakpoint_store, cx| {
11022 breakpoint_store.toggle_breakpoint(
11023 buffer,
11024 BreakpointWithPosition {
11025 position: breakpoint_position.text_anchor,
11026 bp: breakpoint,
11027 },
11028 edit_action,
11029 cx,
11030 );
11031 });
11032
11033 cx.notify();
11034 }
11035
11036 #[cfg(any(test, feature = "test-support"))]
11037 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11038 self.breakpoint_store.clone()
11039 }
11040
11041 pub fn prepare_restore_change(
11042 &self,
11043 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11044 hunk: &MultiBufferDiffHunk,
11045 cx: &mut App,
11046 ) -> Option<()> {
11047 if hunk.is_created_file() {
11048 return None;
11049 }
11050 let buffer = self.buffer.read(cx);
11051 let diff = buffer.diff_for(hunk.buffer_id)?;
11052 let buffer = buffer.buffer(hunk.buffer_id)?;
11053 let buffer = buffer.read(cx);
11054 let original_text = diff
11055 .read(cx)
11056 .base_text()
11057 .as_rope()
11058 .slice(hunk.diff_base_byte_range.clone());
11059 let buffer_snapshot = buffer.snapshot();
11060 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11061 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11062 probe
11063 .0
11064 .start
11065 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11066 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11067 }) {
11068 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11069 Some(())
11070 } else {
11071 None
11072 }
11073 }
11074
11075 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11076 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11077 }
11078
11079 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11080 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11081 }
11082
11083 fn manipulate_lines<M>(
11084 &mut self,
11085 window: &mut Window,
11086 cx: &mut Context<Self>,
11087 mut manipulate: M,
11088 ) where
11089 M: FnMut(&str) -> LineManipulationResult,
11090 {
11091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11092
11093 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11094 let buffer = self.buffer.read(cx).snapshot(cx);
11095
11096 let mut edits = Vec::new();
11097
11098 let selections = self.selections.all::<Point>(&display_map);
11099 let mut selections = selections.iter().peekable();
11100 let mut contiguous_row_selections = Vec::new();
11101 let mut new_selections = Vec::new();
11102 let mut added_lines = 0;
11103 let mut removed_lines = 0;
11104
11105 while let Some(selection) = selections.next() {
11106 let (start_row, end_row) = consume_contiguous_rows(
11107 &mut contiguous_row_selections,
11108 selection,
11109 &display_map,
11110 &mut selections,
11111 );
11112
11113 let start_point = Point::new(start_row.0, 0);
11114 let end_point = Point::new(
11115 end_row.previous_row().0,
11116 buffer.line_len(end_row.previous_row()),
11117 );
11118 let text = buffer
11119 .text_for_range(start_point..end_point)
11120 .collect::<String>();
11121
11122 let LineManipulationResult {
11123 new_text,
11124 line_count_before,
11125 line_count_after,
11126 } = manipulate(&text);
11127
11128 edits.push((start_point..end_point, new_text));
11129
11130 // Selections must change based on added and removed line count
11131 let start_row =
11132 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11133 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11134 new_selections.push(Selection {
11135 id: selection.id,
11136 start: start_row,
11137 end: end_row,
11138 goal: SelectionGoal::None,
11139 reversed: selection.reversed,
11140 });
11141
11142 if line_count_after > line_count_before {
11143 added_lines += line_count_after - line_count_before;
11144 } else if line_count_before > line_count_after {
11145 removed_lines += line_count_before - line_count_after;
11146 }
11147 }
11148
11149 self.transact(window, cx, |this, window, cx| {
11150 let buffer = this.buffer.update(cx, |buffer, cx| {
11151 buffer.edit(edits, None, cx);
11152 buffer.snapshot(cx)
11153 });
11154
11155 // Recalculate offsets on newly edited buffer
11156 let new_selections = new_selections
11157 .iter()
11158 .map(|s| {
11159 let start_point = Point::new(s.start.0, 0);
11160 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11161 Selection {
11162 id: s.id,
11163 start: buffer.point_to_offset(start_point),
11164 end: buffer.point_to_offset(end_point),
11165 goal: s.goal,
11166 reversed: s.reversed,
11167 }
11168 })
11169 .collect();
11170
11171 this.change_selections(Default::default(), window, cx, |s| {
11172 s.select(new_selections);
11173 });
11174
11175 this.request_autoscroll(Autoscroll::fit(), cx);
11176 });
11177 }
11178
11179 fn manipulate_immutable_lines<Fn>(
11180 &mut self,
11181 window: &mut Window,
11182 cx: &mut Context<Self>,
11183 mut callback: Fn,
11184 ) where
11185 Fn: FnMut(&mut Vec<&str>),
11186 {
11187 self.manipulate_lines(window, cx, |text| {
11188 let mut lines: Vec<&str> = text.split('\n').collect();
11189 let line_count_before = lines.len();
11190
11191 callback(&mut lines);
11192
11193 LineManipulationResult {
11194 new_text: lines.join("\n"),
11195 line_count_before,
11196 line_count_after: lines.len(),
11197 }
11198 });
11199 }
11200
11201 fn manipulate_mutable_lines<Fn>(
11202 &mut self,
11203 window: &mut Window,
11204 cx: &mut Context<Self>,
11205 mut callback: Fn,
11206 ) where
11207 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11208 {
11209 self.manipulate_lines(window, cx, |text| {
11210 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11211 let line_count_before = lines.len();
11212
11213 callback(&mut lines);
11214
11215 LineManipulationResult {
11216 new_text: lines.join("\n"),
11217 line_count_before,
11218 line_count_after: lines.len(),
11219 }
11220 });
11221 }
11222
11223 pub fn convert_indentation_to_spaces(
11224 &mut self,
11225 _: &ConvertIndentationToSpaces,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) {
11229 let settings = self.buffer.read(cx).language_settings(cx);
11230 let tab_size = settings.tab_size.get() as usize;
11231
11232 self.manipulate_mutable_lines(window, cx, |lines| {
11233 // Allocates a reasonably sized scratch buffer once for the whole loop
11234 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11235 // Avoids recomputing spaces that could be inserted many times
11236 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11237 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11238 .collect();
11239
11240 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11241 let mut chars = line.as_ref().chars();
11242 let mut col = 0;
11243 let mut changed = false;
11244
11245 for ch in chars.by_ref() {
11246 match ch {
11247 ' ' => {
11248 reindented_line.push(' ');
11249 col += 1;
11250 }
11251 '\t' => {
11252 // \t are converted to spaces depending on the current column
11253 let spaces_len = tab_size - (col % tab_size);
11254 reindented_line.extend(&space_cache[spaces_len - 1]);
11255 col += spaces_len;
11256 changed = true;
11257 }
11258 _ => {
11259 // If we dont append before break, the character is consumed
11260 reindented_line.push(ch);
11261 break;
11262 }
11263 }
11264 }
11265
11266 if !changed {
11267 reindented_line.clear();
11268 continue;
11269 }
11270 // Append the rest of the line and replace old reference with new one
11271 reindented_line.extend(chars);
11272 *line = Cow::Owned(reindented_line.clone());
11273 reindented_line.clear();
11274 }
11275 });
11276 }
11277
11278 pub fn convert_indentation_to_tabs(
11279 &mut self,
11280 _: &ConvertIndentationToTabs,
11281 window: &mut Window,
11282 cx: &mut Context<Self>,
11283 ) {
11284 let settings = self.buffer.read(cx).language_settings(cx);
11285 let tab_size = settings.tab_size.get() as usize;
11286
11287 self.manipulate_mutable_lines(window, cx, |lines| {
11288 // Allocates a reasonably sized buffer once for the whole loop
11289 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11290 // Avoids recomputing spaces that could be inserted many times
11291 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11292 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11293 .collect();
11294
11295 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11296 let mut chars = line.chars();
11297 let mut spaces_count = 0;
11298 let mut first_non_indent_char = None;
11299 let mut changed = false;
11300
11301 for ch in chars.by_ref() {
11302 match ch {
11303 ' ' => {
11304 // Keep track of spaces. Append \t when we reach tab_size
11305 spaces_count += 1;
11306 changed = true;
11307 if spaces_count == tab_size {
11308 reindented_line.push('\t');
11309 spaces_count = 0;
11310 }
11311 }
11312 '\t' => {
11313 reindented_line.push('\t');
11314 spaces_count = 0;
11315 }
11316 _ => {
11317 // Dont append it yet, we might have remaining spaces
11318 first_non_indent_char = Some(ch);
11319 break;
11320 }
11321 }
11322 }
11323
11324 if !changed {
11325 reindented_line.clear();
11326 continue;
11327 }
11328 // Remaining spaces that didn't make a full tab stop
11329 if spaces_count > 0 {
11330 reindented_line.extend(&space_cache[spaces_count - 1]);
11331 }
11332 // If we consume an extra character that was not indentation, add it back
11333 if let Some(extra_char) = first_non_indent_char {
11334 reindented_line.push(extra_char);
11335 }
11336 // Append the rest of the line and replace old reference with new one
11337 reindented_line.extend(chars);
11338 *line = Cow::Owned(reindented_line.clone());
11339 reindented_line.clear();
11340 }
11341 });
11342 }
11343
11344 pub fn convert_to_upper_case(
11345 &mut self,
11346 _: &ConvertToUpperCase,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 ) {
11350 self.manipulate_text(window, cx, |text| text.to_uppercase())
11351 }
11352
11353 pub fn convert_to_lower_case(
11354 &mut self,
11355 _: &ConvertToLowerCase,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 self.manipulate_text(window, cx, |text| text.to_lowercase())
11360 }
11361
11362 pub fn convert_to_title_case(
11363 &mut self,
11364 _: &ConvertToTitleCase,
11365 window: &mut Window,
11366 cx: &mut Context<Self>,
11367 ) {
11368 self.manipulate_text(window, cx, |text| {
11369 text.split('\n')
11370 .map(|line| line.to_case(Case::Title))
11371 .join("\n")
11372 })
11373 }
11374
11375 pub fn convert_to_snake_case(
11376 &mut self,
11377 _: &ConvertToSnakeCase,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11382 }
11383
11384 pub fn convert_to_kebab_case(
11385 &mut self,
11386 _: &ConvertToKebabCase,
11387 window: &mut Window,
11388 cx: &mut Context<Self>,
11389 ) {
11390 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11391 }
11392
11393 pub fn convert_to_upper_camel_case(
11394 &mut self,
11395 _: &ConvertToUpperCamelCase,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 self.manipulate_text(window, cx, |text| {
11400 text.split('\n')
11401 .map(|line| line.to_case(Case::UpperCamel))
11402 .join("\n")
11403 })
11404 }
11405
11406 pub fn convert_to_lower_camel_case(
11407 &mut self,
11408 _: &ConvertToLowerCamelCase,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11413 }
11414
11415 pub fn convert_to_opposite_case(
11416 &mut self,
11417 _: &ConvertToOppositeCase,
11418 window: &mut Window,
11419 cx: &mut Context<Self>,
11420 ) {
11421 self.manipulate_text(window, cx, |text| {
11422 text.chars()
11423 .fold(String::with_capacity(text.len()), |mut t, c| {
11424 if c.is_uppercase() {
11425 t.extend(c.to_lowercase());
11426 } else {
11427 t.extend(c.to_uppercase());
11428 }
11429 t
11430 })
11431 })
11432 }
11433
11434 pub fn convert_to_sentence_case(
11435 &mut self,
11436 _: &ConvertToSentenceCase,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11441 }
11442
11443 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11444 self.manipulate_text(window, cx, |text| {
11445 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11446 if has_upper_case_characters {
11447 text.to_lowercase()
11448 } else {
11449 text.to_uppercase()
11450 }
11451 })
11452 }
11453
11454 pub fn convert_to_rot13(
11455 &mut self,
11456 _: &ConvertToRot13,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 self.manipulate_text(window, cx, |text| {
11461 text.chars()
11462 .map(|c| match c {
11463 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11464 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11465 _ => c,
11466 })
11467 .collect()
11468 })
11469 }
11470
11471 pub fn convert_to_rot47(
11472 &mut self,
11473 _: &ConvertToRot47,
11474 window: &mut Window,
11475 cx: &mut Context<Self>,
11476 ) {
11477 self.manipulate_text(window, cx, |text| {
11478 text.chars()
11479 .map(|c| {
11480 let code_point = c as u32;
11481 if code_point >= 33 && code_point <= 126 {
11482 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11483 }
11484 c
11485 })
11486 .collect()
11487 })
11488 }
11489
11490 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11491 where
11492 Fn: FnMut(&str) -> String,
11493 {
11494 let buffer = self.buffer.read(cx).snapshot(cx);
11495
11496 let mut new_selections = Vec::new();
11497 let mut edits = Vec::new();
11498 let mut selection_adjustment = 0i32;
11499
11500 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11501 let selection_is_empty = selection.is_empty();
11502
11503 let (start, end) = if selection_is_empty {
11504 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11505 (word_range.start, word_range.end)
11506 } else {
11507 (
11508 buffer.point_to_offset(selection.start),
11509 buffer.point_to_offset(selection.end),
11510 )
11511 };
11512
11513 let text = buffer.text_for_range(start..end).collect::<String>();
11514 let old_length = text.len() as i32;
11515 let text = callback(&text);
11516
11517 new_selections.push(Selection {
11518 start: (start as i32 - selection_adjustment) as usize,
11519 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11520 goal: SelectionGoal::None,
11521 id: selection.id,
11522 reversed: selection.reversed,
11523 });
11524
11525 selection_adjustment += old_length - text.len() as i32;
11526
11527 edits.push((start..end, text));
11528 }
11529
11530 self.transact(window, cx, |this, window, cx| {
11531 this.buffer.update(cx, |buffer, cx| {
11532 buffer.edit(edits, None, cx);
11533 });
11534
11535 this.change_selections(Default::default(), window, cx, |s| {
11536 s.select(new_selections);
11537 });
11538
11539 this.request_autoscroll(Autoscroll::fit(), cx);
11540 });
11541 }
11542
11543 pub fn move_selection_on_drop(
11544 &mut self,
11545 selection: &Selection<Anchor>,
11546 target: DisplayPoint,
11547 is_cut: bool,
11548 window: &mut Window,
11549 cx: &mut Context<Self>,
11550 ) {
11551 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11552 let buffer = display_map.buffer_snapshot();
11553 let mut edits = Vec::new();
11554 let insert_point = display_map
11555 .clip_point(target, Bias::Left)
11556 .to_point(&display_map);
11557 let text = buffer
11558 .text_for_range(selection.start..selection.end)
11559 .collect::<String>();
11560 if is_cut {
11561 edits.push(((selection.start..selection.end), String::new()));
11562 }
11563 let insert_anchor = buffer.anchor_before(insert_point);
11564 edits.push(((insert_anchor..insert_anchor), text));
11565 let last_edit_start = insert_anchor.bias_left(buffer);
11566 let last_edit_end = insert_anchor.bias_right(buffer);
11567 self.transact(window, cx, |this, window, cx| {
11568 this.buffer.update(cx, |buffer, cx| {
11569 buffer.edit(edits, None, cx);
11570 });
11571 this.change_selections(Default::default(), window, cx, |s| {
11572 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11573 });
11574 });
11575 }
11576
11577 pub fn clear_selection_drag_state(&mut self) {
11578 self.selection_drag_state = SelectionDragState::None;
11579 }
11580
11581 pub fn duplicate(
11582 &mut self,
11583 upwards: bool,
11584 whole_lines: bool,
11585 window: &mut Window,
11586 cx: &mut Context<Self>,
11587 ) {
11588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11589
11590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11591 let buffer = display_map.buffer_snapshot();
11592 let selections = self.selections.all::<Point>(&display_map);
11593
11594 let mut edits = Vec::new();
11595 let mut selections_iter = selections.iter().peekable();
11596 while let Some(selection) = selections_iter.next() {
11597 let mut rows = selection.spanned_rows(false, &display_map);
11598 // duplicate line-wise
11599 if whole_lines || selection.start == selection.end {
11600 // Avoid duplicating the same lines twice.
11601 while let Some(next_selection) = selections_iter.peek() {
11602 let next_rows = next_selection.spanned_rows(false, &display_map);
11603 if next_rows.start < rows.end {
11604 rows.end = next_rows.end;
11605 selections_iter.next().unwrap();
11606 } else {
11607 break;
11608 }
11609 }
11610
11611 // Copy the text from the selected row region and splice it either at the start
11612 // or end of the region.
11613 let start = Point::new(rows.start.0, 0);
11614 let end = Point::new(
11615 rows.end.previous_row().0,
11616 buffer.line_len(rows.end.previous_row()),
11617 );
11618
11619 let mut text = buffer.text_for_range(start..end).collect::<String>();
11620
11621 let insert_location = if upwards {
11622 // When duplicating upward, we need to insert before the current line.
11623 // If we're on the last line and it doesn't end with a newline,
11624 // we need to add a newline before the duplicated content.
11625 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11626 && buffer.max_point().column > 0
11627 && !text.ends_with('\n');
11628
11629 if needs_leading_newline {
11630 text.insert(0, '\n');
11631 end
11632 } else {
11633 text.push('\n');
11634 Point::new(rows.start.0, 0)
11635 }
11636 } else {
11637 text.push('\n');
11638 start
11639 };
11640 edits.push((insert_location..insert_location, text));
11641 } else {
11642 // duplicate character-wise
11643 let start = selection.start;
11644 let end = selection.end;
11645 let text = buffer.text_for_range(start..end).collect::<String>();
11646 edits.push((selection.end..selection.end, text));
11647 }
11648 }
11649
11650 self.transact(window, cx, |this, window, cx| {
11651 this.buffer.update(cx, |buffer, cx| {
11652 buffer.edit(edits, None, cx);
11653 });
11654
11655 // When duplicating upward with whole lines, move the cursor to the duplicated line
11656 if upwards && whole_lines {
11657 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11658
11659 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11660 let mut new_ranges = Vec::new();
11661 let selections = s.all::<Point>(&display_map);
11662 let mut selections_iter = selections.iter().peekable();
11663
11664 while let Some(first_selection) = selections_iter.next() {
11665 // Group contiguous selections together to find the total row span
11666 let mut group_selections = vec![first_selection];
11667 let mut rows = first_selection.spanned_rows(false, &display_map);
11668
11669 while let Some(next_selection) = selections_iter.peek() {
11670 let next_rows = next_selection.spanned_rows(false, &display_map);
11671 if next_rows.start < rows.end {
11672 rows.end = next_rows.end;
11673 group_selections.push(selections_iter.next().unwrap());
11674 } else {
11675 break;
11676 }
11677 }
11678
11679 let row_count = rows.end.0 - rows.start.0;
11680
11681 // Move all selections in this group up by the total number of duplicated rows
11682 for selection in group_selections {
11683 let new_start = Point::new(
11684 selection.start.row.saturating_sub(row_count),
11685 selection.start.column,
11686 );
11687
11688 let new_end = Point::new(
11689 selection.end.row.saturating_sub(row_count),
11690 selection.end.column,
11691 );
11692
11693 new_ranges.push(new_start..new_end);
11694 }
11695 }
11696
11697 s.select_ranges(new_ranges);
11698 });
11699 }
11700
11701 this.request_autoscroll(Autoscroll::fit(), cx);
11702 });
11703 }
11704
11705 pub fn duplicate_line_up(
11706 &mut self,
11707 _: &DuplicateLineUp,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) {
11711 self.duplicate(true, true, window, cx);
11712 }
11713
11714 pub fn duplicate_line_down(
11715 &mut self,
11716 _: &DuplicateLineDown,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 self.duplicate(false, true, window, cx);
11721 }
11722
11723 pub fn duplicate_selection(
11724 &mut self,
11725 _: &DuplicateSelection,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 self.duplicate(false, false, window, cx);
11730 }
11731
11732 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11734 if self.mode.is_single_line() {
11735 cx.propagate();
11736 return;
11737 }
11738
11739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11740 let buffer = self.buffer.read(cx).snapshot(cx);
11741
11742 let mut edits = Vec::new();
11743 let mut unfold_ranges = Vec::new();
11744 let mut refold_creases = Vec::new();
11745
11746 let selections = self.selections.all::<Point>(&display_map);
11747 let mut selections = selections.iter().peekable();
11748 let mut contiguous_row_selections = Vec::new();
11749 let mut new_selections = Vec::new();
11750
11751 while let Some(selection) = selections.next() {
11752 // Find all the selections that span a contiguous row range
11753 let (start_row, end_row) = consume_contiguous_rows(
11754 &mut contiguous_row_selections,
11755 selection,
11756 &display_map,
11757 &mut selections,
11758 );
11759
11760 // Move the text spanned by the row range to be before the line preceding the row range
11761 if start_row.0 > 0 {
11762 let range_to_move = Point::new(
11763 start_row.previous_row().0,
11764 buffer.line_len(start_row.previous_row()),
11765 )
11766 ..Point::new(
11767 end_row.previous_row().0,
11768 buffer.line_len(end_row.previous_row()),
11769 );
11770 let insertion_point = display_map
11771 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11772 .0;
11773
11774 // Don't move lines across excerpts
11775 if buffer
11776 .excerpt_containing(insertion_point..range_to_move.end)
11777 .is_some()
11778 {
11779 let text = buffer
11780 .text_for_range(range_to_move.clone())
11781 .flat_map(|s| s.chars())
11782 .skip(1)
11783 .chain(['\n'])
11784 .collect::<String>();
11785
11786 edits.push((
11787 buffer.anchor_after(range_to_move.start)
11788 ..buffer.anchor_before(range_to_move.end),
11789 String::new(),
11790 ));
11791 let insertion_anchor = buffer.anchor_after(insertion_point);
11792 edits.push((insertion_anchor..insertion_anchor, text));
11793
11794 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11795
11796 // Move selections up
11797 new_selections.extend(contiguous_row_selections.drain(..).map(
11798 |mut selection| {
11799 selection.start.row -= row_delta;
11800 selection.end.row -= row_delta;
11801 selection
11802 },
11803 ));
11804
11805 // Move folds up
11806 unfold_ranges.push(range_to_move.clone());
11807 for fold in display_map.folds_in_range(
11808 buffer.anchor_before(range_to_move.start)
11809 ..buffer.anchor_after(range_to_move.end),
11810 ) {
11811 let mut start = fold.range.start.to_point(&buffer);
11812 let mut end = fold.range.end.to_point(&buffer);
11813 start.row -= row_delta;
11814 end.row -= row_delta;
11815 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11816 }
11817 }
11818 }
11819
11820 // If we didn't move line(s), preserve the existing selections
11821 new_selections.append(&mut contiguous_row_selections);
11822 }
11823
11824 self.transact(window, cx, |this, window, cx| {
11825 this.unfold_ranges(&unfold_ranges, true, true, cx);
11826 this.buffer.update(cx, |buffer, cx| {
11827 for (range, text) in edits {
11828 buffer.edit([(range, text)], None, cx);
11829 }
11830 });
11831 this.fold_creases(refold_creases, true, window, cx);
11832 this.change_selections(Default::default(), window, cx, |s| {
11833 s.select(new_selections);
11834 })
11835 });
11836 }
11837
11838 pub fn move_line_down(
11839 &mut self,
11840 _: &MoveLineDown,
11841 window: &mut Window,
11842 cx: &mut Context<Self>,
11843 ) {
11844 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11845 if self.mode.is_single_line() {
11846 cx.propagate();
11847 return;
11848 }
11849
11850 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11851 let buffer = self.buffer.read(cx).snapshot(cx);
11852
11853 let mut edits = Vec::new();
11854 let mut unfold_ranges = Vec::new();
11855 let mut refold_creases = Vec::new();
11856
11857 let selections = self.selections.all::<Point>(&display_map);
11858 let mut selections = selections.iter().peekable();
11859 let mut contiguous_row_selections = Vec::new();
11860 let mut new_selections = Vec::new();
11861
11862 while let Some(selection) = selections.next() {
11863 // Find all the selections that span a contiguous row range
11864 let (start_row, end_row) = consume_contiguous_rows(
11865 &mut contiguous_row_selections,
11866 selection,
11867 &display_map,
11868 &mut selections,
11869 );
11870
11871 // Move the text spanned by the row range to be after the last line of the row range
11872 if end_row.0 <= buffer.max_point().row {
11873 let range_to_move =
11874 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11875 let insertion_point = display_map
11876 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11877 .0;
11878
11879 // Don't move lines across excerpt boundaries
11880 if buffer
11881 .excerpt_containing(range_to_move.start..insertion_point)
11882 .is_some()
11883 {
11884 let mut text = String::from("\n");
11885 text.extend(buffer.text_for_range(range_to_move.clone()));
11886 text.pop(); // Drop trailing newline
11887 edits.push((
11888 buffer.anchor_after(range_to_move.start)
11889 ..buffer.anchor_before(range_to_move.end),
11890 String::new(),
11891 ));
11892 let insertion_anchor = buffer.anchor_after(insertion_point);
11893 edits.push((insertion_anchor..insertion_anchor, text));
11894
11895 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11896
11897 // Move selections down
11898 new_selections.extend(contiguous_row_selections.drain(..).map(
11899 |mut selection| {
11900 selection.start.row += row_delta;
11901 selection.end.row += row_delta;
11902 selection
11903 },
11904 ));
11905
11906 // Move folds down
11907 unfold_ranges.push(range_to_move.clone());
11908 for fold in display_map.folds_in_range(
11909 buffer.anchor_before(range_to_move.start)
11910 ..buffer.anchor_after(range_to_move.end),
11911 ) {
11912 let mut start = fold.range.start.to_point(&buffer);
11913 let mut end = fold.range.end.to_point(&buffer);
11914 start.row += row_delta;
11915 end.row += row_delta;
11916 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11917 }
11918 }
11919 }
11920
11921 // If we didn't move line(s), preserve the existing selections
11922 new_selections.append(&mut contiguous_row_selections);
11923 }
11924
11925 self.transact(window, cx, |this, window, cx| {
11926 this.unfold_ranges(&unfold_ranges, true, true, cx);
11927 this.buffer.update(cx, |buffer, cx| {
11928 for (range, text) in edits {
11929 buffer.edit([(range, text)], None, cx);
11930 }
11931 });
11932 this.fold_creases(refold_creases, true, window, cx);
11933 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11934 });
11935 }
11936
11937 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11938 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11939 let text_layout_details = &self.text_layout_details(window);
11940 self.transact(window, cx, |this, window, cx| {
11941 let edits = this.change_selections(Default::default(), window, cx, |s| {
11942 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11943 s.move_with(|display_map, selection| {
11944 if !selection.is_empty() {
11945 return;
11946 }
11947
11948 let mut head = selection.head();
11949 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11950 if head.column() == display_map.line_len(head.row()) {
11951 transpose_offset = display_map
11952 .buffer_snapshot()
11953 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11954 }
11955
11956 if transpose_offset == 0 {
11957 return;
11958 }
11959
11960 *head.column_mut() += 1;
11961 head = display_map.clip_point(head, Bias::Right);
11962 let goal = SelectionGoal::HorizontalPosition(
11963 display_map
11964 .x_for_display_point(head, text_layout_details)
11965 .into(),
11966 );
11967 selection.collapse_to(head, goal);
11968
11969 let transpose_start = display_map
11970 .buffer_snapshot()
11971 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11972 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11973 let transpose_end = display_map
11974 .buffer_snapshot()
11975 .clip_offset(transpose_offset + 1, Bias::Right);
11976 if let Some(ch) = display_map
11977 .buffer_snapshot()
11978 .chars_at(transpose_start)
11979 .next()
11980 {
11981 edits.push((transpose_start..transpose_offset, String::new()));
11982 edits.push((transpose_end..transpose_end, ch.to_string()));
11983 }
11984 }
11985 });
11986 edits
11987 });
11988 this.buffer
11989 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11990 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11991 this.change_selections(Default::default(), window, cx, |s| {
11992 s.select(selections);
11993 });
11994 });
11995 }
11996
11997 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11999 if self.mode.is_single_line() {
12000 cx.propagate();
12001 return;
12002 }
12003
12004 self.rewrap_impl(RewrapOptions::default(), cx)
12005 }
12006
12007 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12008 let buffer = self.buffer.read(cx).snapshot(cx);
12009 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12010
12011 #[derive(Clone, Debug, PartialEq)]
12012 enum CommentFormat {
12013 /// single line comment, with prefix for line
12014 Line(String),
12015 /// single line within a block comment, with prefix for line
12016 BlockLine(String),
12017 /// a single line of a block comment that includes the initial delimiter
12018 BlockCommentWithStart(BlockCommentConfig),
12019 /// a single line of a block comment that includes the ending delimiter
12020 BlockCommentWithEnd(BlockCommentConfig),
12021 }
12022
12023 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12024 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12025 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12026 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12027 .peekable();
12028
12029 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12030 row
12031 } else {
12032 return Vec::new();
12033 };
12034
12035 let language_settings = buffer.language_settings_at(selection.head(), cx);
12036 let language_scope = buffer.language_scope_at(selection.head());
12037
12038 let indent_and_prefix_for_row =
12039 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12040 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12041 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12042 &language_scope
12043 {
12044 let indent_end = Point::new(row, indent.len);
12045 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12046 let line_text_after_indent = buffer
12047 .text_for_range(indent_end..line_end)
12048 .collect::<String>();
12049
12050 let is_within_comment_override = buffer
12051 .language_scope_at(indent_end)
12052 .is_some_and(|scope| scope.override_name() == Some("comment"));
12053 let comment_delimiters = if is_within_comment_override {
12054 // we are within a comment syntax node, but we don't
12055 // yet know what kind of comment: block, doc or line
12056 match (
12057 language_scope.documentation_comment(),
12058 language_scope.block_comment(),
12059 ) {
12060 (Some(config), _) | (_, Some(config))
12061 if buffer.contains_str_at(indent_end, &config.start) =>
12062 {
12063 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12064 }
12065 (Some(config), _) | (_, Some(config))
12066 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12067 {
12068 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12069 }
12070 (Some(config), _) | (_, Some(config))
12071 if buffer.contains_str_at(indent_end, &config.prefix) =>
12072 {
12073 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12074 }
12075 (_, _) => language_scope
12076 .line_comment_prefixes()
12077 .iter()
12078 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12079 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12080 }
12081 } else {
12082 // we not in an overridden comment node, but we may
12083 // be within a non-overridden line comment node
12084 language_scope
12085 .line_comment_prefixes()
12086 .iter()
12087 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12088 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12089 };
12090
12091 let rewrap_prefix = language_scope
12092 .rewrap_prefixes()
12093 .iter()
12094 .find_map(|prefix_regex| {
12095 prefix_regex.find(&line_text_after_indent).map(|mat| {
12096 if mat.start() == 0 {
12097 Some(mat.as_str().to_string())
12098 } else {
12099 None
12100 }
12101 })
12102 })
12103 .flatten();
12104 (comment_delimiters, rewrap_prefix)
12105 } else {
12106 (None, None)
12107 };
12108 (indent, comment_prefix, rewrap_prefix)
12109 };
12110
12111 let mut ranges = Vec::new();
12112 let from_empty_selection = selection.is_empty();
12113
12114 let mut current_range_start = first_row;
12115 let mut prev_row = first_row;
12116 let (
12117 mut current_range_indent,
12118 mut current_range_comment_delimiters,
12119 mut current_range_rewrap_prefix,
12120 ) = indent_and_prefix_for_row(first_row);
12121
12122 for row in non_blank_rows_iter.skip(1) {
12123 let has_paragraph_break = row > prev_row + 1;
12124
12125 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12126 indent_and_prefix_for_row(row);
12127
12128 let has_indent_change = row_indent != current_range_indent;
12129 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12130
12131 let has_boundary_change = has_comment_change
12132 || row_rewrap_prefix.is_some()
12133 || (has_indent_change && current_range_comment_delimiters.is_some());
12134
12135 if has_paragraph_break || has_boundary_change {
12136 ranges.push((
12137 language_settings.clone(),
12138 Point::new(current_range_start, 0)
12139 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12140 current_range_indent,
12141 current_range_comment_delimiters.clone(),
12142 current_range_rewrap_prefix.clone(),
12143 from_empty_selection,
12144 ));
12145 current_range_start = row;
12146 current_range_indent = row_indent;
12147 current_range_comment_delimiters = row_comment_delimiters;
12148 current_range_rewrap_prefix = row_rewrap_prefix;
12149 }
12150 prev_row = row;
12151 }
12152
12153 ranges.push((
12154 language_settings.clone(),
12155 Point::new(current_range_start, 0)
12156 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12157 current_range_indent,
12158 current_range_comment_delimiters,
12159 current_range_rewrap_prefix,
12160 from_empty_selection,
12161 ));
12162
12163 ranges
12164 });
12165
12166 let mut edits = Vec::new();
12167 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12168
12169 for (
12170 language_settings,
12171 wrap_range,
12172 mut indent_size,
12173 comment_prefix,
12174 rewrap_prefix,
12175 from_empty_selection,
12176 ) in wrap_ranges
12177 {
12178 let mut start_row = wrap_range.start.row;
12179 let mut end_row = wrap_range.end.row;
12180
12181 // Skip selections that overlap with a range that has already been rewrapped.
12182 let selection_range = start_row..end_row;
12183 if rewrapped_row_ranges
12184 .iter()
12185 .any(|range| range.overlaps(&selection_range))
12186 {
12187 continue;
12188 }
12189
12190 let tab_size = language_settings.tab_size;
12191
12192 let (line_prefix, inside_comment) = match &comment_prefix {
12193 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12194 (Some(prefix.as_str()), true)
12195 }
12196 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12197 (Some(prefix.as_ref()), true)
12198 }
12199 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12200 start: _,
12201 end: _,
12202 prefix,
12203 tab_size,
12204 })) => {
12205 indent_size.len += tab_size;
12206 (Some(prefix.as_ref()), true)
12207 }
12208 None => (None, false),
12209 };
12210 let indent_prefix = indent_size.chars().collect::<String>();
12211 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12212
12213 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12214 RewrapBehavior::InComments => inside_comment,
12215 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12216 RewrapBehavior::Anywhere => true,
12217 };
12218
12219 let should_rewrap = options.override_language_settings
12220 || allow_rewrap_based_on_language
12221 || self.hard_wrap.is_some();
12222 if !should_rewrap {
12223 continue;
12224 }
12225
12226 if from_empty_selection {
12227 'expand_upwards: while start_row > 0 {
12228 let prev_row = start_row - 1;
12229 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12230 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12231 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12232 {
12233 start_row = prev_row;
12234 } else {
12235 break 'expand_upwards;
12236 }
12237 }
12238
12239 'expand_downwards: while end_row < buffer.max_point().row {
12240 let next_row = end_row + 1;
12241 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12242 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12243 && !buffer.is_line_blank(MultiBufferRow(next_row))
12244 {
12245 end_row = next_row;
12246 } else {
12247 break 'expand_downwards;
12248 }
12249 }
12250 }
12251
12252 let start = Point::new(start_row, 0);
12253 let start_offset = ToOffset::to_offset(&start, &buffer);
12254 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12255 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12256 let mut first_line_delimiter = None;
12257 let mut last_line_delimiter = None;
12258 let Some(lines_without_prefixes) = selection_text
12259 .lines()
12260 .enumerate()
12261 .map(|(ix, line)| {
12262 let line_trimmed = line.trim_start();
12263 if rewrap_prefix.is_some() && ix > 0 {
12264 Ok(line_trimmed)
12265 } else if let Some(
12266 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12267 start,
12268 prefix,
12269 end,
12270 tab_size,
12271 })
12272 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12273 start,
12274 prefix,
12275 end,
12276 tab_size,
12277 }),
12278 ) = &comment_prefix
12279 {
12280 let line_trimmed = line_trimmed
12281 .strip_prefix(start.as_ref())
12282 .map(|s| {
12283 let mut indent_size = indent_size;
12284 indent_size.len -= tab_size;
12285 let indent_prefix: String = indent_size.chars().collect();
12286 first_line_delimiter = Some((indent_prefix, start));
12287 s.trim_start()
12288 })
12289 .unwrap_or(line_trimmed);
12290 let line_trimmed = line_trimmed
12291 .strip_suffix(end.as_ref())
12292 .map(|s| {
12293 last_line_delimiter = Some(end);
12294 s.trim_end()
12295 })
12296 .unwrap_or(line_trimmed);
12297 let line_trimmed = line_trimmed
12298 .strip_prefix(prefix.as_ref())
12299 .unwrap_or(line_trimmed);
12300 Ok(line_trimmed)
12301 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12302 line_trimmed.strip_prefix(prefix).with_context(|| {
12303 format!("line did not start with prefix {prefix:?}: {line:?}")
12304 })
12305 } else {
12306 line_trimmed
12307 .strip_prefix(&line_prefix.trim_start())
12308 .with_context(|| {
12309 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12310 })
12311 }
12312 })
12313 .collect::<Result<Vec<_>, _>>()
12314 .log_err()
12315 else {
12316 continue;
12317 };
12318
12319 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12320 buffer
12321 .language_settings_at(Point::new(start_row, 0), cx)
12322 .preferred_line_length as usize
12323 });
12324
12325 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12326 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12327 } else {
12328 line_prefix.clone()
12329 };
12330
12331 let wrapped_text = {
12332 let mut wrapped_text = wrap_with_prefix(
12333 line_prefix,
12334 subsequent_lines_prefix,
12335 lines_without_prefixes.join("\n"),
12336 wrap_column,
12337 tab_size,
12338 options.preserve_existing_whitespace,
12339 );
12340
12341 if let Some((indent, delimiter)) = first_line_delimiter {
12342 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12343 }
12344 if let Some(last_line) = last_line_delimiter {
12345 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12346 }
12347
12348 wrapped_text
12349 };
12350
12351 // TODO: should always use char-based diff while still supporting cursor behavior that
12352 // matches vim.
12353 let mut diff_options = DiffOptions::default();
12354 if options.override_language_settings {
12355 diff_options.max_word_diff_len = 0;
12356 diff_options.max_word_diff_line_count = 0;
12357 } else {
12358 diff_options.max_word_diff_len = usize::MAX;
12359 diff_options.max_word_diff_line_count = usize::MAX;
12360 }
12361
12362 for (old_range, new_text) in
12363 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12364 {
12365 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12366 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12367 edits.push((edit_start..edit_end, new_text));
12368 }
12369
12370 rewrapped_row_ranges.push(start_row..=end_row);
12371 }
12372
12373 self.buffer
12374 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12375 }
12376
12377 pub fn cut_common(
12378 &mut self,
12379 cut_no_selection_line: bool,
12380 window: &mut Window,
12381 cx: &mut Context<Self>,
12382 ) -> ClipboardItem {
12383 let mut text = String::new();
12384 let buffer = self.buffer.read(cx).snapshot(cx);
12385 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12386 let mut clipboard_selections = Vec::with_capacity(selections.len());
12387 {
12388 let max_point = buffer.max_point();
12389 let mut is_first = true;
12390 for selection in &mut selections {
12391 let is_entire_line =
12392 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12393 if is_entire_line {
12394 selection.start = Point::new(selection.start.row, 0);
12395 if !selection.is_empty() && selection.end.column == 0 {
12396 selection.end = cmp::min(max_point, selection.end);
12397 } else {
12398 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12399 }
12400 selection.goal = SelectionGoal::None;
12401 }
12402 if is_first {
12403 is_first = false;
12404 } else {
12405 text += "\n";
12406 }
12407 let mut len = 0;
12408 for chunk in buffer.text_for_range(selection.start..selection.end) {
12409 text.push_str(chunk);
12410 len += chunk.len();
12411 }
12412 clipboard_selections.push(ClipboardSelection {
12413 len,
12414 is_entire_line,
12415 first_line_indent: buffer
12416 .indent_size_for_line(MultiBufferRow(selection.start.row))
12417 .len,
12418 });
12419 }
12420 }
12421
12422 self.transact(window, cx, |this, window, cx| {
12423 this.change_selections(Default::default(), window, cx, |s| {
12424 s.select(selections);
12425 });
12426 this.insert("", window, cx);
12427 });
12428 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12429 }
12430
12431 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12433 let item = self.cut_common(true, window, cx);
12434 cx.write_to_clipboard(item);
12435 }
12436
12437 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12440 s.move_with(|snapshot, sel| {
12441 if sel.is_empty() {
12442 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12443 }
12444 if sel.is_empty() {
12445 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12446 }
12447 });
12448 });
12449 let item = self.cut_common(false, window, cx);
12450 cx.set_global(KillRing(item))
12451 }
12452
12453 pub fn kill_ring_yank(
12454 &mut self,
12455 _: &KillRingYank,
12456 window: &mut Window,
12457 cx: &mut Context<Self>,
12458 ) {
12459 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12460 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12461 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12462 (kill_ring.text().to_string(), kill_ring.metadata_json())
12463 } else {
12464 return;
12465 }
12466 } else {
12467 return;
12468 };
12469 self.do_paste(&text, metadata, false, window, cx);
12470 }
12471
12472 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12473 self.do_copy(true, cx);
12474 }
12475
12476 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12477 self.do_copy(false, cx);
12478 }
12479
12480 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12481 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12482 let buffer = self.buffer.read(cx).read(cx);
12483 let mut text = String::new();
12484
12485 let mut clipboard_selections = Vec::with_capacity(selections.len());
12486 {
12487 let max_point = buffer.max_point();
12488 let mut is_first = true;
12489 for selection in &selections {
12490 let mut start = selection.start;
12491 let mut end = selection.end;
12492 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12493 let mut add_trailing_newline = false;
12494 if is_entire_line {
12495 start = Point::new(start.row, 0);
12496 let next_line_start = Point::new(end.row + 1, 0);
12497 if next_line_start <= max_point {
12498 end = next_line_start;
12499 } else {
12500 // We're on the last line without a trailing newline.
12501 // Copy to the end of the line and add a newline afterwards.
12502 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12503 add_trailing_newline = true;
12504 }
12505 }
12506
12507 let mut trimmed_selections = Vec::new();
12508 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12509 let row = MultiBufferRow(start.row);
12510 let first_indent = buffer.indent_size_for_line(row);
12511 if first_indent.len == 0 || start.column > first_indent.len {
12512 trimmed_selections.push(start..end);
12513 } else {
12514 trimmed_selections.push(
12515 Point::new(row.0, first_indent.len)
12516 ..Point::new(row.0, buffer.line_len(row)),
12517 );
12518 for row in start.row + 1..=end.row {
12519 let mut line_len = buffer.line_len(MultiBufferRow(row));
12520 if row == end.row {
12521 line_len = end.column;
12522 }
12523 if line_len == 0 {
12524 trimmed_selections
12525 .push(Point::new(row, 0)..Point::new(row, line_len));
12526 continue;
12527 }
12528 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12529 if row_indent_size.len >= first_indent.len {
12530 trimmed_selections.push(
12531 Point::new(row, first_indent.len)..Point::new(row, line_len),
12532 );
12533 } else {
12534 trimmed_selections.clear();
12535 trimmed_selections.push(start..end);
12536 break;
12537 }
12538 }
12539 }
12540 } else {
12541 trimmed_selections.push(start..end);
12542 }
12543
12544 for trimmed_range in trimmed_selections {
12545 if is_first {
12546 is_first = false;
12547 } else {
12548 text += "\n";
12549 }
12550 let mut len = 0;
12551 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12552 text.push_str(chunk);
12553 len += chunk.len();
12554 }
12555 if add_trailing_newline {
12556 text.push('\n');
12557 len += 1;
12558 }
12559 clipboard_selections.push(ClipboardSelection {
12560 len,
12561 is_entire_line,
12562 first_line_indent: buffer
12563 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12564 .len,
12565 });
12566 }
12567 }
12568 }
12569
12570 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12571 text,
12572 clipboard_selections,
12573 ));
12574 }
12575
12576 pub fn do_paste(
12577 &mut self,
12578 text: &String,
12579 clipboard_selections: Option<Vec<ClipboardSelection>>,
12580 handle_entire_lines: bool,
12581 window: &mut Window,
12582 cx: &mut Context<Self>,
12583 ) {
12584 if self.read_only(cx) {
12585 return;
12586 }
12587
12588 let clipboard_text = Cow::Borrowed(text.as_str());
12589
12590 self.transact(window, cx, |this, window, cx| {
12591 let had_active_edit_prediction = this.has_active_edit_prediction();
12592 let display_map = this.display_snapshot(cx);
12593 let old_selections = this.selections.all::<usize>(&display_map);
12594 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12595
12596 if let Some(mut clipboard_selections) = clipboard_selections {
12597 let all_selections_were_entire_line =
12598 clipboard_selections.iter().all(|s| s.is_entire_line);
12599 let first_selection_indent_column =
12600 clipboard_selections.first().map(|s| s.first_line_indent);
12601 if clipboard_selections.len() != old_selections.len() {
12602 clipboard_selections.drain(..);
12603 }
12604 let mut auto_indent_on_paste = true;
12605
12606 this.buffer.update(cx, |buffer, cx| {
12607 let snapshot = buffer.read(cx);
12608 auto_indent_on_paste = snapshot
12609 .language_settings_at(cursor_offset, cx)
12610 .auto_indent_on_paste;
12611
12612 let mut start_offset = 0;
12613 let mut edits = Vec::new();
12614 let mut original_indent_columns = Vec::new();
12615 for (ix, selection) in old_selections.iter().enumerate() {
12616 let to_insert;
12617 let entire_line;
12618 let original_indent_column;
12619 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12620 let end_offset = start_offset + clipboard_selection.len;
12621 to_insert = &clipboard_text[start_offset..end_offset];
12622 entire_line = clipboard_selection.is_entire_line;
12623 start_offset = end_offset + 1;
12624 original_indent_column = Some(clipboard_selection.first_line_indent);
12625 } else {
12626 to_insert = &*clipboard_text;
12627 entire_line = all_selections_were_entire_line;
12628 original_indent_column = first_selection_indent_column
12629 }
12630
12631 let (range, to_insert) =
12632 if selection.is_empty() && handle_entire_lines && entire_line {
12633 // If the corresponding selection was empty when this slice of the
12634 // clipboard text was written, then the entire line containing the
12635 // selection was copied. If this selection is also currently empty,
12636 // then paste the line before the current line of the buffer.
12637 let column = selection.start.to_point(&snapshot).column as usize;
12638 let line_start = selection.start - column;
12639 (line_start..line_start, Cow::Borrowed(to_insert))
12640 } else {
12641 let language = snapshot.language_at(selection.head());
12642 let range = selection.range();
12643 if let Some(language) = language
12644 && language.name() == "Markdown".into()
12645 {
12646 edit_for_markdown_paste(
12647 &snapshot,
12648 range,
12649 to_insert,
12650 url::Url::parse(to_insert).ok(),
12651 )
12652 } else {
12653 (range, Cow::Borrowed(to_insert))
12654 }
12655 };
12656
12657 edits.push((range, to_insert));
12658 original_indent_columns.push(original_indent_column);
12659 }
12660 drop(snapshot);
12661
12662 buffer.edit(
12663 edits,
12664 if auto_indent_on_paste {
12665 Some(AutoindentMode::Block {
12666 original_indent_columns,
12667 })
12668 } else {
12669 None
12670 },
12671 cx,
12672 );
12673 });
12674
12675 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12676 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12677 } else {
12678 let url = url::Url::parse(&clipboard_text).ok();
12679
12680 let auto_indent_mode = if !clipboard_text.is_empty() {
12681 Some(AutoindentMode::Block {
12682 original_indent_columns: Vec::new(),
12683 })
12684 } else {
12685 None
12686 };
12687
12688 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12689 let snapshot = buffer.snapshot(cx);
12690
12691 let anchors = old_selections
12692 .iter()
12693 .map(|s| {
12694 let anchor = snapshot.anchor_after(s.head());
12695 s.map(|_| anchor)
12696 })
12697 .collect::<Vec<_>>();
12698
12699 let mut edits = Vec::new();
12700
12701 for selection in old_selections.iter() {
12702 let language = snapshot.language_at(selection.head());
12703 let range = selection.range();
12704
12705 let (edit_range, edit_text) = if let Some(language) = language
12706 && language.name() == "Markdown".into()
12707 {
12708 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12709 } else {
12710 (range, clipboard_text.clone())
12711 };
12712
12713 edits.push((edit_range, edit_text));
12714 }
12715
12716 drop(snapshot);
12717 buffer.edit(edits, auto_indent_mode, cx);
12718
12719 anchors
12720 });
12721
12722 this.change_selections(Default::default(), window, cx, |s| {
12723 s.select_anchors(selection_anchors);
12724 });
12725 }
12726
12727 let trigger_in_words =
12728 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12729
12730 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12731 });
12732 }
12733
12734 pub fn diff_clipboard_with_selection(
12735 &mut self,
12736 _: &DiffClipboardWithSelection,
12737 window: &mut Window,
12738 cx: &mut Context<Self>,
12739 ) {
12740 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12741
12742 if selections.is_empty() {
12743 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12744 return;
12745 };
12746
12747 let clipboard_text = match cx.read_from_clipboard() {
12748 Some(item) => match item.entries().first() {
12749 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12750 _ => None,
12751 },
12752 None => None,
12753 };
12754
12755 let Some(clipboard_text) = clipboard_text else {
12756 log::warn!("Clipboard doesn't contain text.");
12757 return;
12758 };
12759
12760 window.dispatch_action(
12761 Box::new(DiffClipboardWithSelectionData {
12762 clipboard_text,
12763 editor: cx.entity(),
12764 }),
12765 cx,
12766 );
12767 }
12768
12769 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12770 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12771 if let Some(item) = cx.read_from_clipboard() {
12772 let entries = item.entries();
12773
12774 match entries.first() {
12775 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12776 // of all the pasted entries.
12777 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12778 .do_paste(
12779 clipboard_string.text(),
12780 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12781 true,
12782 window,
12783 cx,
12784 ),
12785 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12786 }
12787 }
12788 }
12789
12790 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12791 if self.read_only(cx) {
12792 return;
12793 }
12794
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12796
12797 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12798 if let Some((selections, _)) =
12799 self.selection_history.transaction(transaction_id).cloned()
12800 {
12801 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12802 s.select_anchors(selections.to_vec());
12803 });
12804 } else {
12805 log::error!(
12806 "No entry in selection_history found for undo. \
12807 This may correspond to a bug where undo does not update the selection. \
12808 If this is occurring, please add details to \
12809 https://github.com/zed-industries/zed/issues/22692"
12810 );
12811 }
12812 self.request_autoscroll(Autoscroll::fit(), cx);
12813 self.unmark_text(window, cx);
12814 self.refresh_edit_prediction(true, false, window, cx);
12815 cx.emit(EditorEvent::Edited { transaction_id });
12816 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12817 }
12818 }
12819
12820 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12821 if self.read_only(cx) {
12822 return;
12823 }
12824
12825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12826
12827 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12828 if let Some((_, Some(selections))) =
12829 self.selection_history.transaction(transaction_id).cloned()
12830 {
12831 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12832 s.select_anchors(selections.to_vec());
12833 });
12834 } else {
12835 log::error!(
12836 "No entry in selection_history found for redo. \
12837 This may correspond to a bug where undo does not update the selection. \
12838 If this is occurring, please add details to \
12839 https://github.com/zed-industries/zed/issues/22692"
12840 );
12841 }
12842 self.request_autoscroll(Autoscroll::fit(), cx);
12843 self.unmark_text(window, cx);
12844 self.refresh_edit_prediction(true, false, window, cx);
12845 cx.emit(EditorEvent::Edited { transaction_id });
12846 }
12847 }
12848
12849 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12850 self.buffer
12851 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12852 }
12853
12854 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12855 self.buffer
12856 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12857 }
12858
12859 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12861 self.change_selections(Default::default(), window, cx, |s| {
12862 s.move_with(|map, selection| {
12863 let cursor = if selection.is_empty() {
12864 movement::left(map, selection.start)
12865 } else {
12866 selection.start
12867 };
12868 selection.collapse_to(cursor, SelectionGoal::None);
12869 });
12870 })
12871 }
12872
12873 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12875 self.change_selections(Default::default(), window, cx, |s| {
12876 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12877 })
12878 }
12879
12880 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12882 self.change_selections(Default::default(), window, cx, |s| {
12883 s.move_with(|map, selection| {
12884 let cursor = if selection.is_empty() {
12885 movement::right(map, selection.end)
12886 } else {
12887 selection.end
12888 };
12889 selection.collapse_to(cursor, SelectionGoal::None)
12890 });
12891 })
12892 }
12893
12894 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12896 self.change_selections(Default::default(), window, cx, |s| {
12897 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12898 });
12899 }
12900
12901 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12902 if self.take_rename(true, window, cx).is_some() {
12903 return;
12904 }
12905
12906 if self.mode.is_single_line() {
12907 cx.propagate();
12908 return;
12909 }
12910
12911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12912
12913 let text_layout_details = &self.text_layout_details(window);
12914 let selection_count = self.selections.count();
12915 let first_selection = self.selections.first_anchor();
12916
12917 self.change_selections(Default::default(), window, cx, |s| {
12918 s.move_with(|map, selection| {
12919 if !selection.is_empty() {
12920 selection.goal = SelectionGoal::None;
12921 }
12922 let (cursor, goal) = movement::up(
12923 map,
12924 selection.start,
12925 selection.goal,
12926 false,
12927 text_layout_details,
12928 );
12929 selection.collapse_to(cursor, goal);
12930 });
12931 });
12932
12933 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12934 {
12935 cx.propagate();
12936 }
12937 }
12938
12939 pub fn move_up_by_lines(
12940 &mut self,
12941 action: &MoveUpByLines,
12942 window: &mut Window,
12943 cx: &mut Context<Self>,
12944 ) {
12945 if self.take_rename(true, window, cx).is_some() {
12946 return;
12947 }
12948
12949 if self.mode.is_single_line() {
12950 cx.propagate();
12951 return;
12952 }
12953
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955
12956 let text_layout_details = &self.text_layout_details(window);
12957
12958 self.change_selections(Default::default(), window, cx, |s| {
12959 s.move_with(|map, selection| {
12960 if !selection.is_empty() {
12961 selection.goal = SelectionGoal::None;
12962 }
12963 let (cursor, goal) = movement::up_by_rows(
12964 map,
12965 selection.start,
12966 action.lines,
12967 selection.goal,
12968 false,
12969 text_layout_details,
12970 );
12971 selection.collapse_to(cursor, goal);
12972 });
12973 })
12974 }
12975
12976 pub fn move_down_by_lines(
12977 &mut self,
12978 action: &MoveDownByLines,
12979 window: &mut Window,
12980 cx: &mut Context<Self>,
12981 ) {
12982 if self.take_rename(true, window, cx).is_some() {
12983 return;
12984 }
12985
12986 if self.mode.is_single_line() {
12987 cx.propagate();
12988 return;
12989 }
12990
12991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12992
12993 let text_layout_details = &self.text_layout_details(window);
12994
12995 self.change_selections(Default::default(), window, cx, |s| {
12996 s.move_with(|map, selection| {
12997 if !selection.is_empty() {
12998 selection.goal = SelectionGoal::None;
12999 }
13000 let (cursor, goal) = movement::down_by_rows(
13001 map,
13002 selection.start,
13003 action.lines,
13004 selection.goal,
13005 false,
13006 text_layout_details,
13007 );
13008 selection.collapse_to(cursor, goal);
13009 });
13010 })
13011 }
13012
13013 pub fn select_down_by_lines(
13014 &mut self,
13015 action: &SelectDownByLines,
13016 window: &mut Window,
13017 cx: &mut Context<Self>,
13018 ) {
13019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13020 let text_layout_details = &self.text_layout_details(window);
13021 self.change_selections(Default::default(), window, cx, |s| {
13022 s.move_heads_with(|map, head, goal| {
13023 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13024 })
13025 })
13026 }
13027
13028 pub fn select_up_by_lines(
13029 &mut self,
13030 action: &SelectUpByLines,
13031 window: &mut Window,
13032 cx: &mut Context<Self>,
13033 ) {
13034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13035 let text_layout_details = &self.text_layout_details(window);
13036 self.change_selections(Default::default(), window, cx, |s| {
13037 s.move_heads_with(|map, head, goal| {
13038 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13039 })
13040 })
13041 }
13042
13043 pub fn select_page_up(
13044 &mut self,
13045 _: &SelectPageUp,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 let Some(row_count) = self.visible_row_count() else {
13050 return;
13051 };
13052
13053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13054
13055 let text_layout_details = &self.text_layout_details(window);
13056
13057 self.change_selections(Default::default(), window, cx, |s| {
13058 s.move_heads_with(|map, head, goal| {
13059 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13060 })
13061 })
13062 }
13063
13064 pub fn move_page_up(
13065 &mut self,
13066 action: &MovePageUp,
13067 window: &mut Window,
13068 cx: &mut Context<Self>,
13069 ) {
13070 if self.take_rename(true, window, cx).is_some() {
13071 return;
13072 }
13073
13074 if self
13075 .context_menu
13076 .borrow_mut()
13077 .as_mut()
13078 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13079 .unwrap_or(false)
13080 {
13081 return;
13082 }
13083
13084 if matches!(self.mode, EditorMode::SingleLine) {
13085 cx.propagate();
13086 return;
13087 }
13088
13089 let Some(row_count) = self.visible_row_count() else {
13090 return;
13091 };
13092
13093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13094
13095 let effects = if action.center_cursor {
13096 SelectionEffects::scroll(Autoscroll::center())
13097 } else {
13098 SelectionEffects::default()
13099 };
13100
13101 let text_layout_details = &self.text_layout_details(window);
13102
13103 self.change_selections(effects, window, cx, |s| {
13104 s.move_with(|map, selection| {
13105 if !selection.is_empty() {
13106 selection.goal = SelectionGoal::None;
13107 }
13108 let (cursor, goal) = movement::up_by_rows(
13109 map,
13110 selection.end,
13111 row_count,
13112 selection.goal,
13113 false,
13114 text_layout_details,
13115 );
13116 selection.collapse_to(cursor, goal);
13117 });
13118 });
13119 }
13120
13121 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13123 let text_layout_details = &self.text_layout_details(window);
13124 self.change_selections(Default::default(), window, cx, |s| {
13125 s.move_heads_with(|map, head, goal| {
13126 movement::up(map, head, goal, false, text_layout_details)
13127 })
13128 })
13129 }
13130
13131 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13132 self.take_rename(true, window, cx);
13133
13134 if self.mode.is_single_line() {
13135 cx.propagate();
13136 return;
13137 }
13138
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140
13141 let text_layout_details = &self.text_layout_details(window);
13142 let selection_count = self.selections.count();
13143 let first_selection = self.selections.first_anchor();
13144
13145 self.change_selections(Default::default(), window, cx, |s| {
13146 s.move_with(|map, selection| {
13147 if !selection.is_empty() {
13148 selection.goal = SelectionGoal::None;
13149 }
13150 let (cursor, goal) = movement::down(
13151 map,
13152 selection.end,
13153 selection.goal,
13154 false,
13155 text_layout_details,
13156 );
13157 selection.collapse_to(cursor, goal);
13158 });
13159 });
13160
13161 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13162 {
13163 cx.propagate();
13164 }
13165 }
13166
13167 pub fn select_page_down(
13168 &mut self,
13169 _: &SelectPageDown,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 let Some(row_count) = self.visible_row_count() else {
13174 return;
13175 };
13176
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13178
13179 let text_layout_details = &self.text_layout_details(window);
13180
13181 self.change_selections(Default::default(), window, cx, |s| {
13182 s.move_heads_with(|map, head, goal| {
13183 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13184 })
13185 })
13186 }
13187
13188 pub fn move_page_down(
13189 &mut self,
13190 action: &MovePageDown,
13191 window: &mut Window,
13192 cx: &mut Context<Self>,
13193 ) {
13194 if self.take_rename(true, window, cx).is_some() {
13195 return;
13196 }
13197
13198 if self
13199 .context_menu
13200 .borrow_mut()
13201 .as_mut()
13202 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13203 .unwrap_or(false)
13204 {
13205 return;
13206 }
13207
13208 if matches!(self.mode, EditorMode::SingleLine) {
13209 cx.propagate();
13210 return;
13211 }
13212
13213 let Some(row_count) = self.visible_row_count() else {
13214 return;
13215 };
13216
13217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13218
13219 let effects = if action.center_cursor {
13220 SelectionEffects::scroll(Autoscroll::center())
13221 } else {
13222 SelectionEffects::default()
13223 };
13224
13225 let text_layout_details = &self.text_layout_details(window);
13226 self.change_selections(effects, window, cx, |s| {
13227 s.move_with(|map, selection| {
13228 if !selection.is_empty() {
13229 selection.goal = SelectionGoal::None;
13230 }
13231 let (cursor, goal) = movement::down_by_rows(
13232 map,
13233 selection.end,
13234 row_count,
13235 selection.goal,
13236 false,
13237 text_layout_details,
13238 );
13239 selection.collapse_to(cursor, goal);
13240 });
13241 });
13242 }
13243
13244 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246 let text_layout_details = &self.text_layout_details(window);
13247 self.change_selections(Default::default(), window, cx, |s| {
13248 s.move_heads_with(|map, head, goal| {
13249 movement::down(map, head, goal, false, text_layout_details)
13250 })
13251 });
13252 }
13253
13254 pub fn context_menu_first(
13255 &mut self,
13256 _: &ContextMenuFirst,
13257 window: &mut Window,
13258 cx: &mut Context<Self>,
13259 ) {
13260 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13261 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13262 }
13263 }
13264
13265 pub fn context_menu_prev(
13266 &mut self,
13267 _: &ContextMenuPrevious,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13272 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13273 }
13274 }
13275
13276 pub fn context_menu_next(
13277 &mut self,
13278 _: &ContextMenuNext,
13279 window: &mut Window,
13280 cx: &mut Context<Self>,
13281 ) {
13282 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13283 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13284 }
13285 }
13286
13287 pub fn context_menu_last(
13288 &mut self,
13289 _: &ContextMenuLast,
13290 window: &mut Window,
13291 cx: &mut Context<Self>,
13292 ) {
13293 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13294 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13295 }
13296 }
13297
13298 pub fn signature_help_prev(
13299 &mut self,
13300 _: &SignatureHelpPrevious,
13301 _: &mut Window,
13302 cx: &mut Context<Self>,
13303 ) {
13304 if let Some(popover) = self.signature_help_state.popover_mut() {
13305 if popover.current_signature == 0 {
13306 popover.current_signature = popover.signatures.len() - 1;
13307 } else {
13308 popover.current_signature -= 1;
13309 }
13310 cx.notify();
13311 }
13312 }
13313
13314 pub fn signature_help_next(
13315 &mut self,
13316 _: &SignatureHelpNext,
13317 _: &mut Window,
13318 cx: &mut Context<Self>,
13319 ) {
13320 if let Some(popover) = self.signature_help_state.popover_mut() {
13321 if popover.current_signature + 1 == popover.signatures.len() {
13322 popover.current_signature = 0;
13323 } else {
13324 popover.current_signature += 1;
13325 }
13326 cx.notify();
13327 }
13328 }
13329
13330 pub fn move_to_previous_word_start(
13331 &mut self,
13332 _: &MoveToPreviousWordStart,
13333 window: &mut Window,
13334 cx: &mut Context<Self>,
13335 ) {
13336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13337 self.change_selections(Default::default(), window, cx, |s| {
13338 s.move_cursors_with(|map, head, _| {
13339 (
13340 movement::previous_word_start(map, head),
13341 SelectionGoal::None,
13342 )
13343 });
13344 })
13345 }
13346
13347 pub fn move_to_previous_subword_start(
13348 &mut self,
13349 _: &MoveToPreviousSubwordStart,
13350 window: &mut Window,
13351 cx: &mut Context<Self>,
13352 ) {
13353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13354 self.change_selections(Default::default(), window, cx, |s| {
13355 s.move_cursors_with(|map, head, _| {
13356 (
13357 movement::previous_subword_start(map, head),
13358 SelectionGoal::None,
13359 )
13360 });
13361 })
13362 }
13363
13364 pub fn select_to_previous_word_start(
13365 &mut self,
13366 _: &SelectToPreviousWordStart,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13371 self.change_selections(Default::default(), window, cx, |s| {
13372 s.move_heads_with(|map, head, _| {
13373 (
13374 movement::previous_word_start(map, head),
13375 SelectionGoal::None,
13376 )
13377 });
13378 })
13379 }
13380
13381 pub fn select_to_previous_subword_start(
13382 &mut self,
13383 _: &SelectToPreviousSubwordStart,
13384 window: &mut Window,
13385 cx: &mut Context<Self>,
13386 ) {
13387 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13388 self.change_selections(Default::default(), window, cx, |s| {
13389 s.move_heads_with(|map, head, _| {
13390 (
13391 movement::previous_subword_start(map, head),
13392 SelectionGoal::None,
13393 )
13394 });
13395 })
13396 }
13397
13398 pub fn delete_to_previous_word_start(
13399 &mut self,
13400 action: &DeleteToPreviousWordStart,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13405 self.transact(window, cx, |this, window, cx| {
13406 this.select_autoclose_pair(window, cx);
13407 this.change_selections(Default::default(), window, cx, |s| {
13408 s.move_with(|map, selection| {
13409 if selection.is_empty() {
13410 let mut cursor = if action.ignore_newlines {
13411 movement::previous_word_start(map, selection.head())
13412 } else {
13413 movement::previous_word_start_or_newline(map, selection.head())
13414 };
13415 cursor = movement::adjust_greedy_deletion(
13416 map,
13417 selection.head(),
13418 cursor,
13419 action.ignore_brackets,
13420 );
13421 selection.set_head(cursor, SelectionGoal::None);
13422 }
13423 });
13424 });
13425 this.insert("", window, cx);
13426 });
13427 }
13428
13429 pub fn delete_to_previous_subword_start(
13430 &mut self,
13431 _: &DeleteToPreviousSubwordStart,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13436 self.transact(window, cx, |this, window, cx| {
13437 this.select_autoclose_pair(window, cx);
13438 this.change_selections(Default::default(), window, cx, |s| {
13439 s.move_with(|map, selection| {
13440 if selection.is_empty() {
13441 let mut cursor = movement::previous_subword_start(map, selection.head());
13442 cursor =
13443 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13444 selection.set_head(cursor, SelectionGoal::None);
13445 }
13446 });
13447 });
13448 this.insert("", window, cx);
13449 });
13450 }
13451
13452 pub fn move_to_next_word_end(
13453 &mut self,
13454 _: &MoveToNextWordEnd,
13455 window: &mut Window,
13456 cx: &mut Context<Self>,
13457 ) {
13458 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13459 self.change_selections(Default::default(), window, cx, |s| {
13460 s.move_cursors_with(|map, head, _| {
13461 (movement::next_word_end(map, head), SelectionGoal::None)
13462 });
13463 })
13464 }
13465
13466 pub fn move_to_next_subword_end(
13467 &mut self,
13468 _: &MoveToNextSubwordEnd,
13469 window: &mut Window,
13470 cx: &mut Context<Self>,
13471 ) {
13472 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13473 self.change_selections(Default::default(), window, cx, |s| {
13474 s.move_cursors_with(|map, head, _| {
13475 (movement::next_subword_end(map, head), SelectionGoal::None)
13476 });
13477 })
13478 }
13479
13480 pub fn select_to_next_word_end(
13481 &mut self,
13482 _: &SelectToNextWordEnd,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13487 self.change_selections(Default::default(), window, cx, |s| {
13488 s.move_heads_with(|map, head, _| {
13489 (movement::next_word_end(map, head), SelectionGoal::None)
13490 });
13491 })
13492 }
13493
13494 pub fn select_to_next_subword_end(
13495 &mut self,
13496 _: &SelectToNextSubwordEnd,
13497 window: &mut Window,
13498 cx: &mut Context<Self>,
13499 ) {
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501 self.change_selections(Default::default(), window, cx, |s| {
13502 s.move_heads_with(|map, head, _| {
13503 (movement::next_subword_end(map, head), SelectionGoal::None)
13504 });
13505 })
13506 }
13507
13508 pub fn delete_to_next_word_end(
13509 &mut self,
13510 action: &DeleteToNextWordEnd,
13511 window: &mut Window,
13512 cx: &mut Context<Self>,
13513 ) {
13514 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13515 self.transact(window, cx, |this, window, cx| {
13516 this.change_selections(Default::default(), window, cx, |s| {
13517 s.move_with(|map, selection| {
13518 if selection.is_empty() {
13519 let mut cursor = if action.ignore_newlines {
13520 movement::next_word_end(map, selection.head())
13521 } else {
13522 movement::next_word_end_or_newline(map, selection.head())
13523 };
13524 cursor = movement::adjust_greedy_deletion(
13525 map,
13526 selection.head(),
13527 cursor,
13528 action.ignore_brackets,
13529 );
13530 selection.set_head(cursor, SelectionGoal::None);
13531 }
13532 });
13533 });
13534 this.insert("", window, cx);
13535 });
13536 }
13537
13538 pub fn delete_to_next_subword_end(
13539 &mut self,
13540 _: &DeleteToNextSubwordEnd,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) {
13544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13545 self.transact(window, cx, |this, window, cx| {
13546 this.change_selections(Default::default(), window, cx, |s| {
13547 s.move_with(|map, selection| {
13548 if selection.is_empty() {
13549 let mut cursor = movement::next_subword_end(map, selection.head());
13550 cursor =
13551 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13552 selection.set_head(cursor, SelectionGoal::None);
13553 }
13554 });
13555 });
13556 this.insert("", window, cx);
13557 });
13558 }
13559
13560 pub fn move_to_beginning_of_line(
13561 &mut self,
13562 action: &MoveToBeginningOfLine,
13563 window: &mut Window,
13564 cx: &mut Context<Self>,
13565 ) {
13566 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13567 self.change_selections(Default::default(), window, cx, |s| {
13568 s.move_cursors_with(|map, head, _| {
13569 (
13570 movement::indented_line_beginning(
13571 map,
13572 head,
13573 action.stop_at_soft_wraps,
13574 action.stop_at_indent,
13575 ),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_beginning_of_line(
13583 &mut self,
13584 action: &SelectToBeginningOfLine,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_heads_with(|map, head, _| {
13591 (
13592 movement::indented_line_beginning(
13593 map,
13594 head,
13595 action.stop_at_soft_wraps,
13596 action.stop_at_indent,
13597 ),
13598 SelectionGoal::None,
13599 )
13600 });
13601 });
13602 }
13603
13604 pub fn delete_to_beginning_of_line(
13605 &mut self,
13606 action: &DeleteToBeginningOfLine,
13607 window: &mut Window,
13608 cx: &mut Context<Self>,
13609 ) {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13611 self.transact(window, cx, |this, window, cx| {
13612 this.change_selections(Default::default(), window, cx, |s| {
13613 s.move_with(|_, selection| {
13614 selection.reversed = true;
13615 });
13616 });
13617
13618 this.select_to_beginning_of_line(
13619 &SelectToBeginningOfLine {
13620 stop_at_soft_wraps: false,
13621 stop_at_indent: action.stop_at_indent,
13622 },
13623 window,
13624 cx,
13625 );
13626 this.backspace(&Backspace, window, cx);
13627 });
13628 }
13629
13630 pub fn move_to_end_of_line(
13631 &mut self,
13632 action: &MoveToEndOfLine,
13633 window: &mut Window,
13634 cx: &mut Context<Self>,
13635 ) {
13636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13637 self.change_selections(Default::default(), window, cx, |s| {
13638 s.move_cursors_with(|map, head, _| {
13639 (
13640 movement::line_end(map, head, action.stop_at_soft_wraps),
13641 SelectionGoal::None,
13642 )
13643 });
13644 })
13645 }
13646
13647 pub fn select_to_end_of_line(
13648 &mut self,
13649 action: &SelectToEndOfLine,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.move_heads_with(|map, head, _| {
13656 (
13657 movement::line_end(map, head, action.stop_at_soft_wraps),
13658 SelectionGoal::None,
13659 )
13660 });
13661 })
13662 }
13663
13664 pub fn delete_to_end_of_line(
13665 &mut self,
13666 _: &DeleteToEndOfLine,
13667 window: &mut Window,
13668 cx: &mut Context<Self>,
13669 ) {
13670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13671 self.transact(window, cx, |this, window, cx| {
13672 this.select_to_end_of_line(
13673 &SelectToEndOfLine {
13674 stop_at_soft_wraps: false,
13675 },
13676 window,
13677 cx,
13678 );
13679 this.delete(&Delete, window, cx);
13680 });
13681 }
13682
13683 pub fn cut_to_end_of_line(
13684 &mut self,
13685 action: &CutToEndOfLine,
13686 window: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13690 self.transact(window, cx, |this, window, cx| {
13691 this.select_to_end_of_line(
13692 &SelectToEndOfLine {
13693 stop_at_soft_wraps: false,
13694 },
13695 window,
13696 cx,
13697 );
13698 if !action.stop_at_newlines {
13699 this.change_selections(Default::default(), window, cx, |s| {
13700 s.move_with(|_, sel| {
13701 if sel.is_empty() {
13702 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13703 }
13704 });
13705 });
13706 }
13707 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13708 let item = this.cut_common(false, window, cx);
13709 cx.write_to_clipboard(item);
13710 });
13711 }
13712
13713 pub fn move_to_start_of_paragraph(
13714 &mut self,
13715 _: &MoveToStartOfParagraph,
13716 window: &mut Window,
13717 cx: &mut Context<Self>,
13718 ) {
13719 if matches!(self.mode, EditorMode::SingleLine) {
13720 cx.propagate();
13721 return;
13722 }
13723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|map, selection| {
13726 selection.collapse_to(
13727 movement::start_of_paragraph(map, selection.head(), 1),
13728 SelectionGoal::None,
13729 )
13730 });
13731 })
13732 }
13733
13734 pub fn move_to_end_of_paragraph(
13735 &mut self,
13736 _: &MoveToEndOfParagraph,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 if matches!(self.mode, EditorMode::SingleLine) {
13741 cx.propagate();
13742 return;
13743 }
13744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13745 self.change_selections(Default::default(), window, cx, |s| {
13746 s.move_with(|map, selection| {
13747 selection.collapse_to(
13748 movement::end_of_paragraph(map, selection.head(), 1),
13749 SelectionGoal::None,
13750 )
13751 });
13752 })
13753 }
13754
13755 pub fn select_to_start_of_paragraph(
13756 &mut self,
13757 _: &SelectToStartOfParagraph,
13758 window: &mut Window,
13759 cx: &mut Context<Self>,
13760 ) {
13761 if matches!(self.mode, EditorMode::SingleLine) {
13762 cx.propagate();
13763 return;
13764 }
13765 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13766 self.change_selections(Default::default(), window, cx, |s| {
13767 s.move_heads_with(|map, head, _| {
13768 (
13769 movement::start_of_paragraph(map, head, 1),
13770 SelectionGoal::None,
13771 )
13772 });
13773 })
13774 }
13775
13776 pub fn select_to_end_of_paragraph(
13777 &mut self,
13778 _: &SelectToEndOfParagraph,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) {
13782 if matches!(self.mode, EditorMode::SingleLine) {
13783 cx.propagate();
13784 return;
13785 }
13786 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13787 self.change_selections(Default::default(), window, cx, |s| {
13788 s.move_heads_with(|map, head, _| {
13789 (
13790 movement::end_of_paragraph(map, head, 1),
13791 SelectionGoal::None,
13792 )
13793 });
13794 })
13795 }
13796
13797 pub fn move_to_start_of_excerpt(
13798 &mut self,
13799 _: &MoveToStartOfExcerpt,
13800 window: &mut Window,
13801 cx: &mut Context<Self>,
13802 ) {
13803 if matches!(self.mode, EditorMode::SingleLine) {
13804 cx.propagate();
13805 return;
13806 }
13807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13808 self.change_selections(Default::default(), window, cx, |s| {
13809 s.move_with(|map, selection| {
13810 selection.collapse_to(
13811 movement::start_of_excerpt(
13812 map,
13813 selection.head(),
13814 workspace::searchable::Direction::Prev,
13815 ),
13816 SelectionGoal::None,
13817 )
13818 });
13819 })
13820 }
13821
13822 pub fn move_to_start_of_next_excerpt(
13823 &mut self,
13824 _: &MoveToStartOfNextExcerpt,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 if matches!(self.mode, EditorMode::SingleLine) {
13829 cx.propagate();
13830 return;
13831 }
13832
13833 self.change_selections(Default::default(), window, cx, |s| {
13834 s.move_with(|map, selection| {
13835 selection.collapse_to(
13836 movement::start_of_excerpt(
13837 map,
13838 selection.head(),
13839 workspace::searchable::Direction::Next,
13840 ),
13841 SelectionGoal::None,
13842 )
13843 });
13844 })
13845 }
13846
13847 pub fn move_to_end_of_excerpt(
13848 &mut self,
13849 _: &MoveToEndOfExcerpt,
13850 window: &mut Window,
13851 cx: &mut Context<Self>,
13852 ) {
13853 if matches!(self.mode, EditorMode::SingleLine) {
13854 cx.propagate();
13855 return;
13856 }
13857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13858 self.change_selections(Default::default(), window, cx, |s| {
13859 s.move_with(|map, selection| {
13860 selection.collapse_to(
13861 movement::end_of_excerpt(
13862 map,
13863 selection.head(),
13864 workspace::searchable::Direction::Next,
13865 ),
13866 SelectionGoal::None,
13867 )
13868 });
13869 })
13870 }
13871
13872 pub fn move_to_end_of_previous_excerpt(
13873 &mut self,
13874 _: &MoveToEndOfPreviousExcerpt,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 if matches!(self.mode, EditorMode::SingleLine) {
13879 cx.propagate();
13880 return;
13881 }
13882 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.move_with(|map, selection| {
13885 selection.collapse_to(
13886 movement::end_of_excerpt(
13887 map,
13888 selection.head(),
13889 workspace::searchable::Direction::Prev,
13890 ),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn select_to_start_of_excerpt(
13898 &mut self,
13899 _: &SelectToStartOfExcerpt,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if matches!(self.mode, EditorMode::SingleLine) {
13904 cx.propagate();
13905 return;
13906 }
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 self.change_selections(Default::default(), window, cx, |s| {
13909 s.move_heads_with(|map, head, _| {
13910 (
13911 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13912 SelectionGoal::None,
13913 )
13914 });
13915 })
13916 }
13917
13918 pub fn select_to_start_of_next_excerpt(
13919 &mut self,
13920 _: &SelectToStartOfNextExcerpt,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 if matches!(self.mode, EditorMode::SingleLine) {
13925 cx.propagate();
13926 return;
13927 }
13928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13929 self.change_selections(Default::default(), window, cx, |s| {
13930 s.move_heads_with(|map, head, _| {
13931 (
13932 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13933 SelectionGoal::None,
13934 )
13935 });
13936 })
13937 }
13938
13939 pub fn select_to_end_of_excerpt(
13940 &mut self,
13941 _: &SelectToEndOfExcerpt,
13942 window: &mut Window,
13943 cx: &mut Context<Self>,
13944 ) {
13945 if matches!(self.mode, EditorMode::SingleLine) {
13946 cx.propagate();
13947 return;
13948 }
13949 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13950 self.change_selections(Default::default(), window, cx, |s| {
13951 s.move_heads_with(|map, head, _| {
13952 (
13953 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13954 SelectionGoal::None,
13955 )
13956 });
13957 })
13958 }
13959
13960 pub fn select_to_end_of_previous_excerpt(
13961 &mut self,
13962 _: &SelectToEndOfPreviousExcerpt,
13963 window: &mut Window,
13964 cx: &mut Context<Self>,
13965 ) {
13966 if matches!(self.mode, EditorMode::SingleLine) {
13967 cx.propagate();
13968 return;
13969 }
13970 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13971 self.change_selections(Default::default(), window, cx, |s| {
13972 s.move_heads_with(|map, head, _| {
13973 (
13974 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13975 SelectionGoal::None,
13976 )
13977 });
13978 })
13979 }
13980
13981 pub fn move_to_beginning(
13982 &mut self,
13983 _: &MoveToBeginning,
13984 window: &mut Window,
13985 cx: &mut Context<Self>,
13986 ) {
13987 if matches!(self.mode, EditorMode::SingleLine) {
13988 cx.propagate();
13989 return;
13990 }
13991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13992 self.change_selections(Default::default(), window, cx, |s| {
13993 s.select_ranges(vec![0..0]);
13994 });
13995 }
13996
13997 pub fn select_to_beginning(
13998 &mut self,
13999 _: &SelectToBeginning,
14000 window: &mut Window,
14001 cx: &mut Context<Self>,
14002 ) {
14003 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14004 selection.set_head(Point::zero(), SelectionGoal::None);
14005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14006 self.change_selections(Default::default(), window, cx, |s| {
14007 s.select(vec![selection]);
14008 });
14009 }
14010
14011 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14012 if matches!(self.mode, EditorMode::SingleLine) {
14013 cx.propagate();
14014 return;
14015 }
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 let cursor = self.buffer.read(cx).read(cx).len();
14018 self.change_selections(Default::default(), window, cx, |s| {
14019 s.select_ranges(vec![cursor..cursor])
14020 });
14021 }
14022
14023 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14024 self.nav_history = nav_history;
14025 }
14026
14027 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14028 self.nav_history.as_ref()
14029 }
14030
14031 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14032 self.push_to_nav_history(
14033 self.selections.newest_anchor().head(),
14034 None,
14035 false,
14036 true,
14037 cx,
14038 );
14039 }
14040
14041 fn push_to_nav_history(
14042 &mut self,
14043 cursor_anchor: Anchor,
14044 new_position: Option<Point>,
14045 is_deactivate: bool,
14046 always: bool,
14047 cx: &mut Context<Self>,
14048 ) {
14049 if let Some(nav_history) = self.nav_history.as_mut() {
14050 let buffer = self.buffer.read(cx).read(cx);
14051 let cursor_position = cursor_anchor.to_point(&buffer);
14052 let scroll_state = self.scroll_manager.anchor();
14053 let scroll_top_row = scroll_state.top_row(&buffer);
14054 drop(buffer);
14055
14056 if let Some(new_position) = new_position {
14057 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14058 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14059 return;
14060 }
14061 }
14062
14063 nav_history.push(
14064 Some(NavigationData {
14065 cursor_anchor,
14066 cursor_position,
14067 scroll_anchor: scroll_state,
14068 scroll_top_row,
14069 }),
14070 cx,
14071 );
14072 cx.emit(EditorEvent::PushedToNavHistory {
14073 anchor: cursor_anchor,
14074 is_deactivate,
14075 })
14076 }
14077 }
14078
14079 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14081 let buffer = self.buffer.read(cx).snapshot(cx);
14082 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14083 selection.set_head(buffer.len(), SelectionGoal::None);
14084 self.change_selections(Default::default(), window, cx, |s| {
14085 s.select(vec![selection]);
14086 });
14087 }
14088
14089 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 let end = self.buffer.read(cx).read(cx).len();
14092 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14093 s.select_ranges(vec![0..end]);
14094 });
14095 }
14096
14097 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14100 let mut selections = self.selections.all::<Point>(&display_map);
14101 let max_point = display_map.buffer_snapshot().max_point();
14102 for selection in &mut selections {
14103 let rows = selection.spanned_rows(true, &display_map);
14104 selection.start = Point::new(rows.start.0, 0);
14105 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14106 selection.reversed = false;
14107 }
14108 self.change_selections(Default::default(), window, cx, |s| {
14109 s.select(selections);
14110 });
14111 }
14112
14113 pub fn split_selection_into_lines(
14114 &mut self,
14115 action: &SplitSelectionIntoLines,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) {
14119 let selections = self
14120 .selections
14121 .all::<Point>(&self.display_snapshot(cx))
14122 .into_iter()
14123 .map(|selection| selection.start..selection.end)
14124 .collect::<Vec<_>>();
14125 self.unfold_ranges(&selections, true, true, cx);
14126
14127 let mut new_selection_ranges = Vec::new();
14128 {
14129 let buffer = self.buffer.read(cx).read(cx);
14130 for selection in selections {
14131 for row in selection.start.row..selection.end.row {
14132 let line_start = Point::new(row, 0);
14133 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14134
14135 if action.keep_selections {
14136 // Keep the selection range for each line
14137 let selection_start = if row == selection.start.row {
14138 selection.start
14139 } else {
14140 line_start
14141 };
14142 new_selection_ranges.push(selection_start..line_end);
14143 } else {
14144 // Collapse to cursor at end of line
14145 new_selection_ranges.push(line_end..line_end);
14146 }
14147 }
14148
14149 let is_multiline_selection = selection.start.row != selection.end.row;
14150 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14151 // so this action feels more ergonomic when paired with other selection operations
14152 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14153 if !should_skip_last {
14154 if action.keep_selections {
14155 if is_multiline_selection {
14156 let line_start = Point::new(selection.end.row, 0);
14157 new_selection_ranges.push(line_start..selection.end);
14158 } else {
14159 new_selection_ranges.push(selection.start..selection.end);
14160 }
14161 } else {
14162 new_selection_ranges.push(selection.end..selection.end);
14163 }
14164 }
14165 }
14166 }
14167 self.change_selections(Default::default(), window, cx, |s| {
14168 s.select_ranges(new_selection_ranges);
14169 });
14170 }
14171
14172 pub fn add_selection_above(
14173 &mut self,
14174 action: &AddSelectionAbove,
14175 window: &mut Window,
14176 cx: &mut Context<Self>,
14177 ) {
14178 self.add_selection(true, action.skip_soft_wrap, window, cx);
14179 }
14180
14181 pub fn add_selection_below(
14182 &mut self,
14183 action: &AddSelectionBelow,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 self.add_selection(false, action.skip_soft_wrap, window, cx);
14188 }
14189
14190 fn add_selection(
14191 &mut self,
14192 above: bool,
14193 skip_soft_wrap: bool,
14194 window: &mut Window,
14195 cx: &mut Context<Self>,
14196 ) {
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14198
14199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14200 let all_selections = self.selections.all::<Point>(&display_map);
14201 let text_layout_details = self.text_layout_details(window);
14202
14203 let (mut columnar_selections, new_selections_to_columnarize) = {
14204 if let Some(state) = self.add_selections_state.as_ref() {
14205 let columnar_selection_ids: HashSet<_> = state
14206 .groups
14207 .iter()
14208 .flat_map(|group| group.stack.iter())
14209 .copied()
14210 .collect();
14211
14212 all_selections
14213 .into_iter()
14214 .partition(|s| columnar_selection_ids.contains(&s.id))
14215 } else {
14216 (Vec::new(), all_selections)
14217 }
14218 };
14219
14220 let mut state = self
14221 .add_selections_state
14222 .take()
14223 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14224
14225 for selection in new_selections_to_columnarize {
14226 let range = selection.display_range(&display_map).sorted();
14227 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14228 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14229 let positions = start_x.min(end_x)..start_x.max(end_x);
14230 let mut stack = Vec::new();
14231 for row in range.start.row().0..=range.end.row().0 {
14232 if let Some(selection) = self.selections.build_columnar_selection(
14233 &display_map,
14234 DisplayRow(row),
14235 &positions,
14236 selection.reversed,
14237 &text_layout_details,
14238 ) {
14239 stack.push(selection.id);
14240 columnar_selections.push(selection);
14241 }
14242 }
14243 if !stack.is_empty() {
14244 if above {
14245 stack.reverse();
14246 }
14247 state.groups.push(AddSelectionsGroup { above, stack });
14248 }
14249 }
14250
14251 let mut final_selections = Vec::new();
14252 let end_row = if above {
14253 DisplayRow(0)
14254 } else {
14255 display_map.max_point().row()
14256 };
14257
14258 let mut last_added_item_per_group = HashMap::default();
14259 for group in state.groups.iter_mut() {
14260 if let Some(last_id) = group.stack.last() {
14261 last_added_item_per_group.insert(*last_id, group);
14262 }
14263 }
14264
14265 for selection in columnar_selections {
14266 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14267 if above == group.above {
14268 let range = selection.display_range(&display_map).sorted();
14269 debug_assert_eq!(range.start.row(), range.end.row());
14270 let mut row = range.start.row();
14271 let positions =
14272 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14273 Pixels::from(start)..Pixels::from(end)
14274 } else {
14275 let start_x =
14276 display_map.x_for_display_point(range.start, &text_layout_details);
14277 let end_x =
14278 display_map.x_for_display_point(range.end, &text_layout_details);
14279 start_x.min(end_x)..start_x.max(end_x)
14280 };
14281
14282 let mut maybe_new_selection = None;
14283 let direction = if above { -1 } else { 1 };
14284
14285 while row != end_row {
14286 if skip_soft_wrap {
14287 row = display_map
14288 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14289 .row();
14290 } else if above {
14291 row.0 -= 1;
14292 } else {
14293 row.0 += 1;
14294 }
14295
14296 if let Some(new_selection) = self.selections.build_columnar_selection(
14297 &display_map,
14298 row,
14299 &positions,
14300 selection.reversed,
14301 &text_layout_details,
14302 ) {
14303 maybe_new_selection = Some(new_selection);
14304 break;
14305 }
14306 }
14307
14308 if let Some(new_selection) = maybe_new_selection {
14309 group.stack.push(new_selection.id);
14310 if above {
14311 final_selections.push(new_selection);
14312 final_selections.push(selection);
14313 } else {
14314 final_selections.push(selection);
14315 final_selections.push(new_selection);
14316 }
14317 } else {
14318 final_selections.push(selection);
14319 }
14320 } else {
14321 group.stack.pop();
14322 }
14323 } else {
14324 final_selections.push(selection);
14325 }
14326 }
14327
14328 self.change_selections(Default::default(), window, cx, |s| {
14329 s.select(final_selections);
14330 });
14331
14332 let final_selection_ids: HashSet<_> = self
14333 .selections
14334 .all::<Point>(&display_map)
14335 .iter()
14336 .map(|s| s.id)
14337 .collect();
14338 state.groups.retain_mut(|group| {
14339 // selections might get merged above so we remove invalid items from stacks
14340 group.stack.retain(|id| final_selection_ids.contains(id));
14341
14342 // single selection in stack can be treated as initial state
14343 group.stack.len() > 1
14344 });
14345
14346 if !state.groups.is_empty() {
14347 self.add_selections_state = Some(state);
14348 }
14349 }
14350
14351 fn select_match_ranges(
14352 &mut self,
14353 range: Range<usize>,
14354 reversed: bool,
14355 replace_newest: bool,
14356 auto_scroll: Option<Autoscroll>,
14357 window: &mut Window,
14358 cx: &mut Context<Editor>,
14359 ) {
14360 self.unfold_ranges(
14361 std::slice::from_ref(&range),
14362 false,
14363 auto_scroll.is_some(),
14364 cx,
14365 );
14366 let effects = if let Some(scroll) = auto_scroll {
14367 SelectionEffects::scroll(scroll)
14368 } else {
14369 SelectionEffects::no_scroll()
14370 };
14371 self.change_selections(effects, window, cx, |s| {
14372 if replace_newest {
14373 s.delete(s.newest_anchor().id);
14374 }
14375 if reversed {
14376 s.insert_range(range.end..range.start);
14377 } else {
14378 s.insert_range(range);
14379 }
14380 });
14381 }
14382
14383 pub fn select_next_match_internal(
14384 &mut self,
14385 display_map: &DisplaySnapshot,
14386 replace_newest: bool,
14387 autoscroll: Option<Autoscroll>,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) -> Result<()> {
14391 let buffer = display_map.buffer_snapshot();
14392 let mut selections = self.selections.all::<usize>(&display_map);
14393 if let Some(mut select_next_state) = self.select_next_state.take() {
14394 let query = &select_next_state.query;
14395 if !select_next_state.done {
14396 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14397 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14398 let mut next_selected_range = None;
14399
14400 let bytes_after_last_selection =
14401 buffer.bytes_in_range(last_selection.end..buffer.len());
14402 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14403 let query_matches = query
14404 .stream_find_iter(bytes_after_last_selection)
14405 .map(|result| (last_selection.end, result))
14406 .chain(
14407 query
14408 .stream_find_iter(bytes_before_first_selection)
14409 .map(|result| (0, result)),
14410 );
14411
14412 for (start_offset, query_match) in query_matches {
14413 let query_match = query_match.unwrap(); // can only fail due to I/O
14414 let offset_range =
14415 start_offset + query_match.start()..start_offset + query_match.end();
14416
14417 if !select_next_state.wordwise
14418 || (!buffer.is_inside_word(offset_range.start, None)
14419 && !buffer.is_inside_word(offset_range.end, None))
14420 {
14421 let idx = selections
14422 .partition_point(|selection| selection.end <= offset_range.start);
14423 let overlaps = selections
14424 .get(idx)
14425 .map_or(false, |selection| selection.start < offset_range.end);
14426
14427 if !overlaps {
14428 next_selected_range = Some(offset_range);
14429 break;
14430 }
14431 }
14432 }
14433
14434 if let Some(next_selected_range) = next_selected_range {
14435 self.select_match_ranges(
14436 next_selected_range,
14437 last_selection.reversed,
14438 replace_newest,
14439 autoscroll,
14440 window,
14441 cx,
14442 );
14443 } else {
14444 select_next_state.done = true;
14445 }
14446 }
14447
14448 self.select_next_state = Some(select_next_state);
14449 } else {
14450 let mut only_carets = true;
14451 let mut same_text_selected = true;
14452 let mut selected_text = None;
14453
14454 let mut selections_iter = selections.iter().peekable();
14455 while let Some(selection) = selections_iter.next() {
14456 if selection.start != selection.end {
14457 only_carets = false;
14458 }
14459
14460 if same_text_selected {
14461 if selected_text.is_none() {
14462 selected_text =
14463 Some(buffer.text_for_range(selection.range()).collect::<String>());
14464 }
14465
14466 if let Some(next_selection) = selections_iter.peek() {
14467 if next_selection.range().len() == selection.range().len() {
14468 let next_selected_text = buffer
14469 .text_for_range(next_selection.range())
14470 .collect::<String>();
14471 if Some(next_selected_text) != selected_text {
14472 same_text_selected = false;
14473 selected_text = None;
14474 }
14475 } else {
14476 same_text_selected = false;
14477 selected_text = None;
14478 }
14479 }
14480 }
14481 }
14482
14483 if only_carets {
14484 for selection in &mut selections {
14485 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14486 selection.start = word_range.start;
14487 selection.end = word_range.end;
14488 selection.goal = SelectionGoal::None;
14489 selection.reversed = false;
14490 self.select_match_ranges(
14491 selection.start..selection.end,
14492 selection.reversed,
14493 replace_newest,
14494 autoscroll,
14495 window,
14496 cx,
14497 );
14498 }
14499
14500 if selections.len() == 1 {
14501 let selection = selections
14502 .last()
14503 .expect("ensured that there's only one selection");
14504 let query = buffer
14505 .text_for_range(selection.start..selection.end)
14506 .collect::<String>();
14507 let is_empty = query.is_empty();
14508 let select_state = SelectNextState {
14509 query: AhoCorasick::new(&[query])?,
14510 wordwise: true,
14511 done: is_empty,
14512 };
14513 self.select_next_state = Some(select_state);
14514 } else {
14515 self.select_next_state = None;
14516 }
14517 } else if let Some(selected_text) = selected_text {
14518 self.select_next_state = Some(SelectNextState {
14519 query: AhoCorasick::new(&[selected_text])?,
14520 wordwise: false,
14521 done: false,
14522 });
14523 self.select_next_match_internal(
14524 display_map,
14525 replace_newest,
14526 autoscroll,
14527 window,
14528 cx,
14529 )?;
14530 }
14531 }
14532 Ok(())
14533 }
14534
14535 pub fn select_all_matches(
14536 &mut self,
14537 _action: &SelectAllMatches,
14538 window: &mut Window,
14539 cx: &mut Context<Self>,
14540 ) -> Result<()> {
14541 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14542
14543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14544
14545 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14546 let Some(select_next_state) = self.select_next_state.as_mut() else {
14547 return Ok(());
14548 };
14549 if select_next_state.done {
14550 return Ok(());
14551 }
14552
14553 let mut new_selections = Vec::new();
14554
14555 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14556 let buffer = display_map.buffer_snapshot();
14557 let query_matches = select_next_state
14558 .query
14559 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14560
14561 for query_match in query_matches.into_iter() {
14562 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14563 let offset_range = if reversed {
14564 query_match.end()..query_match.start()
14565 } else {
14566 query_match.start()..query_match.end()
14567 };
14568
14569 if !select_next_state.wordwise
14570 || (!buffer.is_inside_word(offset_range.start, None)
14571 && !buffer.is_inside_word(offset_range.end, None))
14572 {
14573 new_selections.push(offset_range.start..offset_range.end);
14574 }
14575 }
14576
14577 select_next_state.done = true;
14578
14579 if new_selections.is_empty() {
14580 log::error!("bug: new_selections is empty in select_all_matches");
14581 return Ok(());
14582 }
14583
14584 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14585 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14586 selections.select_ranges(new_selections)
14587 });
14588
14589 Ok(())
14590 }
14591
14592 pub fn select_next(
14593 &mut self,
14594 action: &SelectNext,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) -> Result<()> {
14598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14599 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14600 self.select_next_match_internal(
14601 &display_map,
14602 action.replace_newest,
14603 Some(Autoscroll::newest()),
14604 window,
14605 cx,
14606 )?;
14607 Ok(())
14608 }
14609
14610 pub fn select_previous(
14611 &mut self,
14612 action: &SelectPrevious,
14613 window: &mut Window,
14614 cx: &mut Context<Self>,
14615 ) -> Result<()> {
14616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14617 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14618 let buffer = display_map.buffer_snapshot();
14619 let mut selections = self.selections.all::<usize>(&display_map);
14620 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14621 let query = &select_prev_state.query;
14622 if !select_prev_state.done {
14623 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14624 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14625 let mut next_selected_range = None;
14626 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14627 let bytes_before_last_selection =
14628 buffer.reversed_bytes_in_range(0..last_selection.start);
14629 let bytes_after_first_selection =
14630 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14631 let query_matches = query
14632 .stream_find_iter(bytes_before_last_selection)
14633 .map(|result| (last_selection.start, result))
14634 .chain(
14635 query
14636 .stream_find_iter(bytes_after_first_selection)
14637 .map(|result| (buffer.len(), result)),
14638 );
14639 for (end_offset, query_match) in query_matches {
14640 let query_match = query_match.unwrap(); // can only fail due to I/O
14641 let offset_range =
14642 end_offset - query_match.end()..end_offset - query_match.start();
14643
14644 if !select_prev_state.wordwise
14645 || (!buffer.is_inside_word(offset_range.start, None)
14646 && !buffer.is_inside_word(offset_range.end, None))
14647 {
14648 next_selected_range = Some(offset_range);
14649 break;
14650 }
14651 }
14652
14653 if let Some(next_selected_range) = next_selected_range {
14654 self.select_match_ranges(
14655 next_selected_range,
14656 last_selection.reversed,
14657 action.replace_newest,
14658 Some(Autoscroll::newest()),
14659 window,
14660 cx,
14661 );
14662 } else {
14663 select_prev_state.done = true;
14664 }
14665 }
14666
14667 self.select_prev_state = Some(select_prev_state);
14668 } else {
14669 let mut only_carets = true;
14670 let mut same_text_selected = true;
14671 let mut selected_text = None;
14672
14673 let mut selections_iter = selections.iter().peekable();
14674 while let Some(selection) = selections_iter.next() {
14675 if selection.start != selection.end {
14676 only_carets = false;
14677 }
14678
14679 if same_text_selected {
14680 if selected_text.is_none() {
14681 selected_text =
14682 Some(buffer.text_for_range(selection.range()).collect::<String>());
14683 }
14684
14685 if let Some(next_selection) = selections_iter.peek() {
14686 if next_selection.range().len() == selection.range().len() {
14687 let next_selected_text = buffer
14688 .text_for_range(next_selection.range())
14689 .collect::<String>();
14690 if Some(next_selected_text) != selected_text {
14691 same_text_selected = false;
14692 selected_text = None;
14693 }
14694 } else {
14695 same_text_selected = false;
14696 selected_text = None;
14697 }
14698 }
14699 }
14700 }
14701
14702 if only_carets {
14703 for selection in &mut selections {
14704 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14705 selection.start = word_range.start;
14706 selection.end = word_range.end;
14707 selection.goal = SelectionGoal::None;
14708 selection.reversed = false;
14709 self.select_match_ranges(
14710 selection.start..selection.end,
14711 selection.reversed,
14712 action.replace_newest,
14713 Some(Autoscroll::newest()),
14714 window,
14715 cx,
14716 );
14717 }
14718 if selections.len() == 1 {
14719 let selection = selections
14720 .last()
14721 .expect("ensured that there's only one selection");
14722 let query = buffer
14723 .text_for_range(selection.start..selection.end)
14724 .collect::<String>();
14725 let is_empty = query.is_empty();
14726 let select_state = SelectNextState {
14727 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14728 wordwise: true,
14729 done: is_empty,
14730 };
14731 self.select_prev_state = Some(select_state);
14732 } else {
14733 self.select_prev_state = None;
14734 }
14735 } else if let Some(selected_text) = selected_text {
14736 self.select_prev_state = Some(SelectNextState {
14737 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14738 wordwise: false,
14739 done: false,
14740 });
14741 self.select_previous(action, window, cx)?;
14742 }
14743 }
14744 Ok(())
14745 }
14746
14747 pub fn find_next_match(
14748 &mut self,
14749 _: &FindNextMatch,
14750 window: &mut Window,
14751 cx: &mut Context<Self>,
14752 ) -> Result<()> {
14753 let selections = self.selections.disjoint_anchors_arc();
14754 match selections.first() {
14755 Some(first) if selections.len() >= 2 => {
14756 self.change_selections(Default::default(), window, cx, |s| {
14757 s.select_ranges([first.range()]);
14758 });
14759 }
14760 _ => self.select_next(
14761 &SelectNext {
14762 replace_newest: true,
14763 },
14764 window,
14765 cx,
14766 )?,
14767 }
14768 Ok(())
14769 }
14770
14771 pub fn find_previous_match(
14772 &mut self,
14773 _: &FindPreviousMatch,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) -> Result<()> {
14777 let selections = self.selections.disjoint_anchors_arc();
14778 match selections.last() {
14779 Some(last) if selections.len() >= 2 => {
14780 self.change_selections(Default::default(), window, cx, |s| {
14781 s.select_ranges([last.range()]);
14782 });
14783 }
14784 _ => self.select_previous(
14785 &SelectPrevious {
14786 replace_newest: true,
14787 },
14788 window,
14789 cx,
14790 )?,
14791 }
14792 Ok(())
14793 }
14794
14795 pub fn toggle_comments(
14796 &mut self,
14797 action: &ToggleComments,
14798 window: &mut Window,
14799 cx: &mut Context<Self>,
14800 ) {
14801 if self.read_only(cx) {
14802 return;
14803 }
14804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14805 let text_layout_details = &self.text_layout_details(window);
14806 self.transact(window, cx, |this, window, cx| {
14807 let mut selections = this
14808 .selections
14809 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14810 let mut edits = Vec::new();
14811 let mut selection_edit_ranges = Vec::new();
14812 let mut last_toggled_row = None;
14813 let snapshot = this.buffer.read(cx).read(cx);
14814 let empty_str: Arc<str> = Arc::default();
14815 let mut suffixes_inserted = Vec::new();
14816 let ignore_indent = action.ignore_indent;
14817
14818 fn comment_prefix_range(
14819 snapshot: &MultiBufferSnapshot,
14820 row: MultiBufferRow,
14821 comment_prefix: &str,
14822 comment_prefix_whitespace: &str,
14823 ignore_indent: bool,
14824 ) -> Range<Point> {
14825 let indent_size = if ignore_indent {
14826 0
14827 } else {
14828 snapshot.indent_size_for_line(row).len
14829 };
14830
14831 let start = Point::new(row.0, indent_size);
14832
14833 let mut line_bytes = snapshot
14834 .bytes_in_range(start..snapshot.max_point())
14835 .flatten()
14836 .copied();
14837
14838 // If this line currently begins with the line comment prefix, then record
14839 // the range containing the prefix.
14840 if line_bytes
14841 .by_ref()
14842 .take(comment_prefix.len())
14843 .eq(comment_prefix.bytes())
14844 {
14845 // Include any whitespace that matches the comment prefix.
14846 let matching_whitespace_len = line_bytes
14847 .zip(comment_prefix_whitespace.bytes())
14848 .take_while(|(a, b)| a == b)
14849 .count() as u32;
14850 let end = Point::new(
14851 start.row,
14852 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14853 );
14854 start..end
14855 } else {
14856 start..start
14857 }
14858 }
14859
14860 fn comment_suffix_range(
14861 snapshot: &MultiBufferSnapshot,
14862 row: MultiBufferRow,
14863 comment_suffix: &str,
14864 comment_suffix_has_leading_space: bool,
14865 ) -> Range<Point> {
14866 let end = Point::new(row.0, snapshot.line_len(row));
14867 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14868
14869 let mut line_end_bytes = snapshot
14870 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14871 .flatten()
14872 .copied();
14873
14874 let leading_space_len = if suffix_start_column > 0
14875 && line_end_bytes.next() == Some(b' ')
14876 && comment_suffix_has_leading_space
14877 {
14878 1
14879 } else {
14880 0
14881 };
14882
14883 // If this line currently begins with the line comment prefix, then record
14884 // the range containing the prefix.
14885 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14886 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14887 start..end
14888 } else {
14889 end..end
14890 }
14891 }
14892
14893 // TODO: Handle selections that cross excerpts
14894 for selection in &mut selections {
14895 let start_column = snapshot
14896 .indent_size_for_line(MultiBufferRow(selection.start.row))
14897 .len;
14898 let language = if let Some(language) =
14899 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14900 {
14901 language
14902 } else {
14903 continue;
14904 };
14905
14906 selection_edit_ranges.clear();
14907
14908 // If multiple selections contain a given row, avoid processing that
14909 // row more than once.
14910 let mut start_row = MultiBufferRow(selection.start.row);
14911 if last_toggled_row == Some(start_row) {
14912 start_row = start_row.next_row();
14913 }
14914 let end_row =
14915 if selection.end.row > selection.start.row && selection.end.column == 0 {
14916 MultiBufferRow(selection.end.row - 1)
14917 } else {
14918 MultiBufferRow(selection.end.row)
14919 };
14920 last_toggled_row = Some(end_row);
14921
14922 if start_row > end_row {
14923 continue;
14924 }
14925
14926 // If the language has line comments, toggle those.
14927 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14928
14929 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14930 if ignore_indent {
14931 full_comment_prefixes = full_comment_prefixes
14932 .into_iter()
14933 .map(|s| Arc::from(s.trim_end()))
14934 .collect();
14935 }
14936
14937 if !full_comment_prefixes.is_empty() {
14938 let first_prefix = full_comment_prefixes
14939 .first()
14940 .expect("prefixes is non-empty");
14941 let prefix_trimmed_lengths = full_comment_prefixes
14942 .iter()
14943 .map(|p| p.trim_end_matches(' ').len())
14944 .collect::<SmallVec<[usize; 4]>>();
14945
14946 let mut all_selection_lines_are_comments = true;
14947
14948 for row in start_row.0..=end_row.0 {
14949 let row = MultiBufferRow(row);
14950 if start_row < end_row && snapshot.is_line_blank(row) {
14951 continue;
14952 }
14953
14954 let prefix_range = full_comment_prefixes
14955 .iter()
14956 .zip(prefix_trimmed_lengths.iter().copied())
14957 .map(|(prefix, trimmed_prefix_len)| {
14958 comment_prefix_range(
14959 snapshot.deref(),
14960 row,
14961 &prefix[..trimmed_prefix_len],
14962 &prefix[trimmed_prefix_len..],
14963 ignore_indent,
14964 )
14965 })
14966 .max_by_key(|range| range.end.column - range.start.column)
14967 .expect("prefixes is non-empty");
14968
14969 if prefix_range.is_empty() {
14970 all_selection_lines_are_comments = false;
14971 }
14972
14973 selection_edit_ranges.push(prefix_range);
14974 }
14975
14976 if all_selection_lines_are_comments {
14977 edits.extend(
14978 selection_edit_ranges
14979 .iter()
14980 .cloned()
14981 .map(|range| (range, empty_str.clone())),
14982 );
14983 } else {
14984 let min_column = selection_edit_ranges
14985 .iter()
14986 .map(|range| range.start.column)
14987 .min()
14988 .unwrap_or(0);
14989 edits.extend(selection_edit_ranges.iter().map(|range| {
14990 let position = Point::new(range.start.row, min_column);
14991 (position..position, first_prefix.clone())
14992 }));
14993 }
14994 } else if let Some(BlockCommentConfig {
14995 start: full_comment_prefix,
14996 end: comment_suffix,
14997 ..
14998 }) = language.block_comment()
14999 {
15000 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15001 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15002 let prefix_range = comment_prefix_range(
15003 snapshot.deref(),
15004 start_row,
15005 comment_prefix,
15006 comment_prefix_whitespace,
15007 ignore_indent,
15008 );
15009 let suffix_range = comment_suffix_range(
15010 snapshot.deref(),
15011 end_row,
15012 comment_suffix.trim_start_matches(' '),
15013 comment_suffix.starts_with(' '),
15014 );
15015
15016 if prefix_range.is_empty() || suffix_range.is_empty() {
15017 edits.push((
15018 prefix_range.start..prefix_range.start,
15019 full_comment_prefix.clone(),
15020 ));
15021 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15022 suffixes_inserted.push((end_row, comment_suffix.len()));
15023 } else {
15024 edits.push((prefix_range, empty_str.clone()));
15025 edits.push((suffix_range, empty_str.clone()));
15026 }
15027 } else {
15028 continue;
15029 }
15030 }
15031
15032 drop(snapshot);
15033 this.buffer.update(cx, |buffer, cx| {
15034 buffer.edit(edits, None, cx);
15035 });
15036
15037 // Adjust selections so that they end before any comment suffixes that
15038 // were inserted.
15039 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15040 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15041 let snapshot = this.buffer.read(cx).read(cx);
15042 for selection in &mut selections {
15043 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15044 match row.cmp(&MultiBufferRow(selection.end.row)) {
15045 Ordering::Less => {
15046 suffixes_inserted.next();
15047 continue;
15048 }
15049 Ordering::Greater => break,
15050 Ordering::Equal => {
15051 if selection.end.column == snapshot.line_len(row) {
15052 if selection.is_empty() {
15053 selection.start.column -= suffix_len as u32;
15054 }
15055 selection.end.column -= suffix_len as u32;
15056 }
15057 break;
15058 }
15059 }
15060 }
15061 }
15062
15063 drop(snapshot);
15064 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15065
15066 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15067 let selections_on_single_row = selections.windows(2).all(|selections| {
15068 selections[0].start.row == selections[1].start.row
15069 && selections[0].end.row == selections[1].end.row
15070 && selections[0].start.row == selections[0].end.row
15071 });
15072 let selections_selecting = selections
15073 .iter()
15074 .any(|selection| selection.start != selection.end);
15075 let advance_downwards = action.advance_downwards
15076 && selections_on_single_row
15077 && !selections_selecting
15078 && !matches!(this.mode, EditorMode::SingleLine);
15079
15080 if advance_downwards {
15081 let snapshot = this.buffer.read(cx).snapshot(cx);
15082
15083 this.change_selections(Default::default(), window, cx, |s| {
15084 s.move_cursors_with(|display_snapshot, display_point, _| {
15085 let mut point = display_point.to_point(display_snapshot);
15086 point.row += 1;
15087 point = snapshot.clip_point(point, Bias::Left);
15088 let display_point = point.to_display_point(display_snapshot);
15089 let goal = SelectionGoal::HorizontalPosition(
15090 display_snapshot
15091 .x_for_display_point(display_point, text_layout_details)
15092 .into(),
15093 );
15094 (display_point, goal)
15095 })
15096 });
15097 }
15098 });
15099 }
15100
15101 pub fn select_enclosing_symbol(
15102 &mut self,
15103 _: &SelectEnclosingSymbol,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) {
15107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15108
15109 let buffer = self.buffer.read(cx).snapshot(cx);
15110 let old_selections = self
15111 .selections
15112 .all::<usize>(&self.display_snapshot(cx))
15113 .into_boxed_slice();
15114
15115 fn update_selection(
15116 selection: &Selection<usize>,
15117 buffer_snap: &MultiBufferSnapshot,
15118 ) -> Option<Selection<usize>> {
15119 let cursor = selection.head();
15120 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15121 for symbol in symbols.iter().rev() {
15122 let start = symbol.range.start.to_offset(buffer_snap);
15123 let end = symbol.range.end.to_offset(buffer_snap);
15124 let new_range = start..end;
15125 if start < selection.start || end > selection.end {
15126 return Some(Selection {
15127 id: selection.id,
15128 start: new_range.start,
15129 end: new_range.end,
15130 goal: SelectionGoal::None,
15131 reversed: selection.reversed,
15132 });
15133 }
15134 }
15135 None
15136 }
15137
15138 let mut selected_larger_symbol = false;
15139 let new_selections = old_selections
15140 .iter()
15141 .map(|selection| match update_selection(selection, &buffer) {
15142 Some(new_selection) => {
15143 if new_selection.range() != selection.range() {
15144 selected_larger_symbol = true;
15145 }
15146 new_selection
15147 }
15148 None => selection.clone(),
15149 })
15150 .collect::<Vec<_>>();
15151
15152 if selected_larger_symbol {
15153 self.change_selections(Default::default(), window, cx, |s| {
15154 s.select(new_selections);
15155 });
15156 }
15157 }
15158
15159 pub fn select_larger_syntax_node(
15160 &mut self,
15161 _: &SelectLargerSyntaxNode,
15162 window: &mut Window,
15163 cx: &mut Context<Self>,
15164 ) {
15165 let Some(visible_row_count) = self.visible_row_count() else {
15166 return;
15167 };
15168 let old_selections: Box<[_]> = self
15169 .selections
15170 .all::<usize>(&self.display_snapshot(cx))
15171 .into();
15172 if old_selections.is_empty() {
15173 return;
15174 }
15175
15176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15177
15178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15179 let buffer = self.buffer.read(cx).snapshot(cx);
15180
15181 let mut selected_larger_node = false;
15182 let mut new_selections = old_selections
15183 .iter()
15184 .map(|selection| {
15185 let old_range = selection.start..selection.end;
15186
15187 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15188 // manually select word at selection
15189 if ["string_content", "inline"].contains(&node.kind()) {
15190 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15191 // ignore if word is already selected
15192 if !word_range.is_empty() && old_range != word_range {
15193 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15194 // only select word if start and end point belongs to same word
15195 if word_range == last_word_range {
15196 selected_larger_node = true;
15197 return Selection {
15198 id: selection.id,
15199 start: word_range.start,
15200 end: word_range.end,
15201 goal: SelectionGoal::None,
15202 reversed: selection.reversed,
15203 };
15204 }
15205 }
15206 }
15207 }
15208
15209 let mut new_range = old_range.clone();
15210 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15211 new_range = range;
15212 if !node.is_named() {
15213 continue;
15214 }
15215 if !display_map.intersects_fold(new_range.start)
15216 && !display_map.intersects_fold(new_range.end)
15217 {
15218 break;
15219 }
15220 }
15221
15222 selected_larger_node |= new_range != old_range;
15223 Selection {
15224 id: selection.id,
15225 start: new_range.start,
15226 end: new_range.end,
15227 goal: SelectionGoal::None,
15228 reversed: selection.reversed,
15229 }
15230 })
15231 .collect::<Vec<_>>();
15232
15233 if !selected_larger_node {
15234 return; // don't put this call in the history
15235 }
15236
15237 // scroll based on transformation done to the last selection created by the user
15238 let (last_old, last_new) = old_selections
15239 .last()
15240 .zip(new_selections.last().cloned())
15241 .expect("old_selections isn't empty");
15242
15243 // revert selection
15244 let is_selection_reversed = {
15245 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15246 new_selections.last_mut().expect("checked above").reversed =
15247 should_newest_selection_be_reversed;
15248 should_newest_selection_be_reversed
15249 };
15250
15251 if selected_larger_node {
15252 self.select_syntax_node_history.disable_clearing = true;
15253 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15254 s.select(new_selections.clone());
15255 });
15256 self.select_syntax_node_history.disable_clearing = false;
15257 }
15258
15259 let start_row = last_new.start.to_display_point(&display_map).row().0;
15260 let end_row = last_new.end.to_display_point(&display_map).row().0;
15261 let selection_height = end_row - start_row + 1;
15262 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15263
15264 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15265 let scroll_behavior = if fits_on_the_screen {
15266 self.request_autoscroll(Autoscroll::fit(), cx);
15267 SelectSyntaxNodeScrollBehavior::FitSelection
15268 } else if is_selection_reversed {
15269 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15270 SelectSyntaxNodeScrollBehavior::CursorTop
15271 } else {
15272 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15273 SelectSyntaxNodeScrollBehavior::CursorBottom
15274 };
15275
15276 self.select_syntax_node_history.push((
15277 old_selections,
15278 scroll_behavior,
15279 is_selection_reversed,
15280 ));
15281 }
15282
15283 pub fn select_smaller_syntax_node(
15284 &mut self,
15285 _: &SelectSmallerSyntaxNode,
15286 window: &mut Window,
15287 cx: &mut Context<Self>,
15288 ) {
15289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15290
15291 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15292 self.select_syntax_node_history.pop()
15293 {
15294 if let Some(selection) = selections.last_mut() {
15295 selection.reversed = is_selection_reversed;
15296 }
15297
15298 self.select_syntax_node_history.disable_clearing = true;
15299 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15300 s.select(selections.to_vec());
15301 });
15302 self.select_syntax_node_history.disable_clearing = false;
15303
15304 match scroll_behavior {
15305 SelectSyntaxNodeScrollBehavior::CursorTop => {
15306 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15307 }
15308 SelectSyntaxNodeScrollBehavior::FitSelection => {
15309 self.request_autoscroll(Autoscroll::fit(), cx);
15310 }
15311 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15312 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15313 }
15314 }
15315 }
15316 }
15317
15318 pub fn unwrap_syntax_node(
15319 &mut self,
15320 _: &UnwrapSyntaxNode,
15321 window: &mut Window,
15322 cx: &mut Context<Self>,
15323 ) {
15324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15325
15326 let buffer = self.buffer.read(cx).snapshot(cx);
15327 let selections = self
15328 .selections
15329 .all::<usize>(&self.display_snapshot(cx))
15330 .into_iter()
15331 // subtracting the offset requires sorting
15332 .sorted_by_key(|i| i.start);
15333
15334 let full_edits = selections
15335 .into_iter()
15336 .filter_map(|selection| {
15337 let child = if selection.is_empty()
15338 && let Some((_, ancestor_range)) =
15339 buffer.syntax_ancestor(selection.start..selection.end)
15340 {
15341 ancestor_range
15342 } else {
15343 selection.range()
15344 };
15345
15346 let mut parent = child.clone();
15347 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15348 parent = ancestor_range;
15349 if parent.start < child.start || parent.end > child.end {
15350 break;
15351 }
15352 }
15353
15354 if parent == child {
15355 return None;
15356 }
15357 let text = buffer.text_for_range(child).collect::<String>();
15358 Some((selection.id, parent, text))
15359 })
15360 .collect::<Vec<_>>();
15361 if full_edits.is_empty() {
15362 return;
15363 }
15364
15365 self.transact(window, cx, |this, window, cx| {
15366 this.buffer.update(cx, |buffer, cx| {
15367 buffer.edit(
15368 full_edits
15369 .iter()
15370 .map(|(_, p, t)| (p.clone(), t.clone()))
15371 .collect::<Vec<_>>(),
15372 None,
15373 cx,
15374 );
15375 });
15376 this.change_selections(Default::default(), window, cx, |s| {
15377 let mut offset = 0;
15378 let mut selections = vec![];
15379 for (id, parent, text) in full_edits {
15380 let start = parent.start - offset;
15381 offset += parent.len() - text.len();
15382 selections.push(Selection {
15383 id,
15384 start,
15385 end: start + text.len(),
15386 reversed: false,
15387 goal: Default::default(),
15388 });
15389 }
15390 s.select(selections);
15391 });
15392 });
15393 }
15394
15395 pub fn select_next_syntax_node(
15396 &mut self,
15397 _: &SelectNextSyntaxNode,
15398 window: &mut Window,
15399 cx: &mut Context<Self>,
15400 ) {
15401 let old_selections: Box<[_]> = self
15402 .selections
15403 .all::<usize>(&self.display_snapshot(cx))
15404 .into();
15405 if old_selections.is_empty() {
15406 return;
15407 }
15408
15409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15410
15411 let buffer = self.buffer.read(cx).snapshot(cx);
15412 let mut selected_sibling = false;
15413
15414 let new_selections = old_selections
15415 .iter()
15416 .map(|selection| {
15417 let old_range = selection.start..selection.end;
15418
15419 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15420 let new_range = node.byte_range();
15421 selected_sibling = true;
15422 Selection {
15423 id: selection.id,
15424 start: new_range.start,
15425 end: new_range.end,
15426 goal: SelectionGoal::None,
15427 reversed: selection.reversed,
15428 }
15429 } else {
15430 selection.clone()
15431 }
15432 })
15433 .collect::<Vec<_>>();
15434
15435 if selected_sibling {
15436 self.change_selections(
15437 SelectionEffects::scroll(Autoscroll::fit()),
15438 window,
15439 cx,
15440 |s| {
15441 s.select(new_selections);
15442 },
15443 );
15444 }
15445 }
15446
15447 pub fn select_prev_syntax_node(
15448 &mut self,
15449 _: &SelectPreviousSyntaxNode,
15450 window: &mut Window,
15451 cx: &mut Context<Self>,
15452 ) {
15453 let old_selections: Box<[_]> = self
15454 .selections
15455 .all::<usize>(&self.display_snapshot(cx))
15456 .into();
15457 if old_selections.is_empty() {
15458 return;
15459 }
15460
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let buffer = self.buffer.read(cx).snapshot(cx);
15464 let mut selected_sibling = false;
15465
15466 let new_selections = old_selections
15467 .iter()
15468 .map(|selection| {
15469 let old_range = selection.start..selection.end;
15470
15471 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15472 let new_range = node.byte_range();
15473 selected_sibling = true;
15474 Selection {
15475 id: selection.id,
15476 start: new_range.start,
15477 end: new_range.end,
15478 goal: SelectionGoal::None,
15479 reversed: selection.reversed,
15480 }
15481 } else {
15482 selection.clone()
15483 }
15484 })
15485 .collect::<Vec<_>>();
15486
15487 if selected_sibling {
15488 self.change_selections(
15489 SelectionEffects::scroll(Autoscroll::fit()),
15490 window,
15491 cx,
15492 |s| {
15493 s.select(new_selections);
15494 },
15495 );
15496 }
15497 }
15498
15499 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15500 if !EditorSettings::get_global(cx).gutter.runnables {
15501 self.clear_tasks();
15502 return Task::ready(());
15503 }
15504 let project = self.project().map(Entity::downgrade);
15505 let task_sources = self.lsp_task_sources(cx);
15506 let multi_buffer = self.buffer.downgrade();
15507 cx.spawn_in(window, async move |editor, cx| {
15508 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15509 let Some(project) = project.and_then(|p| p.upgrade()) else {
15510 return;
15511 };
15512 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15513 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15514 }) else {
15515 return;
15516 };
15517
15518 let hide_runnables = project
15519 .update(cx, |project, _| project.is_via_collab())
15520 .unwrap_or(true);
15521 if hide_runnables {
15522 return;
15523 }
15524 let new_rows =
15525 cx.background_spawn({
15526 let snapshot = display_snapshot.clone();
15527 async move {
15528 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15529 }
15530 })
15531 .await;
15532 let Ok(lsp_tasks) =
15533 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15534 else {
15535 return;
15536 };
15537 let lsp_tasks = lsp_tasks.await;
15538
15539 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15540 lsp_tasks
15541 .into_iter()
15542 .flat_map(|(kind, tasks)| {
15543 tasks.into_iter().filter_map(move |(location, task)| {
15544 Some((kind.clone(), location?, task))
15545 })
15546 })
15547 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15548 let buffer = location.target.buffer;
15549 let buffer_snapshot = buffer.read(cx).snapshot();
15550 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15551 |(excerpt_id, snapshot, _)| {
15552 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15553 display_snapshot
15554 .buffer_snapshot()
15555 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15556 } else {
15557 None
15558 }
15559 },
15560 );
15561 if let Some(offset) = offset {
15562 let task_buffer_range =
15563 location.target.range.to_point(&buffer_snapshot);
15564 let context_buffer_range =
15565 task_buffer_range.to_offset(&buffer_snapshot);
15566 let context_range = BufferOffset(context_buffer_range.start)
15567 ..BufferOffset(context_buffer_range.end);
15568
15569 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15570 .or_insert_with(|| RunnableTasks {
15571 templates: Vec::new(),
15572 offset,
15573 column: task_buffer_range.start.column,
15574 extra_variables: HashMap::default(),
15575 context_range,
15576 })
15577 .templates
15578 .push((kind, task.original_task().clone()));
15579 }
15580
15581 acc
15582 })
15583 }) else {
15584 return;
15585 };
15586
15587 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15588 buffer.language_settings(cx).tasks.prefer_lsp
15589 }) else {
15590 return;
15591 };
15592
15593 let rows = Self::runnable_rows(
15594 project,
15595 display_snapshot,
15596 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15597 new_rows,
15598 cx.clone(),
15599 )
15600 .await;
15601 editor
15602 .update(cx, |editor, _| {
15603 editor.clear_tasks();
15604 for (key, mut value) in rows {
15605 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15606 value.templates.extend(lsp_tasks.templates);
15607 }
15608
15609 editor.insert_tasks(key, value);
15610 }
15611 for (key, value) in lsp_tasks_by_rows {
15612 editor.insert_tasks(key, value);
15613 }
15614 })
15615 .ok();
15616 })
15617 }
15618 fn fetch_runnable_ranges(
15619 snapshot: &DisplaySnapshot,
15620 range: Range<Anchor>,
15621 ) -> Vec<language::RunnableRange> {
15622 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15623 }
15624
15625 fn runnable_rows(
15626 project: Entity<Project>,
15627 snapshot: DisplaySnapshot,
15628 prefer_lsp: bool,
15629 runnable_ranges: Vec<RunnableRange>,
15630 cx: AsyncWindowContext,
15631 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15632 cx.spawn(async move |cx| {
15633 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15634 for mut runnable in runnable_ranges {
15635 let Some(tasks) = cx
15636 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15637 .ok()
15638 else {
15639 continue;
15640 };
15641 let mut tasks = tasks.await;
15642
15643 if prefer_lsp {
15644 tasks.retain(|(task_kind, _)| {
15645 !matches!(task_kind, TaskSourceKind::Language { .. })
15646 });
15647 }
15648 if tasks.is_empty() {
15649 continue;
15650 }
15651
15652 let point = runnable
15653 .run_range
15654 .start
15655 .to_point(&snapshot.buffer_snapshot());
15656 let Some(row) = snapshot
15657 .buffer_snapshot()
15658 .buffer_line_for_row(MultiBufferRow(point.row))
15659 .map(|(_, range)| range.start.row)
15660 else {
15661 continue;
15662 };
15663
15664 let context_range =
15665 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15666 runnable_rows.push((
15667 (runnable.buffer_id, row),
15668 RunnableTasks {
15669 templates: tasks,
15670 offset: snapshot
15671 .buffer_snapshot()
15672 .anchor_before(runnable.run_range.start),
15673 context_range,
15674 column: point.column,
15675 extra_variables: runnable.extra_captures,
15676 },
15677 ));
15678 }
15679 runnable_rows
15680 })
15681 }
15682
15683 fn templates_with_tags(
15684 project: &Entity<Project>,
15685 runnable: &mut Runnable,
15686 cx: &mut App,
15687 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15688 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15689 let (worktree_id, file) = project
15690 .buffer_for_id(runnable.buffer, cx)
15691 .and_then(|buffer| buffer.read(cx).file())
15692 .map(|file| (file.worktree_id(cx), file.clone()))
15693 .unzip();
15694
15695 (
15696 project.task_store().read(cx).task_inventory().cloned(),
15697 worktree_id,
15698 file,
15699 )
15700 });
15701
15702 let tags = mem::take(&mut runnable.tags);
15703 let language = runnable.language.clone();
15704 cx.spawn(async move |cx| {
15705 let mut templates_with_tags = Vec::new();
15706 if let Some(inventory) = inventory {
15707 for RunnableTag(tag) in tags {
15708 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15709 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15710 }) else {
15711 return templates_with_tags;
15712 };
15713 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15714 move |(_, template)| {
15715 template.tags.iter().any(|source_tag| source_tag == &tag)
15716 },
15717 ));
15718 }
15719 }
15720 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15721
15722 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15723 // Strongest source wins; if we have worktree tag binding, prefer that to
15724 // global and language bindings;
15725 // if we have a global binding, prefer that to language binding.
15726 let first_mismatch = templates_with_tags
15727 .iter()
15728 .position(|(tag_source, _)| tag_source != leading_tag_source);
15729 if let Some(index) = first_mismatch {
15730 templates_with_tags.truncate(index);
15731 }
15732 }
15733
15734 templates_with_tags
15735 })
15736 }
15737
15738 pub fn move_to_enclosing_bracket(
15739 &mut self,
15740 _: &MoveToEnclosingBracket,
15741 window: &mut Window,
15742 cx: &mut Context<Self>,
15743 ) {
15744 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15745 self.change_selections(Default::default(), window, cx, |s| {
15746 s.move_offsets_with(|snapshot, selection| {
15747 let Some(enclosing_bracket_ranges) =
15748 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15749 else {
15750 return;
15751 };
15752
15753 let mut best_length = usize::MAX;
15754 let mut best_inside = false;
15755 let mut best_in_bracket_range = false;
15756 let mut best_destination = None;
15757 for (open, close) in enclosing_bracket_ranges {
15758 let close = close.to_inclusive();
15759 let length = close.end() - open.start;
15760 let inside = selection.start >= open.end && selection.end <= *close.start();
15761 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15762 || close.contains(&selection.head());
15763
15764 // If best is next to a bracket and current isn't, skip
15765 if !in_bracket_range && best_in_bracket_range {
15766 continue;
15767 }
15768
15769 // Prefer smaller lengths unless best is inside and current isn't
15770 if length > best_length && (best_inside || !inside) {
15771 continue;
15772 }
15773
15774 best_length = length;
15775 best_inside = inside;
15776 best_in_bracket_range = in_bracket_range;
15777 best_destination = Some(
15778 if close.contains(&selection.start) && close.contains(&selection.end) {
15779 if inside { open.end } else { open.start }
15780 } else if inside {
15781 *close.start()
15782 } else {
15783 *close.end()
15784 },
15785 );
15786 }
15787
15788 if let Some(destination) = best_destination {
15789 selection.collapse_to(destination, SelectionGoal::None);
15790 }
15791 })
15792 });
15793 }
15794
15795 pub fn undo_selection(
15796 &mut self,
15797 _: &UndoSelection,
15798 window: &mut Window,
15799 cx: &mut Context<Self>,
15800 ) {
15801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15802 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15803 self.selection_history.mode = SelectionHistoryMode::Undoing;
15804 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15805 this.end_selection(window, cx);
15806 this.change_selections(
15807 SelectionEffects::scroll(Autoscroll::newest()),
15808 window,
15809 cx,
15810 |s| s.select_anchors(entry.selections.to_vec()),
15811 );
15812 });
15813 self.selection_history.mode = SelectionHistoryMode::Normal;
15814
15815 self.select_next_state = entry.select_next_state;
15816 self.select_prev_state = entry.select_prev_state;
15817 self.add_selections_state = entry.add_selections_state;
15818 }
15819 }
15820
15821 pub fn redo_selection(
15822 &mut self,
15823 _: &RedoSelection,
15824 window: &mut Window,
15825 cx: &mut Context<Self>,
15826 ) {
15827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15828 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15829 self.selection_history.mode = SelectionHistoryMode::Redoing;
15830 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15831 this.end_selection(window, cx);
15832 this.change_selections(
15833 SelectionEffects::scroll(Autoscroll::newest()),
15834 window,
15835 cx,
15836 |s| s.select_anchors(entry.selections.to_vec()),
15837 );
15838 });
15839 self.selection_history.mode = SelectionHistoryMode::Normal;
15840
15841 self.select_next_state = entry.select_next_state;
15842 self.select_prev_state = entry.select_prev_state;
15843 self.add_selections_state = entry.add_selections_state;
15844 }
15845 }
15846
15847 pub fn expand_excerpts(
15848 &mut self,
15849 action: &ExpandExcerpts,
15850 _: &mut Window,
15851 cx: &mut Context<Self>,
15852 ) {
15853 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15854 }
15855
15856 pub fn expand_excerpts_down(
15857 &mut self,
15858 action: &ExpandExcerptsDown,
15859 _: &mut Window,
15860 cx: &mut Context<Self>,
15861 ) {
15862 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15863 }
15864
15865 pub fn expand_excerpts_up(
15866 &mut self,
15867 action: &ExpandExcerptsUp,
15868 _: &mut Window,
15869 cx: &mut Context<Self>,
15870 ) {
15871 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15872 }
15873
15874 pub fn expand_excerpts_for_direction(
15875 &mut self,
15876 lines: u32,
15877 direction: ExpandExcerptDirection,
15878
15879 cx: &mut Context<Self>,
15880 ) {
15881 let selections = self.selections.disjoint_anchors_arc();
15882
15883 let lines = if lines == 0 {
15884 EditorSettings::get_global(cx).expand_excerpt_lines
15885 } else {
15886 lines
15887 };
15888
15889 self.buffer.update(cx, |buffer, cx| {
15890 let snapshot = buffer.snapshot(cx);
15891 let mut excerpt_ids = selections
15892 .iter()
15893 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15894 .collect::<Vec<_>>();
15895 excerpt_ids.sort();
15896 excerpt_ids.dedup();
15897 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15898 })
15899 }
15900
15901 pub fn expand_excerpt(
15902 &mut self,
15903 excerpt: ExcerptId,
15904 direction: ExpandExcerptDirection,
15905 window: &mut Window,
15906 cx: &mut Context<Self>,
15907 ) {
15908 let current_scroll_position = self.scroll_position(cx);
15909 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15910 let mut scroll = None;
15911
15912 if direction == ExpandExcerptDirection::Down {
15913 let multi_buffer = self.buffer.read(cx);
15914 let snapshot = multi_buffer.snapshot(cx);
15915 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15916 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15917 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15918 {
15919 let buffer_snapshot = buffer.read(cx).snapshot();
15920 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15921 let last_row = buffer_snapshot.max_point().row;
15922 let lines_below = last_row.saturating_sub(excerpt_end_row);
15923 if lines_below >= lines_to_expand {
15924 scroll = Some(
15925 current_scroll_position
15926 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15927 );
15928 }
15929 }
15930 }
15931 if direction == ExpandExcerptDirection::Up
15932 && self
15933 .buffer
15934 .read(cx)
15935 .snapshot(cx)
15936 .excerpt_before(excerpt)
15937 .is_none()
15938 {
15939 scroll = Some(current_scroll_position);
15940 }
15941
15942 self.buffer.update(cx, |buffer, cx| {
15943 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15944 });
15945
15946 if let Some(new_scroll_position) = scroll {
15947 self.set_scroll_position(new_scroll_position, window, cx);
15948 }
15949 }
15950
15951 pub fn go_to_singleton_buffer_point(
15952 &mut self,
15953 point: Point,
15954 window: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 self.go_to_singleton_buffer_range(point..point, window, cx);
15958 }
15959
15960 pub fn go_to_singleton_buffer_range(
15961 &mut self,
15962 range: Range<Point>,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 let multibuffer = self.buffer().read(cx);
15967 let Some(buffer) = multibuffer.as_singleton() else {
15968 return;
15969 };
15970 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15971 return;
15972 };
15973 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15974 return;
15975 };
15976 self.change_selections(
15977 SelectionEffects::default().nav_history(true),
15978 window,
15979 cx,
15980 |s| s.select_anchor_ranges([start..end]),
15981 );
15982 }
15983
15984 pub fn go_to_diagnostic(
15985 &mut self,
15986 action: &GoToDiagnostic,
15987 window: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) {
15990 if !self.diagnostics_enabled() {
15991 return;
15992 }
15993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15994 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15995 }
15996
15997 pub fn go_to_prev_diagnostic(
15998 &mut self,
15999 action: &GoToPreviousDiagnostic,
16000 window: &mut Window,
16001 cx: &mut Context<Self>,
16002 ) {
16003 if !self.diagnostics_enabled() {
16004 return;
16005 }
16006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16007 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16008 }
16009
16010 pub fn go_to_diagnostic_impl(
16011 &mut self,
16012 direction: Direction,
16013 severity: GoToDiagnosticSeverityFilter,
16014 window: &mut Window,
16015 cx: &mut Context<Self>,
16016 ) {
16017 let buffer = self.buffer.read(cx).snapshot(cx);
16018 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16019
16020 let mut active_group_id = None;
16021 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16022 && active_group.active_range.start.to_offset(&buffer) == selection.start
16023 {
16024 active_group_id = Some(active_group.group_id);
16025 }
16026
16027 fn filtered<'a>(
16028 snapshot: EditorSnapshot,
16029 severity: GoToDiagnosticSeverityFilter,
16030 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16031 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16032 diagnostics
16033 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16034 .filter(|entry| entry.range.start != entry.range.end)
16035 .filter(|entry| !entry.diagnostic.is_unnecessary)
16036 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16037 }
16038
16039 let snapshot = self.snapshot(window, cx);
16040 let before = filtered(
16041 snapshot.clone(),
16042 severity,
16043 buffer
16044 .diagnostics_in_range(0..selection.start)
16045 .filter(|entry| entry.range.start <= selection.start),
16046 );
16047 let after = filtered(
16048 snapshot,
16049 severity,
16050 buffer
16051 .diagnostics_in_range(selection.start..buffer.len())
16052 .filter(|entry| entry.range.start >= selection.start),
16053 );
16054
16055 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16056 if direction == Direction::Prev {
16057 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16058 {
16059 for diagnostic in prev_diagnostics.into_iter().rev() {
16060 if diagnostic.range.start != selection.start
16061 || active_group_id
16062 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16063 {
16064 found = Some(diagnostic);
16065 break 'outer;
16066 }
16067 }
16068 }
16069 } else {
16070 for diagnostic in after.chain(before) {
16071 if diagnostic.range.start != selection.start
16072 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16073 {
16074 found = Some(diagnostic);
16075 break;
16076 }
16077 }
16078 }
16079 let Some(next_diagnostic) = found else {
16080 return;
16081 };
16082
16083 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16084 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16085 return;
16086 };
16087 self.change_selections(Default::default(), window, cx, |s| {
16088 s.select_ranges(vec![
16089 next_diagnostic.range.start..next_diagnostic.range.start,
16090 ])
16091 });
16092 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16093 self.refresh_edit_prediction(false, true, window, cx);
16094 }
16095
16096 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16098 let snapshot = self.snapshot(window, cx);
16099 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16100 self.go_to_hunk_before_or_after_position(
16101 &snapshot,
16102 selection.head(),
16103 Direction::Next,
16104 window,
16105 cx,
16106 );
16107 }
16108
16109 pub fn go_to_hunk_before_or_after_position(
16110 &mut self,
16111 snapshot: &EditorSnapshot,
16112 position: Point,
16113 direction: Direction,
16114 window: &mut Window,
16115 cx: &mut Context<Editor>,
16116 ) {
16117 let row = if direction == Direction::Next {
16118 self.hunk_after_position(snapshot, position)
16119 .map(|hunk| hunk.row_range.start)
16120 } else {
16121 self.hunk_before_position(snapshot, position)
16122 };
16123
16124 if let Some(row) = row {
16125 let destination = Point::new(row.0, 0);
16126 let autoscroll = Autoscroll::center();
16127
16128 self.unfold_ranges(&[destination..destination], false, false, cx);
16129 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16130 s.select_ranges([destination..destination]);
16131 });
16132 }
16133 }
16134
16135 fn hunk_after_position(
16136 &mut self,
16137 snapshot: &EditorSnapshot,
16138 position: Point,
16139 ) -> Option<MultiBufferDiffHunk> {
16140 snapshot
16141 .buffer_snapshot()
16142 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16143 .find(|hunk| hunk.row_range.start.0 > position.row)
16144 .or_else(|| {
16145 snapshot
16146 .buffer_snapshot()
16147 .diff_hunks_in_range(Point::zero()..position)
16148 .find(|hunk| hunk.row_range.end.0 < position.row)
16149 })
16150 }
16151
16152 fn go_to_prev_hunk(
16153 &mut self,
16154 _: &GoToPreviousHunk,
16155 window: &mut Window,
16156 cx: &mut Context<Self>,
16157 ) {
16158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16159 let snapshot = self.snapshot(window, cx);
16160 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16161 self.go_to_hunk_before_or_after_position(
16162 &snapshot,
16163 selection.head(),
16164 Direction::Prev,
16165 window,
16166 cx,
16167 );
16168 }
16169
16170 fn hunk_before_position(
16171 &mut self,
16172 snapshot: &EditorSnapshot,
16173 position: Point,
16174 ) -> Option<MultiBufferRow> {
16175 snapshot
16176 .buffer_snapshot()
16177 .diff_hunk_before(position)
16178 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16179 }
16180
16181 fn go_to_next_change(
16182 &mut self,
16183 _: &GoToNextChange,
16184 window: &mut Window,
16185 cx: &mut Context<Self>,
16186 ) {
16187 if let Some(selections) = self
16188 .change_list
16189 .next_change(1, Direction::Next)
16190 .map(|s| s.to_vec())
16191 {
16192 self.change_selections(Default::default(), window, cx, |s| {
16193 let map = s.display_map();
16194 s.select_display_ranges(selections.iter().map(|a| {
16195 let point = a.to_display_point(&map);
16196 point..point
16197 }))
16198 })
16199 }
16200 }
16201
16202 fn go_to_previous_change(
16203 &mut self,
16204 _: &GoToPreviousChange,
16205 window: &mut Window,
16206 cx: &mut Context<Self>,
16207 ) {
16208 if let Some(selections) = self
16209 .change_list
16210 .next_change(1, Direction::Prev)
16211 .map(|s| s.to_vec())
16212 {
16213 self.change_selections(Default::default(), window, cx, |s| {
16214 let map = s.display_map();
16215 s.select_display_ranges(selections.iter().map(|a| {
16216 let point = a.to_display_point(&map);
16217 point..point
16218 }))
16219 })
16220 }
16221 }
16222
16223 pub fn go_to_next_document_highlight(
16224 &mut self,
16225 _: &GoToNextDocumentHighlight,
16226 window: &mut Window,
16227 cx: &mut Context<Self>,
16228 ) {
16229 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16230 }
16231
16232 pub fn go_to_prev_document_highlight(
16233 &mut self,
16234 _: &GoToPreviousDocumentHighlight,
16235 window: &mut Window,
16236 cx: &mut Context<Self>,
16237 ) {
16238 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16239 }
16240
16241 pub fn go_to_document_highlight_before_or_after_position(
16242 &mut self,
16243 direction: Direction,
16244 window: &mut Window,
16245 cx: &mut Context<Editor>,
16246 ) {
16247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16248 let snapshot = self.snapshot(window, cx);
16249 let buffer = &snapshot.buffer_snapshot();
16250 let position = self
16251 .selections
16252 .newest::<Point>(&snapshot.display_snapshot)
16253 .head();
16254 let anchor_position = buffer.anchor_after(position);
16255
16256 // Get all document highlights (both read and write)
16257 let mut all_highlights = Vec::new();
16258
16259 if let Some((_, read_highlights)) = self
16260 .background_highlights
16261 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16262 {
16263 all_highlights.extend(read_highlights.iter());
16264 }
16265
16266 if let Some((_, write_highlights)) = self
16267 .background_highlights
16268 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16269 {
16270 all_highlights.extend(write_highlights.iter());
16271 }
16272
16273 if all_highlights.is_empty() {
16274 return;
16275 }
16276
16277 // Sort highlights by position
16278 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16279
16280 let target_highlight = match direction {
16281 Direction::Next => {
16282 // Find the first highlight after the current position
16283 all_highlights
16284 .iter()
16285 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16286 }
16287 Direction::Prev => {
16288 // Find the last highlight before the current position
16289 all_highlights
16290 .iter()
16291 .rev()
16292 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16293 }
16294 };
16295
16296 if let Some(highlight) = target_highlight {
16297 let destination = highlight.start.to_point(buffer);
16298 let autoscroll = Autoscroll::center();
16299
16300 self.unfold_ranges(&[destination..destination], false, false, cx);
16301 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16302 s.select_ranges([destination..destination]);
16303 });
16304 }
16305 }
16306
16307 fn go_to_line<T: 'static>(
16308 &mut self,
16309 position: Anchor,
16310 highlight_color: Option<Hsla>,
16311 window: &mut Window,
16312 cx: &mut Context<Self>,
16313 ) {
16314 let snapshot = self.snapshot(window, cx).display_snapshot;
16315 let position = position.to_point(&snapshot.buffer_snapshot());
16316 let start = snapshot
16317 .buffer_snapshot()
16318 .clip_point(Point::new(position.row, 0), Bias::Left);
16319 let end = start + Point::new(1, 0);
16320 let start = snapshot.buffer_snapshot().anchor_before(start);
16321 let end = snapshot.buffer_snapshot().anchor_before(end);
16322
16323 self.highlight_rows::<T>(
16324 start..end,
16325 highlight_color
16326 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16327 Default::default(),
16328 cx,
16329 );
16330
16331 if self.buffer.read(cx).is_singleton() {
16332 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16333 }
16334 }
16335
16336 pub fn go_to_definition(
16337 &mut self,
16338 _: &GoToDefinition,
16339 window: &mut Window,
16340 cx: &mut Context<Self>,
16341 ) -> Task<Result<Navigated>> {
16342 let definition =
16343 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16344 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16345 cx.spawn_in(window, async move |editor, cx| {
16346 if definition.await? == Navigated::Yes {
16347 return Ok(Navigated::Yes);
16348 }
16349 match fallback_strategy {
16350 GoToDefinitionFallback::None => Ok(Navigated::No),
16351 GoToDefinitionFallback::FindAllReferences => {
16352 match editor.update_in(cx, |editor, window, cx| {
16353 editor.find_all_references(&FindAllReferences, window, cx)
16354 })? {
16355 Some(references) => references.await,
16356 None => Ok(Navigated::No),
16357 }
16358 }
16359 }
16360 })
16361 }
16362
16363 pub fn go_to_declaration(
16364 &mut self,
16365 _: &GoToDeclaration,
16366 window: &mut Window,
16367 cx: &mut Context<Self>,
16368 ) -> Task<Result<Navigated>> {
16369 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16370 }
16371
16372 pub fn go_to_declaration_split(
16373 &mut self,
16374 _: &GoToDeclaration,
16375 window: &mut Window,
16376 cx: &mut Context<Self>,
16377 ) -> Task<Result<Navigated>> {
16378 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16379 }
16380
16381 pub fn go_to_implementation(
16382 &mut self,
16383 _: &GoToImplementation,
16384 window: &mut Window,
16385 cx: &mut Context<Self>,
16386 ) -> Task<Result<Navigated>> {
16387 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16388 }
16389
16390 pub fn go_to_implementation_split(
16391 &mut self,
16392 _: &GoToImplementationSplit,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) -> Task<Result<Navigated>> {
16396 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16397 }
16398
16399 pub fn go_to_type_definition(
16400 &mut self,
16401 _: &GoToTypeDefinition,
16402 window: &mut Window,
16403 cx: &mut Context<Self>,
16404 ) -> Task<Result<Navigated>> {
16405 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16406 }
16407
16408 pub fn go_to_definition_split(
16409 &mut self,
16410 _: &GoToDefinitionSplit,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) -> Task<Result<Navigated>> {
16414 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16415 }
16416
16417 pub fn go_to_type_definition_split(
16418 &mut self,
16419 _: &GoToTypeDefinitionSplit,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16424 }
16425
16426 fn go_to_definition_of_kind(
16427 &mut self,
16428 kind: GotoDefinitionKind,
16429 split: bool,
16430 window: &mut Window,
16431 cx: &mut Context<Self>,
16432 ) -> Task<Result<Navigated>> {
16433 let Some(provider) = self.semantics_provider.clone() else {
16434 return Task::ready(Ok(Navigated::No));
16435 };
16436 let head = self
16437 .selections
16438 .newest::<usize>(&self.display_snapshot(cx))
16439 .head();
16440 let buffer = self.buffer.read(cx);
16441 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16442 return Task::ready(Ok(Navigated::No));
16443 };
16444 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16445 return Task::ready(Ok(Navigated::No));
16446 };
16447
16448 cx.spawn_in(window, async move |editor, cx| {
16449 let Some(definitions) = definitions.await? else {
16450 return Ok(Navigated::No);
16451 };
16452 let navigated = editor
16453 .update_in(cx, |editor, window, cx| {
16454 editor.navigate_to_hover_links(
16455 Some(kind),
16456 definitions
16457 .into_iter()
16458 .filter(|location| {
16459 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16460 })
16461 .map(HoverLink::Text)
16462 .collect::<Vec<_>>(),
16463 split,
16464 window,
16465 cx,
16466 )
16467 })?
16468 .await?;
16469 anyhow::Ok(navigated)
16470 })
16471 }
16472
16473 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16474 let selection = self.selections.newest_anchor();
16475 let head = selection.head();
16476 let tail = selection.tail();
16477
16478 let Some((buffer, start_position)) =
16479 self.buffer.read(cx).text_anchor_for_position(head, cx)
16480 else {
16481 return;
16482 };
16483
16484 let end_position = if head != tail {
16485 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16486 return;
16487 };
16488 Some(pos)
16489 } else {
16490 None
16491 };
16492
16493 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16494 let url = if let Some(end_pos) = end_position {
16495 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16496 } else {
16497 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16498 };
16499
16500 if let Some(url) = url {
16501 cx.update(|window, cx| {
16502 if parse_zed_link(&url, cx).is_some() {
16503 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16504 } else {
16505 cx.open_url(&url);
16506 }
16507 })?;
16508 }
16509
16510 anyhow::Ok(())
16511 });
16512
16513 url_finder.detach();
16514 }
16515
16516 pub fn open_selected_filename(
16517 &mut self,
16518 _: &OpenSelectedFilename,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) {
16522 let Some(workspace) = self.workspace() else {
16523 return;
16524 };
16525
16526 let position = self.selections.newest_anchor().head();
16527
16528 let Some((buffer, buffer_position)) =
16529 self.buffer.read(cx).text_anchor_for_position(position, cx)
16530 else {
16531 return;
16532 };
16533
16534 let project = self.project.clone();
16535
16536 cx.spawn_in(window, async move |_, cx| {
16537 let result = find_file(&buffer, project, buffer_position, cx).await;
16538
16539 if let Some((_, path)) = result {
16540 workspace
16541 .update_in(cx, |workspace, window, cx| {
16542 workspace.open_resolved_path(path, window, cx)
16543 })?
16544 .await?;
16545 }
16546 anyhow::Ok(())
16547 })
16548 .detach();
16549 }
16550
16551 pub(crate) fn navigate_to_hover_links(
16552 &mut self,
16553 kind: Option<GotoDefinitionKind>,
16554 definitions: Vec<HoverLink>,
16555 split: bool,
16556 window: &mut Window,
16557 cx: &mut Context<Editor>,
16558 ) -> Task<Result<Navigated>> {
16559 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16560 let mut first_url_or_file = None;
16561 let definitions: Vec<_> = definitions
16562 .into_iter()
16563 .filter_map(|def| match def {
16564 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16565 HoverLink::InlayHint(lsp_location, server_id) => {
16566 let computation =
16567 self.compute_target_location(lsp_location, server_id, window, cx);
16568 Some(cx.background_spawn(computation))
16569 }
16570 HoverLink::Url(url) => {
16571 first_url_or_file = Some(Either::Left(url));
16572 None
16573 }
16574 HoverLink::File(path) => {
16575 first_url_or_file = Some(Either::Right(path));
16576 None
16577 }
16578 })
16579 .collect();
16580
16581 let workspace = self.workspace();
16582
16583 cx.spawn_in(window, async move |editor, cx| {
16584 let locations: Vec<Location> = future::join_all(definitions)
16585 .await
16586 .into_iter()
16587 .filter_map(|location| location.transpose())
16588 .collect::<Result<_>>()
16589 .context("location tasks")?;
16590 let mut locations = cx.update(|_, cx| {
16591 locations
16592 .into_iter()
16593 .map(|location| {
16594 let buffer = location.buffer.read(cx);
16595 (location.buffer, location.range.to_point(buffer))
16596 })
16597 .into_group_map()
16598 })?;
16599 let mut num_locations = 0;
16600 for ranges in locations.values_mut() {
16601 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16602 ranges.dedup();
16603 num_locations += ranges.len();
16604 }
16605
16606 if num_locations > 1 {
16607 let Some(workspace) = workspace else {
16608 return Ok(Navigated::No);
16609 };
16610
16611 let tab_kind = match kind {
16612 Some(GotoDefinitionKind::Implementation) => "Implementations",
16613 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16614 Some(GotoDefinitionKind::Declaration) => "Declarations",
16615 Some(GotoDefinitionKind::Type) => "Types",
16616 };
16617 let title = editor
16618 .update_in(cx, |_, _, cx| {
16619 let target = locations
16620 .iter()
16621 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16622 .map(|(buffer, location)| {
16623 buffer
16624 .read(cx)
16625 .text_for_range(location.clone())
16626 .collect::<String>()
16627 })
16628 .filter(|text| !text.contains('\n'))
16629 .unique()
16630 .take(3)
16631 .join(", ");
16632 if target.is_empty() {
16633 tab_kind.to_owned()
16634 } else {
16635 format!("{tab_kind} for {target}")
16636 }
16637 })
16638 .context("buffer title")?;
16639
16640 let opened = workspace
16641 .update_in(cx, |workspace, window, cx| {
16642 Self::open_locations_in_multibuffer(
16643 workspace,
16644 locations,
16645 title,
16646 split,
16647 MultibufferSelectionMode::First,
16648 window,
16649 cx,
16650 )
16651 })
16652 .is_ok();
16653
16654 anyhow::Ok(Navigated::from_bool(opened))
16655 } else if num_locations == 0 {
16656 // If there is one url or file, open it directly
16657 match first_url_or_file {
16658 Some(Either::Left(url)) => {
16659 cx.update(|_, cx| cx.open_url(&url))?;
16660 Ok(Navigated::Yes)
16661 }
16662 Some(Either::Right(path)) => {
16663 let Some(workspace) = workspace else {
16664 return Ok(Navigated::No);
16665 };
16666
16667 workspace
16668 .update_in(cx, |workspace, window, cx| {
16669 workspace.open_resolved_path(path, window, cx)
16670 })?
16671 .await?;
16672 Ok(Navigated::Yes)
16673 }
16674 None => Ok(Navigated::No),
16675 }
16676 } else {
16677 let Some(workspace) = workspace else {
16678 return Ok(Navigated::No);
16679 };
16680
16681 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16682 let target_range = target_ranges.first().unwrap().clone();
16683
16684 editor.update_in(cx, |editor, window, cx| {
16685 let range = target_range.to_point(target_buffer.read(cx));
16686 let range = editor.range_for_match(&range, false);
16687 let range = collapse_multiline_range(range);
16688
16689 if !split
16690 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16691 {
16692 editor.go_to_singleton_buffer_range(range, window, cx);
16693 } else {
16694 let pane = workspace.read(cx).active_pane().clone();
16695 window.defer(cx, move |window, cx| {
16696 let target_editor: Entity<Self> =
16697 workspace.update(cx, |workspace, cx| {
16698 let pane = if split {
16699 workspace.adjacent_pane(window, cx)
16700 } else {
16701 workspace.active_pane().clone()
16702 };
16703
16704 workspace.open_project_item(
16705 pane,
16706 target_buffer.clone(),
16707 true,
16708 true,
16709 window,
16710 cx,
16711 )
16712 });
16713 target_editor.update(cx, |target_editor, cx| {
16714 // When selecting a definition in a different buffer, disable the nav history
16715 // to avoid creating a history entry at the previous cursor location.
16716 pane.update(cx, |pane, _| pane.disable_history());
16717 target_editor.go_to_singleton_buffer_range(range, window, cx);
16718 pane.update(cx, |pane, _| pane.enable_history());
16719 });
16720 });
16721 }
16722 Navigated::Yes
16723 })
16724 }
16725 })
16726 }
16727
16728 fn compute_target_location(
16729 &self,
16730 lsp_location: lsp::Location,
16731 server_id: LanguageServerId,
16732 window: &mut Window,
16733 cx: &mut Context<Self>,
16734 ) -> Task<anyhow::Result<Option<Location>>> {
16735 let Some(project) = self.project.clone() else {
16736 return Task::ready(Ok(None));
16737 };
16738
16739 cx.spawn_in(window, async move |editor, cx| {
16740 let location_task = editor.update(cx, |_, cx| {
16741 project.update(cx, |project, cx| {
16742 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16743 })
16744 })?;
16745 let location = Some({
16746 let target_buffer_handle = location_task.await.context("open local buffer")?;
16747 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16748 let target_start = target_buffer
16749 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16750 let target_end = target_buffer
16751 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16752 target_buffer.anchor_after(target_start)
16753 ..target_buffer.anchor_before(target_end)
16754 })?;
16755 Location {
16756 buffer: target_buffer_handle,
16757 range,
16758 }
16759 });
16760 Ok(location)
16761 })
16762 }
16763
16764 fn go_to_next_reference(
16765 &mut self,
16766 _: &GoToNextReference,
16767 window: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) {
16770 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16771 if let Some(task) = task {
16772 task.detach();
16773 };
16774 }
16775
16776 fn go_to_prev_reference(
16777 &mut self,
16778 _: &GoToPreviousReference,
16779 window: &mut Window,
16780 cx: &mut Context<Self>,
16781 ) {
16782 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16783 if let Some(task) = task {
16784 task.detach();
16785 };
16786 }
16787
16788 pub fn go_to_reference_before_or_after_position(
16789 &mut self,
16790 direction: Direction,
16791 count: usize,
16792 window: &mut Window,
16793 cx: &mut Context<Self>,
16794 ) -> Option<Task<Result<()>>> {
16795 let selection = self.selections.newest_anchor();
16796 let head = selection.head();
16797
16798 let multi_buffer = self.buffer.read(cx);
16799
16800 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16801 let workspace = self.workspace()?;
16802 let project = workspace.read(cx).project().clone();
16803 let references =
16804 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16805 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16806 let Some(locations) = references.await? else {
16807 return Ok(());
16808 };
16809
16810 if locations.is_empty() {
16811 // totally normal - the cursor may be on something which is not
16812 // a symbol (e.g. a keyword)
16813 log::info!("no references found under cursor");
16814 return Ok(());
16815 }
16816
16817 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16818
16819 let multi_buffer_snapshot =
16820 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16821
16822 let (locations, current_location_index) =
16823 multi_buffer.update(cx, |multi_buffer, cx| {
16824 let mut locations = locations
16825 .into_iter()
16826 .filter_map(|loc| {
16827 let start = multi_buffer.buffer_anchor_to_anchor(
16828 &loc.buffer,
16829 loc.range.start,
16830 cx,
16831 )?;
16832 let end = multi_buffer.buffer_anchor_to_anchor(
16833 &loc.buffer,
16834 loc.range.end,
16835 cx,
16836 )?;
16837 Some(start..end)
16838 })
16839 .collect::<Vec<_>>();
16840
16841 // There is an O(n) implementation, but given this list will be
16842 // small (usually <100 items), the extra O(log(n)) factor isn't
16843 // worth the (surprisingly large amount of) extra complexity.
16844 locations
16845 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16846
16847 let head_offset = head.to_offset(&multi_buffer_snapshot);
16848
16849 let current_location_index = locations.iter().position(|loc| {
16850 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16851 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16852 });
16853
16854 (locations, current_location_index)
16855 })?;
16856
16857 let Some(current_location_index) = current_location_index else {
16858 // This indicates something has gone wrong, because we already
16859 // handle the "no references" case above
16860 log::error!(
16861 "failed to find current reference under cursor. Total references: {}",
16862 locations.len()
16863 );
16864 return Ok(());
16865 };
16866
16867 let destination_location_index = match direction {
16868 Direction::Next => (current_location_index + count) % locations.len(),
16869 Direction::Prev => {
16870 (current_location_index + locations.len() - count % locations.len())
16871 % locations.len()
16872 }
16873 };
16874
16875 // TODO(cameron): is this needed?
16876 // the thinking is to avoid "jumping to the current location" (avoid
16877 // polluting "jumplist" in vim terms)
16878 if current_location_index == destination_location_index {
16879 return Ok(());
16880 }
16881
16882 let Range { start, end } = locations[destination_location_index];
16883
16884 editor.update_in(cx, |editor, window, cx| {
16885 let effects = SelectionEffects::default();
16886
16887 editor.unfold_ranges(&[start..end], false, false, cx);
16888 editor.change_selections(effects, window, cx, |s| {
16889 s.select_ranges([start..start]);
16890 });
16891 })?;
16892
16893 Ok(())
16894 }))
16895 }
16896
16897 pub fn find_all_references(
16898 &mut self,
16899 _: &FindAllReferences,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) -> Option<Task<Result<Navigated>>> {
16903 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16904 let multi_buffer = self.buffer.read(cx);
16905 let head = selection.head();
16906
16907 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16908 let head_anchor = multi_buffer_snapshot.anchor_at(
16909 head,
16910 if head < selection.tail() {
16911 Bias::Right
16912 } else {
16913 Bias::Left
16914 },
16915 );
16916
16917 match self
16918 .find_all_references_task_sources
16919 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16920 {
16921 Ok(_) => {
16922 log::info!(
16923 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16924 );
16925 return None;
16926 }
16927 Err(i) => {
16928 self.find_all_references_task_sources.insert(i, head_anchor);
16929 }
16930 }
16931
16932 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16933 let workspace = self.workspace()?;
16934 let project = workspace.read(cx).project().clone();
16935 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16936 Some(cx.spawn_in(window, async move |editor, cx| {
16937 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16938 if let Ok(i) = editor
16939 .find_all_references_task_sources
16940 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16941 {
16942 editor.find_all_references_task_sources.remove(i);
16943 }
16944 });
16945
16946 let Some(locations) = references.await? else {
16947 return anyhow::Ok(Navigated::No);
16948 };
16949 let mut locations = cx.update(|_, cx| {
16950 locations
16951 .into_iter()
16952 .map(|location| {
16953 let buffer = location.buffer.read(cx);
16954 (location.buffer, location.range.to_point(buffer))
16955 })
16956 .into_group_map()
16957 })?;
16958 if locations.is_empty() {
16959 return anyhow::Ok(Navigated::No);
16960 }
16961 for ranges in locations.values_mut() {
16962 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16963 ranges.dedup();
16964 }
16965
16966 workspace.update_in(cx, |workspace, window, cx| {
16967 let target = locations
16968 .iter()
16969 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16970 .map(|(buffer, location)| {
16971 buffer
16972 .read(cx)
16973 .text_for_range(location.clone())
16974 .collect::<String>()
16975 })
16976 .filter(|text| !text.contains('\n'))
16977 .unique()
16978 .take(3)
16979 .join(", ");
16980 let title = if target.is_empty() {
16981 "References".to_owned()
16982 } else {
16983 format!("References to {target}")
16984 };
16985 Self::open_locations_in_multibuffer(
16986 workspace,
16987 locations,
16988 title,
16989 false,
16990 MultibufferSelectionMode::First,
16991 window,
16992 cx,
16993 );
16994 Navigated::Yes
16995 })
16996 }))
16997 }
16998
16999 /// Opens a multibuffer with the given project locations in it
17000 pub fn open_locations_in_multibuffer(
17001 workspace: &mut Workspace,
17002 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17003 title: String,
17004 split: bool,
17005 multibuffer_selection_mode: MultibufferSelectionMode,
17006 window: &mut Window,
17007 cx: &mut Context<Workspace>,
17008 ) {
17009 if locations.is_empty() {
17010 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17011 return;
17012 }
17013
17014 let capability = workspace.project().read(cx).capability();
17015 let mut ranges = <Vec<Range<Anchor>>>::new();
17016
17017 // a key to find existing multibuffer editors with the same set of locations
17018 // to prevent us from opening more and more multibuffer tabs for searches and the like
17019 let mut key = (title.clone(), vec![]);
17020 let excerpt_buffer = cx.new(|cx| {
17021 let key = &mut key.1;
17022 let mut multibuffer = MultiBuffer::new(capability);
17023 for (buffer, mut ranges_for_buffer) in locations {
17024 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17025 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17026 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17027 PathKey::for_buffer(&buffer, cx),
17028 buffer.clone(),
17029 ranges_for_buffer,
17030 multibuffer_context_lines(cx),
17031 cx,
17032 );
17033 ranges.extend(new_ranges)
17034 }
17035
17036 multibuffer.with_title(title)
17037 });
17038 let existing = workspace.active_pane().update(cx, |pane, cx| {
17039 pane.items()
17040 .filter_map(|item| item.downcast::<Editor>())
17041 .find(|editor| {
17042 editor
17043 .read(cx)
17044 .lookup_key
17045 .as_ref()
17046 .and_then(|it| {
17047 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17048 })
17049 .is_some_and(|it| *it == key)
17050 })
17051 });
17052 let editor = existing.unwrap_or_else(|| {
17053 cx.new(|cx| {
17054 let mut editor = Editor::for_multibuffer(
17055 excerpt_buffer,
17056 Some(workspace.project().clone()),
17057 window,
17058 cx,
17059 );
17060 editor.lookup_key = Some(Box::new(key));
17061 editor
17062 })
17063 });
17064 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17065 MultibufferSelectionMode::First => {
17066 if let Some(first_range) = ranges.first() {
17067 editor.change_selections(
17068 SelectionEffects::no_scroll(),
17069 window,
17070 cx,
17071 |selections| {
17072 selections.clear_disjoint();
17073 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17074 },
17075 );
17076 }
17077 editor.highlight_background::<Self>(
17078 &ranges,
17079 |theme| theme.colors().editor_highlighted_line_background,
17080 cx,
17081 );
17082 }
17083 MultibufferSelectionMode::All => {
17084 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17085 selections.clear_disjoint();
17086 selections.select_anchor_ranges(ranges);
17087 });
17088 }
17089 });
17090
17091 let item = Box::new(editor);
17092 let item_id = item.item_id();
17093
17094 if split {
17095 let pane = workspace.adjacent_pane(window, cx);
17096 workspace.add_item(pane, item, None, true, true, window, cx);
17097 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17098 let (preview_item_id, preview_item_idx) =
17099 workspace.active_pane().read_with(cx, |pane, _| {
17100 (pane.preview_item_id(), pane.preview_item_idx())
17101 });
17102
17103 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17104
17105 if let Some(preview_item_id) = preview_item_id {
17106 workspace.active_pane().update(cx, |pane, cx| {
17107 pane.remove_item(preview_item_id, false, false, window, cx);
17108 });
17109 }
17110 } else {
17111 workspace.add_item_to_active_pane(item, None, true, window, cx);
17112 }
17113 workspace.active_pane().update(cx, |pane, cx| {
17114 pane.set_preview_item_id(Some(item_id), cx);
17115 });
17116 }
17117
17118 pub fn rename(
17119 &mut self,
17120 _: &Rename,
17121 window: &mut Window,
17122 cx: &mut Context<Self>,
17123 ) -> Option<Task<Result<()>>> {
17124 use language::ToOffset as _;
17125
17126 let provider = self.semantics_provider.clone()?;
17127 let selection = self.selections.newest_anchor().clone();
17128 let (cursor_buffer, cursor_buffer_position) = self
17129 .buffer
17130 .read(cx)
17131 .text_anchor_for_position(selection.head(), cx)?;
17132 let (tail_buffer, cursor_buffer_position_end) = self
17133 .buffer
17134 .read(cx)
17135 .text_anchor_for_position(selection.tail(), cx)?;
17136 if tail_buffer != cursor_buffer {
17137 return None;
17138 }
17139
17140 let snapshot = cursor_buffer.read(cx).snapshot();
17141 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17142 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17143 let prepare_rename = provider
17144 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17145 .unwrap_or_else(|| Task::ready(Ok(None)));
17146 drop(snapshot);
17147
17148 Some(cx.spawn_in(window, async move |this, cx| {
17149 let rename_range = if let Some(range) = prepare_rename.await? {
17150 Some(range)
17151 } else {
17152 this.update(cx, |this, cx| {
17153 let buffer = this.buffer.read(cx).snapshot(cx);
17154 let mut buffer_highlights = this
17155 .document_highlights_for_position(selection.head(), &buffer)
17156 .filter(|highlight| {
17157 highlight.start.excerpt_id == selection.head().excerpt_id
17158 && highlight.end.excerpt_id == selection.head().excerpt_id
17159 });
17160 buffer_highlights
17161 .next()
17162 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17163 })?
17164 };
17165 if let Some(rename_range) = rename_range {
17166 this.update_in(cx, |this, window, cx| {
17167 let snapshot = cursor_buffer.read(cx).snapshot();
17168 let rename_buffer_range = rename_range.to_offset(&snapshot);
17169 let cursor_offset_in_rename_range =
17170 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17171 let cursor_offset_in_rename_range_end =
17172 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17173
17174 this.take_rename(false, window, cx);
17175 let buffer = this.buffer.read(cx).read(cx);
17176 let cursor_offset = selection.head().to_offset(&buffer);
17177 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17178 let rename_end = rename_start + rename_buffer_range.len();
17179 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17180 let mut old_highlight_id = None;
17181 let old_name: Arc<str> = buffer
17182 .chunks(rename_start..rename_end, true)
17183 .map(|chunk| {
17184 if old_highlight_id.is_none() {
17185 old_highlight_id = chunk.syntax_highlight_id;
17186 }
17187 chunk.text
17188 })
17189 .collect::<String>()
17190 .into();
17191
17192 drop(buffer);
17193
17194 // Position the selection in the rename editor so that it matches the current selection.
17195 this.show_local_selections = false;
17196 let rename_editor = cx.new(|cx| {
17197 let mut editor = Editor::single_line(window, cx);
17198 editor.buffer.update(cx, |buffer, cx| {
17199 buffer.edit([(0..0, old_name.clone())], None, cx)
17200 });
17201 let rename_selection_range = match cursor_offset_in_rename_range
17202 .cmp(&cursor_offset_in_rename_range_end)
17203 {
17204 Ordering::Equal => {
17205 editor.select_all(&SelectAll, window, cx);
17206 return editor;
17207 }
17208 Ordering::Less => {
17209 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17210 }
17211 Ordering::Greater => {
17212 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17213 }
17214 };
17215 if rename_selection_range.end > old_name.len() {
17216 editor.select_all(&SelectAll, window, cx);
17217 } else {
17218 editor.change_selections(Default::default(), window, cx, |s| {
17219 s.select_ranges([rename_selection_range]);
17220 });
17221 }
17222 editor
17223 });
17224 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17225 if e == &EditorEvent::Focused {
17226 cx.emit(EditorEvent::FocusedIn)
17227 }
17228 })
17229 .detach();
17230
17231 let write_highlights =
17232 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17233 let read_highlights =
17234 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17235 let ranges = write_highlights
17236 .iter()
17237 .flat_map(|(_, ranges)| ranges.iter())
17238 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17239 .cloned()
17240 .collect();
17241
17242 this.highlight_text::<Rename>(
17243 ranges,
17244 HighlightStyle {
17245 fade_out: Some(0.6),
17246 ..Default::default()
17247 },
17248 cx,
17249 );
17250 let rename_focus_handle = rename_editor.focus_handle(cx);
17251 window.focus(&rename_focus_handle);
17252 let block_id = this.insert_blocks(
17253 [BlockProperties {
17254 style: BlockStyle::Flex,
17255 placement: BlockPlacement::Below(range.start),
17256 height: Some(1),
17257 render: Arc::new({
17258 let rename_editor = rename_editor.clone();
17259 move |cx: &mut BlockContext| {
17260 let mut text_style = cx.editor_style.text.clone();
17261 if let Some(highlight_style) = old_highlight_id
17262 .and_then(|h| h.style(&cx.editor_style.syntax))
17263 {
17264 text_style = text_style.highlight(highlight_style);
17265 }
17266 div()
17267 .block_mouse_except_scroll()
17268 .pl(cx.anchor_x)
17269 .child(EditorElement::new(
17270 &rename_editor,
17271 EditorStyle {
17272 background: cx.theme().system().transparent,
17273 local_player: cx.editor_style.local_player,
17274 text: text_style,
17275 scrollbar_width: cx.editor_style.scrollbar_width,
17276 syntax: cx.editor_style.syntax.clone(),
17277 status: cx.editor_style.status.clone(),
17278 inlay_hints_style: HighlightStyle {
17279 font_weight: Some(FontWeight::BOLD),
17280 ..make_inlay_hints_style(cx.app)
17281 },
17282 edit_prediction_styles: make_suggestion_styles(
17283 cx.app,
17284 ),
17285 ..EditorStyle::default()
17286 },
17287 ))
17288 .into_any_element()
17289 }
17290 }),
17291 priority: 0,
17292 }],
17293 Some(Autoscroll::fit()),
17294 cx,
17295 )[0];
17296 this.pending_rename = Some(RenameState {
17297 range,
17298 old_name,
17299 editor: rename_editor,
17300 block_id,
17301 });
17302 })?;
17303 }
17304
17305 Ok(())
17306 }))
17307 }
17308
17309 pub fn confirm_rename(
17310 &mut self,
17311 _: &ConfirmRename,
17312 window: &mut Window,
17313 cx: &mut Context<Self>,
17314 ) -> Option<Task<Result<()>>> {
17315 let rename = self.take_rename(false, window, cx)?;
17316 let workspace = self.workspace()?.downgrade();
17317 let (buffer, start) = self
17318 .buffer
17319 .read(cx)
17320 .text_anchor_for_position(rename.range.start, cx)?;
17321 let (end_buffer, _) = self
17322 .buffer
17323 .read(cx)
17324 .text_anchor_for_position(rename.range.end, cx)?;
17325 if buffer != end_buffer {
17326 return None;
17327 }
17328
17329 let old_name = rename.old_name;
17330 let new_name = rename.editor.read(cx).text(cx);
17331
17332 let rename = self.semantics_provider.as_ref()?.perform_rename(
17333 &buffer,
17334 start,
17335 new_name.clone(),
17336 cx,
17337 )?;
17338
17339 Some(cx.spawn_in(window, async move |editor, cx| {
17340 let project_transaction = rename.await?;
17341 Self::open_project_transaction(
17342 &editor,
17343 workspace,
17344 project_transaction,
17345 format!("Rename: {} → {}", old_name, new_name),
17346 cx,
17347 )
17348 .await?;
17349
17350 editor.update(cx, |editor, cx| {
17351 editor.refresh_document_highlights(cx);
17352 })?;
17353 Ok(())
17354 }))
17355 }
17356
17357 fn take_rename(
17358 &mut self,
17359 moving_cursor: bool,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) -> Option<RenameState> {
17363 let rename = self.pending_rename.take()?;
17364 if rename.editor.focus_handle(cx).is_focused(window) {
17365 window.focus(&self.focus_handle);
17366 }
17367
17368 self.remove_blocks(
17369 [rename.block_id].into_iter().collect(),
17370 Some(Autoscroll::fit()),
17371 cx,
17372 );
17373 self.clear_highlights::<Rename>(cx);
17374 self.show_local_selections = true;
17375
17376 if moving_cursor {
17377 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17378 editor
17379 .selections
17380 .newest::<usize>(&editor.display_snapshot(cx))
17381 .head()
17382 });
17383
17384 // Update the selection to match the position of the selection inside
17385 // the rename editor.
17386 let snapshot = self.buffer.read(cx).read(cx);
17387 let rename_range = rename.range.to_offset(&snapshot);
17388 let cursor_in_editor = snapshot
17389 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17390 .min(rename_range.end);
17391 drop(snapshot);
17392
17393 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17394 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17395 });
17396 } else {
17397 self.refresh_document_highlights(cx);
17398 }
17399
17400 Some(rename)
17401 }
17402
17403 pub fn pending_rename(&self) -> Option<&RenameState> {
17404 self.pending_rename.as_ref()
17405 }
17406
17407 fn format(
17408 &mut self,
17409 _: &Format,
17410 window: &mut Window,
17411 cx: &mut Context<Self>,
17412 ) -> Option<Task<Result<()>>> {
17413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17414
17415 let project = match &self.project {
17416 Some(project) => project.clone(),
17417 None => return None,
17418 };
17419
17420 Some(self.perform_format(
17421 project,
17422 FormatTrigger::Manual,
17423 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17424 window,
17425 cx,
17426 ))
17427 }
17428
17429 fn format_selections(
17430 &mut self,
17431 _: &FormatSelections,
17432 window: &mut Window,
17433 cx: &mut Context<Self>,
17434 ) -> Option<Task<Result<()>>> {
17435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17436
17437 let project = match &self.project {
17438 Some(project) => project.clone(),
17439 None => return None,
17440 };
17441
17442 let ranges = self
17443 .selections
17444 .all_adjusted(&self.display_snapshot(cx))
17445 .into_iter()
17446 .map(|selection| selection.range())
17447 .collect_vec();
17448
17449 Some(self.perform_format(
17450 project,
17451 FormatTrigger::Manual,
17452 FormatTarget::Ranges(ranges),
17453 window,
17454 cx,
17455 ))
17456 }
17457
17458 fn perform_format(
17459 &mut self,
17460 project: Entity<Project>,
17461 trigger: FormatTrigger,
17462 target: FormatTarget,
17463 window: &mut Window,
17464 cx: &mut Context<Self>,
17465 ) -> Task<Result<()>> {
17466 let buffer = self.buffer.clone();
17467 let (buffers, target) = match target {
17468 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17469 FormatTarget::Ranges(selection_ranges) => {
17470 let multi_buffer = buffer.read(cx);
17471 let snapshot = multi_buffer.read(cx);
17472 let mut buffers = HashSet::default();
17473 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17474 BTreeMap::new();
17475 for selection_range in selection_ranges {
17476 for (buffer, buffer_range, _) in
17477 snapshot.range_to_buffer_ranges(selection_range)
17478 {
17479 let buffer_id = buffer.remote_id();
17480 let start = buffer.anchor_before(buffer_range.start);
17481 let end = buffer.anchor_after(buffer_range.end);
17482 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17483 buffer_id_to_ranges
17484 .entry(buffer_id)
17485 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17486 .or_insert_with(|| vec![start..end]);
17487 }
17488 }
17489 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17490 }
17491 };
17492
17493 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17494 let selections_prev = transaction_id_prev
17495 .and_then(|transaction_id_prev| {
17496 // default to selections as they were after the last edit, if we have them,
17497 // instead of how they are now.
17498 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17499 // will take you back to where you made the last edit, instead of staying where you scrolled
17500 self.selection_history
17501 .transaction(transaction_id_prev)
17502 .map(|t| t.0.clone())
17503 })
17504 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17505
17506 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17507 let format = project.update(cx, |project, cx| {
17508 project.format(buffers, target, true, trigger, cx)
17509 });
17510
17511 cx.spawn_in(window, async move |editor, cx| {
17512 let transaction = futures::select_biased! {
17513 transaction = format.log_err().fuse() => transaction,
17514 () = timeout => {
17515 log::warn!("timed out waiting for formatting");
17516 None
17517 }
17518 };
17519
17520 buffer
17521 .update(cx, |buffer, cx| {
17522 if let Some(transaction) = transaction
17523 && !buffer.is_singleton()
17524 {
17525 buffer.push_transaction(&transaction.0, cx);
17526 }
17527 cx.notify();
17528 })
17529 .ok();
17530
17531 if let Some(transaction_id_now) =
17532 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17533 {
17534 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17535 if has_new_transaction {
17536 _ = editor.update(cx, |editor, _| {
17537 editor
17538 .selection_history
17539 .insert_transaction(transaction_id_now, selections_prev);
17540 });
17541 }
17542 }
17543
17544 Ok(())
17545 })
17546 }
17547
17548 fn organize_imports(
17549 &mut self,
17550 _: &OrganizeImports,
17551 window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) -> Option<Task<Result<()>>> {
17554 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17555 let project = match &self.project {
17556 Some(project) => project.clone(),
17557 None => return None,
17558 };
17559 Some(self.perform_code_action_kind(
17560 project,
17561 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17562 window,
17563 cx,
17564 ))
17565 }
17566
17567 fn perform_code_action_kind(
17568 &mut self,
17569 project: Entity<Project>,
17570 kind: CodeActionKind,
17571 window: &mut Window,
17572 cx: &mut Context<Self>,
17573 ) -> Task<Result<()>> {
17574 let buffer = self.buffer.clone();
17575 let buffers = buffer.read(cx).all_buffers();
17576 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17577 let apply_action = project.update(cx, |project, cx| {
17578 project.apply_code_action_kind(buffers, kind, true, cx)
17579 });
17580 cx.spawn_in(window, async move |_, cx| {
17581 let transaction = futures::select_biased! {
17582 () = timeout => {
17583 log::warn!("timed out waiting for executing code action");
17584 None
17585 }
17586 transaction = apply_action.log_err().fuse() => transaction,
17587 };
17588 buffer
17589 .update(cx, |buffer, cx| {
17590 // check if we need this
17591 if let Some(transaction) = transaction
17592 && !buffer.is_singleton()
17593 {
17594 buffer.push_transaction(&transaction.0, cx);
17595 }
17596 cx.notify();
17597 })
17598 .ok();
17599 Ok(())
17600 })
17601 }
17602
17603 pub fn restart_language_server(
17604 &mut self,
17605 _: &RestartLanguageServer,
17606 _: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) {
17609 if let Some(project) = self.project.clone() {
17610 self.buffer.update(cx, |multi_buffer, cx| {
17611 project.update(cx, |project, cx| {
17612 project.restart_language_servers_for_buffers(
17613 multi_buffer.all_buffers().into_iter().collect(),
17614 HashSet::default(),
17615 cx,
17616 );
17617 });
17618 })
17619 }
17620 }
17621
17622 pub fn stop_language_server(
17623 &mut self,
17624 _: &StopLanguageServer,
17625 _: &mut Window,
17626 cx: &mut Context<Self>,
17627 ) {
17628 if let Some(project) = self.project.clone() {
17629 self.buffer.update(cx, |multi_buffer, cx| {
17630 project.update(cx, |project, cx| {
17631 project.stop_language_servers_for_buffers(
17632 multi_buffer.all_buffers().into_iter().collect(),
17633 HashSet::default(),
17634 cx,
17635 );
17636 });
17637 });
17638 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17639 }
17640 }
17641
17642 fn cancel_language_server_work(
17643 workspace: &mut Workspace,
17644 _: &actions::CancelLanguageServerWork,
17645 _: &mut Window,
17646 cx: &mut Context<Workspace>,
17647 ) {
17648 let project = workspace.project();
17649 let buffers = workspace
17650 .active_item(cx)
17651 .and_then(|item| item.act_as::<Editor>(cx))
17652 .map_or(HashSet::default(), |editor| {
17653 editor.read(cx).buffer.read(cx).all_buffers()
17654 });
17655 project.update(cx, |project, cx| {
17656 project.cancel_language_server_work_for_buffers(buffers, cx);
17657 });
17658 }
17659
17660 fn show_character_palette(
17661 &mut self,
17662 _: &ShowCharacterPalette,
17663 window: &mut Window,
17664 _: &mut Context<Self>,
17665 ) {
17666 window.show_character_palette();
17667 }
17668
17669 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17670 if !self.diagnostics_enabled() {
17671 return;
17672 }
17673
17674 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17675 let buffer = self.buffer.read(cx).snapshot(cx);
17676 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17677 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17678 let is_valid = buffer
17679 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17680 .any(|entry| {
17681 entry.diagnostic.is_primary
17682 && !entry.range.is_empty()
17683 && entry.range.start == primary_range_start
17684 && entry.diagnostic.message == active_diagnostics.active_message
17685 });
17686
17687 if !is_valid {
17688 self.dismiss_diagnostics(cx);
17689 }
17690 }
17691 }
17692
17693 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17694 match &self.active_diagnostics {
17695 ActiveDiagnostic::Group(group) => Some(group),
17696 _ => None,
17697 }
17698 }
17699
17700 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17701 if !self.diagnostics_enabled() {
17702 return;
17703 }
17704 self.dismiss_diagnostics(cx);
17705 self.active_diagnostics = ActiveDiagnostic::All;
17706 }
17707
17708 fn activate_diagnostics(
17709 &mut self,
17710 buffer_id: BufferId,
17711 diagnostic: DiagnosticEntryRef<'_, usize>,
17712 window: &mut Window,
17713 cx: &mut Context<Self>,
17714 ) {
17715 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17716 return;
17717 }
17718 self.dismiss_diagnostics(cx);
17719 let snapshot = self.snapshot(window, cx);
17720 let buffer = self.buffer.read(cx).snapshot(cx);
17721 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17722 return;
17723 };
17724
17725 let diagnostic_group = buffer
17726 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17727 .collect::<Vec<_>>();
17728
17729 let blocks =
17730 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17731
17732 let blocks = self.display_map.update(cx, |display_map, cx| {
17733 display_map.insert_blocks(blocks, cx).into_iter().collect()
17734 });
17735 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17736 active_range: buffer.anchor_before(diagnostic.range.start)
17737 ..buffer.anchor_after(diagnostic.range.end),
17738 active_message: diagnostic.diagnostic.message.clone(),
17739 group_id: diagnostic.diagnostic.group_id,
17740 blocks,
17741 });
17742 cx.notify();
17743 }
17744
17745 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17746 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17747 return;
17748 };
17749
17750 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17751 if let ActiveDiagnostic::Group(group) = prev {
17752 self.display_map.update(cx, |display_map, cx| {
17753 display_map.remove_blocks(group.blocks, cx);
17754 });
17755 cx.notify();
17756 }
17757 }
17758
17759 /// Disable inline diagnostics rendering for this editor.
17760 pub fn disable_inline_diagnostics(&mut self) {
17761 self.inline_diagnostics_enabled = false;
17762 self.inline_diagnostics_update = Task::ready(());
17763 self.inline_diagnostics.clear();
17764 }
17765
17766 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17767 self.diagnostics_enabled = false;
17768 self.dismiss_diagnostics(cx);
17769 self.inline_diagnostics_update = Task::ready(());
17770 self.inline_diagnostics.clear();
17771 }
17772
17773 pub fn disable_word_completions(&mut self) {
17774 self.word_completions_enabled = false;
17775 }
17776
17777 pub fn diagnostics_enabled(&self) -> bool {
17778 self.diagnostics_enabled && self.mode.is_full()
17779 }
17780
17781 pub fn inline_diagnostics_enabled(&self) -> bool {
17782 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17783 }
17784
17785 pub fn show_inline_diagnostics(&self) -> bool {
17786 self.show_inline_diagnostics
17787 }
17788
17789 pub fn toggle_inline_diagnostics(
17790 &mut self,
17791 _: &ToggleInlineDiagnostics,
17792 window: &mut Window,
17793 cx: &mut Context<Editor>,
17794 ) {
17795 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17796 self.refresh_inline_diagnostics(false, window, cx);
17797 }
17798
17799 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17800 self.diagnostics_max_severity = severity;
17801 self.display_map.update(cx, |display_map, _| {
17802 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17803 });
17804 }
17805
17806 pub fn toggle_diagnostics(
17807 &mut self,
17808 _: &ToggleDiagnostics,
17809 window: &mut Window,
17810 cx: &mut Context<Editor>,
17811 ) {
17812 if !self.diagnostics_enabled() {
17813 return;
17814 }
17815
17816 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17817 EditorSettings::get_global(cx)
17818 .diagnostics_max_severity
17819 .filter(|severity| severity != &DiagnosticSeverity::Off)
17820 .unwrap_or(DiagnosticSeverity::Hint)
17821 } else {
17822 DiagnosticSeverity::Off
17823 };
17824 self.set_max_diagnostics_severity(new_severity, cx);
17825 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17826 self.active_diagnostics = ActiveDiagnostic::None;
17827 self.inline_diagnostics_update = Task::ready(());
17828 self.inline_diagnostics.clear();
17829 } else {
17830 self.refresh_inline_diagnostics(false, window, cx);
17831 }
17832
17833 cx.notify();
17834 }
17835
17836 pub fn toggle_minimap(
17837 &mut self,
17838 _: &ToggleMinimap,
17839 window: &mut Window,
17840 cx: &mut Context<Editor>,
17841 ) {
17842 if self.supports_minimap(cx) {
17843 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17844 }
17845 }
17846
17847 fn refresh_inline_diagnostics(
17848 &mut self,
17849 debounce: bool,
17850 window: &mut Window,
17851 cx: &mut Context<Self>,
17852 ) {
17853 let max_severity = ProjectSettings::get_global(cx)
17854 .diagnostics
17855 .inline
17856 .max_severity
17857 .unwrap_or(self.diagnostics_max_severity);
17858
17859 if !self.inline_diagnostics_enabled()
17860 || !self.diagnostics_enabled()
17861 || !self.show_inline_diagnostics
17862 || max_severity == DiagnosticSeverity::Off
17863 {
17864 self.inline_diagnostics_update = Task::ready(());
17865 self.inline_diagnostics.clear();
17866 return;
17867 }
17868
17869 let debounce_ms = ProjectSettings::get_global(cx)
17870 .diagnostics
17871 .inline
17872 .update_debounce_ms;
17873 let debounce = if debounce && debounce_ms > 0 {
17874 Some(Duration::from_millis(debounce_ms))
17875 } else {
17876 None
17877 };
17878 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17879 if let Some(debounce) = debounce {
17880 cx.background_executor().timer(debounce).await;
17881 }
17882 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17883 editor
17884 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17885 .ok()
17886 }) else {
17887 return;
17888 };
17889
17890 let new_inline_diagnostics = cx
17891 .background_spawn(async move {
17892 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17893 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17894 let message = diagnostic_entry
17895 .diagnostic
17896 .message
17897 .split_once('\n')
17898 .map(|(line, _)| line)
17899 .map(SharedString::new)
17900 .unwrap_or_else(|| {
17901 SharedString::new(&*diagnostic_entry.diagnostic.message)
17902 });
17903 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17904 let (Ok(i) | Err(i)) = inline_diagnostics
17905 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17906 inline_diagnostics.insert(
17907 i,
17908 (
17909 start_anchor,
17910 InlineDiagnostic {
17911 message,
17912 group_id: diagnostic_entry.diagnostic.group_id,
17913 start: diagnostic_entry.range.start.to_point(&snapshot),
17914 is_primary: diagnostic_entry.diagnostic.is_primary,
17915 severity: diagnostic_entry.diagnostic.severity,
17916 },
17917 ),
17918 );
17919 }
17920 inline_diagnostics
17921 })
17922 .await;
17923
17924 editor
17925 .update(cx, |editor, cx| {
17926 editor.inline_diagnostics = new_inline_diagnostics;
17927 cx.notify();
17928 })
17929 .ok();
17930 });
17931 }
17932
17933 fn pull_diagnostics(
17934 &mut self,
17935 buffer_id: Option<BufferId>,
17936 window: &Window,
17937 cx: &mut Context<Self>,
17938 ) -> Option<()> {
17939 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17940 return None;
17941 }
17942 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17943 .diagnostics
17944 .lsp_pull_diagnostics;
17945 if !pull_diagnostics_settings.enabled {
17946 return None;
17947 }
17948 let project = self.project()?.downgrade();
17949 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17950 let mut buffers = self.buffer.read(cx).all_buffers();
17951 buffers.retain(|buffer| {
17952 let buffer_id_to_retain = buffer.read(cx).remote_id();
17953 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17954 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17955 });
17956 if buffers.is_empty() {
17957 self.pull_diagnostics_task = Task::ready(());
17958 return None;
17959 }
17960
17961 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17962 cx.background_executor().timer(debounce).await;
17963
17964 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17965 buffers
17966 .into_iter()
17967 .filter_map(|buffer| {
17968 project
17969 .update(cx, |project, cx| {
17970 project.lsp_store().update(cx, |lsp_store, cx| {
17971 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17972 })
17973 })
17974 .ok()
17975 })
17976 .collect::<FuturesUnordered<_>>()
17977 }) else {
17978 return;
17979 };
17980
17981 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17982 match pull_task {
17983 Ok(()) => {
17984 if editor
17985 .update_in(cx, |editor, window, cx| {
17986 editor.update_diagnostics_state(window, cx);
17987 })
17988 .is_err()
17989 {
17990 return;
17991 }
17992 }
17993 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17994 }
17995 }
17996 });
17997
17998 Some(())
17999 }
18000
18001 pub fn set_selections_from_remote(
18002 &mut self,
18003 selections: Vec<Selection<Anchor>>,
18004 pending_selection: Option<Selection<Anchor>>,
18005 window: &mut Window,
18006 cx: &mut Context<Self>,
18007 ) {
18008 let old_cursor_position = self.selections.newest_anchor().head();
18009 self.selections.change_with(cx, |s| {
18010 s.select_anchors(selections);
18011 if let Some(pending_selection) = pending_selection {
18012 s.set_pending(pending_selection, SelectMode::Character);
18013 } else {
18014 s.clear_pending();
18015 }
18016 });
18017 self.selections_did_change(
18018 false,
18019 &old_cursor_position,
18020 SelectionEffects::default(),
18021 window,
18022 cx,
18023 );
18024 }
18025
18026 pub fn transact(
18027 &mut self,
18028 window: &mut Window,
18029 cx: &mut Context<Self>,
18030 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18031 ) -> Option<TransactionId> {
18032 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18033 this.start_transaction_at(Instant::now(), window, cx);
18034 update(this, window, cx);
18035 this.end_transaction_at(Instant::now(), cx)
18036 })
18037 }
18038
18039 pub fn start_transaction_at(
18040 &mut self,
18041 now: Instant,
18042 window: &mut Window,
18043 cx: &mut Context<Self>,
18044 ) -> Option<TransactionId> {
18045 self.end_selection(window, cx);
18046 if let Some(tx_id) = self
18047 .buffer
18048 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18049 {
18050 self.selection_history
18051 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18052 cx.emit(EditorEvent::TransactionBegun {
18053 transaction_id: tx_id,
18054 });
18055 Some(tx_id)
18056 } else {
18057 None
18058 }
18059 }
18060
18061 pub fn end_transaction_at(
18062 &mut self,
18063 now: Instant,
18064 cx: &mut Context<Self>,
18065 ) -> Option<TransactionId> {
18066 if let Some(transaction_id) = self
18067 .buffer
18068 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18069 {
18070 if let Some((_, end_selections)) =
18071 self.selection_history.transaction_mut(transaction_id)
18072 {
18073 *end_selections = Some(self.selections.disjoint_anchors_arc());
18074 } else {
18075 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18076 }
18077
18078 cx.emit(EditorEvent::Edited { transaction_id });
18079 Some(transaction_id)
18080 } else {
18081 None
18082 }
18083 }
18084
18085 pub fn modify_transaction_selection_history(
18086 &mut self,
18087 transaction_id: TransactionId,
18088 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18089 ) -> bool {
18090 self.selection_history
18091 .transaction_mut(transaction_id)
18092 .map(modify)
18093 .is_some()
18094 }
18095
18096 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18097 if self.selection_mark_mode {
18098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18099 s.move_with(|_, sel| {
18100 sel.collapse_to(sel.head(), SelectionGoal::None);
18101 });
18102 })
18103 }
18104 self.selection_mark_mode = true;
18105 cx.notify();
18106 }
18107
18108 pub fn swap_selection_ends(
18109 &mut self,
18110 _: &actions::SwapSelectionEnds,
18111 window: &mut Window,
18112 cx: &mut Context<Self>,
18113 ) {
18114 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18115 s.move_with(|_, sel| {
18116 if sel.start != sel.end {
18117 sel.reversed = !sel.reversed
18118 }
18119 });
18120 });
18121 self.request_autoscroll(Autoscroll::newest(), cx);
18122 cx.notify();
18123 }
18124
18125 pub fn toggle_focus(
18126 workspace: &mut Workspace,
18127 _: &actions::ToggleFocus,
18128 window: &mut Window,
18129 cx: &mut Context<Workspace>,
18130 ) {
18131 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18132 return;
18133 };
18134 workspace.activate_item(&item, true, true, window, cx);
18135 }
18136
18137 pub fn toggle_fold(
18138 &mut self,
18139 _: &actions::ToggleFold,
18140 window: &mut Window,
18141 cx: &mut Context<Self>,
18142 ) {
18143 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18144 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18145 let selection = self.selections.newest::<Point>(&display_map);
18146
18147 let range = if selection.is_empty() {
18148 let point = selection.head().to_display_point(&display_map);
18149 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18150 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18151 .to_point(&display_map);
18152 start..end
18153 } else {
18154 selection.range()
18155 };
18156 if display_map.folds_in_range(range).next().is_some() {
18157 self.unfold_lines(&Default::default(), window, cx)
18158 } else {
18159 self.fold(&Default::default(), window, cx)
18160 }
18161 } else {
18162 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18163 let buffer_ids: HashSet<_> = self
18164 .selections
18165 .disjoint_anchor_ranges()
18166 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18167 .collect();
18168
18169 let should_unfold = buffer_ids
18170 .iter()
18171 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18172
18173 for buffer_id in buffer_ids {
18174 if should_unfold {
18175 self.unfold_buffer(buffer_id, cx);
18176 } else {
18177 self.fold_buffer(buffer_id, cx);
18178 }
18179 }
18180 }
18181 }
18182
18183 pub fn toggle_fold_recursive(
18184 &mut self,
18185 _: &actions::ToggleFoldRecursive,
18186 window: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) {
18189 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18190
18191 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18192 let range = if selection.is_empty() {
18193 let point = selection.head().to_display_point(&display_map);
18194 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18195 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18196 .to_point(&display_map);
18197 start..end
18198 } else {
18199 selection.range()
18200 };
18201 if display_map.folds_in_range(range).next().is_some() {
18202 self.unfold_recursive(&Default::default(), window, cx)
18203 } else {
18204 self.fold_recursive(&Default::default(), window, cx)
18205 }
18206 }
18207
18208 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18209 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18210 let mut to_fold = Vec::new();
18211 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18212 let selections = self.selections.all_adjusted(&display_map);
18213
18214 for selection in selections {
18215 let range = selection.range().sorted();
18216 let buffer_start_row = range.start.row;
18217
18218 if range.start.row != range.end.row {
18219 let mut found = false;
18220 let mut row = range.start.row;
18221 while row <= range.end.row {
18222 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18223 {
18224 found = true;
18225 row = crease.range().end.row + 1;
18226 to_fold.push(crease);
18227 } else {
18228 row += 1
18229 }
18230 }
18231 if found {
18232 continue;
18233 }
18234 }
18235
18236 for row in (0..=range.start.row).rev() {
18237 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18238 && crease.range().end.row >= buffer_start_row
18239 {
18240 to_fold.push(crease);
18241 if row <= range.start.row {
18242 break;
18243 }
18244 }
18245 }
18246 }
18247
18248 self.fold_creases(to_fold, true, window, cx);
18249 } else {
18250 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18251 let buffer_ids = self
18252 .selections
18253 .disjoint_anchor_ranges()
18254 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18255 .collect::<HashSet<_>>();
18256 for buffer_id in buffer_ids {
18257 self.fold_buffer(buffer_id, cx);
18258 }
18259 }
18260 }
18261
18262 pub fn toggle_fold_all(
18263 &mut self,
18264 _: &actions::ToggleFoldAll,
18265 window: &mut Window,
18266 cx: &mut Context<Self>,
18267 ) {
18268 if self.buffer.read(cx).is_singleton() {
18269 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18270 let has_folds = display_map
18271 .folds_in_range(0..display_map.buffer_snapshot().len())
18272 .next()
18273 .is_some();
18274
18275 if has_folds {
18276 self.unfold_all(&actions::UnfoldAll, window, cx);
18277 } else {
18278 self.fold_all(&actions::FoldAll, window, cx);
18279 }
18280 } else {
18281 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18282 let should_unfold = buffer_ids
18283 .iter()
18284 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18285
18286 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18287 editor
18288 .update_in(cx, |editor, _, cx| {
18289 for buffer_id in buffer_ids {
18290 if should_unfold {
18291 editor.unfold_buffer(buffer_id, cx);
18292 } else {
18293 editor.fold_buffer(buffer_id, cx);
18294 }
18295 }
18296 })
18297 .ok();
18298 });
18299 }
18300 }
18301
18302 fn fold_at_level(
18303 &mut self,
18304 fold_at: &FoldAtLevel,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) {
18308 if !self.buffer.read(cx).is_singleton() {
18309 return;
18310 }
18311
18312 let fold_at_level = fold_at.0;
18313 let snapshot = self.buffer.read(cx).snapshot(cx);
18314 let mut to_fold = Vec::new();
18315 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18316
18317 let row_ranges_to_keep: Vec<Range<u32>> = self
18318 .selections
18319 .all::<Point>(&self.display_snapshot(cx))
18320 .into_iter()
18321 .map(|sel| sel.start.row..sel.end.row)
18322 .collect();
18323
18324 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18325 while start_row < end_row {
18326 match self
18327 .snapshot(window, cx)
18328 .crease_for_buffer_row(MultiBufferRow(start_row))
18329 {
18330 Some(crease) => {
18331 let nested_start_row = crease.range().start.row + 1;
18332 let nested_end_row = crease.range().end.row;
18333
18334 if current_level < fold_at_level {
18335 stack.push((nested_start_row, nested_end_row, current_level + 1));
18336 } else if current_level == fold_at_level {
18337 // Fold iff there is no selection completely contained within the fold region
18338 if !row_ranges_to_keep.iter().any(|selection| {
18339 selection.end >= nested_start_row
18340 && selection.start <= nested_end_row
18341 }) {
18342 to_fold.push(crease);
18343 }
18344 }
18345
18346 start_row = nested_end_row + 1;
18347 }
18348 None => start_row += 1,
18349 }
18350 }
18351 }
18352
18353 self.fold_creases(to_fold, true, window, cx);
18354 }
18355
18356 pub fn fold_at_level_1(
18357 &mut self,
18358 _: &actions::FoldAtLevel1,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18363 }
18364
18365 pub fn fold_at_level_2(
18366 &mut self,
18367 _: &actions::FoldAtLevel2,
18368 window: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) {
18371 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18372 }
18373
18374 pub fn fold_at_level_3(
18375 &mut self,
18376 _: &actions::FoldAtLevel3,
18377 window: &mut Window,
18378 cx: &mut Context<Self>,
18379 ) {
18380 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18381 }
18382
18383 pub fn fold_at_level_4(
18384 &mut self,
18385 _: &actions::FoldAtLevel4,
18386 window: &mut Window,
18387 cx: &mut Context<Self>,
18388 ) {
18389 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18390 }
18391
18392 pub fn fold_at_level_5(
18393 &mut self,
18394 _: &actions::FoldAtLevel5,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) {
18398 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18399 }
18400
18401 pub fn fold_at_level_6(
18402 &mut self,
18403 _: &actions::FoldAtLevel6,
18404 window: &mut Window,
18405 cx: &mut Context<Self>,
18406 ) {
18407 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18408 }
18409
18410 pub fn fold_at_level_7(
18411 &mut self,
18412 _: &actions::FoldAtLevel7,
18413 window: &mut Window,
18414 cx: &mut Context<Self>,
18415 ) {
18416 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18417 }
18418
18419 pub fn fold_at_level_8(
18420 &mut self,
18421 _: &actions::FoldAtLevel8,
18422 window: &mut Window,
18423 cx: &mut Context<Self>,
18424 ) {
18425 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18426 }
18427
18428 pub fn fold_at_level_9(
18429 &mut self,
18430 _: &actions::FoldAtLevel9,
18431 window: &mut Window,
18432 cx: &mut Context<Self>,
18433 ) {
18434 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18435 }
18436
18437 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18438 if self.buffer.read(cx).is_singleton() {
18439 let mut fold_ranges = Vec::new();
18440 let snapshot = self.buffer.read(cx).snapshot(cx);
18441
18442 for row in 0..snapshot.max_row().0 {
18443 if let Some(foldable_range) = self
18444 .snapshot(window, cx)
18445 .crease_for_buffer_row(MultiBufferRow(row))
18446 {
18447 fold_ranges.push(foldable_range);
18448 }
18449 }
18450
18451 self.fold_creases(fold_ranges, true, window, cx);
18452 } else {
18453 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18454 editor
18455 .update_in(cx, |editor, _, cx| {
18456 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18457 editor.fold_buffer(buffer_id, cx);
18458 }
18459 })
18460 .ok();
18461 });
18462 }
18463 }
18464
18465 pub fn fold_function_bodies(
18466 &mut self,
18467 _: &actions::FoldFunctionBodies,
18468 window: &mut Window,
18469 cx: &mut Context<Self>,
18470 ) {
18471 let snapshot = self.buffer.read(cx).snapshot(cx);
18472
18473 let ranges = snapshot
18474 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18475 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18476 .collect::<Vec<_>>();
18477
18478 let creases = ranges
18479 .into_iter()
18480 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18481 .collect();
18482
18483 self.fold_creases(creases, true, window, cx);
18484 }
18485
18486 pub fn fold_recursive(
18487 &mut self,
18488 _: &actions::FoldRecursive,
18489 window: &mut Window,
18490 cx: &mut Context<Self>,
18491 ) {
18492 let mut to_fold = Vec::new();
18493 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18494 let selections = self.selections.all_adjusted(&display_map);
18495
18496 for selection in selections {
18497 let range = selection.range().sorted();
18498 let buffer_start_row = range.start.row;
18499
18500 if range.start.row != range.end.row {
18501 let mut found = false;
18502 for row in range.start.row..=range.end.row {
18503 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18504 found = true;
18505 to_fold.push(crease);
18506 }
18507 }
18508 if found {
18509 continue;
18510 }
18511 }
18512
18513 for row in (0..=range.start.row).rev() {
18514 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18515 if crease.range().end.row >= buffer_start_row {
18516 to_fold.push(crease);
18517 } else {
18518 break;
18519 }
18520 }
18521 }
18522 }
18523
18524 self.fold_creases(to_fold, true, window, cx);
18525 }
18526
18527 pub fn fold_at(
18528 &mut self,
18529 buffer_row: MultiBufferRow,
18530 window: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18534
18535 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18536 let autoscroll = self
18537 .selections
18538 .all::<Point>(&display_map)
18539 .iter()
18540 .any(|selection| crease.range().overlaps(&selection.range()));
18541
18542 self.fold_creases(vec![crease], autoscroll, window, cx);
18543 }
18544 }
18545
18546 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18547 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18548 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18549 let buffer = display_map.buffer_snapshot();
18550 let selections = self.selections.all::<Point>(&display_map);
18551 let ranges = selections
18552 .iter()
18553 .map(|s| {
18554 let range = s.display_range(&display_map).sorted();
18555 let mut start = range.start.to_point(&display_map);
18556 let mut end = range.end.to_point(&display_map);
18557 start.column = 0;
18558 end.column = buffer.line_len(MultiBufferRow(end.row));
18559 start..end
18560 })
18561 .collect::<Vec<_>>();
18562
18563 self.unfold_ranges(&ranges, true, true, cx);
18564 } else {
18565 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18566 let buffer_ids = self
18567 .selections
18568 .disjoint_anchor_ranges()
18569 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18570 .collect::<HashSet<_>>();
18571 for buffer_id in buffer_ids {
18572 self.unfold_buffer(buffer_id, cx);
18573 }
18574 }
18575 }
18576
18577 pub fn unfold_recursive(
18578 &mut self,
18579 _: &UnfoldRecursive,
18580 _window: &mut Window,
18581 cx: &mut Context<Self>,
18582 ) {
18583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18584 let selections = self.selections.all::<Point>(&display_map);
18585 let ranges = selections
18586 .iter()
18587 .map(|s| {
18588 let mut range = s.display_range(&display_map).sorted();
18589 *range.start.column_mut() = 0;
18590 *range.end.column_mut() = display_map.line_len(range.end.row());
18591 let start = range.start.to_point(&display_map);
18592 let end = range.end.to_point(&display_map);
18593 start..end
18594 })
18595 .collect::<Vec<_>>();
18596
18597 self.unfold_ranges(&ranges, true, true, cx);
18598 }
18599
18600 pub fn unfold_at(
18601 &mut self,
18602 buffer_row: MultiBufferRow,
18603 _window: &mut Window,
18604 cx: &mut Context<Self>,
18605 ) {
18606 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18607
18608 let intersection_range = Point::new(buffer_row.0, 0)
18609 ..Point::new(
18610 buffer_row.0,
18611 display_map.buffer_snapshot().line_len(buffer_row),
18612 );
18613
18614 let autoscroll = self
18615 .selections
18616 .all::<Point>(&display_map)
18617 .iter()
18618 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18619
18620 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18621 }
18622
18623 pub fn unfold_all(
18624 &mut self,
18625 _: &actions::UnfoldAll,
18626 _window: &mut Window,
18627 cx: &mut Context<Self>,
18628 ) {
18629 if self.buffer.read(cx).is_singleton() {
18630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18631 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18632 } else {
18633 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18634 editor
18635 .update(cx, |editor, cx| {
18636 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18637 editor.unfold_buffer(buffer_id, cx);
18638 }
18639 })
18640 .ok();
18641 });
18642 }
18643 }
18644
18645 pub fn fold_selected_ranges(
18646 &mut self,
18647 _: &FoldSelectedRanges,
18648 window: &mut Window,
18649 cx: &mut Context<Self>,
18650 ) {
18651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18652 let selections = self.selections.all_adjusted(&display_map);
18653 let ranges = selections
18654 .into_iter()
18655 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18656 .collect::<Vec<_>>();
18657 self.fold_creases(ranges, true, window, cx);
18658 }
18659
18660 pub fn fold_ranges<T: ToOffset + Clone>(
18661 &mut self,
18662 ranges: Vec<Range<T>>,
18663 auto_scroll: bool,
18664 window: &mut Window,
18665 cx: &mut Context<Self>,
18666 ) {
18667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18668 let ranges = ranges
18669 .into_iter()
18670 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18671 .collect::<Vec<_>>();
18672 self.fold_creases(ranges, auto_scroll, window, cx);
18673 }
18674
18675 pub fn fold_creases<T: ToOffset + Clone>(
18676 &mut self,
18677 creases: Vec<Crease<T>>,
18678 auto_scroll: bool,
18679 _window: &mut Window,
18680 cx: &mut Context<Self>,
18681 ) {
18682 if creases.is_empty() {
18683 return;
18684 }
18685
18686 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18687
18688 if auto_scroll {
18689 self.request_autoscroll(Autoscroll::fit(), cx);
18690 }
18691
18692 cx.notify();
18693
18694 self.scrollbar_marker_state.dirty = true;
18695 self.folds_did_change(cx);
18696 }
18697
18698 /// Removes any folds whose ranges intersect any of the given ranges.
18699 pub fn unfold_ranges<T: ToOffset + Clone>(
18700 &mut self,
18701 ranges: &[Range<T>],
18702 inclusive: bool,
18703 auto_scroll: bool,
18704 cx: &mut Context<Self>,
18705 ) {
18706 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18707 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18708 });
18709 self.folds_did_change(cx);
18710 }
18711
18712 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18713 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18714 return;
18715 }
18716 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18717 self.display_map.update(cx, |display_map, cx| {
18718 display_map.fold_buffers([buffer_id], cx)
18719 });
18720 cx.emit(EditorEvent::BufferFoldToggled {
18721 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18722 folded: true,
18723 });
18724 cx.notify();
18725 }
18726
18727 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18728 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18729 return;
18730 }
18731 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18732 self.display_map.update(cx, |display_map, cx| {
18733 display_map.unfold_buffers([buffer_id], cx);
18734 });
18735 cx.emit(EditorEvent::BufferFoldToggled {
18736 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18737 folded: false,
18738 });
18739 cx.notify();
18740 }
18741
18742 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18743 self.display_map.read(cx).is_buffer_folded(buffer)
18744 }
18745
18746 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18747 self.display_map.read(cx).folded_buffers()
18748 }
18749
18750 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18751 self.display_map.update(cx, |display_map, cx| {
18752 display_map.disable_header_for_buffer(buffer_id, cx);
18753 });
18754 cx.notify();
18755 }
18756
18757 /// Removes any folds with the given ranges.
18758 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18759 &mut self,
18760 ranges: &[Range<T>],
18761 type_id: TypeId,
18762 auto_scroll: bool,
18763 cx: &mut Context<Self>,
18764 ) {
18765 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18766 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18767 });
18768 self.folds_did_change(cx);
18769 }
18770
18771 fn remove_folds_with<T: ToOffset + Clone>(
18772 &mut self,
18773 ranges: &[Range<T>],
18774 auto_scroll: bool,
18775 cx: &mut Context<Self>,
18776 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18777 ) {
18778 if ranges.is_empty() {
18779 return;
18780 }
18781
18782 let mut buffers_affected = HashSet::default();
18783 let multi_buffer = self.buffer().read(cx);
18784 for range in ranges {
18785 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18786 buffers_affected.insert(buffer.read(cx).remote_id());
18787 };
18788 }
18789
18790 self.display_map.update(cx, update);
18791
18792 if auto_scroll {
18793 self.request_autoscroll(Autoscroll::fit(), cx);
18794 }
18795
18796 cx.notify();
18797 self.scrollbar_marker_state.dirty = true;
18798 self.active_indent_guides_state.dirty = true;
18799 }
18800
18801 pub fn update_renderer_widths(
18802 &mut self,
18803 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18804 cx: &mut Context<Self>,
18805 ) -> bool {
18806 self.display_map
18807 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18808 }
18809
18810 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18811 self.display_map.read(cx).fold_placeholder.clone()
18812 }
18813
18814 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18815 self.buffer.update(cx, |buffer, cx| {
18816 buffer.set_all_diff_hunks_expanded(cx);
18817 });
18818 }
18819
18820 pub fn expand_all_diff_hunks(
18821 &mut self,
18822 _: &ExpandAllDiffHunks,
18823 _window: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) {
18826 self.buffer.update(cx, |buffer, cx| {
18827 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18828 });
18829 }
18830
18831 pub fn collapse_all_diff_hunks(
18832 &mut self,
18833 _: &CollapseAllDiffHunks,
18834 _window: &mut Window,
18835 cx: &mut Context<Self>,
18836 ) {
18837 self.buffer.update(cx, |buffer, cx| {
18838 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18839 });
18840 }
18841
18842 pub fn toggle_selected_diff_hunks(
18843 &mut self,
18844 _: &ToggleSelectedDiffHunks,
18845 _window: &mut Window,
18846 cx: &mut Context<Self>,
18847 ) {
18848 let ranges: Vec<_> = self
18849 .selections
18850 .disjoint_anchors()
18851 .iter()
18852 .map(|s| s.range())
18853 .collect();
18854 self.toggle_diff_hunks_in_ranges(ranges, cx);
18855 }
18856
18857 pub fn diff_hunks_in_ranges<'a>(
18858 &'a self,
18859 ranges: &'a [Range<Anchor>],
18860 buffer: &'a MultiBufferSnapshot,
18861 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18862 ranges.iter().flat_map(move |range| {
18863 let end_excerpt_id = range.end.excerpt_id;
18864 let range = range.to_point(buffer);
18865 let mut peek_end = range.end;
18866 if range.end.row < buffer.max_row().0 {
18867 peek_end = Point::new(range.end.row + 1, 0);
18868 }
18869 buffer
18870 .diff_hunks_in_range(range.start..peek_end)
18871 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18872 })
18873 }
18874
18875 pub fn has_stageable_diff_hunks_in_ranges(
18876 &self,
18877 ranges: &[Range<Anchor>],
18878 snapshot: &MultiBufferSnapshot,
18879 ) -> bool {
18880 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18881 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18882 }
18883
18884 pub fn toggle_staged_selected_diff_hunks(
18885 &mut self,
18886 _: &::git::ToggleStaged,
18887 _: &mut Window,
18888 cx: &mut Context<Self>,
18889 ) {
18890 let snapshot = self.buffer.read(cx).snapshot(cx);
18891 let ranges: Vec<_> = self
18892 .selections
18893 .disjoint_anchors()
18894 .iter()
18895 .map(|s| s.range())
18896 .collect();
18897 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18898 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18899 }
18900
18901 pub fn set_render_diff_hunk_controls(
18902 &mut self,
18903 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18904 cx: &mut Context<Self>,
18905 ) {
18906 self.render_diff_hunk_controls = render_diff_hunk_controls;
18907 cx.notify();
18908 }
18909
18910 pub fn stage_and_next(
18911 &mut self,
18912 _: &::git::StageAndNext,
18913 window: &mut Window,
18914 cx: &mut Context<Self>,
18915 ) {
18916 self.do_stage_or_unstage_and_next(true, window, cx);
18917 }
18918
18919 pub fn unstage_and_next(
18920 &mut self,
18921 _: &::git::UnstageAndNext,
18922 window: &mut Window,
18923 cx: &mut Context<Self>,
18924 ) {
18925 self.do_stage_or_unstage_and_next(false, window, cx);
18926 }
18927
18928 pub fn stage_or_unstage_diff_hunks(
18929 &mut self,
18930 stage: bool,
18931 ranges: Vec<Range<Anchor>>,
18932 cx: &mut Context<Self>,
18933 ) {
18934 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18935 cx.spawn(async move |this, cx| {
18936 task.await?;
18937 this.update(cx, |this, cx| {
18938 let snapshot = this.buffer.read(cx).snapshot(cx);
18939 let chunk_by = this
18940 .diff_hunks_in_ranges(&ranges, &snapshot)
18941 .chunk_by(|hunk| hunk.buffer_id);
18942 for (buffer_id, hunks) in &chunk_by {
18943 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18944 }
18945 })
18946 })
18947 .detach_and_log_err(cx);
18948 }
18949
18950 fn save_buffers_for_ranges_if_needed(
18951 &mut self,
18952 ranges: &[Range<Anchor>],
18953 cx: &mut Context<Editor>,
18954 ) -> Task<Result<()>> {
18955 let multibuffer = self.buffer.read(cx);
18956 let snapshot = multibuffer.read(cx);
18957 let buffer_ids: HashSet<_> = ranges
18958 .iter()
18959 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18960 .collect();
18961 drop(snapshot);
18962
18963 let mut buffers = HashSet::default();
18964 for buffer_id in buffer_ids {
18965 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18966 let buffer = buffer_entity.read(cx);
18967 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18968 {
18969 buffers.insert(buffer_entity);
18970 }
18971 }
18972 }
18973
18974 if let Some(project) = &self.project {
18975 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18976 } else {
18977 Task::ready(Ok(()))
18978 }
18979 }
18980
18981 fn do_stage_or_unstage_and_next(
18982 &mut self,
18983 stage: bool,
18984 window: &mut Window,
18985 cx: &mut Context<Self>,
18986 ) {
18987 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18988
18989 if ranges.iter().any(|range| range.start != range.end) {
18990 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18991 return;
18992 }
18993
18994 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18995 let snapshot = self.snapshot(window, cx);
18996 let position = self
18997 .selections
18998 .newest::<Point>(&snapshot.display_snapshot)
18999 .head();
19000 let mut row = snapshot
19001 .buffer_snapshot()
19002 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19003 .find(|hunk| hunk.row_range.start.0 > position.row)
19004 .map(|hunk| hunk.row_range.start);
19005
19006 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19007 // Outside of the project diff editor, wrap around to the beginning.
19008 if !all_diff_hunks_expanded {
19009 row = row.or_else(|| {
19010 snapshot
19011 .buffer_snapshot()
19012 .diff_hunks_in_range(Point::zero()..position)
19013 .find(|hunk| hunk.row_range.end.0 < position.row)
19014 .map(|hunk| hunk.row_range.start)
19015 });
19016 }
19017
19018 if let Some(row) = row {
19019 let destination = Point::new(row.0, 0);
19020 let autoscroll = Autoscroll::center();
19021
19022 self.unfold_ranges(&[destination..destination], false, false, cx);
19023 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19024 s.select_ranges([destination..destination]);
19025 });
19026 }
19027 }
19028
19029 fn do_stage_or_unstage(
19030 &self,
19031 stage: bool,
19032 buffer_id: BufferId,
19033 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19034 cx: &mut App,
19035 ) -> Option<()> {
19036 let project = self.project()?;
19037 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19038 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19039 let buffer_snapshot = buffer.read(cx).snapshot();
19040 let file_exists = buffer_snapshot
19041 .file()
19042 .is_some_and(|file| file.disk_state().exists());
19043 diff.update(cx, |diff, cx| {
19044 diff.stage_or_unstage_hunks(
19045 stage,
19046 &hunks
19047 .map(|hunk| buffer_diff::DiffHunk {
19048 buffer_range: hunk.buffer_range,
19049 diff_base_byte_range: hunk.diff_base_byte_range,
19050 secondary_status: hunk.secondary_status,
19051 range: Point::zero()..Point::zero(), // unused
19052 })
19053 .collect::<Vec<_>>(),
19054 &buffer_snapshot,
19055 file_exists,
19056 cx,
19057 )
19058 });
19059 None
19060 }
19061
19062 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19063 let ranges: Vec<_> = self
19064 .selections
19065 .disjoint_anchors()
19066 .iter()
19067 .map(|s| s.range())
19068 .collect();
19069 self.buffer
19070 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19071 }
19072
19073 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19074 self.buffer.update(cx, |buffer, cx| {
19075 let ranges = vec![Anchor::min()..Anchor::max()];
19076 if !buffer.all_diff_hunks_expanded()
19077 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19078 {
19079 buffer.collapse_diff_hunks(ranges, cx);
19080 true
19081 } else {
19082 false
19083 }
19084 })
19085 }
19086
19087 fn toggle_diff_hunks_in_ranges(
19088 &mut self,
19089 ranges: Vec<Range<Anchor>>,
19090 cx: &mut Context<Editor>,
19091 ) {
19092 self.buffer.update(cx, |buffer, cx| {
19093 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19094 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19095 })
19096 }
19097
19098 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19099 self.buffer.update(cx, |buffer, cx| {
19100 let snapshot = buffer.snapshot(cx);
19101 let excerpt_id = range.end.excerpt_id;
19102 let point_range = range.to_point(&snapshot);
19103 let expand = !buffer.single_hunk_is_expanded(range, cx);
19104 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19105 })
19106 }
19107
19108 pub(crate) fn apply_all_diff_hunks(
19109 &mut self,
19110 _: &ApplyAllDiffHunks,
19111 window: &mut Window,
19112 cx: &mut Context<Self>,
19113 ) {
19114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19115
19116 let buffers = self.buffer.read(cx).all_buffers();
19117 for branch_buffer in buffers {
19118 branch_buffer.update(cx, |branch_buffer, cx| {
19119 branch_buffer.merge_into_base(Vec::new(), cx);
19120 });
19121 }
19122
19123 if let Some(project) = self.project.clone() {
19124 self.save(
19125 SaveOptions {
19126 format: true,
19127 autosave: false,
19128 },
19129 project,
19130 window,
19131 cx,
19132 )
19133 .detach_and_log_err(cx);
19134 }
19135 }
19136
19137 pub(crate) fn apply_selected_diff_hunks(
19138 &mut self,
19139 _: &ApplyDiffHunk,
19140 window: &mut Window,
19141 cx: &mut Context<Self>,
19142 ) {
19143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19144 let snapshot = self.snapshot(window, cx);
19145 let hunks = snapshot.hunks_for_ranges(
19146 self.selections
19147 .all(&snapshot.display_snapshot)
19148 .into_iter()
19149 .map(|selection| selection.range()),
19150 );
19151 let mut ranges_by_buffer = HashMap::default();
19152 self.transact(window, cx, |editor, _window, cx| {
19153 for hunk in hunks {
19154 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19155 ranges_by_buffer
19156 .entry(buffer.clone())
19157 .or_insert_with(Vec::new)
19158 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19159 }
19160 }
19161
19162 for (buffer, ranges) in ranges_by_buffer {
19163 buffer.update(cx, |buffer, cx| {
19164 buffer.merge_into_base(ranges, cx);
19165 });
19166 }
19167 });
19168
19169 if let Some(project) = self.project.clone() {
19170 self.save(
19171 SaveOptions {
19172 format: true,
19173 autosave: false,
19174 },
19175 project,
19176 window,
19177 cx,
19178 )
19179 .detach_and_log_err(cx);
19180 }
19181 }
19182
19183 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19184 if hovered != self.gutter_hovered {
19185 self.gutter_hovered = hovered;
19186 cx.notify();
19187 }
19188 }
19189
19190 pub fn insert_blocks(
19191 &mut self,
19192 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19193 autoscroll: Option<Autoscroll>,
19194 cx: &mut Context<Self>,
19195 ) -> Vec<CustomBlockId> {
19196 let blocks = self
19197 .display_map
19198 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19199 if let Some(autoscroll) = autoscroll {
19200 self.request_autoscroll(autoscroll, cx);
19201 }
19202 cx.notify();
19203 blocks
19204 }
19205
19206 pub fn resize_blocks(
19207 &mut self,
19208 heights: HashMap<CustomBlockId, u32>,
19209 autoscroll: Option<Autoscroll>,
19210 cx: &mut Context<Self>,
19211 ) {
19212 self.display_map
19213 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19214 if let Some(autoscroll) = autoscroll {
19215 self.request_autoscroll(autoscroll, cx);
19216 }
19217 cx.notify();
19218 }
19219
19220 pub fn replace_blocks(
19221 &mut self,
19222 renderers: HashMap<CustomBlockId, RenderBlock>,
19223 autoscroll: Option<Autoscroll>,
19224 cx: &mut Context<Self>,
19225 ) {
19226 self.display_map
19227 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19228 if let Some(autoscroll) = autoscroll {
19229 self.request_autoscroll(autoscroll, cx);
19230 }
19231 cx.notify();
19232 }
19233
19234 pub fn remove_blocks(
19235 &mut self,
19236 block_ids: HashSet<CustomBlockId>,
19237 autoscroll: Option<Autoscroll>,
19238 cx: &mut Context<Self>,
19239 ) {
19240 self.display_map.update(cx, |display_map, cx| {
19241 display_map.remove_blocks(block_ids, cx)
19242 });
19243 if let Some(autoscroll) = autoscroll {
19244 self.request_autoscroll(autoscroll, cx);
19245 }
19246 cx.notify();
19247 }
19248
19249 pub fn row_for_block(
19250 &self,
19251 block_id: CustomBlockId,
19252 cx: &mut Context<Self>,
19253 ) -> Option<DisplayRow> {
19254 self.display_map
19255 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19256 }
19257
19258 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19259 self.focused_block = Some(focused_block);
19260 }
19261
19262 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19263 self.focused_block.take()
19264 }
19265
19266 pub fn insert_creases(
19267 &mut self,
19268 creases: impl IntoIterator<Item = Crease<Anchor>>,
19269 cx: &mut Context<Self>,
19270 ) -> Vec<CreaseId> {
19271 self.display_map
19272 .update(cx, |map, cx| map.insert_creases(creases, cx))
19273 }
19274
19275 pub fn remove_creases(
19276 &mut self,
19277 ids: impl IntoIterator<Item = CreaseId>,
19278 cx: &mut Context<Self>,
19279 ) -> Vec<(CreaseId, Range<Anchor>)> {
19280 self.display_map
19281 .update(cx, |map, cx| map.remove_creases(ids, cx))
19282 }
19283
19284 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19285 self.display_map
19286 .update(cx, |map, cx| map.snapshot(cx))
19287 .longest_row()
19288 }
19289
19290 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19291 self.display_map
19292 .update(cx, |map, cx| map.snapshot(cx))
19293 .max_point()
19294 }
19295
19296 pub fn text(&self, cx: &App) -> String {
19297 self.buffer.read(cx).read(cx).text()
19298 }
19299
19300 pub fn is_empty(&self, cx: &App) -> bool {
19301 self.buffer.read(cx).read(cx).is_empty()
19302 }
19303
19304 pub fn text_option(&self, cx: &App) -> Option<String> {
19305 let text = self.text(cx);
19306 let text = text.trim();
19307
19308 if text.is_empty() {
19309 return None;
19310 }
19311
19312 Some(text.to_string())
19313 }
19314
19315 pub fn set_text(
19316 &mut self,
19317 text: impl Into<Arc<str>>,
19318 window: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 self.transact(window, cx, |this, _, cx| {
19322 this.buffer
19323 .read(cx)
19324 .as_singleton()
19325 .expect("you can only call set_text on editors for singleton buffers")
19326 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19327 });
19328 }
19329
19330 pub fn display_text(&self, cx: &mut App) -> String {
19331 self.display_map
19332 .update(cx, |map, cx| map.snapshot(cx))
19333 .text()
19334 }
19335
19336 fn create_minimap(
19337 &self,
19338 minimap_settings: MinimapSettings,
19339 window: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) -> Option<Entity<Self>> {
19342 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19343 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19344 }
19345
19346 fn initialize_new_minimap(
19347 &self,
19348 minimap_settings: MinimapSettings,
19349 window: &mut Window,
19350 cx: &mut Context<Self>,
19351 ) -> Entity<Self> {
19352 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19353
19354 let mut minimap = Editor::new_internal(
19355 EditorMode::Minimap {
19356 parent: cx.weak_entity(),
19357 },
19358 self.buffer.clone(),
19359 None,
19360 Some(self.display_map.clone()),
19361 window,
19362 cx,
19363 );
19364 minimap.scroll_manager.clone_state(&self.scroll_manager);
19365 minimap.set_text_style_refinement(TextStyleRefinement {
19366 font_size: Some(MINIMAP_FONT_SIZE),
19367 font_weight: Some(MINIMAP_FONT_WEIGHT),
19368 ..Default::default()
19369 });
19370 minimap.update_minimap_configuration(minimap_settings, cx);
19371 cx.new(|_| minimap)
19372 }
19373
19374 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19375 let current_line_highlight = minimap_settings
19376 .current_line_highlight
19377 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19378 self.set_current_line_highlight(Some(current_line_highlight));
19379 }
19380
19381 pub fn minimap(&self) -> Option<&Entity<Self>> {
19382 self.minimap
19383 .as_ref()
19384 .filter(|_| self.minimap_visibility.visible())
19385 }
19386
19387 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19388 let mut wrap_guides = smallvec![];
19389
19390 if self.show_wrap_guides == Some(false) {
19391 return wrap_guides;
19392 }
19393
19394 let settings = self.buffer.read(cx).language_settings(cx);
19395 if settings.show_wrap_guides {
19396 match self.soft_wrap_mode(cx) {
19397 SoftWrap::Column(soft_wrap) => {
19398 wrap_guides.push((soft_wrap as usize, true));
19399 }
19400 SoftWrap::Bounded(soft_wrap) => {
19401 wrap_guides.push((soft_wrap as usize, true));
19402 }
19403 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19404 }
19405 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19406 }
19407
19408 wrap_guides
19409 }
19410
19411 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19412 let settings = self.buffer.read(cx).language_settings(cx);
19413 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19414 match mode {
19415 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19416 SoftWrap::None
19417 }
19418 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19419 language_settings::SoftWrap::PreferredLineLength => {
19420 SoftWrap::Column(settings.preferred_line_length)
19421 }
19422 language_settings::SoftWrap::Bounded => {
19423 SoftWrap::Bounded(settings.preferred_line_length)
19424 }
19425 }
19426 }
19427
19428 pub fn set_soft_wrap_mode(
19429 &mut self,
19430 mode: language_settings::SoftWrap,
19431
19432 cx: &mut Context<Self>,
19433 ) {
19434 self.soft_wrap_mode_override = Some(mode);
19435 cx.notify();
19436 }
19437
19438 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19439 self.hard_wrap = hard_wrap;
19440 cx.notify();
19441 }
19442
19443 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19444 self.text_style_refinement = Some(style);
19445 }
19446
19447 /// called by the Element so we know what style we were most recently rendered with.
19448 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19449 // We intentionally do not inform the display map about the minimap style
19450 // so that wrapping is not recalculated and stays consistent for the editor
19451 // and its linked minimap.
19452 if !self.mode.is_minimap() {
19453 let font = style.text.font();
19454 let font_size = style.text.font_size.to_pixels(window.rem_size());
19455 let display_map = self
19456 .placeholder_display_map
19457 .as_ref()
19458 .filter(|_| self.is_empty(cx))
19459 .unwrap_or(&self.display_map);
19460
19461 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19462 }
19463 self.style = Some(style);
19464 }
19465
19466 pub fn style(&self) -> Option<&EditorStyle> {
19467 self.style.as_ref()
19468 }
19469
19470 // Called by the element. This method is not designed to be called outside of the editor
19471 // element's layout code because it does not notify when rewrapping is computed synchronously.
19472 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19473 if self.is_empty(cx) {
19474 self.placeholder_display_map
19475 .as_ref()
19476 .map_or(false, |display_map| {
19477 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19478 })
19479 } else {
19480 self.display_map
19481 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19482 }
19483 }
19484
19485 pub fn set_soft_wrap(&mut self) {
19486 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19487 }
19488
19489 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19490 if self.soft_wrap_mode_override.is_some() {
19491 self.soft_wrap_mode_override.take();
19492 } else {
19493 let soft_wrap = match self.soft_wrap_mode(cx) {
19494 SoftWrap::GitDiff => return,
19495 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19496 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19497 language_settings::SoftWrap::None
19498 }
19499 };
19500 self.soft_wrap_mode_override = Some(soft_wrap);
19501 }
19502 cx.notify();
19503 }
19504
19505 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19506 let Some(workspace) = self.workspace() else {
19507 return;
19508 };
19509 let fs = workspace.read(cx).app_state().fs.clone();
19510 let current_show = TabBarSettings::get_global(cx).show;
19511 update_settings_file(fs, cx, move |setting, _| {
19512 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19513 });
19514 }
19515
19516 pub fn toggle_indent_guides(
19517 &mut self,
19518 _: &ToggleIndentGuides,
19519 _: &mut Window,
19520 cx: &mut Context<Self>,
19521 ) {
19522 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19523 self.buffer
19524 .read(cx)
19525 .language_settings(cx)
19526 .indent_guides
19527 .enabled
19528 });
19529 self.show_indent_guides = Some(!currently_enabled);
19530 cx.notify();
19531 }
19532
19533 fn should_show_indent_guides(&self) -> Option<bool> {
19534 self.show_indent_guides
19535 }
19536
19537 pub fn toggle_line_numbers(
19538 &mut self,
19539 _: &ToggleLineNumbers,
19540 _: &mut Window,
19541 cx: &mut Context<Self>,
19542 ) {
19543 let mut editor_settings = EditorSettings::get_global(cx).clone();
19544 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19545 EditorSettings::override_global(editor_settings, cx);
19546 }
19547
19548 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19549 if let Some(show_line_numbers) = self.show_line_numbers {
19550 return show_line_numbers;
19551 }
19552 EditorSettings::get_global(cx).gutter.line_numbers
19553 }
19554
19555 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19556 match (
19557 self.use_relative_line_numbers,
19558 EditorSettings::get_global(cx).relative_line_numbers,
19559 ) {
19560 (None, setting) => setting,
19561 (Some(false), _) => RelativeLineNumbers::Disabled,
19562 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19563 (Some(true), _) => RelativeLineNumbers::Enabled,
19564 }
19565 }
19566
19567 pub fn toggle_relative_line_numbers(
19568 &mut self,
19569 _: &ToggleRelativeLineNumbers,
19570 _: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 let is_relative = self.relative_line_numbers(cx);
19574 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19575 }
19576
19577 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19578 self.use_relative_line_numbers = is_relative;
19579 cx.notify();
19580 }
19581
19582 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19583 self.show_gutter = show_gutter;
19584 cx.notify();
19585 }
19586
19587 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19588 self.show_scrollbars = ScrollbarAxes {
19589 horizontal: show,
19590 vertical: show,
19591 };
19592 cx.notify();
19593 }
19594
19595 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19596 self.show_scrollbars.vertical = show;
19597 cx.notify();
19598 }
19599
19600 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19601 self.show_scrollbars.horizontal = show;
19602 cx.notify();
19603 }
19604
19605 pub fn set_minimap_visibility(
19606 &mut self,
19607 minimap_visibility: MinimapVisibility,
19608 window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 if self.minimap_visibility != minimap_visibility {
19612 if minimap_visibility.visible() && self.minimap.is_none() {
19613 let minimap_settings = EditorSettings::get_global(cx).minimap;
19614 self.minimap =
19615 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19616 }
19617 self.minimap_visibility = minimap_visibility;
19618 cx.notify();
19619 }
19620 }
19621
19622 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19623 self.set_show_scrollbars(false, cx);
19624 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19625 }
19626
19627 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19628 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19629 }
19630
19631 /// Normally the text in full mode and auto height editors is padded on the
19632 /// left side by roughly half a character width for improved hit testing.
19633 ///
19634 /// Use this method to disable this for cases where this is not wanted (e.g.
19635 /// if you want to align the editor text with some other text above or below)
19636 /// or if you want to add this padding to single-line editors.
19637 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19638 self.offset_content = offset_content;
19639 cx.notify();
19640 }
19641
19642 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19643 self.show_line_numbers = Some(show_line_numbers);
19644 cx.notify();
19645 }
19646
19647 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19648 self.disable_expand_excerpt_buttons = true;
19649 cx.notify();
19650 }
19651
19652 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19653 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19654 cx.notify();
19655 }
19656
19657 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19658 self.show_code_actions = Some(show_code_actions);
19659 cx.notify();
19660 }
19661
19662 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19663 self.show_runnables = Some(show_runnables);
19664 cx.notify();
19665 }
19666
19667 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19668 self.show_breakpoints = Some(show_breakpoints);
19669 cx.notify();
19670 }
19671
19672 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19673 if self.display_map.read(cx).masked != masked {
19674 self.display_map.update(cx, |map, _| map.masked = masked);
19675 }
19676 cx.notify()
19677 }
19678
19679 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19680 self.show_wrap_guides = Some(show_wrap_guides);
19681 cx.notify();
19682 }
19683
19684 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19685 self.show_indent_guides = Some(show_indent_guides);
19686 cx.notify();
19687 }
19688
19689 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19690 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19691 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19692 && let Some(dir) = file.abs_path(cx).parent()
19693 {
19694 return Some(dir.to_owned());
19695 }
19696 }
19697
19698 None
19699 }
19700
19701 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19702 self.active_excerpt(cx)?
19703 .1
19704 .read(cx)
19705 .file()
19706 .and_then(|f| f.as_local())
19707 }
19708
19709 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19710 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19711 let buffer = buffer.read(cx);
19712 if let Some(project_path) = buffer.project_path(cx) {
19713 let project = self.project()?.read(cx);
19714 project.absolute_path(&project_path, cx)
19715 } else {
19716 buffer
19717 .file()
19718 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19719 }
19720 })
19721 }
19722
19723 pub fn reveal_in_finder(
19724 &mut self,
19725 _: &RevealInFileManager,
19726 _window: &mut Window,
19727 cx: &mut Context<Self>,
19728 ) {
19729 if let Some(target) = self.target_file(cx) {
19730 cx.reveal_path(&target.abs_path(cx));
19731 }
19732 }
19733
19734 pub fn copy_path(
19735 &mut self,
19736 _: &zed_actions::workspace::CopyPath,
19737 _window: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) {
19740 if let Some(path) = self.target_file_abs_path(cx)
19741 && let Some(path) = path.to_str()
19742 {
19743 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19744 } else {
19745 cx.propagate();
19746 }
19747 }
19748
19749 pub fn copy_relative_path(
19750 &mut self,
19751 _: &zed_actions::workspace::CopyRelativePath,
19752 _window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19756 let project = self.project()?.read(cx);
19757 let path = buffer.read(cx).file()?.path();
19758 let path = path.display(project.path_style(cx));
19759 Some(path)
19760 }) {
19761 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19762 } else {
19763 cx.propagate();
19764 }
19765 }
19766
19767 /// Returns the project path for the editor's buffer, if any buffer is
19768 /// opened in the editor.
19769 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19770 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19771 buffer.read(cx).project_path(cx)
19772 } else {
19773 None
19774 }
19775 }
19776
19777 // Returns true if the editor handled a go-to-line request
19778 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19779 maybe!({
19780 let breakpoint_store = self.breakpoint_store.as_ref()?;
19781
19782 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19783 else {
19784 self.clear_row_highlights::<ActiveDebugLine>();
19785 return None;
19786 };
19787
19788 let position = active_stack_frame.position;
19789 let buffer_id = position.buffer_id?;
19790 let snapshot = self
19791 .project
19792 .as_ref()?
19793 .read(cx)
19794 .buffer_for_id(buffer_id, cx)?
19795 .read(cx)
19796 .snapshot();
19797
19798 let mut handled = false;
19799 for (id, ExcerptRange { context, .. }) in
19800 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19801 {
19802 if context.start.cmp(&position, &snapshot).is_ge()
19803 || context.end.cmp(&position, &snapshot).is_lt()
19804 {
19805 continue;
19806 }
19807 let snapshot = self.buffer.read(cx).snapshot(cx);
19808 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19809
19810 handled = true;
19811 self.clear_row_highlights::<ActiveDebugLine>();
19812
19813 self.go_to_line::<ActiveDebugLine>(
19814 multibuffer_anchor,
19815 Some(cx.theme().colors().editor_debugger_active_line_background),
19816 window,
19817 cx,
19818 );
19819
19820 cx.notify();
19821 }
19822
19823 handled.then_some(())
19824 })
19825 .is_some()
19826 }
19827
19828 pub fn copy_file_name_without_extension(
19829 &mut self,
19830 _: &CopyFileNameWithoutExtension,
19831 _: &mut Window,
19832 cx: &mut Context<Self>,
19833 ) {
19834 if let Some(file) = self.target_file(cx)
19835 && let Some(file_stem) = file.path().file_stem()
19836 {
19837 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19838 }
19839 }
19840
19841 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19842 if let Some(file) = self.target_file(cx)
19843 && let Some(name) = file.path().file_name()
19844 {
19845 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19846 }
19847 }
19848
19849 pub fn toggle_git_blame(
19850 &mut self,
19851 _: &::git::Blame,
19852 window: &mut Window,
19853 cx: &mut Context<Self>,
19854 ) {
19855 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19856
19857 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19858 self.start_git_blame(true, window, cx);
19859 }
19860
19861 cx.notify();
19862 }
19863
19864 pub fn toggle_git_blame_inline(
19865 &mut self,
19866 _: &ToggleGitBlameInline,
19867 window: &mut Window,
19868 cx: &mut Context<Self>,
19869 ) {
19870 self.toggle_git_blame_inline_internal(true, window, cx);
19871 cx.notify();
19872 }
19873
19874 pub fn open_git_blame_commit(
19875 &mut self,
19876 _: &OpenGitBlameCommit,
19877 window: &mut Window,
19878 cx: &mut Context<Self>,
19879 ) {
19880 self.open_git_blame_commit_internal(window, cx);
19881 }
19882
19883 fn open_git_blame_commit_internal(
19884 &mut self,
19885 window: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) -> Option<()> {
19888 let blame = self.blame.as_ref()?;
19889 let snapshot = self.snapshot(window, cx);
19890 let cursor = self
19891 .selections
19892 .newest::<Point>(&snapshot.display_snapshot)
19893 .head();
19894 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19895 let (_, blame_entry) = blame
19896 .update(cx, |blame, cx| {
19897 blame
19898 .blame_for_rows(
19899 &[RowInfo {
19900 buffer_id: Some(buffer.remote_id()),
19901 buffer_row: Some(point.row),
19902 ..Default::default()
19903 }],
19904 cx,
19905 )
19906 .next()
19907 })
19908 .flatten()?;
19909 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19910 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19911 let workspace = self.workspace()?.downgrade();
19912 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19913 None
19914 }
19915
19916 pub fn git_blame_inline_enabled(&self) -> bool {
19917 self.git_blame_inline_enabled
19918 }
19919
19920 pub fn toggle_selection_menu(
19921 &mut self,
19922 _: &ToggleSelectionMenu,
19923 _: &mut Window,
19924 cx: &mut Context<Self>,
19925 ) {
19926 self.show_selection_menu = self
19927 .show_selection_menu
19928 .map(|show_selections_menu| !show_selections_menu)
19929 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19930
19931 cx.notify();
19932 }
19933
19934 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19935 self.show_selection_menu
19936 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19937 }
19938
19939 fn start_git_blame(
19940 &mut self,
19941 user_triggered: bool,
19942 window: &mut Window,
19943 cx: &mut Context<Self>,
19944 ) {
19945 if let Some(project) = self.project() {
19946 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19947 && buffer.read(cx).file().is_none()
19948 {
19949 return;
19950 }
19951
19952 let focused = self.focus_handle(cx).contains_focused(window, cx);
19953
19954 let project = project.clone();
19955 let blame = cx
19956 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19957 self.blame_subscription =
19958 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19959 self.blame = Some(blame);
19960 }
19961 }
19962
19963 fn toggle_git_blame_inline_internal(
19964 &mut self,
19965 user_triggered: bool,
19966 window: &mut Window,
19967 cx: &mut Context<Self>,
19968 ) {
19969 if self.git_blame_inline_enabled {
19970 self.git_blame_inline_enabled = false;
19971 self.show_git_blame_inline = false;
19972 self.show_git_blame_inline_delay_task.take();
19973 } else {
19974 self.git_blame_inline_enabled = true;
19975 self.start_git_blame_inline(user_triggered, window, cx);
19976 }
19977
19978 cx.notify();
19979 }
19980
19981 fn start_git_blame_inline(
19982 &mut self,
19983 user_triggered: bool,
19984 window: &mut Window,
19985 cx: &mut Context<Self>,
19986 ) {
19987 self.start_git_blame(user_triggered, window, cx);
19988
19989 if ProjectSettings::get_global(cx)
19990 .git
19991 .inline_blame_delay()
19992 .is_some()
19993 {
19994 self.start_inline_blame_timer(window, cx);
19995 } else {
19996 self.show_git_blame_inline = true
19997 }
19998 }
19999
20000 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20001 self.blame.as_ref()
20002 }
20003
20004 pub fn show_git_blame_gutter(&self) -> bool {
20005 self.show_git_blame_gutter
20006 }
20007
20008 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20009 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20010 }
20011
20012 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20013 self.show_git_blame_inline
20014 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20015 && !self.newest_selection_head_on_empty_line(cx)
20016 && self.has_blame_entries(cx)
20017 }
20018
20019 fn has_blame_entries(&self, cx: &App) -> bool {
20020 self.blame()
20021 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20022 }
20023
20024 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20025 let cursor_anchor = self.selections.newest_anchor().head();
20026
20027 let snapshot = self.buffer.read(cx).snapshot(cx);
20028 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20029
20030 snapshot.line_len(buffer_row) == 0
20031 }
20032
20033 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20034 let buffer_and_selection = maybe!({
20035 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20036 let selection_range = selection.range();
20037
20038 let multi_buffer = self.buffer().read(cx);
20039 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20040 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20041
20042 let (buffer, range, _) = if selection.reversed {
20043 buffer_ranges.first()
20044 } else {
20045 buffer_ranges.last()
20046 }?;
20047
20048 let selection = text::ToPoint::to_point(&range.start, buffer).row
20049 ..text::ToPoint::to_point(&range.end, buffer).row;
20050 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20051 });
20052
20053 let Some((buffer, selection)) = buffer_and_selection else {
20054 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20055 };
20056
20057 let Some(project) = self.project() else {
20058 return Task::ready(Err(anyhow!("editor does not have project")));
20059 };
20060
20061 project.update(cx, |project, cx| {
20062 project.get_permalink_to_line(&buffer, selection, cx)
20063 })
20064 }
20065
20066 pub fn copy_permalink_to_line(
20067 &mut self,
20068 _: &CopyPermalinkToLine,
20069 window: &mut Window,
20070 cx: &mut Context<Self>,
20071 ) {
20072 let permalink_task = self.get_permalink_to_line(cx);
20073 let workspace = self.workspace();
20074
20075 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20076 Ok(permalink) => {
20077 cx.update(|_, cx| {
20078 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20079 })
20080 .ok();
20081 }
20082 Err(err) => {
20083 let message = format!("Failed to copy permalink: {err}");
20084
20085 anyhow::Result::<()>::Err(err).log_err();
20086
20087 if let Some(workspace) = workspace {
20088 workspace
20089 .update_in(cx, |workspace, _, cx| {
20090 struct CopyPermalinkToLine;
20091
20092 workspace.show_toast(
20093 Toast::new(
20094 NotificationId::unique::<CopyPermalinkToLine>(),
20095 message,
20096 ),
20097 cx,
20098 )
20099 })
20100 .ok();
20101 }
20102 }
20103 })
20104 .detach();
20105 }
20106
20107 pub fn copy_file_location(
20108 &mut self,
20109 _: &CopyFileLocation,
20110 _: &mut Window,
20111 cx: &mut Context<Self>,
20112 ) {
20113 let selection = self
20114 .selections
20115 .newest::<Point>(&self.display_snapshot(cx))
20116 .start
20117 .row
20118 + 1;
20119 if let Some(file) = self.target_file(cx) {
20120 let path = file.path().display(file.path_style(cx));
20121 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20122 }
20123 }
20124
20125 pub fn open_permalink_to_line(
20126 &mut self,
20127 _: &OpenPermalinkToLine,
20128 window: &mut Window,
20129 cx: &mut Context<Self>,
20130 ) {
20131 let permalink_task = self.get_permalink_to_line(cx);
20132 let workspace = self.workspace();
20133
20134 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20135 Ok(permalink) => {
20136 cx.update(|_, cx| {
20137 cx.open_url(permalink.as_ref());
20138 })
20139 .ok();
20140 }
20141 Err(err) => {
20142 let message = format!("Failed to open permalink: {err}");
20143
20144 anyhow::Result::<()>::Err(err).log_err();
20145
20146 if let Some(workspace) = workspace {
20147 workspace
20148 .update(cx, |workspace, cx| {
20149 struct OpenPermalinkToLine;
20150
20151 workspace.show_toast(
20152 Toast::new(
20153 NotificationId::unique::<OpenPermalinkToLine>(),
20154 message,
20155 ),
20156 cx,
20157 )
20158 })
20159 .ok();
20160 }
20161 }
20162 })
20163 .detach();
20164 }
20165
20166 pub fn insert_uuid_v4(
20167 &mut self,
20168 _: &InsertUuidV4,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 self.insert_uuid(UuidVersion::V4, window, cx);
20173 }
20174
20175 pub fn insert_uuid_v7(
20176 &mut self,
20177 _: &InsertUuidV7,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 self.insert_uuid(UuidVersion::V7, window, cx);
20182 }
20183
20184 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20185 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20186 self.transact(window, cx, |this, window, cx| {
20187 let edits = this
20188 .selections
20189 .all::<Point>(&this.display_snapshot(cx))
20190 .into_iter()
20191 .map(|selection| {
20192 let uuid = match version {
20193 UuidVersion::V4 => uuid::Uuid::new_v4(),
20194 UuidVersion::V7 => uuid::Uuid::now_v7(),
20195 };
20196
20197 (selection.range(), uuid.to_string())
20198 });
20199 this.edit(edits, cx);
20200 this.refresh_edit_prediction(true, false, window, cx);
20201 });
20202 }
20203
20204 pub fn open_selections_in_multibuffer(
20205 &mut self,
20206 _: &OpenSelectionsInMultibuffer,
20207 window: &mut Window,
20208 cx: &mut Context<Self>,
20209 ) {
20210 let multibuffer = self.buffer.read(cx);
20211
20212 let Some(buffer) = multibuffer.as_singleton() else {
20213 return;
20214 };
20215
20216 let Some(workspace) = self.workspace() else {
20217 return;
20218 };
20219
20220 let title = multibuffer.title(cx).to_string();
20221
20222 let locations = self
20223 .selections
20224 .all_anchors(cx)
20225 .iter()
20226 .map(|selection| {
20227 (
20228 buffer.clone(),
20229 (selection.start.text_anchor..selection.end.text_anchor)
20230 .to_point(buffer.read(cx)),
20231 )
20232 })
20233 .into_group_map();
20234
20235 cx.spawn_in(window, async move |_, cx| {
20236 workspace.update_in(cx, |workspace, window, cx| {
20237 Self::open_locations_in_multibuffer(
20238 workspace,
20239 locations,
20240 format!("Selections for '{title}'"),
20241 false,
20242 MultibufferSelectionMode::All,
20243 window,
20244 cx,
20245 );
20246 })
20247 })
20248 .detach();
20249 }
20250
20251 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20252 /// last highlight added will be used.
20253 ///
20254 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20255 pub fn highlight_rows<T: 'static>(
20256 &mut self,
20257 range: Range<Anchor>,
20258 color: Hsla,
20259 options: RowHighlightOptions,
20260 cx: &mut Context<Self>,
20261 ) {
20262 let snapshot = self.buffer().read(cx).snapshot(cx);
20263 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20264 let ix = row_highlights.binary_search_by(|highlight| {
20265 Ordering::Equal
20266 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20267 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20268 });
20269
20270 if let Err(mut ix) = ix {
20271 let index = post_inc(&mut self.highlight_order);
20272
20273 // If this range intersects with the preceding highlight, then merge it with
20274 // the preceding highlight. Otherwise insert a new highlight.
20275 let mut merged = false;
20276 if ix > 0 {
20277 let prev_highlight = &mut row_highlights[ix - 1];
20278 if prev_highlight
20279 .range
20280 .end
20281 .cmp(&range.start, &snapshot)
20282 .is_ge()
20283 {
20284 ix -= 1;
20285 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20286 prev_highlight.range.end = range.end;
20287 }
20288 merged = true;
20289 prev_highlight.index = index;
20290 prev_highlight.color = color;
20291 prev_highlight.options = options;
20292 }
20293 }
20294
20295 if !merged {
20296 row_highlights.insert(
20297 ix,
20298 RowHighlight {
20299 range,
20300 index,
20301 color,
20302 options,
20303 type_id: TypeId::of::<T>(),
20304 },
20305 );
20306 }
20307
20308 // If any of the following highlights intersect with this one, merge them.
20309 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20310 let highlight = &row_highlights[ix];
20311 if next_highlight
20312 .range
20313 .start
20314 .cmp(&highlight.range.end, &snapshot)
20315 .is_le()
20316 {
20317 if next_highlight
20318 .range
20319 .end
20320 .cmp(&highlight.range.end, &snapshot)
20321 .is_gt()
20322 {
20323 row_highlights[ix].range.end = next_highlight.range.end;
20324 }
20325 row_highlights.remove(ix + 1);
20326 } else {
20327 break;
20328 }
20329 }
20330 }
20331 }
20332
20333 /// Remove any highlighted row ranges of the given type that intersect the
20334 /// given ranges.
20335 pub fn remove_highlighted_rows<T: 'static>(
20336 &mut self,
20337 ranges_to_remove: Vec<Range<Anchor>>,
20338 cx: &mut Context<Self>,
20339 ) {
20340 let snapshot = self.buffer().read(cx).snapshot(cx);
20341 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20342 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20343 row_highlights.retain(|highlight| {
20344 while let Some(range_to_remove) = ranges_to_remove.peek() {
20345 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20346 Ordering::Less | Ordering::Equal => {
20347 ranges_to_remove.next();
20348 }
20349 Ordering::Greater => {
20350 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20351 Ordering::Less | Ordering::Equal => {
20352 return false;
20353 }
20354 Ordering::Greater => break,
20355 }
20356 }
20357 }
20358 }
20359
20360 true
20361 })
20362 }
20363
20364 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20365 pub fn clear_row_highlights<T: 'static>(&mut self) {
20366 self.highlighted_rows.remove(&TypeId::of::<T>());
20367 }
20368
20369 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20370 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20371 self.highlighted_rows
20372 .get(&TypeId::of::<T>())
20373 .map_or(&[] as &[_], |vec| vec.as_slice())
20374 .iter()
20375 .map(|highlight| (highlight.range.clone(), highlight.color))
20376 }
20377
20378 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20379 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20380 /// Allows to ignore certain kinds of highlights.
20381 pub fn highlighted_display_rows(
20382 &self,
20383 window: &mut Window,
20384 cx: &mut App,
20385 ) -> BTreeMap<DisplayRow, LineHighlight> {
20386 let snapshot = self.snapshot(window, cx);
20387 let mut used_highlight_orders = HashMap::default();
20388 self.highlighted_rows
20389 .iter()
20390 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20391 .fold(
20392 BTreeMap::<DisplayRow, LineHighlight>::new(),
20393 |mut unique_rows, highlight| {
20394 let start = highlight.range.start.to_display_point(&snapshot);
20395 let end = highlight.range.end.to_display_point(&snapshot);
20396 let start_row = start.row().0;
20397 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20398 && end.column() == 0
20399 {
20400 end.row().0.saturating_sub(1)
20401 } else {
20402 end.row().0
20403 };
20404 for row in start_row..=end_row {
20405 let used_index =
20406 used_highlight_orders.entry(row).or_insert(highlight.index);
20407 if highlight.index >= *used_index {
20408 *used_index = highlight.index;
20409 unique_rows.insert(
20410 DisplayRow(row),
20411 LineHighlight {
20412 include_gutter: highlight.options.include_gutter,
20413 border: None,
20414 background: highlight.color.into(),
20415 type_id: Some(highlight.type_id),
20416 },
20417 );
20418 }
20419 }
20420 unique_rows
20421 },
20422 )
20423 }
20424
20425 pub fn highlighted_display_row_for_autoscroll(
20426 &self,
20427 snapshot: &DisplaySnapshot,
20428 ) -> Option<DisplayRow> {
20429 self.highlighted_rows
20430 .values()
20431 .flat_map(|highlighted_rows| highlighted_rows.iter())
20432 .filter_map(|highlight| {
20433 if highlight.options.autoscroll {
20434 Some(highlight.range.start.to_display_point(snapshot).row())
20435 } else {
20436 None
20437 }
20438 })
20439 .min()
20440 }
20441
20442 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20443 self.highlight_background::<SearchWithinRange>(
20444 ranges,
20445 |colors| colors.colors().editor_document_highlight_read_background,
20446 cx,
20447 )
20448 }
20449
20450 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20451 self.breadcrumb_header = Some(new_header);
20452 }
20453
20454 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20455 self.clear_background_highlights::<SearchWithinRange>(cx);
20456 }
20457
20458 pub fn highlight_background<T: 'static>(
20459 &mut self,
20460 ranges: &[Range<Anchor>],
20461 color_fetcher: fn(&Theme) -> Hsla,
20462 cx: &mut Context<Self>,
20463 ) {
20464 self.background_highlights.insert(
20465 HighlightKey::Type(TypeId::of::<T>()),
20466 (color_fetcher, Arc::from(ranges)),
20467 );
20468 self.scrollbar_marker_state.dirty = true;
20469 cx.notify();
20470 }
20471
20472 pub fn highlight_background_key<T: 'static>(
20473 &mut self,
20474 key: usize,
20475 ranges: &[Range<Anchor>],
20476 color_fetcher: fn(&Theme) -> Hsla,
20477 cx: &mut Context<Self>,
20478 ) {
20479 self.background_highlights.insert(
20480 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20481 (color_fetcher, Arc::from(ranges)),
20482 );
20483 self.scrollbar_marker_state.dirty = true;
20484 cx.notify();
20485 }
20486
20487 pub fn clear_background_highlights<T: 'static>(
20488 &mut self,
20489 cx: &mut Context<Self>,
20490 ) -> Option<BackgroundHighlight> {
20491 let text_highlights = self
20492 .background_highlights
20493 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20494 if !text_highlights.1.is_empty() {
20495 self.scrollbar_marker_state.dirty = true;
20496 cx.notify();
20497 }
20498 Some(text_highlights)
20499 }
20500
20501 pub fn highlight_gutter<T: 'static>(
20502 &mut self,
20503 ranges: impl Into<Vec<Range<Anchor>>>,
20504 color_fetcher: fn(&App) -> Hsla,
20505 cx: &mut Context<Self>,
20506 ) {
20507 self.gutter_highlights
20508 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20509 cx.notify();
20510 }
20511
20512 pub fn clear_gutter_highlights<T: 'static>(
20513 &mut self,
20514 cx: &mut Context<Self>,
20515 ) -> Option<GutterHighlight> {
20516 cx.notify();
20517 self.gutter_highlights.remove(&TypeId::of::<T>())
20518 }
20519
20520 pub fn insert_gutter_highlight<T: 'static>(
20521 &mut self,
20522 range: Range<Anchor>,
20523 color_fetcher: fn(&App) -> Hsla,
20524 cx: &mut Context<Self>,
20525 ) {
20526 let snapshot = self.buffer().read(cx).snapshot(cx);
20527 let mut highlights = self
20528 .gutter_highlights
20529 .remove(&TypeId::of::<T>())
20530 .map(|(_, highlights)| highlights)
20531 .unwrap_or_default();
20532 let ix = highlights.binary_search_by(|highlight| {
20533 Ordering::Equal
20534 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20535 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20536 });
20537 if let Err(ix) = ix {
20538 highlights.insert(ix, range);
20539 }
20540 self.gutter_highlights
20541 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20542 }
20543
20544 pub fn remove_gutter_highlights<T: 'static>(
20545 &mut self,
20546 ranges_to_remove: Vec<Range<Anchor>>,
20547 cx: &mut Context<Self>,
20548 ) {
20549 let snapshot = self.buffer().read(cx).snapshot(cx);
20550 let Some((color_fetcher, mut gutter_highlights)) =
20551 self.gutter_highlights.remove(&TypeId::of::<T>())
20552 else {
20553 return;
20554 };
20555 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20556 gutter_highlights.retain(|highlight| {
20557 while let Some(range_to_remove) = ranges_to_remove.peek() {
20558 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20559 Ordering::Less | Ordering::Equal => {
20560 ranges_to_remove.next();
20561 }
20562 Ordering::Greater => {
20563 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20564 Ordering::Less | Ordering::Equal => {
20565 return false;
20566 }
20567 Ordering::Greater => break,
20568 }
20569 }
20570 }
20571 }
20572
20573 true
20574 });
20575 self.gutter_highlights
20576 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20577 }
20578
20579 #[cfg(feature = "test-support")]
20580 pub fn all_text_highlights(
20581 &self,
20582 window: &mut Window,
20583 cx: &mut Context<Self>,
20584 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20585 let snapshot = self.snapshot(window, cx);
20586 self.display_map.update(cx, |display_map, _| {
20587 display_map
20588 .all_text_highlights()
20589 .map(|highlight| {
20590 let (style, ranges) = highlight.as_ref();
20591 (
20592 *style,
20593 ranges
20594 .iter()
20595 .map(|range| range.clone().to_display_points(&snapshot))
20596 .collect(),
20597 )
20598 })
20599 .collect()
20600 })
20601 }
20602
20603 #[cfg(feature = "test-support")]
20604 pub fn all_text_background_highlights(
20605 &self,
20606 window: &mut Window,
20607 cx: &mut Context<Self>,
20608 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20609 let snapshot = self.snapshot(window, cx);
20610 let buffer = &snapshot.buffer_snapshot();
20611 let start = buffer.anchor_before(0);
20612 let end = buffer.anchor_after(buffer.len());
20613 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20614 }
20615
20616 #[cfg(any(test, feature = "test-support"))]
20617 pub fn sorted_background_highlights_in_range(
20618 &self,
20619 search_range: Range<Anchor>,
20620 display_snapshot: &DisplaySnapshot,
20621 theme: &Theme,
20622 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20623 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20624 res.sort_by(|a, b| {
20625 a.0.start
20626 .cmp(&b.0.start)
20627 .then_with(|| a.0.end.cmp(&b.0.end))
20628 .then_with(|| a.1.cmp(&b.1))
20629 });
20630 res
20631 }
20632
20633 #[cfg(feature = "test-support")]
20634 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20635 let snapshot = self.buffer().read(cx).snapshot(cx);
20636
20637 let highlights = self
20638 .background_highlights
20639 .get(&HighlightKey::Type(TypeId::of::<
20640 items::BufferSearchHighlights,
20641 >()));
20642
20643 if let Some((_color, ranges)) = highlights {
20644 ranges
20645 .iter()
20646 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20647 .collect_vec()
20648 } else {
20649 vec![]
20650 }
20651 }
20652
20653 fn document_highlights_for_position<'a>(
20654 &'a self,
20655 position: Anchor,
20656 buffer: &'a MultiBufferSnapshot,
20657 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20658 let read_highlights = self
20659 .background_highlights
20660 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20661 .map(|h| &h.1);
20662 let write_highlights = self
20663 .background_highlights
20664 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20665 .map(|h| &h.1);
20666 let left_position = position.bias_left(buffer);
20667 let right_position = position.bias_right(buffer);
20668 read_highlights
20669 .into_iter()
20670 .chain(write_highlights)
20671 .flat_map(move |ranges| {
20672 let start_ix = match ranges.binary_search_by(|probe| {
20673 let cmp = probe.end.cmp(&left_position, buffer);
20674 if cmp.is_ge() {
20675 Ordering::Greater
20676 } else {
20677 Ordering::Less
20678 }
20679 }) {
20680 Ok(i) | Err(i) => i,
20681 };
20682
20683 ranges[start_ix..]
20684 .iter()
20685 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20686 })
20687 }
20688
20689 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20690 self.background_highlights
20691 .get(&HighlightKey::Type(TypeId::of::<T>()))
20692 .is_some_and(|(_, highlights)| !highlights.is_empty())
20693 }
20694
20695 /// Returns all background highlights for a given range.
20696 ///
20697 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20698 pub fn background_highlights_in_range(
20699 &self,
20700 search_range: Range<Anchor>,
20701 display_snapshot: &DisplaySnapshot,
20702 theme: &Theme,
20703 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20704 let mut results = Vec::new();
20705 for (color_fetcher, ranges) in self.background_highlights.values() {
20706 let color = color_fetcher(theme);
20707 let start_ix = match ranges.binary_search_by(|probe| {
20708 let cmp = probe
20709 .end
20710 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20711 if cmp.is_gt() {
20712 Ordering::Greater
20713 } else {
20714 Ordering::Less
20715 }
20716 }) {
20717 Ok(i) | Err(i) => i,
20718 };
20719 for range in &ranges[start_ix..] {
20720 if range
20721 .start
20722 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20723 .is_ge()
20724 {
20725 break;
20726 }
20727
20728 let start = range.start.to_display_point(display_snapshot);
20729 let end = range.end.to_display_point(display_snapshot);
20730 results.push((start..end, color))
20731 }
20732 }
20733 results
20734 }
20735
20736 pub fn gutter_highlights_in_range(
20737 &self,
20738 search_range: Range<Anchor>,
20739 display_snapshot: &DisplaySnapshot,
20740 cx: &App,
20741 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20742 let mut results = Vec::new();
20743 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20744 let color = color_fetcher(cx);
20745 let start_ix = match ranges.binary_search_by(|probe| {
20746 let cmp = probe
20747 .end
20748 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20749 if cmp.is_gt() {
20750 Ordering::Greater
20751 } else {
20752 Ordering::Less
20753 }
20754 }) {
20755 Ok(i) | Err(i) => i,
20756 };
20757 for range in &ranges[start_ix..] {
20758 if range
20759 .start
20760 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20761 .is_ge()
20762 {
20763 break;
20764 }
20765
20766 let start = range.start.to_display_point(display_snapshot);
20767 let end = range.end.to_display_point(display_snapshot);
20768 results.push((start..end, color))
20769 }
20770 }
20771 results
20772 }
20773
20774 /// Get the text ranges corresponding to the redaction query
20775 pub fn redacted_ranges(
20776 &self,
20777 search_range: Range<Anchor>,
20778 display_snapshot: &DisplaySnapshot,
20779 cx: &App,
20780 ) -> Vec<Range<DisplayPoint>> {
20781 display_snapshot
20782 .buffer_snapshot()
20783 .redacted_ranges(search_range, |file| {
20784 if let Some(file) = file {
20785 file.is_private()
20786 && EditorSettings::get(
20787 Some(SettingsLocation {
20788 worktree_id: file.worktree_id(cx),
20789 path: file.path().as_ref(),
20790 }),
20791 cx,
20792 )
20793 .redact_private_values
20794 } else {
20795 false
20796 }
20797 })
20798 .map(|range| {
20799 range.start.to_display_point(display_snapshot)
20800 ..range.end.to_display_point(display_snapshot)
20801 })
20802 .collect()
20803 }
20804
20805 pub fn highlight_text_key<T: 'static>(
20806 &mut self,
20807 key: usize,
20808 ranges: Vec<Range<Anchor>>,
20809 style: HighlightStyle,
20810 cx: &mut Context<Self>,
20811 ) {
20812 self.display_map.update(cx, |map, _| {
20813 map.highlight_text(
20814 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20815 ranges,
20816 style,
20817 );
20818 });
20819 cx.notify();
20820 }
20821
20822 pub fn highlight_text<T: 'static>(
20823 &mut self,
20824 ranges: Vec<Range<Anchor>>,
20825 style: HighlightStyle,
20826 cx: &mut Context<Self>,
20827 ) {
20828 self.display_map.update(cx, |map, _| {
20829 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20830 });
20831 cx.notify();
20832 }
20833
20834 pub fn text_highlights<'a, T: 'static>(
20835 &'a self,
20836 cx: &'a App,
20837 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20838 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20839 }
20840
20841 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20842 let cleared = self
20843 .display_map
20844 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20845 if cleared {
20846 cx.notify();
20847 }
20848 }
20849
20850 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20851 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20852 && self.focus_handle.is_focused(window)
20853 }
20854
20855 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20856 self.show_cursor_when_unfocused = is_enabled;
20857 cx.notify();
20858 }
20859
20860 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20861 cx.notify();
20862 }
20863
20864 fn on_debug_session_event(
20865 &mut self,
20866 _session: Entity<Session>,
20867 event: &SessionEvent,
20868 cx: &mut Context<Self>,
20869 ) {
20870 if let SessionEvent::InvalidateInlineValue = event {
20871 self.refresh_inline_values(cx);
20872 }
20873 }
20874
20875 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20876 let Some(project) = self.project.clone() else {
20877 return;
20878 };
20879
20880 if !self.inline_value_cache.enabled {
20881 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20882 self.splice_inlays(&inlays, Vec::new(), cx);
20883 return;
20884 }
20885
20886 let current_execution_position = self
20887 .highlighted_rows
20888 .get(&TypeId::of::<ActiveDebugLine>())
20889 .and_then(|lines| lines.last().map(|line| line.range.end));
20890
20891 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20892 let inline_values = editor
20893 .update(cx, |editor, cx| {
20894 let Some(current_execution_position) = current_execution_position else {
20895 return Some(Task::ready(Ok(Vec::new())));
20896 };
20897
20898 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20899 let snapshot = buffer.snapshot(cx);
20900
20901 let excerpt = snapshot.excerpt_containing(
20902 current_execution_position..current_execution_position,
20903 )?;
20904
20905 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20906 })?;
20907
20908 let range =
20909 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20910
20911 project.inline_values(buffer, range, cx)
20912 })
20913 .ok()
20914 .flatten()?
20915 .await
20916 .context("refreshing debugger inlays")
20917 .log_err()?;
20918
20919 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20920
20921 for (buffer_id, inline_value) in inline_values
20922 .into_iter()
20923 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20924 {
20925 buffer_inline_values
20926 .entry(buffer_id)
20927 .or_default()
20928 .push(inline_value);
20929 }
20930
20931 editor
20932 .update(cx, |editor, cx| {
20933 let snapshot = editor.buffer.read(cx).snapshot(cx);
20934 let mut new_inlays = Vec::default();
20935
20936 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20937 let buffer_id = buffer_snapshot.remote_id();
20938 buffer_inline_values
20939 .get(&buffer_id)
20940 .into_iter()
20941 .flatten()
20942 .for_each(|hint| {
20943 let inlay = Inlay::debugger(
20944 post_inc(&mut editor.next_inlay_id),
20945 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20946 hint.text(),
20947 );
20948 if !inlay.text().chars().contains(&'\n') {
20949 new_inlays.push(inlay);
20950 }
20951 });
20952 }
20953
20954 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20955 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20956
20957 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20958 })
20959 .ok()?;
20960 Some(())
20961 });
20962 }
20963
20964 fn on_buffer_event(
20965 &mut self,
20966 multibuffer: &Entity<MultiBuffer>,
20967 event: &multi_buffer::Event,
20968 window: &mut Window,
20969 cx: &mut Context<Self>,
20970 ) {
20971 match event {
20972 multi_buffer::Event::Edited { edited_buffer } => {
20973 self.scrollbar_marker_state.dirty = true;
20974 self.active_indent_guides_state.dirty = true;
20975 self.refresh_active_diagnostics(cx);
20976 self.refresh_code_actions(window, cx);
20977 self.refresh_selected_text_highlights(true, window, cx);
20978 self.refresh_single_line_folds(window, cx);
20979 self.refresh_matching_bracket_highlights(window, cx);
20980 if self.has_active_edit_prediction() {
20981 self.update_visible_edit_prediction(window, cx);
20982 }
20983
20984 if let Some(buffer) = edited_buffer {
20985 if buffer.read(cx).file().is_none() {
20986 cx.emit(EditorEvent::TitleChanged);
20987 }
20988
20989 if self.project.is_some() {
20990 let buffer_id = buffer.read(cx).remote_id();
20991 self.register_buffer(buffer_id, cx);
20992 self.update_lsp_data(Some(buffer_id), window, cx);
20993 self.refresh_inlay_hints(
20994 InlayHintRefreshReason::BufferEdited(buffer_id),
20995 cx,
20996 );
20997 }
20998 }
20999
21000 cx.emit(EditorEvent::BufferEdited);
21001 cx.emit(SearchEvent::MatchesInvalidated);
21002
21003 let Some(project) = &self.project else { return };
21004 let (telemetry, is_via_ssh) = {
21005 let project = project.read(cx);
21006 let telemetry = project.client().telemetry().clone();
21007 let is_via_ssh = project.is_via_remote_server();
21008 (telemetry, is_via_ssh)
21009 };
21010 telemetry.log_edit_event("editor", is_via_ssh);
21011 }
21012 multi_buffer::Event::ExcerptsAdded {
21013 buffer,
21014 predecessor,
21015 excerpts,
21016 } => {
21017 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21018 let buffer_id = buffer.read(cx).remote_id();
21019 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21020 && let Some(project) = &self.project
21021 {
21022 update_uncommitted_diff_for_buffer(
21023 cx.entity(),
21024 project,
21025 [buffer.clone()],
21026 self.buffer.clone(),
21027 cx,
21028 )
21029 .detach();
21030 }
21031 self.update_lsp_data(Some(buffer_id), window, cx);
21032 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21033 cx.emit(EditorEvent::ExcerptsAdded {
21034 buffer: buffer.clone(),
21035 predecessor: *predecessor,
21036 excerpts: excerpts.clone(),
21037 });
21038 }
21039 multi_buffer::Event::ExcerptsRemoved {
21040 ids,
21041 removed_buffer_ids,
21042 } => {
21043 if let Some(inlay_hints) = &mut self.inlay_hints {
21044 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21045 }
21046 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21047 for buffer_id in removed_buffer_ids {
21048 self.registered_buffers.remove(buffer_id);
21049 }
21050 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21051 cx.emit(EditorEvent::ExcerptsRemoved {
21052 ids: ids.clone(),
21053 removed_buffer_ids: removed_buffer_ids.clone(),
21054 });
21055 }
21056 multi_buffer::Event::ExcerptsEdited {
21057 excerpt_ids,
21058 buffer_ids,
21059 } => {
21060 self.display_map.update(cx, |map, cx| {
21061 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21062 });
21063 cx.emit(EditorEvent::ExcerptsEdited {
21064 ids: excerpt_ids.clone(),
21065 });
21066 }
21067 multi_buffer::Event::ExcerptsExpanded { ids } => {
21068 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21069 self.refresh_document_highlights(cx);
21070 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21071 }
21072 multi_buffer::Event::Reparsed(buffer_id) => {
21073 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21074 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21075
21076 cx.emit(EditorEvent::Reparsed(*buffer_id));
21077 }
21078 multi_buffer::Event::DiffHunksToggled => {
21079 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21080 }
21081 multi_buffer::Event::LanguageChanged(buffer_id) => {
21082 self.registered_buffers.remove(&buffer_id);
21083 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21084 cx.emit(EditorEvent::Reparsed(*buffer_id));
21085 cx.notify();
21086 }
21087 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21088 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21089 multi_buffer::Event::FileHandleChanged
21090 | multi_buffer::Event::Reloaded
21091 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21092 multi_buffer::Event::DiagnosticsUpdated => {
21093 self.update_diagnostics_state(window, cx);
21094 }
21095 _ => {}
21096 };
21097 }
21098
21099 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21100 if !self.diagnostics_enabled() {
21101 return;
21102 }
21103 self.refresh_active_diagnostics(cx);
21104 self.refresh_inline_diagnostics(true, window, cx);
21105 self.scrollbar_marker_state.dirty = true;
21106 cx.notify();
21107 }
21108
21109 pub fn start_temporary_diff_override(&mut self) {
21110 self.load_diff_task.take();
21111 self.temporary_diff_override = true;
21112 }
21113
21114 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21115 self.temporary_diff_override = false;
21116 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21117 self.buffer.update(cx, |buffer, cx| {
21118 buffer.set_all_diff_hunks_collapsed(cx);
21119 });
21120
21121 if let Some(project) = self.project.clone() {
21122 self.load_diff_task = Some(
21123 update_uncommitted_diff_for_buffer(
21124 cx.entity(),
21125 &project,
21126 self.buffer.read(cx).all_buffers(),
21127 self.buffer.clone(),
21128 cx,
21129 )
21130 .shared(),
21131 );
21132 }
21133 }
21134
21135 fn on_display_map_changed(
21136 &mut self,
21137 _: Entity<DisplayMap>,
21138 _: &mut Window,
21139 cx: &mut Context<Self>,
21140 ) {
21141 cx.notify();
21142 }
21143
21144 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21145 if self.diagnostics_enabled() {
21146 let new_severity = EditorSettings::get_global(cx)
21147 .diagnostics_max_severity
21148 .unwrap_or(DiagnosticSeverity::Hint);
21149 self.set_max_diagnostics_severity(new_severity, cx);
21150 }
21151 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21152 self.update_edit_prediction_settings(cx);
21153 self.refresh_edit_prediction(true, false, window, cx);
21154 self.refresh_inline_values(cx);
21155 self.refresh_inlay_hints(
21156 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21157 self.selections.newest_anchor().head(),
21158 &self.buffer.read(cx).snapshot(cx),
21159 cx,
21160 )),
21161 cx,
21162 );
21163
21164 let old_cursor_shape = self.cursor_shape;
21165 let old_show_breadcrumbs = self.show_breadcrumbs;
21166
21167 {
21168 let editor_settings = EditorSettings::get_global(cx);
21169 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21170 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21171 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21172 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21173 }
21174
21175 if old_cursor_shape != self.cursor_shape {
21176 cx.emit(EditorEvent::CursorShapeChanged);
21177 }
21178
21179 if old_show_breadcrumbs != self.show_breadcrumbs {
21180 cx.emit(EditorEvent::BreadcrumbsChanged);
21181 }
21182
21183 let project_settings = ProjectSettings::get_global(cx);
21184 self.serialize_dirty_buffers =
21185 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21186
21187 if self.mode.is_full() {
21188 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21189 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21190 if self.show_inline_diagnostics != show_inline_diagnostics {
21191 self.show_inline_diagnostics = show_inline_diagnostics;
21192 self.refresh_inline_diagnostics(false, window, cx);
21193 }
21194
21195 if self.git_blame_inline_enabled != inline_blame_enabled {
21196 self.toggle_git_blame_inline_internal(false, window, cx);
21197 }
21198
21199 let minimap_settings = EditorSettings::get_global(cx).minimap;
21200 if self.minimap_visibility != MinimapVisibility::Disabled {
21201 if self.minimap_visibility.settings_visibility()
21202 != minimap_settings.minimap_enabled()
21203 {
21204 self.set_minimap_visibility(
21205 MinimapVisibility::for_mode(self.mode(), cx),
21206 window,
21207 cx,
21208 );
21209 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21210 minimap_entity.update(cx, |minimap_editor, cx| {
21211 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21212 })
21213 }
21214 }
21215 }
21216
21217 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21218 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21219 }) {
21220 if !inlay_splice.is_empty() {
21221 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21222 }
21223 self.refresh_colors_for_visible_range(None, window, cx);
21224 }
21225
21226 cx.notify();
21227 }
21228
21229 pub fn set_searchable(&mut self, searchable: bool) {
21230 self.searchable = searchable;
21231 }
21232
21233 pub fn searchable(&self) -> bool {
21234 self.searchable
21235 }
21236
21237 pub fn open_excerpts_in_split(
21238 &mut self,
21239 _: &OpenExcerptsSplit,
21240 window: &mut Window,
21241 cx: &mut Context<Self>,
21242 ) {
21243 self.open_excerpts_common(None, true, window, cx)
21244 }
21245
21246 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21247 self.open_excerpts_common(None, false, window, cx)
21248 }
21249
21250 fn open_excerpts_common(
21251 &mut self,
21252 jump_data: Option<JumpData>,
21253 split: bool,
21254 window: &mut Window,
21255 cx: &mut Context<Self>,
21256 ) {
21257 let Some(workspace) = self.workspace() else {
21258 cx.propagate();
21259 return;
21260 };
21261
21262 if self.buffer.read(cx).is_singleton() {
21263 cx.propagate();
21264 return;
21265 }
21266
21267 let mut new_selections_by_buffer = HashMap::default();
21268 match &jump_data {
21269 Some(JumpData::MultiBufferPoint {
21270 excerpt_id,
21271 position,
21272 anchor,
21273 line_offset_from_top,
21274 }) => {
21275 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21276 if let Some(buffer) = multi_buffer_snapshot
21277 .buffer_id_for_excerpt(*excerpt_id)
21278 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21279 {
21280 let buffer_snapshot = buffer.read(cx).snapshot();
21281 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21282 language::ToPoint::to_point(anchor, &buffer_snapshot)
21283 } else {
21284 buffer_snapshot.clip_point(*position, Bias::Left)
21285 };
21286 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21287 new_selections_by_buffer.insert(
21288 buffer,
21289 (
21290 vec![jump_to_offset..jump_to_offset],
21291 Some(*line_offset_from_top),
21292 ),
21293 );
21294 }
21295 }
21296 Some(JumpData::MultiBufferRow {
21297 row,
21298 line_offset_from_top,
21299 }) => {
21300 let point = MultiBufferPoint::new(row.0, 0);
21301 if let Some((buffer, buffer_point, _)) =
21302 self.buffer.read(cx).point_to_buffer_point(point, cx)
21303 {
21304 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21305 new_selections_by_buffer
21306 .entry(buffer)
21307 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21308 .0
21309 .push(buffer_offset..buffer_offset)
21310 }
21311 }
21312 None => {
21313 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21314 let multi_buffer = self.buffer.read(cx);
21315 for selection in selections {
21316 for (snapshot, range, _, anchor) in multi_buffer
21317 .snapshot(cx)
21318 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21319 {
21320 if let Some(anchor) = anchor {
21321 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21322 else {
21323 continue;
21324 };
21325 let offset = text::ToOffset::to_offset(
21326 &anchor.text_anchor,
21327 &buffer_handle.read(cx).snapshot(),
21328 );
21329 let range = offset..offset;
21330 new_selections_by_buffer
21331 .entry(buffer_handle)
21332 .or_insert((Vec::new(), None))
21333 .0
21334 .push(range)
21335 } else {
21336 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21337 else {
21338 continue;
21339 };
21340 new_selections_by_buffer
21341 .entry(buffer_handle)
21342 .or_insert((Vec::new(), None))
21343 .0
21344 .push(range)
21345 }
21346 }
21347 }
21348 }
21349 }
21350
21351 new_selections_by_buffer
21352 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21353
21354 if new_selections_by_buffer.is_empty() {
21355 return;
21356 }
21357
21358 // We defer the pane interaction because we ourselves are a workspace item
21359 // and activating a new item causes the pane to call a method on us reentrantly,
21360 // which panics if we're on the stack.
21361 window.defer(cx, move |window, cx| {
21362 workspace.update(cx, |workspace, cx| {
21363 let pane = if split {
21364 workspace.adjacent_pane(window, cx)
21365 } else {
21366 workspace.active_pane().clone()
21367 };
21368
21369 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21370 let editor = buffer
21371 .read(cx)
21372 .file()
21373 .is_none()
21374 .then(|| {
21375 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21376 // so `workspace.open_project_item` will never find them, always opening a new editor.
21377 // Instead, we try to activate the existing editor in the pane first.
21378 let (editor, pane_item_index) =
21379 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21380 let editor = item.downcast::<Editor>()?;
21381 let singleton_buffer =
21382 editor.read(cx).buffer().read(cx).as_singleton()?;
21383 if singleton_buffer == buffer {
21384 Some((editor, i))
21385 } else {
21386 None
21387 }
21388 })?;
21389 pane.update(cx, |pane, cx| {
21390 pane.activate_item(pane_item_index, true, true, window, cx)
21391 });
21392 Some(editor)
21393 })
21394 .flatten()
21395 .unwrap_or_else(|| {
21396 workspace.open_project_item::<Self>(
21397 pane.clone(),
21398 buffer,
21399 true,
21400 true,
21401 window,
21402 cx,
21403 )
21404 });
21405
21406 editor.update(cx, |editor, cx| {
21407 let autoscroll = match scroll_offset {
21408 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21409 None => Autoscroll::newest(),
21410 };
21411 let nav_history = editor.nav_history.take();
21412 editor.change_selections(
21413 SelectionEffects::scroll(autoscroll),
21414 window,
21415 cx,
21416 |s| {
21417 s.select_ranges(ranges);
21418 },
21419 );
21420 editor.nav_history = nav_history;
21421 });
21422 }
21423 })
21424 });
21425 }
21426
21427 // For now, don't allow opening excerpts in buffers that aren't backed by
21428 // regular project files.
21429 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21430 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21431 }
21432
21433 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21434 let snapshot = self.buffer.read(cx).read(cx);
21435 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21436 Some(
21437 ranges
21438 .iter()
21439 .map(move |range| {
21440 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21441 })
21442 .collect(),
21443 )
21444 }
21445
21446 fn selection_replacement_ranges(
21447 &self,
21448 range: Range<OffsetUtf16>,
21449 cx: &mut App,
21450 ) -> Vec<Range<OffsetUtf16>> {
21451 let selections = self
21452 .selections
21453 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21454 let newest_selection = selections
21455 .iter()
21456 .max_by_key(|selection| selection.id)
21457 .unwrap();
21458 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21459 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21460 let snapshot = self.buffer.read(cx).read(cx);
21461 selections
21462 .into_iter()
21463 .map(|mut selection| {
21464 selection.start.0 =
21465 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21466 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21467 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21468 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21469 })
21470 .collect()
21471 }
21472
21473 fn report_editor_event(
21474 &self,
21475 reported_event: ReportEditorEvent,
21476 file_extension: Option<String>,
21477 cx: &App,
21478 ) {
21479 if cfg!(any(test, feature = "test-support")) {
21480 return;
21481 }
21482
21483 let Some(project) = &self.project else { return };
21484
21485 // If None, we are in a file without an extension
21486 let file = self
21487 .buffer
21488 .read(cx)
21489 .as_singleton()
21490 .and_then(|b| b.read(cx).file());
21491 let file_extension = file_extension.or(file
21492 .as_ref()
21493 .and_then(|file| Path::new(file.file_name(cx)).extension())
21494 .and_then(|e| e.to_str())
21495 .map(|a| a.to_string()));
21496
21497 let vim_mode = vim_flavor(cx).is_some();
21498
21499 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21500 let copilot_enabled = edit_predictions_provider
21501 == language::language_settings::EditPredictionProvider::Copilot;
21502 let copilot_enabled_for_language = self
21503 .buffer
21504 .read(cx)
21505 .language_settings(cx)
21506 .show_edit_predictions;
21507
21508 let project = project.read(cx);
21509 let event_type = reported_event.event_type();
21510
21511 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21512 telemetry::event!(
21513 event_type,
21514 type = if auto_saved {"autosave"} else {"manual"},
21515 file_extension,
21516 vim_mode,
21517 copilot_enabled,
21518 copilot_enabled_for_language,
21519 edit_predictions_provider,
21520 is_via_ssh = project.is_via_remote_server(),
21521 );
21522 } else {
21523 telemetry::event!(
21524 event_type,
21525 file_extension,
21526 vim_mode,
21527 copilot_enabled,
21528 copilot_enabled_for_language,
21529 edit_predictions_provider,
21530 is_via_ssh = project.is_via_remote_server(),
21531 );
21532 };
21533 }
21534
21535 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21536 /// with each line being an array of {text, highlight} objects.
21537 fn copy_highlight_json(
21538 &mut self,
21539 _: &CopyHighlightJson,
21540 window: &mut Window,
21541 cx: &mut Context<Self>,
21542 ) {
21543 #[derive(Serialize)]
21544 struct Chunk<'a> {
21545 text: String,
21546 highlight: Option<&'a str>,
21547 }
21548
21549 let snapshot = self.buffer.read(cx).snapshot(cx);
21550 let range = self
21551 .selected_text_range(false, window, cx)
21552 .and_then(|selection| {
21553 if selection.range.is_empty() {
21554 None
21555 } else {
21556 Some(
21557 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21558 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21559 )
21560 }
21561 })
21562 .unwrap_or_else(|| 0..snapshot.len());
21563
21564 let chunks = snapshot.chunks(range, true);
21565 let mut lines = Vec::new();
21566 let mut line: VecDeque<Chunk> = VecDeque::new();
21567
21568 let Some(style) = self.style.as_ref() else {
21569 return;
21570 };
21571
21572 for chunk in chunks {
21573 let highlight = chunk
21574 .syntax_highlight_id
21575 .and_then(|id| id.name(&style.syntax));
21576 let mut chunk_lines = chunk.text.split('\n').peekable();
21577 while let Some(text) = chunk_lines.next() {
21578 let mut merged_with_last_token = false;
21579 if let Some(last_token) = line.back_mut()
21580 && last_token.highlight == highlight
21581 {
21582 last_token.text.push_str(text);
21583 merged_with_last_token = true;
21584 }
21585
21586 if !merged_with_last_token {
21587 line.push_back(Chunk {
21588 text: text.into(),
21589 highlight,
21590 });
21591 }
21592
21593 if chunk_lines.peek().is_some() {
21594 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21595 line.pop_front();
21596 }
21597 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21598 line.pop_back();
21599 }
21600
21601 lines.push(mem::take(&mut line));
21602 }
21603 }
21604 }
21605
21606 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21607 return;
21608 };
21609 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21610 }
21611
21612 pub fn open_context_menu(
21613 &mut self,
21614 _: &OpenContextMenu,
21615 window: &mut Window,
21616 cx: &mut Context<Self>,
21617 ) {
21618 self.request_autoscroll(Autoscroll::newest(), cx);
21619 let position = self
21620 .selections
21621 .newest_display(&self.display_snapshot(cx))
21622 .start;
21623 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21624 }
21625
21626 pub fn replay_insert_event(
21627 &mut self,
21628 text: &str,
21629 relative_utf16_range: Option<Range<isize>>,
21630 window: &mut Window,
21631 cx: &mut Context<Self>,
21632 ) {
21633 if !self.input_enabled {
21634 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21635 return;
21636 }
21637 if let Some(relative_utf16_range) = relative_utf16_range {
21638 let selections = self
21639 .selections
21640 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21641 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21642 let new_ranges = selections.into_iter().map(|range| {
21643 let start = OffsetUtf16(
21644 range
21645 .head()
21646 .0
21647 .saturating_add_signed(relative_utf16_range.start),
21648 );
21649 let end = OffsetUtf16(
21650 range
21651 .head()
21652 .0
21653 .saturating_add_signed(relative_utf16_range.end),
21654 );
21655 start..end
21656 });
21657 s.select_ranges(new_ranges);
21658 });
21659 }
21660
21661 self.handle_input(text, window, cx);
21662 }
21663
21664 pub fn is_focused(&self, window: &Window) -> bool {
21665 self.focus_handle.is_focused(window)
21666 }
21667
21668 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21669 cx.emit(EditorEvent::Focused);
21670
21671 if let Some(descendant) = self
21672 .last_focused_descendant
21673 .take()
21674 .and_then(|descendant| descendant.upgrade())
21675 {
21676 window.focus(&descendant);
21677 } else {
21678 if let Some(blame) = self.blame.as_ref() {
21679 blame.update(cx, GitBlame::focus)
21680 }
21681
21682 self.blink_manager.update(cx, BlinkManager::enable);
21683 self.show_cursor_names(window, cx);
21684 self.buffer.update(cx, |buffer, cx| {
21685 buffer.finalize_last_transaction(cx);
21686 if self.leader_id.is_none() {
21687 buffer.set_active_selections(
21688 &self.selections.disjoint_anchors_arc(),
21689 self.selections.line_mode(),
21690 self.cursor_shape,
21691 cx,
21692 );
21693 }
21694 });
21695 }
21696 }
21697
21698 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21699 cx.emit(EditorEvent::FocusedIn)
21700 }
21701
21702 fn handle_focus_out(
21703 &mut self,
21704 event: FocusOutEvent,
21705 _window: &mut Window,
21706 cx: &mut Context<Self>,
21707 ) {
21708 if event.blurred != self.focus_handle {
21709 self.last_focused_descendant = Some(event.blurred);
21710 }
21711 self.selection_drag_state = SelectionDragState::None;
21712 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21713 }
21714
21715 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21716 self.blink_manager.update(cx, BlinkManager::disable);
21717 self.buffer
21718 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21719
21720 if let Some(blame) = self.blame.as_ref() {
21721 blame.update(cx, GitBlame::blur)
21722 }
21723 if !self.hover_state.focused(window, cx) {
21724 hide_hover(self, cx);
21725 }
21726 if !self
21727 .context_menu
21728 .borrow()
21729 .as_ref()
21730 .is_some_and(|context_menu| context_menu.focused(window, cx))
21731 {
21732 self.hide_context_menu(window, cx);
21733 }
21734 self.take_active_edit_prediction(cx);
21735 cx.emit(EditorEvent::Blurred);
21736 cx.notify();
21737 }
21738
21739 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21740 let mut pending: String = window
21741 .pending_input_keystrokes()
21742 .into_iter()
21743 .flatten()
21744 .filter_map(|keystroke| {
21745 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21746 keystroke.key_char.clone()
21747 } else {
21748 None
21749 }
21750 })
21751 .collect();
21752
21753 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21754 pending = "".to_string();
21755 }
21756
21757 let existing_pending = self
21758 .text_highlights::<PendingInput>(cx)
21759 .map(|(_, ranges)| ranges.to_vec());
21760 if existing_pending.is_none() && pending.is_empty() {
21761 return;
21762 }
21763 let transaction =
21764 self.transact(window, cx, |this, window, cx| {
21765 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21766 let edits = selections
21767 .iter()
21768 .map(|selection| (selection.end..selection.end, pending.clone()));
21769 this.edit(edits, cx);
21770 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21771 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21772 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21773 }));
21774 });
21775 if let Some(existing_ranges) = existing_pending {
21776 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21777 this.edit(edits, cx);
21778 }
21779 });
21780
21781 let snapshot = self.snapshot(window, cx);
21782 let ranges = self
21783 .selections
21784 .all::<usize>(&snapshot.display_snapshot)
21785 .into_iter()
21786 .map(|selection| {
21787 snapshot.buffer_snapshot().anchor_after(selection.end)
21788 ..snapshot
21789 .buffer_snapshot()
21790 .anchor_before(selection.end + pending.len())
21791 })
21792 .collect();
21793
21794 if pending.is_empty() {
21795 self.clear_highlights::<PendingInput>(cx);
21796 } else {
21797 self.highlight_text::<PendingInput>(
21798 ranges,
21799 HighlightStyle {
21800 underline: Some(UnderlineStyle {
21801 thickness: px(1.),
21802 color: None,
21803 wavy: false,
21804 }),
21805 ..Default::default()
21806 },
21807 cx,
21808 );
21809 }
21810
21811 self.ime_transaction = self.ime_transaction.or(transaction);
21812 if let Some(transaction) = self.ime_transaction {
21813 self.buffer.update(cx, |buffer, cx| {
21814 buffer.group_until_transaction(transaction, cx);
21815 });
21816 }
21817
21818 if self.text_highlights::<PendingInput>(cx).is_none() {
21819 self.ime_transaction.take();
21820 }
21821 }
21822
21823 pub fn register_action_renderer(
21824 &mut self,
21825 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21826 ) -> Subscription {
21827 let id = self.next_editor_action_id.post_inc();
21828 self.editor_actions
21829 .borrow_mut()
21830 .insert(id, Box::new(listener));
21831
21832 let editor_actions = self.editor_actions.clone();
21833 Subscription::new(move || {
21834 editor_actions.borrow_mut().remove(&id);
21835 })
21836 }
21837
21838 pub fn register_action<A: Action>(
21839 &mut self,
21840 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21841 ) -> Subscription {
21842 let id = self.next_editor_action_id.post_inc();
21843 let listener = Arc::new(listener);
21844 self.editor_actions.borrow_mut().insert(
21845 id,
21846 Box::new(move |_, window, _| {
21847 let listener = listener.clone();
21848 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21849 let action = action.downcast_ref().unwrap();
21850 if phase == DispatchPhase::Bubble {
21851 listener(action, window, cx)
21852 }
21853 })
21854 }),
21855 );
21856
21857 let editor_actions = self.editor_actions.clone();
21858 Subscription::new(move || {
21859 editor_actions.borrow_mut().remove(&id);
21860 })
21861 }
21862
21863 pub fn file_header_size(&self) -> u32 {
21864 FILE_HEADER_HEIGHT
21865 }
21866
21867 pub fn restore(
21868 &mut self,
21869 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21870 window: &mut Window,
21871 cx: &mut Context<Self>,
21872 ) {
21873 let workspace = self.workspace();
21874 let project = self.project();
21875 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21876 let mut tasks = Vec::new();
21877 for (buffer_id, changes) in revert_changes {
21878 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21879 buffer.update(cx, |buffer, cx| {
21880 buffer.edit(
21881 changes
21882 .into_iter()
21883 .map(|(range, text)| (range, text.to_string())),
21884 None,
21885 cx,
21886 );
21887 });
21888
21889 if let Some(project) =
21890 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21891 {
21892 project.update(cx, |project, cx| {
21893 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21894 })
21895 }
21896 }
21897 }
21898 tasks
21899 });
21900 cx.spawn_in(window, async move |_, cx| {
21901 for (buffer, task) in save_tasks {
21902 let result = task.await;
21903 if result.is_err() {
21904 let Some(path) = buffer
21905 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21906 .ok()
21907 else {
21908 continue;
21909 };
21910 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21911 let Some(task) = cx
21912 .update_window_entity(workspace, |workspace, window, cx| {
21913 workspace
21914 .open_path_preview(path, None, false, false, false, window, cx)
21915 })
21916 .ok()
21917 else {
21918 continue;
21919 };
21920 task.await.log_err();
21921 }
21922 }
21923 }
21924 })
21925 .detach();
21926 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21927 selections.refresh()
21928 });
21929 }
21930
21931 pub fn to_pixel_point(
21932 &self,
21933 source: multi_buffer::Anchor,
21934 editor_snapshot: &EditorSnapshot,
21935 window: &mut Window,
21936 ) -> Option<gpui::Point<Pixels>> {
21937 let source_point = source.to_display_point(editor_snapshot);
21938 self.display_to_pixel_point(source_point, editor_snapshot, window)
21939 }
21940
21941 pub fn display_to_pixel_point(
21942 &self,
21943 source: DisplayPoint,
21944 editor_snapshot: &EditorSnapshot,
21945 window: &mut Window,
21946 ) -> Option<gpui::Point<Pixels>> {
21947 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21948 let text_layout_details = self.text_layout_details(window);
21949 let scroll_top = text_layout_details
21950 .scroll_anchor
21951 .scroll_position(editor_snapshot)
21952 .y;
21953
21954 if source.row().as_f64() < scroll_top.floor() {
21955 return None;
21956 }
21957 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21958 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21959 Some(gpui::Point::new(source_x, source_y))
21960 }
21961
21962 pub fn has_visible_completions_menu(&self) -> bool {
21963 !self.edit_prediction_preview_is_active()
21964 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21965 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21966 })
21967 }
21968
21969 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21970 if self.mode.is_minimap() {
21971 return;
21972 }
21973 self.addons
21974 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21975 }
21976
21977 pub fn unregister_addon<T: Addon>(&mut self) {
21978 self.addons.remove(&std::any::TypeId::of::<T>());
21979 }
21980
21981 pub fn addon<T: Addon>(&self) -> Option<&T> {
21982 let type_id = std::any::TypeId::of::<T>();
21983 self.addons
21984 .get(&type_id)
21985 .and_then(|item| item.to_any().downcast_ref::<T>())
21986 }
21987
21988 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21989 let type_id = std::any::TypeId::of::<T>();
21990 self.addons
21991 .get_mut(&type_id)
21992 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21993 }
21994
21995 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21996 let text_layout_details = self.text_layout_details(window);
21997 let style = &text_layout_details.editor_style;
21998 let font_id = window.text_system().resolve_font(&style.text.font());
21999 let font_size = style.text.font_size.to_pixels(window.rem_size());
22000 let line_height = style.text.line_height_in_pixels(window.rem_size());
22001 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22002 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22003
22004 CharacterDimensions {
22005 em_width,
22006 em_advance,
22007 line_height,
22008 }
22009 }
22010
22011 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22012 self.load_diff_task.clone()
22013 }
22014
22015 fn read_metadata_from_db(
22016 &mut self,
22017 item_id: u64,
22018 workspace_id: WorkspaceId,
22019 window: &mut Window,
22020 cx: &mut Context<Editor>,
22021 ) {
22022 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22023 && !self.mode.is_minimap()
22024 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22025 {
22026 let buffer_snapshot = OnceCell::new();
22027
22028 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22029 && !folds.is_empty()
22030 {
22031 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22032 self.fold_ranges(
22033 folds
22034 .into_iter()
22035 .map(|(start, end)| {
22036 snapshot.clip_offset(start, Bias::Left)
22037 ..snapshot.clip_offset(end, Bias::Right)
22038 })
22039 .collect(),
22040 false,
22041 window,
22042 cx,
22043 );
22044 }
22045
22046 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22047 && !selections.is_empty()
22048 {
22049 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22050 // skip adding the initial selection to selection history
22051 self.selection_history.mode = SelectionHistoryMode::Skipping;
22052 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22053 s.select_ranges(selections.into_iter().map(|(start, end)| {
22054 snapshot.clip_offset(start, Bias::Left)
22055 ..snapshot.clip_offset(end, Bias::Right)
22056 }));
22057 });
22058 self.selection_history.mode = SelectionHistoryMode::Normal;
22059 };
22060 }
22061
22062 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22063 }
22064
22065 fn update_lsp_data(
22066 &mut self,
22067 for_buffer: Option<BufferId>,
22068 window: &mut Window,
22069 cx: &mut Context<'_, Self>,
22070 ) {
22071 self.pull_diagnostics(for_buffer, window, cx);
22072 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22073 }
22074
22075 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22076 if self.ignore_lsp_data() {
22077 return;
22078 }
22079 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22080 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22081 }
22082 }
22083
22084 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22085 if !self.registered_buffers.contains_key(&buffer_id)
22086 && let Some(project) = self.project.as_ref()
22087 {
22088 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22089 project.update(cx, |project, cx| {
22090 self.registered_buffers.insert(
22091 buffer_id,
22092 project.register_buffer_with_language_servers(&buffer, cx),
22093 );
22094 });
22095 } else {
22096 self.registered_buffers.remove(&buffer_id);
22097 }
22098 }
22099 }
22100
22101 fn ignore_lsp_data(&self) -> bool {
22102 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22103 // skip any LSP updates for it.
22104 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22105 }
22106}
22107
22108fn edit_for_markdown_paste<'a>(
22109 buffer: &MultiBufferSnapshot,
22110 range: Range<usize>,
22111 to_insert: &'a str,
22112 url: Option<url::Url>,
22113) -> (Range<usize>, Cow<'a, str>) {
22114 if url.is_none() {
22115 return (range, Cow::Borrowed(to_insert));
22116 };
22117
22118 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22119
22120 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22121 Cow::Borrowed(to_insert)
22122 } else {
22123 Cow::Owned(format!("[{old_text}]({to_insert})"))
22124 };
22125 (range, new_text)
22126}
22127
22128#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22129pub enum VimFlavor {
22130 Vim,
22131 Helix,
22132}
22133
22134pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22135 if vim_mode_setting::HelixModeSetting::try_get(cx)
22136 .map(|helix_mode| helix_mode.0)
22137 .unwrap_or(false)
22138 {
22139 Some(VimFlavor::Helix)
22140 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22141 .map(|vim_mode| vim_mode.0)
22142 .unwrap_or(false)
22143 {
22144 Some(VimFlavor::Vim)
22145 } else {
22146 None // neither vim nor helix mode
22147 }
22148}
22149
22150fn process_completion_for_edit(
22151 completion: &Completion,
22152 intent: CompletionIntent,
22153 buffer: &Entity<Buffer>,
22154 cursor_position: &text::Anchor,
22155 cx: &mut Context<Editor>,
22156) -> CompletionEdit {
22157 let buffer = buffer.read(cx);
22158 let buffer_snapshot = buffer.snapshot();
22159 let (snippet, new_text) = if completion.is_snippet() {
22160 let mut snippet_source = completion.new_text.clone();
22161 // Workaround for typescript language server issues so that methods don't expand within
22162 // strings and functions with type expressions. The previous point is used because the query
22163 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22164 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22165 let previous_point = if previous_point.column > 0 {
22166 cursor_position.to_previous_offset(&buffer_snapshot)
22167 } else {
22168 cursor_position.to_offset(&buffer_snapshot)
22169 };
22170 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22171 && scope.prefers_label_for_snippet_in_completion()
22172 && let Some(label) = completion.label()
22173 && matches!(
22174 completion.kind(),
22175 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22176 )
22177 {
22178 snippet_source = label;
22179 }
22180 match Snippet::parse(&snippet_source).log_err() {
22181 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22182 None => (None, completion.new_text.clone()),
22183 }
22184 } else {
22185 (None, completion.new_text.clone())
22186 };
22187
22188 let mut range_to_replace = {
22189 let replace_range = &completion.replace_range;
22190 if let CompletionSource::Lsp {
22191 insert_range: Some(insert_range),
22192 ..
22193 } = &completion.source
22194 {
22195 debug_assert_eq!(
22196 insert_range.start, replace_range.start,
22197 "insert_range and replace_range should start at the same position"
22198 );
22199 debug_assert!(
22200 insert_range
22201 .start
22202 .cmp(cursor_position, &buffer_snapshot)
22203 .is_le(),
22204 "insert_range should start before or at cursor position"
22205 );
22206 debug_assert!(
22207 replace_range
22208 .start
22209 .cmp(cursor_position, &buffer_snapshot)
22210 .is_le(),
22211 "replace_range should start before or at cursor position"
22212 );
22213
22214 let should_replace = match intent {
22215 CompletionIntent::CompleteWithInsert => false,
22216 CompletionIntent::CompleteWithReplace => true,
22217 CompletionIntent::Complete | CompletionIntent::Compose => {
22218 let insert_mode =
22219 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22220 .completions
22221 .lsp_insert_mode;
22222 match insert_mode {
22223 LspInsertMode::Insert => false,
22224 LspInsertMode::Replace => true,
22225 LspInsertMode::ReplaceSubsequence => {
22226 let mut text_to_replace = buffer.chars_for_range(
22227 buffer.anchor_before(replace_range.start)
22228 ..buffer.anchor_after(replace_range.end),
22229 );
22230 let mut current_needle = text_to_replace.next();
22231 for haystack_ch in completion.label.text.chars() {
22232 if let Some(needle_ch) = current_needle
22233 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22234 {
22235 current_needle = text_to_replace.next();
22236 }
22237 }
22238 current_needle.is_none()
22239 }
22240 LspInsertMode::ReplaceSuffix => {
22241 if replace_range
22242 .end
22243 .cmp(cursor_position, &buffer_snapshot)
22244 .is_gt()
22245 {
22246 let range_after_cursor = *cursor_position..replace_range.end;
22247 let text_after_cursor = buffer
22248 .text_for_range(
22249 buffer.anchor_before(range_after_cursor.start)
22250 ..buffer.anchor_after(range_after_cursor.end),
22251 )
22252 .collect::<String>()
22253 .to_ascii_lowercase();
22254 completion
22255 .label
22256 .text
22257 .to_ascii_lowercase()
22258 .ends_with(&text_after_cursor)
22259 } else {
22260 true
22261 }
22262 }
22263 }
22264 }
22265 };
22266
22267 if should_replace {
22268 replace_range.clone()
22269 } else {
22270 insert_range.clone()
22271 }
22272 } else {
22273 replace_range.clone()
22274 }
22275 };
22276
22277 if range_to_replace
22278 .end
22279 .cmp(cursor_position, &buffer_snapshot)
22280 .is_lt()
22281 {
22282 range_to_replace.end = *cursor_position;
22283 }
22284
22285 CompletionEdit {
22286 new_text,
22287 replace_range: range_to_replace.to_offset(buffer),
22288 snippet,
22289 }
22290}
22291
22292struct CompletionEdit {
22293 new_text: String,
22294 replace_range: Range<usize>,
22295 snippet: Option<Snippet>,
22296}
22297
22298fn insert_extra_newline_brackets(
22299 buffer: &MultiBufferSnapshot,
22300 range: Range<usize>,
22301 language: &language::LanguageScope,
22302) -> bool {
22303 let leading_whitespace_len = buffer
22304 .reversed_chars_at(range.start)
22305 .take_while(|c| c.is_whitespace() && *c != '\n')
22306 .map(|c| c.len_utf8())
22307 .sum::<usize>();
22308 let trailing_whitespace_len = buffer
22309 .chars_at(range.end)
22310 .take_while(|c| c.is_whitespace() && *c != '\n')
22311 .map(|c| c.len_utf8())
22312 .sum::<usize>();
22313 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22314
22315 language.brackets().any(|(pair, enabled)| {
22316 let pair_start = pair.start.trim_end();
22317 let pair_end = pair.end.trim_start();
22318
22319 enabled
22320 && pair.newline
22321 && buffer.contains_str_at(range.end, pair_end)
22322 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22323 })
22324}
22325
22326fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22327 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22328 [(buffer, range, _)] => (*buffer, range.clone()),
22329 _ => return false,
22330 };
22331 let pair = {
22332 let mut result: Option<BracketMatch> = None;
22333
22334 for pair in buffer
22335 .all_bracket_ranges(range.clone())
22336 .filter(move |pair| {
22337 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22338 })
22339 {
22340 let len = pair.close_range.end - pair.open_range.start;
22341
22342 if let Some(existing) = &result {
22343 let existing_len = existing.close_range.end - existing.open_range.start;
22344 if len > existing_len {
22345 continue;
22346 }
22347 }
22348
22349 result = Some(pair);
22350 }
22351
22352 result
22353 };
22354 let Some(pair) = pair else {
22355 return false;
22356 };
22357 pair.newline_only
22358 && buffer
22359 .chars_for_range(pair.open_range.end..range.start)
22360 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22361 .all(|c| c.is_whitespace() && c != '\n')
22362}
22363
22364fn update_uncommitted_diff_for_buffer(
22365 editor: Entity<Editor>,
22366 project: &Entity<Project>,
22367 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22368 buffer: Entity<MultiBuffer>,
22369 cx: &mut App,
22370) -> Task<()> {
22371 let mut tasks = Vec::new();
22372 project.update(cx, |project, cx| {
22373 for buffer in buffers {
22374 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22375 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22376 }
22377 }
22378 });
22379 cx.spawn(async move |cx| {
22380 let diffs = future::join_all(tasks).await;
22381 if editor
22382 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22383 .unwrap_or(false)
22384 {
22385 return;
22386 }
22387
22388 buffer
22389 .update(cx, |buffer, cx| {
22390 for diff in diffs.into_iter().flatten() {
22391 buffer.add_diff(diff, cx);
22392 }
22393 })
22394 .ok();
22395 })
22396}
22397
22398fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22399 let tab_size = tab_size.get() as usize;
22400 let mut width = offset;
22401
22402 for ch in text.chars() {
22403 width += if ch == '\t' {
22404 tab_size - (width % tab_size)
22405 } else {
22406 1
22407 };
22408 }
22409
22410 width - offset
22411}
22412
22413#[cfg(test)]
22414mod tests {
22415 use super::*;
22416
22417 #[test]
22418 fn test_string_size_with_expanded_tabs() {
22419 let nz = |val| NonZeroU32::new(val).unwrap();
22420 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22421 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22422 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22423 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22424 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22425 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22426 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22427 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22428 }
22429}
22430
22431/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22432struct WordBreakingTokenizer<'a> {
22433 input: &'a str,
22434}
22435
22436impl<'a> WordBreakingTokenizer<'a> {
22437 fn new(input: &'a str) -> Self {
22438 Self { input }
22439 }
22440}
22441
22442fn is_char_ideographic(ch: char) -> bool {
22443 use unicode_script::Script::*;
22444 use unicode_script::UnicodeScript;
22445 matches!(ch.script(), Han | Tangut | Yi)
22446}
22447
22448fn is_grapheme_ideographic(text: &str) -> bool {
22449 text.chars().any(is_char_ideographic)
22450}
22451
22452fn is_grapheme_whitespace(text: &str) -> bool {
22453 text.chars().any(|x| x.is_whitespace())
22454}
22455
22456fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22457 text.chars()
22458 .next()
22459 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22460}
22461
22462#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22463enum WordBreakToken<'a> {
22464 Word { token: &'a str, grapheme_len: usize },
22465 InlineWhitespace { token: &'a str, grapheme_len: usize },
22466 Newline,
22467}
22468
22469impl<'a> Iterator for WordBreakingTokenizer<'a> {
22470 /// Yields a span, the count of graphemes in the token, and whether it was
22471 /// whitespace. Note that it also breaks at word boundaries.
22472 type Item = WordBreakToken<'a>;
22473
22474 fn next(&mut self) -> Option<Self::Item> {
22475 use unicode_segmentation::UnicodeSegmentation;
22476 if self.input.is_empty() {
22477 return None;
22478 }
22479
22480 let mut iter = self.input.graphemes(true).peekable();
22481 let mut offset = 0;
22482 let mut grapheme_len = 0;
22483 if let Some(first_grapheme) = iter.next() {
22484 let is_newline = first_grapheme == "\n";
22485 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22486 offset += first_grapheme.len();
22487 grapheme_len += 1;
22488 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22489 if let Some(grapheme) = iter.peek().copied()
22490 && should_stay_with_preceding_ideograph(grapheme)
22491 {
22492 offset += grapheme.len();
22493 grapheme_len += 1;
22494 }
22495 } else {
22496 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22497 let mut next_word_bound = words.peek().copied();
22498 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22499 next_word_bound = words.next();
22500 }
22501 while let Some(grapheme) = iter.peek().copied() {
22502 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22503 break;
22504 };
22505 if is_grapheme_whitespace(grapheme) != is_whitespace
22506 || (grapheme == "\n") != is_newline
22507 {
22508 break;
22509 };
22510 offset += grapheme.len();
22511 grapheme_len += 1;
22512 iter.next();
22513 }
22514 }
22515 let token = &self.input[..offset];
22516 self.input = &self.input[offset..];
22517 if token == "\n" {
22518 Some(WordBreakToken::Newline)
22519 } else if is_whitespace {
22520 Some(WordBreakToken::InlineWhitespace {
22521 token,
22522 grapheme_len,
22523 })
22524 } else {
22525 Some(WordBreakToken::Word {
22526 token,
22527 grapheme_len,
22528 })
22529 }
22530 } else {
22531 None
22532 }
22533 }
22534}
22535
22536#[test]
22537fn test_word_breaking_tokenizer() {
22538 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22539 ("", &[]),
22540 (" ", &[whitespace(" ", 2)]),
22541 ("Ʒ", &[word("Ʒ", 1)]),
22542 ("Ǽ", &[word("Ǽ", 1)]),
22543 ("⋑", &[word("⋑", 1)]),
22544 ("⋑⋑", &[word("⋑⋑", 2)]),
22545 (
22546 "原理,进而",
22547 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22548 ),
22549 (
22550 "hello world",
22551 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22552 ),
22553 (
22554 "hello, world",
22555 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22556 ),
22557 (
22558 " hello world",
22559 &[
22560 whitespace(" ", 2),
22561 word("hello", 5),
22562 whitespace(" ", 1),
22563 word("world", 5),
22564 ],
22565 ),
22566 (
22567 "这是什么 \n 钢笔",
22568 &[
22569 word("这", 1),
22570 word("是", 1),
22571 word("什", 1),
22572 word("么", 1),
22573 whitespace(" ", 1),
22574 newline(),
22575 whitespace(" ", 1),
22576 word("钢", 1),
22577 word("笔", 1),
22578 ],
22579 ),
22580 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22581 ];
22582
22583 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22584 WordBreakToken::Word {
22585 token,
22586 grapheme_len,
22587 }
22588 }
22589
22590 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22591 WordBreakToken::InlineWhitespace {
22592 token,
22593 grapheme_len,
22594 }
22595 }
22596
22597 fn newline() -> WordBreakToken<'static> {
22598 WordBreakToken::Newline
22599 }
22600
22601 for (input, result) in tests {
22602 assert_eq!(
22603 WordBreakingTokenizer::new(input)
22604 .collect::<Vec<_>>()
22605 .as_slice(),
22606 *result,
22607 );
22608 }
22609}
22610
22611fn wrap_with_prefix(
22612 first_line_prefix: String,
22613 subsequent_lines_prefix: String,
22614 unwrapped_text: String,
22615 wrap_column: usize,
22616 tab_size: NonZeroU32,
22617 preserve_existing_whitespace: bool,
22618) -> String {
22619 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22620 let subsequent_lines_prefix_len =
22621 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22622 let mut wrapped_text = String::new();
22623 let mut current_line = first_line_prefix;
22624 let mut is_first_line = true;
22625
22626 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22627 let mut current_line_len = first_line_prefix_len;
22628 let mut in_whitespace = false;
22629 for token in tokenizer {
22630 let have_preceding_whitespace = in_whitespace;
22631 match token {
22632 WordBreakToken::Word {
22633 token,
22634 grapheme_len,
22635 } => {
22636 in_whitespace = false;
22637 let current_prefix_len = if is_first_line {
22638 first_line_prefix_len
22639 } else {
22640 subsequent_lines_prefix_len
22641 };
22642 if current_line_len + grapheme_len > wrap_column
22643 && current_line_len != current_prefix_len
22644 {
22645 wrapped_text.push_str(current_line.trim_end());
22646 wrapped_text.push('\n');
22647 is_first_line = false;
22648 current_line = subsequent_lines_prefix.clone();
22649 current_line_len = subsequent_lines_prefix_len;
22650 }
22651 current_line.push_str(token);
22652 current_line_len += grapheme_len;
22653 }
22654 WordBreakToken::InlineWhitespace {
22655 mut token,
22656 mut grapheme_len,
22657 } => {
22658 in_whitespace = true;
22659 if have_preceding_whitespace && !preserve_existing_whitespace {
22660 continue;
22661 }
22662 if !preserve_existing_whitespace {
22663 // Keep a single whitespace grapheme as-is
22664 if let Some(first) =
22665 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22666 {
22667 token = first;
22668 } else {
22669 token = " ";
22670 }
22671 grapheme_len = 1;
22672 }
22673 let current_prefix_len = if is_first_line {
22674 first_line_prefix_len
22675 } else {
22676 subsequent_lines_prefix_len
22677 };
22678 if current_line_len + grapheme_len > wrap_column {
22679 wrapped_text.push_str(current_line.trim_end());
22680 wrapped_text.push('\n');
22681 is_first_line = false;
22682 current_line = subsequent_lines_prefix.clone();
22683 current_line_len = subsequent_lines_prefix_len;
22684 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22685 current_line.push_str(token);
22686 current_line_len += grapheme_len;
22687 }
22688 }
22689 WordBreakToken::Newline => {
22690 in_whitespace = true;
22691 let current_prefix_len = if is_first_line {
22692 first_line_prefix_len
22693 } else {
22694 subsequent_lines_prefix_len
22695 };
22696 if preserve_existing_whitespace {
22697 wrapped_text.push_str(current_line.trim_end());
22698 wrapped_text.push('\n');
22699 is_first_line = false;
22700 current_line = subsequent_lines_prefix.clone();
22701 current_line_len = subsequent_lines_prefix_len;
22702 } else if have_preceding_whitespace {
22703 continue;
22704 } else if current_line_len + 1 > wrap_column
22705 && current_line_len != current_prefix_len
22706 {
22707 wrapped_text.push_str(current_line.trim_end());
22708 wrapped_text.push('\n');
22709 is_first_line = false;
22710 current_line = subsequent_lines_prefix.clone();
22711 current_line_len = subsequent_lines_prefix_len;
22712 } else if current_line_len != current_prefix_len {
22713 current_line.push(' ');
22714 current_line_len += 1;
22715 }
22716 }
22717 }
22718 }
22719
22720 if !current_line.is_empty() {
22721 wrapped_text.push_str(¤t_line);
22722 }
22723 wrapped_text
22724}
22725
22726#[test]
22727fn test_wrap_with_prefix() {
22728 assert_eq!(
22729 wrap_with_prefix(
22730 "# ".to_string(),
22731 "# ".to_string(),
22732 "abcdefg".to_string(),
22733 4,
22734 NonZeroU32::new(4).unwrap(),
22735 false,
22736 ),
22737 "# abcdefg"
22738 );
22739 assert_eq!(
22740 wrap_with_prefix(
22741 "".to_string(),
22742 "".to_string(),
22743 "\thello world".to_string(),
22744 8,
22745 NonZeroU32::new(4).unwrap(),
22746 false,
22747 ),
22748 "hello\nworld"
22749 );
22750 assert_eq!(
22751 wrap_with_prefix(
22752 "// ".to_string(),
22753 "// ".to_string(),
22754 "xx \nyy zz aa bb cc".to_string(),
22755 12,
22756 NonZeroU32::new(4).unwrap(),
22757 false,
22758 ),
22759 "// xx yy zz\n// aa bb cc"
22760 );
22761 assert_eq!(
22762 wrap_with_prefix(
22763 String::new(),
22764 String::new(),
22765 "这是什么 \n 钢笔".to_string(),
22766 3,
22767 NonZeroU32::new(4).unwrap(),
22768 false,
22769 ),
22770 "这是什\n么 钢\n笔"
22771 );
22772 assert_eq!(
22773 wrap_with_prefix(
22774 String::new(),
22775 String::new(),
22776 format!("foo{}bar", '\u{2009}'), // thin space
22777 80,
22778 NonZeroU32::new(4).unwrap(),
22779 false,
22780 ),
22781 format!("foo{}bar", '\u{2009}')
22782 );
22783}
22784
22785pub trait CollaborationHub {
22786 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22787 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22788 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22789}
22790
22791impl CollaborationHub for Entity<Project> {
22792 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22793 self.read(cx).collaborators()
22794 }
22795
22796 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22797 self.read(cx).user_store().read(cx).participant_indices()
22798 }
22799
22800 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22801 let this = self.read(cx);
22802 let user_ids = this.collaborators().values().map(|c| c.user_id);
22803 this.user_store().read(cx).participant_names(user_ids, cx)
22804 }
22805}
22806
22807pub trait SemanticsProvider {
22808 fn hover(
22809 &self,
22810 buffer: &Entity<Buffer>,
22811 position: text::Anchor,
22812 cx: &mut App,
22813 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22814
22815 fn inline_values(
22816 &self,
22817 buffer_handle: Entity<Buffer>,
22818 range: Range<text::Anchor>,
22819 cx: &mut App,
22820 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22821
22822 fn applicable_inlay_chunks(
22823 &self,
22824 buffer: &Entity<Buffer>,
22825 ranges: &[Range<text::Anchor>],
22826 cx: &mut App,
22827 ) -> Vec<Range<BufferRow>>;
22828
22829 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22830
22831 fn inlay_hints(
22832 &self,
22833 invalidate: InvalidationStrategy,
22834 buffer: Entity<Buffer>,
22835 ranges: Vec<Range<text::Anchor>>,
22836 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22837 cx: &mut App,
22838 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22839
22840 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22841
22842 fn document_highlights(
22843 &self,
22844 buffer: &Entity<Buffer>,
22845 position: text::Anchor,
22846 cx: &mut App,
22847 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22848
22849 fn definitions(
22850 &self,
22851 buffer: &Entity<Buffer>,
22852 position: text::Anchor,
22853 kind: GotoDefinitionKind,
22854 cx: &mut App,
22855 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22856
22857 fn range_for_rename(
22858 &self,
22859 buffer: &Entity<Buffer>,
22860 position: text::Anchor,
22861 cx: &mut App,
22862 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22863
22864 fn perform_rename(
22865 &self,
22866 buffer: &Entity<Buffer>,
22867 position: text::Anchor,
22868 new_name: String,
22869 cx: &mut App,
22870 ) -> Option<Task<Result<ProjectTransaction>>>;
22871}
22872
22873pub trait CompletionProvider {
22874 fn completions(
22875 &self,
22876 excerpt_id: ExcerptId,
22877 buffer: &Entity<Buffer>,
22878 buffer_position: text::Anchor,
22879 trigger: CompletionContext,
22880 window: &mut Window,
22881 cx: &mut Context<Editor>,
22882 ) -> Task<Result<Vec<CompletionResponse>>>;
22883
22884 fn resolve_completions(
22885 &self,
22886 _buffer: Entity<Buffer>,
22887 _completion_indices: Vec<usize>,
22888 _completions: Rc<RefCell<Box<[Completion]>>>,
22889 _cx: &mut Context<Editor>,
22890 ) -> Task<Result<bool>> {
22891 Task::ready(Ok(false))
22892 }
22893
22894 fn apply_additional_edits_for_completion(
22895 &self,
22896 _buffer: Entity<Buffer>,
22897 _completions: Rc<RefCell<Box<[Completion]>>>,
22898 _completion_index: usize,
22899 _push_to_history: bool,
22900 _cx: &mut Context<Editor>,
22901 ) -> Task<Result<Option<language::Transaction>>> {
22902 Task::ready(Ok(None))
22903 }
22904
22905 fn is_completion_trigger(
22906 &self,
22907 buffer: &Entity<Buffer>,
22908 position: language::Anchor,
22909 text: &str,
22910 trigger_in_words: bool,
22911 menu_is_open: bool,
22912 cx: &mut Context<Editor>,
22913 ) -> bool;
22914
22915 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22916
22917 fn sort_completions(&self) -> bool {
22918 true
22919 }
22920
22921 fn filter_completions(&self) -> bool {
22922 true
22923 }
22924}
22925
22926pub trait CodeActionProvider {
22927 fn id(&self) -> Arc<str>;
22928
22929 fn code_actions(
22930 &self,
22931 buffer: &Entity<Buffer>,
22932 range: Range<text::Anchor>,
22933 window: &mut Window,
22934 cx: &mut App,
22935 ) -> Task<Result<Vec<CodeAction>>>;
22936
22937 fn apply_code_action(
22938 &self,
22939 buffer_handle: Entity<Buffer>,
22940 action: CodeAction,
22941 excerpt_id: ExcerptId,
22942 push_to_history: bool,
22943 window: &mut Window,
22944 cx: &mut App,
22945 ) -> Task<Result<ProjectTransaction>>;
22946}
22947
22948impl CodeActionProvider for Entity<Project> {
22949 fn id(&self) -> Arc<str> {
22950 "project".into()
22951 }
22952
22953 fn code_actions(
22954 &self,
22955 buffer: &Entity<Buffer>,
22956 range: Range<text::Anchor>,
22957 _window: &mut Window,
22958 cx: &mut App,
22959 ) -> Task<Result<Vec<CodeAction>>> {
22960 self.update(cx, |project, cx| {
22961 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22962 let code_actions = project.code_actions(buffer, range, None, cx);
22963 cx.background_spawn(async move {
22964 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22965 Ok(code_lens_actions
22966 .context("code lens fetch")?
22967 .into_iter()
22968 .flatten()
22969 .chain(
22970 code_actions
22971 .context("code action fetch")?
22972 .into_iter()
22973 .flatten(),
22974 )
22975 .collect())
22976 })
22977 })
22978 }
22979
22980 fn apply_code_action(
22981 &self,
22982 buffer_handle: Entity<Buffer>,
22983 action: CodeAction,
22984 _excerpt_id: ExcerptId,
22985 push_to_history: bool,
22986 _window: &mut Window,
22987 cx: &mut App,
22988 ) -> Task<Result<ProjectTransaction>> {
22989 self.update(cx, |project, cx| {
22990 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22991 })
22992 }
22993}
22994
22995fn snippet_completions(
22996 project: &Project,
22997 buffer: &Entity<Buffer>,
22998 buffer_position: text::Anchor,
22999 cx: &mut App,
23000) -> Task<Result<CompletionResponse>> {
23001 let languages = buffer.read(cx).languages_at(buffer_position);
23002 let snippet_store = project.snippets().read(cx);
23003
23004 let scopes: Vec<_> = languages
23005 .iter()
23006 .filter_map(|language| {
23007 let language_name = language.lsp_id();
23008 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23009
23010 if snippets.is_empty() {
23011 None
23012 } else {
23013 Some((language.default_scope(), snippets))
23014 }
23015 })
23016 .collect();
23017
23018 if scopes.is_empty() {
23019 return Task::ready(Ok(CompletionResponse {
23020 completions: vec![],
23021 display_options: CompletionDisplayOptions::default(),
23022 is_incomplete: false,
23023 }));
23024 }
23025
23026 let snapshot = buffer.read(cx).text_snapshot();
23027 let executor = cx.background_executor().clone();
23028
23029 cx.background_spawn(async move {
23030 let mut is_incomplete = false;
23031 let mut completions: Vec<Completion> = Vec::new();
23032 for (scope, snippets) in scopes.into_iter() {
23033 let classifier =
23034 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23035
23036 const MAX_WORD_PREFIX_LEN: usize = 128;
23037 let last_word: String = snapshot
23038 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23039 .take(MAX_WORD_PREFIX_LEN)
23040 .take_while(|c| classifier.is_word(*c))
23041 .collect::<String>()
23042 .chars()
23043 .rev()
23044 .collect();
23045
23046 if last_word.is_empty() {
23047 return Ok(CompletionResponse {
23048 completions: vec![],
23049 display_options: CompletionDisplayOptions::default(),
23050 is_incomplete: true,
23051 });
23052 }
23053
23054 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23055 let to_lsp = |point: &text::Anchor| {
23056 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23057 point_to_lsp(end)
23058 };
23059 let lsp_end = to_lsp(&buffer_position);
23060
23061 let candidates = snippets
23062 .iter()
23063 .enumerate()
23064 .flat_map(|(ix, snippet)| {
23065 snippet
23066 .prefix
23067 .iter()
23068 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23069 })
23070 .collect::<Vec<StringMatchCandidate>>();
23071
23072 const MAX_RESULTS: usize = 100;
23073 let mut matches = fuzzy::match_strings(
23074 &candidates,
23075 &last_word,
23076 last_word.chars().any(|c| c.is_uppercase()),
23077 true,
23078 MAX_RESULTS,
23079 &Default::default(),
23080 executor.clone(),
23081 )
23082 .await;
23083
23084 if matches.len() >= MAX_RESULTS {
23085 is_incomplete = true;
23086 }
23087
23088 // Remove all candidates where the query's start does not match the start of any word in the candidate
23089 if let Some(query_start) = last_word.chars().next() {
23090 matches.retain(|string_match| {
23091 split_words(&string_match.string).any(|word| {
23092 // Check that the first codepoint of the word as lowercase matches the first
23093 // codepoint of the query as lowercase
23094 word.chars()
23095 .flat_map(|codepoint| codepoint.to_lowercase())
23096 .zip(query_start.to_lowercase())
23097 .all(|(word_cp, query_cp)| word_cp == query_cp)
23098 })
23099 });
23100 }
23101
23102 let matched_strings = matches
23103 .into_iter()
23104 .map(|m| m.string)
23105 .collect::<HashSet<_>>();
23106
23107 completions.extend(snippets.iter().filter_map(|snippet| {
23108 let matching_prefix = snippet
23109 .prefix
23110 .iter()
23111 .find(|prefix| matched_strings.contains(*prefix))?;
23112 let start = as_offset - last_word.len();
23113 let start = snapshot.anchor_before(start);
23114 let range = start..buffer_position;
23115 let lsp_start = to_lsp(&start);
23116 let lsp_range = lsp::Range {
23117 start: lsp_start,
23118 end: lsp_end,
23119 };
23120 Some(Completion {
23121 replace_range: range,
23122 new_text: snippet.body.clone(),
23123 source: CompletionSource::Lsp {
23124 insert_range: None,
23125 server_id: LanguageServerId(usize::MAX),
23126 resolved: true,
23127 lsp_completion: Box::new(lsp::CompletionItem {
23128 label: snippet.prefix.first().unwrap().clone(),
23129 kind: Some(CompletionItemKind::SNIPPET),
23130 label_details: snippet.description.as_ref().map(|description| {
23131 lsp::CompletionItemLabelDetails {
23132 detail: Some(description.clone()),
23133 description: None,
23134 }
23135 }),
23136 insert_text_format: Some(InsertTextFormat::SNIPPET),
23137 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23138 lsp::InsertReplaceEdit {
23139 new_text: snippet.body.clone(),
23140 insert: lsp_range,
23141 replace: lsp_range,
23142 },
23143 )),
23144 filter_text: Some(snippet.body.clone()),
23145 sort_text: Some(char::MAX.to_string()),
23146 ..lsp::CompletionItem::default()
23147 }),
23148 lsp_defaults: None,
23149 },
23150 label: CodeLabel::plain(matching_prefix.clone(), None),
23151 icon_path: None,
23152 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23153 single_line: snippet.name.clone().into(),
23154 plain_text: snippet
23155 .description
23156 .clone()
23157 .map(|description| description.into()),
23158 }),
23159 insert_text_mode: None,
23160 confirm: None,
23161 })
23162 }))
23163 }
23164
23165 Ok(CompletionResponse {
23166 completions,
23167 display_options: CompletionDisplayOptions::default(),
23168 is_incomplete,
23169 })
23170 })
23171}
23172
23173impl CompletionProvider for Entity<Project> {
23174 fn completions(
23175 &self,
23176 _excerpt_id: ExcerptId,
23177 buffer: &Entity<Buffer>,
23178 buffer_position: text::Anchor,
23179 options: CompletionContext,
23180 _window: &mut Window,
23181 cx: &mut Context<Editor>,
23182 ) -> Task<Result<Vec<CompletionResponse>>> {
23183 self.update(cx, |project, cx| {
23184 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23185 let project_completions = project.completions(buffer, buffer_position, options, cx);
23186 cx.background_spawn(async move {
23187 let mut responses = project_completions.await?;
23188 let snippets = snippets.await?;
23189 if !snippets.completions.is_empty() {
23190 responses.push(snippets);
23191 }
23192 Ok(responses)
23193 })
23194 })
23195 }
23196
23197 fn resolve_completions(
23198 &self,
23199 buffer: Entity<Buffer>,
23200 completion_indices: Vec<usize>,
23201 completions: Rc<RefCell<Box<[Completion]>>>,
23202 cx: &mut Context<Editor>,
23203 ) -> Task<Result<bool>> {
23204 self.update(cx, |project, cx| {
23205 project.lsp_store().update(cx, |lsp_store, cx| {
23206 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23207 })
23208 })
23209 }
23210
23211 fn apply_additional_edits_for_completion(
23212 &self,
23213 buffer: Entity<Buffer>,
23214 completions: Rc<RefCell<Box<[Completion]>>>,
23215 completion_index: usize,
23216 push_to_history: bool,
23217 cx: &mut Context<Editor>,
23218 ) -> Task<Result<Option<language::Transaction>>> {
23219 self.update(cx, |project, cx| {
23220 project.lsp_store().update(cx, |lsp_store, cx| {
23221 lsp_store.apply_additional_edits_for_completion(
23222 buffer,
23223 completions,
23224 completion_index,
23225 push_to_history,
23226 cx,
23227 )
23228 })
23229 })
23230 }
23231
23232 fn is_completion_trigger(
23233 &self,
23234 buffer: &Entity<Buffer>,
23235 position: language::Anchor,
23236 text: &str,
23237 trigger_in_words: bool,
23238 menu_is_open: bool,
23239 cx: &mut Context<Editor>,
23240 ) -> bool {
23241 let mut chars = text.chars();
23242 let char = if let Some(char) = chars.next() {
23243 char
23244 } else {
23245 return false;
23246 };
23247 if chars.next().is_some() {
23248 return false;
23249 }
23250
23251 let buffer = buffer.read(cx);
23252 let snapshot = buffer.snapshot();
23253 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23254 return false;
23255 }
23256 let classifier = snapshot
23257 .char_classifier_at(position)
23258 .scope_context(Some(CharScopeContext::Completion));
23259 if trigger_in_words && classifier.is_word(char) {
23260 return true;
23261 }
23262
23263 buffer.completion_triggers().contains(text)
23264 }
23265}
23266
23267impl SemanticsProvider for Entity<Project> {
23268 fn hover(
23269 &self,
23270 buffer: &Entity<Buffer>,
23271 position: text::Anchor,
23272 cx: &mut App,
23273 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23274 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23275 }
23276
23277 fn document_highlights(
23278 &self,
23279 buffer: &Entity<Buffer>,
23280 position: text::Anchor,
23281 cx: &mut App,
23282 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23283 Some(self.update(cx, |project, cx| {
23284 project.document_highlights(buffer, position, cx)
23285 }))
23286 }
23287
23288 fn definitions(
23289 &self,
23290 buffer: &Entity<Buffer>,
23291 position: text::Anchor,
23292 kind: GotoDefinitionKind,
23293 cx: &mut App,
23294 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23295 Some(self.update(cx, |project, cx| match kind {
23296 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23297 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23298 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23299 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23300 }))
23301 }
23302
23303 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23304 self.update(cx, |project, cx| {
23305 if project
23306 .active_debug_session(cx)
23307 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23308 {
23309 return true;
23310 }
23311
23312 buffer.update(cx, |buffer, cx| {
23313 project.any_language_server_supports_inlay_hints(buffer, cx)
23314 })
23315 })
23316 }
23317
23318 fn inline_values(
23319 &self,
23320 buffer_handle: Entity<Buffer>,
23321 range: Range<text::Anchor>,
23322 cx: &mut App,
23323 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23324 self.update(cx, |project, cx| {
23325 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23326
23327 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23328 })
23329 }
23330
23331 fn applicable_inlay_chunks(
23332 &self,
23333 buffer: &Entity<Buffer>,
23334 ranges: &[Range<text::Anchor>],
23335 cx: &mut App,
23336 ) -> Vec<Range<BufferRow>> {
23337 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23338 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23339 })
23340 }
23341
23342 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23343 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23344 lsp_store.invalidate_inlay_hints(for_buffers)
23345 });
23346 }
23347
23348 fn inlay_hints(
23349 &self,
23350 invalidate: InvalidationStrategy,
23351 buffer: Entity<Buffer>,
23352 ranges: Vec<Range<text::Anchor>>,
23353 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23354 cx: &mut App,
23355 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23356 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23357 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23358 }))
23359 }
23360
23361 fn range_for_rename(
23362 &self,
23363 buffer: &Entity<Buffer>,
23364 position: text::Anchor,
23365 cx: &mut App,
23366 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23367 Some(self.update(cx, |project, cx| {
23368 let buffer = buffer.clone();
23369 let task = project.prepare_rename(buffer.clone(), position, cx);
23370 cx.spawn(async move |_, cx| {
23371 Ok(match task.await? {
23372 PrepareRenameResponse::Success(range) => Some(range),
23373 PrepareRenameResponse::InvalidPosition => None,
23374 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23375 // Fallback on using TreeSitter info to determine identifier range
23376 buffer.read_with(cx, |buffer, _| {
23377 let snapshot = buffer.snapshot();
23378 let (range, kind) = snapshot.surrounding_word(position, None);
23379 if kind != Some(CharKind::Word) {
23380 return None;
23381 }
23382 Some(
23383 snapshot.anchor_before(range.start)
23384 ..snapshot.anchor_after(range.end),
23385 )
23386 })?
23387 }
23388 })
23389 })
23390 }))
23391 }
23392
23393 fn perform_rename(
23394 &self,
23395 buffer: &Entity<Buffer>,
23396 position: text::Anchor,
23397 new_name: String,
23398 cx: &mut App,
23399 ) -> Option<Task<Result<ProjectTransaction>>> {
23400 Some(self.update(cx, |project, cx| {
23401 project.perform_rename(buffer.clone(), position, new_name, cx)
23402 }))
23403 }
23404}
23405
23406fn consume_contiguous_rows(
23407 contiguous_row_selections: &mut Vec<Selection<Point>>,
23408 selection: &Selection<Point>,
23409 display_map: &DisplaySnapshot,
23410 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23411) -> (MultiBufferRow, MultiBufferRow) {
23412 contiguous_row_selections.push(selection.clone());
23413 let start_row = starting_row(selection, display_map);
23414 let mut end_row = ending_row(selection, display_map);
23415
23416 while let Some(next_selection) = selections.peek() {
23417 if next_selection.start.row <= end_row.0 {
23418 end_row = ending_row(next_selection, display_map);
23419 contiguous_row_selections.push(selections.next().unwrap().clone());
23420 } else {
23421 break;
23422 }
23423 }
23424 (start_row, end_row)
23425}
23426
23427fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23428 if selection.start.column > 0 {
23429 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23430 } else {
23431 MultiBufferRow(selection.start.row)
23432 }
23433}
23434
23435fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23436 if next_selection.end.column > 0 || next_selection.is_empty() {
23437 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23438 } else {
23439 MultiBufferRow(next_selection.end.row)
23440 }
23441}
23442
23443impl EditorSnapshot {
23444 pub fn remote_selections_in_range<'a>(
23445 &'a self,
23446 range: &'a Range<Anchor>,
23447 collaboration_hub: &dyn CollaborationHub,
23448 cx: &'a App,
23449 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23450 let participant_names = collaboration_hub.user_names(cx);
23451 let participant_indices = collaboration_hub.user_participant_indices(cx);
23452 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23453 let collaborators_by_replica_id = collaborators_by_peer_id
23454 .values()
23455 .map(|collaborator| (collaborator.replica_id, collaborator))
23456 .collect::<HashMap<_, _>>();
23457 self.buffer_snapshot()
23458 .selections_in_range(range, false)
23459 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23460 if replica_id == ReplicaId::AGENT {
23461 Some(RemoteSelection {
23462 replica_id,
23463 selection,
23464 cursor_shape,
23465 line_mode,
23466 collaborator_id: CollaboratorId::Agent,
23467 user_name: Some("Agent".into()),
23468 color: cx.theme().players().agent(),
23469 })
23470 } else {
23471 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23472 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23473 let user_name = participant_names.get(&collaborator.user_id).cloned();
23474 Some(RemoteSelection {
23475 replica_id,
23476 selection,
23477 cursor_shape,
23478 line_mode,
23479 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23480 user_name,
23481 color: if let Some(index) = participant_index {
23482 cx.theme().players().color_for_participant(index.0)
23483 } else {
23484 cx.theme().players().absent()
23485 },
23486 })
23487 }
23488 })
23489 }
23490
23491 pub fn hunks_for_ranges(
23492 &self,
23493 ranges: impl IntoIterator<Item = Range<Point>>,
23494 ) -> Vec<MultiBufferDiffHunk> {
23495 let mut hunks = Vec::new();
23496 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23497 HashMap::default();
23498 for query_range in ranges {
23499 let query_rows =
23500 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23501 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23502 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23503 ) {
23504 // Include deleted hunks that are adjacent to the query range, because
23505 // otherwise they would be missed.
23506 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23507 if hunk.status().is_deleted() {
23508 intersects_range |= hunk.row_range.start == query_rows.end;
23509 intersects_range |= hunk.row_range.end == query_rows.start;
23510 }
23511 if intersects_range {
23512 if !processed_buffer_rows
23513 .entry(hunk.buffer_id)
23514 .or_default()
23515 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23516 {
23517 continue;
23518 }
23519 hunks.push(hunk);
23520 }
23521 }
23522 }
23523
23524 hunks
23525 }
23526
23527 fn display_diff_hunks_for_rows<'a>(
23528 &'a self,
23529 display_rows: Range<DisplayRow>,
23530 folded_buffers: &'a HashSet<BufferId>,
23531 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23532 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23533 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23534
23535 self.buffer_snapshot()
23536 .diff_hunks_in_range(buffer_start..buffer_end)
23537 .filter_map(|hunk| {
23538 if folded_buffers.contains(&hunk.buffer_id) {
23539 return None;
23540 }
23541
23542 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23543 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23544
23545 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23546 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23547
23548 let display_hunk = if hunk_display_start.column() != 0 {
23549 DisplayDiffHunk::Folded {
23550 display_row: hunk_display_start.row(),
23551 }
23552 } else {
23553 let mut end_row = hunk_display_end.row();
23554 if hunk_display_end.column() > 0 {
23555 end_row.0 += 1;
23556 }
23557 let is_created_file = hunk.is_created_file();
23558 DisplayDiffHunk::Unfolded {
23559 status: hunk.status(),
23560 diff_base_byte_range: hunk.diff_base_byte_range,
23561 display_row_range: hunk_display_start.row()..end_row,
23562 multi_buffer_range: Anchor::range_in_buffer(
23563 hunk.excerpt_id,
23564 hunk.buffer_id,
23565 hunk.buffer_range,
23566 ),
23567 is_created_file,
23568 }
23569 };
23570
23571 Some(display_hunk)
23572 })
23573 }
23574
23575 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23576 self.display_snapshot
23577 .buffer_snapshot()
23578 .language_at(position)
23579 }
23580
23581 pub fn is_focused(&self) -> bool {
23582 self.is_focused
23583 }
23584
23585 pub fn placeholder_text(&self) -> Option<String> {
23586 self.placeholder_display_snapshot
23587 .as_ref()
23588 .map(|display_map| display_map.text())
23589 }
23590
23591 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23592 self.scroll_anchor.scroll_position(&self.display_snapshot)
23593 }
23594
23595 fn gutter_dimensions(
23596 &self,
23597 font_id: FontId,
23598 font_size: Pixels,
23599 max_line_number_width: Pixels,
23600 cx: &App,
23601 ) -> Option<GutterDimensions> {
23602 if !self.show_gutter {
23603 return None;
23604 }
23605
23606 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23607 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23608
23609 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23610 matches!(
23611 ProjectSettings::get_global(cx).git.git_gutter,
23612 GitGutterSetting::TrackedFiles
23613 )
23614 });
23615 let gutter_settings = EditorSettings::get_global(cx).gutter;
23616 let show_line_numbers = self
23617 .show_line_numbers
23618 .unwrap_or(gutter_settings.line_numbers);
23619 let line_gutter_width = if show_line_numbers {
23620 // Avoid flicker-like gutter resizes when the line number gains another digit by
23621 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23622 let min_width_for_number_on_gutter =
23623 ch_advance * gutter_settings.min_line_number_digits as f32;
23624 max_line_number_width.max(min_width_for_number_on_gutter)
23625 } else {
23626 0.0.into()
23627 };
23628
23629 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23630 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23631
23632 let git_blame_entries_width =
23633 self.git_blame_gutter_max_author_length
23634 .map(|max_author_length| {
23635 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23636 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23637
23638 /// The number of characters to dedicate to gaps and margins.
23639 const SPACING_WIDTH: usize = 4;
23640
23641 let max_char_count = max_author_length.min(renderer.max_author_length())
23642 + ::git::SHORT_SHA_LENGTH
23643 + MAX_RELATIVE_TIMESTAMP.len()
23644 + SPACING_WIDTH;
23645
23646 ch_advance * max_char_count
23647 });
23648
23649 let is_singleton = self.buffer_snapshot().is_singleton();
23650
23651 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23652 left_padding += if !is_singleton {
23653 ch_width * 4.0
23654 } else if show_runnables || show_breakpoints {
23655 ch_width * 3.0
23656 } else if show_git_gutter && show_line_numbers {
23657 ch_width * 2.0
23658 } else if show_git_gutter || show_line_numbers {
23659 ch_width
23660 } else {
23661 px(0.)
23662 };
23663
23664 let shows_folds = is_singleton && gutter_settings.folds;
23665
23666 let right_padding = if shows_folds && show_line_numbers {
23667 ch_width * 4.0
23668 } else if shows_folds || (!is_singleton && show_line_numbers) {
23669 ch_width * 3.0
23670 } else if show_line_numbers {
23671 ch_width
23672 } else {
23673 px(0.)
23674 };
23675
23676 Some(GutterDimensions {
23677 left_padding,
23678 right_padding,
23679 width: line_gutter_width + left_padding + right_padding,
23680 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23681 git_blame_entries_width,
23682 })
23683 }
23684
23685 pub fn render_crease_toggle(
23686 &self,
23687 buffer_row: MultiBufferRow,
23688 row_contains_cursor: bool,
23689 editor: Entity<Editor>,
23690 window: &mut Window,
23691 cx: &mut App,
23692 ) -> Option<AnyElement> {
23693 let folded = self.is_line_folded(buffer_row);
23694 let mut is_foldable = false;
23695
23696 if let Some(crease) = self
23697 .crease_snapshot
23698 .query_row(buffer_row, self.buffer_snapshot())
23699 {
23700 is_foldable = true;
23701 match crease {
23702 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23703 if let Some(render_toggle) = render_toggle {
23704 let toggle_callback =
23705 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23706 if folded {
23707 editor.update(cx, |editor, cx| {
23708 editor.fold_at(buffer_row, window, cx)
23709 });
23710 } else {
23711 editor.update(cx, |editor, cx| {
23712 editor.unfold_at(buffer_row, window, cx)
23713 });
23714 }
23715 });
23716 return Some((render_toggle)(
23717 buffer_row,
23718 folded,
23719 toggle_callback,
23720 window,
23721 cx,
23722 ));
23723 }
23724 }
23725 }
23726 }
23727
23728 is_foldable |= self.starts_indent(buffer_row);
23729
23730 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23731 Some(
23732 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23733 .toggle_state(folded)
23734 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23735 if folded {
23736 this.unfold_at(buffer_row, window, cx);
23737 } else {
23738 this.fold_at(buffer_row, window, cx);
23739 }
23740 }))
23741 .into_any_element(),
23742 )
23743 } else {
23744 None
23745 }
23746 }
23747
23748 pub fn render_crease_trailer(
23749 &self,
23750 buffer_row: MultiBufferRow,
23751 window: &mut Window,
23752 cx: &mut App,
23753 ) -> Option<AnyElement> {
23754 let folded = self.is_line_folded(buffer_row);
23755 if let Crease::Inline { render_trailer, .. } = self
23756 .crease_snapshot
23757 .query_row(buffer_row, self.buffer_snapshot())?
23758 {
23759 let render_trailer = render_trailer.as_ref()?;
23760 Some(render_trailer(buffer_row, folded, window, cx))
23761 } else {
23762 None
23763 }
23764 }
23765}
23766
23767impl Deref for EditorSnapshot {
23768 type Target = DisplaySnapshot;
23769
23770 fn deref(&self) -> &Self::Target {
23771 &self.display_snapshot
23772 }
23773}
23774
23775#[derive(Clone, Debug, PartialEq, Eq)]
23776pub enum EditorEvent {
23777 InputIgnored {
23778 text: Arc<str>,
23779 },
23780 InputHandled {
23781 utf16_range_to_replace: Option<Range<isize>>,
23782 text: Arc<str>,
23783 },
23784 ExcerptsAdded {
23785 buffer: Entity<Buffer>,
23786 predecessor: ExcerptId,
23787 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23788 },
23789 ExcerptsRemoved {
23790 ids: Vec<ExcerptId>,
23791 removed_buffer_ids: Vec<BufferId>,
23792 },
23793 BufferFoldToggled {
23794 ids: Vec<ExcerptId>,
23795 folded: bool,
23796 },
23797 ExcerptsEdited {
23798 ids: Vec<ExcerptId>,
23799 },
23800 ExcerptsExpanded {
23801 ids: Vec<ExcerptId>,
23802 },
23803 BufferEdited,
23804 Edited {
23805 transaction_id: clock::Lamport,
23806 },
23807 Reparsed(BufferId),
23808 Focused,
23809 FocusedIn,
23810 Blurred,
23811 DirtyChanged,
23812 Saved,
23813 TitleChanged,
23814 SelectionsChanged {
23815 local: bool,
23816 },
23817 ScrollPositionChanged {
23818 local: bool,
23819 autoscroll: bool,
23820 },
23821 TransactionUndone {
23822 transaction_id: clock::Lamport,
23823 },
23824 TransactionBegun {
23825 transaction_id: clock::Lamport,
23826 },
23827 CursorShapeChanged,
23828 BreadcrumbsChanged,
23829 PushedToNavHistory {
23830 anchor: Anchor,
23831 is_deactivate: bool,
23832 },
23833}
23834
23835impl EventEmitter<EditorEvent> for Editor {}
23836
23837impl Focusable for Editor {
23838 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23839 self.focus_handle.clone()
23840 }
23841}
23842
23843impl Render for Editor {
23844 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23845 let settings = ThemeSettings::get_global(cx);
23846
23847 let mut text_style = match self.mode {
23848 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23849 color: cx.theme().colors().editor_foreground,
23850 font_family: settings.ui_font.family.clone(),
23851 font_features: settings.ui_font.features.clone(),
23852 font_fallbacks: settings.ui_font.fallbacks.clone(),
23853 font_size: rems(0.875).into(),
23854 font_weight: settings.ui_font.weight,
23855 line_height: relative(settings.buffer_line_height.value()),
23856 ..Default::default()
23857 },
23858 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23859 color: cx.theme().colors().editor_foreground,
23860 font_family: settings.buffer_font.family.clone(),
23861 font_features: settings.buffer_font.features.clone(),
23862 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23863 font_size: settings.buffer_font_size(cx).into(),
23864 font_weight: settings.buffer_font.weight,
23865 line_height: relative(settings.buffer_line_height.value()),
23866 ..Default::default()
23867 },
23868 };
23869 if let Some(text_style_refinement) = &self.text_style_refinement {
23870 text_style.refine(text_style_refinement)
23871 }
23872
23873 let background = match self.mode {
23874 EditorMode::SingleLine => cx.theme().system().transparent,
23875 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23876 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23877 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23878 };
23879
23880 EditorElement::new(
23881 &cx.entity(),
23882 EditorStyle {
23883 background,
23884 border: cx.theme().colors().border,
23885 local_player: cx.theme().players().local(),
23886 text: text_style,
23887 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23888 syntax: cx.theme().syntax().clone(),
23889 status: cx.theme().status().clone(),
23890 inlay_hints_style: make_inlay_hints_style(cx),
23891 edit_prediction_styles: make_suggestion_styles(cx),
23892 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23893 show_underlines: self.diagnostics_enabled(),
23894 },
23895 )
23896 }
23897}
23898
23899impl EntityInputHandler for Editor {
23900 fn text_for_range(
23901 &mut self,
23902 range_utf16: Range<usize>,
23903 adjusted_range: &mut Option<Range<usize>>,
23904 _: &mut Window,
23905 cx: &mut Context<Self>,
23906 ) -> Option<String> {
23907 let snapshot = self.buffer.read(cx).read(cx);
23908 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23909 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23910 if (start.0..end.0) != range_utf16 {
23911 adjusted_range.replace(start.0..end.0);
23912 }
23913 Some(snapshot.text_for_range(start..end).collect())
23914 }
23915
23916 fn selected_text_range(
23917 &mut self,
23918 ignore_disabled_input: bool,
23919 _: &mut Window,
23920 cx: &mut Context<Self>,
23921 ) -> Option<UTF16Selection> {
23922 // Prevent the IME menu from appearing when holding down an alphabetic key
23923 // while input is disabled.
23924 if !ignore_disabled_input && !self.input_enabled {
23925 return None;
23926 }
23927
23928 let selection = self
23929 .selections
23930 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23931 let range = selection.range();
23932
23933 Some(UTF16Selection {
23934 range: range.start.0..range.end.0,
23935 reversed: selection.reversed,
23936 })
23937 }
23938
23939 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23940 let snapshot = self.buffer.read(cx).read(cx);
23941 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23942 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23943 }
23944
23945 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23946 self.clear_highlights::<InputComposition>(cx);
23947 self.ime_transaction.take();
23948 }
23949
23950 fn replace_text_in_range(
23951 &mut self,
23952 range_utf16: Option<Range<usize>>,
23953 text: &str,
23954 window: &mut Window,
23955 cx: &mut Context<Self>,
23956 ) {
23957 if !self.input_enabled {
23958 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23959 return;
23960 }
23961
23962 self.transact(window, cx, |this, window, cx| {
23963 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23964 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23965 Some(this.selection_replacement_ranges(range_utf16, cx))
23966 } else {
23967 this.marked_text_ranges(cx)
23968 };
23969
23970 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23971 let newest_selection_id = this.selections.newest_anchor().id;
23972 this.selections
23973 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23974 .iter()
23975 .zip(ranges_to_replace.iter())
23976 .find_map(|(selection, range)| {
23977 if selection.id == newest_selection_id {
23978 Some(
23979 (range.start.0 as isize - selection.head().0 as isize)
23980 ..(range.end.0 as isize - selection.head().0 as isize),
23981 )
23982 } else {
23983 None
23984 }
23985 })
23986 });
23987
23988 cx.emit(EditorEvent::InputHandled {
23989 utf16_range_to_replace: range_to_replace,
23990 text: text.into(),
23991 });
23992
23993 if let Some(new_selected_ranges) = new_selected_ranges {
23994 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23995 selections.select_ranges(new_selected_ranges)
23996 });
23997 this.backspace(&Default::default(), window, cx);
23998 }
23999
24000 this.handle_input(text, window, cx);
24001 });
24002
24003 if let Some(transaction) = self.ime_transaction {
24004 self.buffer.update(cx, |buffer, cx| {
24005 buffer.group_until_transaction(transaction, cx);
24006 });
24007 }
24008
24009 self.unmark_text(window, cx);
24010 }
24011
24012 fn replace_and_mark_text_in_range(
24013 &mut self,
24014 range_utf16: Option<Range<usize>>,
24015 text: &str,
24016 new_selected_range_utf16: Option<Range<usize>>,
24017 window: &mut Window,
24018 cx: &mut Context<Self>,
24019 ) {
24020 if !self.input_enabled {
24021 return;
24022 }
24023
24024 let transaction = self.transact(window, cx, |this, window, cx| {
24025 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24026 let snapshot = this.buffer.read(cx).read(cx);
24027 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24028 for marked_range in &mut marked_ranges {
24029 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24030 marked_range.start.0 += relative_range_utf16.start;
24031 marked_range.start =
24032 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24033 marked_range.end =
24034 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24035 }
24036 }
24037 Some(marked_ranges)
24038 } else if let Some(range_utf16) = range_utf16 {
24039 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24040 Some(this.selection_replacement_ranges(range_utf16, cx))
24041 } else {
24042 None
24043 };
24044
24045 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24046 let newest_selection_id = this.selections.newest_anchor().id;
24047 this.selections
24048 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24049 .iter()
24050 .zip(ranges_to_replace.iter())
24051 .find_map(|(selection, range)| {
24052 if selection.id == newest_selection_id {
24053 Some(
24054 (range.start.0 as isize - selection.head().0 as isize)
24055 ..(range.end.0 as isize - selection.head().0 as isize),
24056 )
24057 } else {
24058 None
24059 }
24060 })
24061 });
24062
24063 cx.emit(EditorEvent::InputHandled {
24064 utf16_range_to_replace: range_to_replace,
24065 text: text.into(),
24066 });
24067
24068 if let Some(ranges) = ranges_to_replace {
24069 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24070 s.select_ranges(ranges)
24071 });
24072 }
24073
24074 let marked_ranges = {
24075 let snapshot = this.buffer.read(cx).read(cx);
24076 this.selections
24077 .disjoint_anchors_arc()
24078 .iter()
24079 .map(|selection| {
24080 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24081 })
24082 .collect::<Vec<_>>()
24083 };
24084
24085 if text.is_empty() {
24086 this.unmark_text(window, cx);
24087 } else {
24088 this.highlight_text::<InputComposition>(
24089 marked_ranges.clone(),
24090 HighlightStyle {
24091 underline: Some(UnderlineStyle {
24092 thickness: px(1.),
24093 color: None,
24094 wavy: false,
24095 }),
24096 ..Default::default()
24097 },
24098 cx,
24099 );
24100 }
24101
24102 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24103 let use_autoclose = this.use_autoclose;
24104 let use_auto_surround = this.use_auto_surround;
24105 this.set_use_autoclose(false);
24106 this.set_use_auto_surround(false);
24107 this.handle_input(text, window, cx);
24108 this.set_use_autoclose(use_autoclose);
24109 this.set_use_auto_surround(use_auto_surround);
24110
24111 if let Some(new_selected_range) = new_selected_range_utf16 {
24112 let snapshot = this.buffer.read(cx).read(cx);
24113 let new_selected_ranges = marked_ranges
24114 .into_iter()
24115 .map(|marked_range| {
24116 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24117 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24118 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24119 snapshot.clip_offset_utf16(new_start, Bias::Left)
24120 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24121 })
24122 .collect::<Vec<_>>();
24123
24124 drop(snapshot);
24125 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24126 selections.select_ranges(new_selected_ranges)
24127 });
24128 }
24129 });
24130
24131 self.ime_transaction = self.ime_transaction.or(transaction);
24132 if let Some(transaction) = self.ime_transaction {
24133 self.buffer.update(cx, |buffer, cx| {
24134 buffer.group_until_transaction(transaction, cx);
24135 });
24136 }
24137
24138 if self.text_highlights::<InputComposition>(cx).is_none() {
24139 self.ime_transaction.take();
24140 }
24141 }
24142
24143 fn bounds_for_range(
24144 &mut self,
24145 range_utf16: Range<usize>,
24146 element_bounds: gpui::Bounds<Pixels>,
24147 window: &mut Window,
24148 cx: &mut Context<Self>,
24149 ) -> Option<gpui::Bounds<Pixels>> {
24150 let text_layout_details = self.text_layout_details(window);
24151 let CharacterDimensions {
24152 em_width,
24153 em_advance,
24154 line_height,
24155 } = self.character_dimensions(window);
24156
24157 let snapshot = self.snapshot(window, cx);
24158 let scroll_position = snapshot.scroll_position();
24159 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24160
24161 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24162 let x = Pixels::from(
24163 ScrollOffset::from(
24164 snapshot.x_for_display_point(start, &text_layout_details)
24165 + self.gutter_dimensions.full_width(),
24166 ) - scroll_left,
24167 );
24168 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24169
24170 Some(Bounds {
24171 origin: element_bounds.origin + point(x, y),
24172 size: size(em_width, line_height),
24173 })
24174 }
24175
24176 fn character_index_for_point(
24177 &mut self,
24178 point: gpui::Point<Pixels>,
24179 _window: &mut Window,
24180 _cx: &mut Context<Self>,
24181 ) -> Option<usize> {
24182 let position_map = self.last_position_map.as_ref()?;
24183 if !position_map.text_hitbox.contains(&point) {
24184 return None;
24185 }
24186 let display_point = position_map.point_for_position(point).previous_valid;
24187 let anchor = position_map
24188 .snapshot
24189 .display_point_to_anchor(display_point, Bias::Left);
24190 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24191 Some(utf16_offset.0)
24192 }
24193}
24194
24195trait SelectionExt {
24196 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24197 fn spanned_rows(
24198 &self,
24199 include_end_if_at_line_start: bool,
24200 map: &DisplaySnapshot,
24201 ) -> Range<MultiBufferRow>;
24202}
24203
24204impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24205 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24206 let start = self
24207 .start
24208 .to_point(map.buffer_snapshot())
24209 .to_display_point(map);
24210 let end = self
24211 .end
24212 .to_point(map.buffer_snapshot())
24213 .to_display_point(map);
24214 if self.reversed {
24215 end..start
24216 } else {
24217 start..end
24218 }
24219 }
24220
24221 fn spanned_rows(
24222 &self,
24223 include_end_if_at_line_start: bool,
24224 map: &DisplaySnapshot,
24225 ) -> Range<MultiBufferRow> {
24226 let start = self.start.to_point(map.buffer_snapshot());
24227 let mut end = self.end.to_point(map.buffer_snapshot());
24228 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24229 end.row -= 1;
24230 }
24231
24232 let buffer_start = map.prev_line_boundary(start).0;
24233 let buffer_end = map.next_line_boundary(end).0;
24234 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24235 }
24236}
24237
24238impl<T: InvalidationRegion> InvalidationStack<T> {
24239 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24240 where
24241 S: Clone + ToOffset,
24242 {
24243 while let Some(region) = self.last() {
24244 let all_selections_inside_invalidation_ranges =
24245 if selections.len() == region.ranges().len() {
24246 selections
24247 .iter()
24248 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24249 .all(|(selection, invalidation_range)| {
24250 let head = selection.head().to_offset(buffer);
24251 invalidation_range.start <= head && invalidation_range.end >= head
24252 })
24253 } else {
24254 false
24255 };
24256
24257 if all_selections_inside_invalidation_ranges {
24258 break;
24259 } else {
24260 self.pop();
24261 }
24262 }
24263 }
24264}
24265
24266impl<T> Default for InvalidationStack<T> {
24267 fn default() -> Self {
24268 Self(Default::default())
24269 }
24270}
24271
24272impl<T> Deref for InvalidationStack<T> {
24273 type Target = Vec<T>;
24274
24275 fn deref(&self) -> &Self::Target {
24276 &self.0
24277 }
24278}
24279
24280impl<T> DerefMut for InvalidationStack<T> {
24281 fn deref_mut(&mut self) -> &mut Self::Target {
24282 &mut self.0
24283 }
24284}
24285
24286impl InvalidationRegion for SnippetState {
24287 fn ranges(&self) -> &[Range<Anchor>] {
24288 &self.ranges[self.active_index]
24289 }
24290}
24291
24292fn edit_prediction_edit_text(
24293 current_snapshot: &BufferSnapshot,
24294 edits: &[(Range<Anchor>, String)],
24295 edit_preview: &EditPreview,
24296 include_deletions: bool,
24297 cx: &App,
24298) -> HighlightedText {
24299 let edits = edits
24300 .iter()
24301 .map(|(anchor, text)| {
24302 (
24303 anchor.start.text_anchor..anchor.end.text_anchor,
24304 text.clone(),
24305 )
24306 })
24307 .collect::<Vec<_>>();
24308
24309 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24310}
24311
24312fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24313 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24314 // Just show the raw edit text with basic styling
24315 let mut text = String::new();
24316 let mut highlights = Vec::new();
24317
24318 let insertion_highlight_style = HighlightStyle {
24319 color: Some(cx.theme().colors().text),
24320 ..Default::default()
24321 };
24322
24323 for (_, edit_text) in edits {
24324 let start_offset = text.len();
24325 text.push_str(edit_text);
24326 let end_offset = text.len();
24327
24328 if start_offset < end_offset {
24329 highlights.push((start_offset..end_offset, insertion_highlight_style));
24330 }
24331 }
24332
24333 HighlightedText {
24334 text: text.into(),
24335 highlights,
24336 }
24337}
24338
24339pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24340 match severity {
24341 lsp::DiagnosticSeverity::ERROR => colors.error,
24342 lsp::DiagnosticSeverity::WARNING => colors.warning,
24343 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24344 lsp::DiagnosticSeverity::HINT => colors.info,
24345 _ => colors.ignored,
24346 }
24347}
24348
24349pub fn styled_runs_for_code_label<'a>(
24350 label: &'a CodeLabel,
24351 syntax_theme: &'a theme::SyntaxTheme,
24352) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24353 let fade_out = HighlightStyle {
24354 fade_out: Some(0.35),
24355 ..Default::default()
24356 };
24357
24358 let mut prev_end = label.filter_range.end;
24359 label
24360 .runs
24361 .iter()
24362 .enumerate()
24363 .flat_map(move |(ix, (range, highlight_id))| {
24364 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24365 style
24366 } else {
24367 return Default::default();
24368 };
24369 let muted_style = style.highlight(fade_out);
24370
24371 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24372 if range.start >= label.filter_range.end {
24373 if range.start > prev_end {
24374 runs.push((prev_end..range.start, fade_out));
24375 }
24376 runs.push((range.clone(), muted_style));
24377 } else if range.end <= label.filter_range.end {
24378 runs.push((range.clone(), style));
24379 } else {
24380 runs.push((range.start..label.filter_range.end, style));
24381 runs.push((label.filter_range.end..range.end, muted_style));
24382 }
24383 prev_end = cmp::max(prev_end, range.end);
24384
24385 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24386 runs.push((prev_end..label.text.len(), fade_out));
24387 }
24388
24389 runs
24390 })
24391}
24392
24393pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24394 let mut prev_index = 0;
24395 let mut prev_codepoint: Option<char> = None;
24396 text.char_indices()
24397 .chain([(text.len(), '\0')])
24398 .filter_map(move |(index, codepoint)| {
24399 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24400 let is_boundary = index == text.len()
24401 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24402 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24403 if is_boundary {
24404 let chunk = &text[prev_index..index];
24405 prev_index = index;
24406 Some(chunk)
24407 } else {
24408 None
24409 }
24410 })
24411}
24412
24413pub trait RangeToAnchorExt: Sized {
24414 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24415
24416 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24417 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24418 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24419 }
24420}
24421
24422impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24423 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24424 let start_offset = self.start.to_offset(snapshot);
24425 let end_offset = self.end.to_offset(snapshot);
24426 if start_offset == end_offset {
24427 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24428 } else {
24429 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24430 }
24431 }
24432}
24433
24434pub trait RowExt {
24435 fn as_f64(&self) -> f64;
24436
24437 fn next_row(&self) -> Self;
24438
24439 fn previous_row(&self) -> Self;
24440
24441 fn minus(&self, other: Self) -> u32;
24442}
24443
24444impl RowExt for DisplayRow {
24445 fn as_f64(&self) -> f64 {
24446 self.0 as _
24447 }
24448
24449 fn next_row(&self) -> Self {
24450 Self(self.0 + 1)
24451 }
24452
24453 fn previous_row(&self) -> Self {
24454 Self(self.0.saturating_sub(1))
24455 }
24456
24457 fn minus(&self, other: Self) -> u32 {
24458 self.0 - other.0
24459 }
24460}
24461
24462impl RowExt for MultiBufferRow {
24463 fn as_f64(&self) -> f64 {
24464 self.0 as _
24465 }
24466
24467 fn next_row(&self) -> Self {
24468 Self(self.0 + 1)
24469 }
24470
24471 fn previous_row(&self) -> Self {
24472 Self(self.0.saturating_sub(1))
24473 }
24474
24475 fn minus(&self, other: Self) -> u32 {
24476 self.0 - other.0
24477 }
24478}
24479
24480trait RowRangeExt {
24481 type Row;
24482
24483 fn len(&self) -> usize;
24484
24485 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24486}
24487
24488impl RowRangeExt for Range<MultiBufferRow> {
24489 type Row = MultiBufferRow;
24490
24491 fn len(&self) -> usize {
24492 (self.end.0 - self.start.0) as usize
24493 }
24494
24495 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24496 (self.start.0..self.end.0).map(MultiBufferRow)
24497 }
24498}
24499
24500impl RowRangeExt for Range<DisplayRow> {
24501 type Row = DisplayRow;
24502
24503 fn len(&self) -> usize {
24504 (self.end.0 - self.start.0) as usize
24505 }
24506
24507 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24508 (self.start.0..self.end.0).map(DisplayRow)
24509 }
24510}
24511
24512/// If select range has more than one line, we
24513/// just point the cursor to range.start.
24514fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24515 if range.start.row == range.end.row {
24516 range
24517 } else {
24518 range.start..range.start
24519 }
24520}
24521pub struct KillRing(ClipboardItem);
24522impl Global for KillRing {}
24523
24524const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24525
24526enum BreakpointPromptEditAction {
24527 Log,
24528 Condition,
24529 HitCondition,
24530}
24531
24532struct BreakpointPromptEditor {
24533 pub(crate) prompt: Entity<Editor>,
24534 editor: WeakEntity<Editor>,
24535 breakpoint_anchor: Anchor,
24536 breakpoint: Breakpoint,
24537 edit_action: BreakpointPromptEditAction,
24538 block_ids: HashSet<CustomBlockId>,
24539 editor_margins: Arc<Mutex<EditorMargins>>,
24540 _subscriptions: Vec<Subscription>,
24541}
24542
24543impl BreakpointPromptEditor {
24544 const MAX_LINES: u8 = 4;
24545
24546 fn new(
24547 editor: WeakEntity<Editor>,
24548 breakpoint_anchor: Anchor,
24549 breakpoint: Breakpoint,
24550 edit_action: BreakpointPromptEditAction,
24551 window: &mut Window,
24552 cx: &mut Context<Self>,
24553 ) -> Self {
24554 let base_text = match edit_action {
24555 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24556 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24557 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24558 }
24559 .map(|msg| msg.to_string())
24560 .unwrap_or_default();
24561
24562 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24563 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24564
24565 let prompt = cx.new(|cx| {
24566 let mut prompt = Editor::new(
24567 EditorMode::AutoHeight {
24568 min_lines: 1,
24569 max_lines: Some(Self::MAX_LINES as usize),
24570 },
24571 buffer,
24572 None,
24573 window,
24574 cx,
24575 );
24576 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24577 prompt.set_show_cursor_when_unfocused(false, cx);
24578 prompt.set_placeholder_text(
24579 match edit_action {
24580 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24581 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24582 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24583 },
24584 window,
24585 cx,
24586 );
24587
24588 prompt
24589 });
24590
24591 Self {
24592 prompt,
24593 editor,
24594 breakpoint_anchor,
24595 breakpoint,
24596 edit_action,
24597 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24598 block_ids: Default::default(),
24599 _subscriptions: vec![],
24600 }
24601 }
24602
24603 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24604 self.block_ids.extend(block_ids)
24605 }
24606
24607 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24608 if let Some(editor) = self.editor.upgrade() {
24609 let message = self
24610 .prompt
24611 .read(cx)
24612 .buffer
24613 .read(cx)
24614 .as_singleton()
24615 .expect("A multi buffer in breakpoint prompt isn't possible")
24616 .read(cx)
24617 .as_rope()
24618 .to_string();
24619
24620 editor.update(cx, |editor, cx| {
24621 editor.edit_breakpoint_at_anchor(
24622 self.breakpoint_anchor,
24623 self.breakpoint.clone(),
24624 match self.edit_action {
24625 BreakpointPromptEditAction::Log => {
24626 BreakpointEditAction::EditLogMessage(message.into())
24627 }
24628 BreakpointPromptEditAction::Condition => {
24629 BreakpointEditAction::EditCondition(message.into())
24630 }
24631 BreakpointPromptEditAction::HitCondition => {
24632 BreakpointEditAction::EditHitCondition(message.into())
24633 }
24634 },
24635 cx,
24636 );
24637
24638 editor.remove_blocks(self.block_ids.clone(), None, cx);
24639 cx.focus_self(window);
24640 });
24641 }
24642 }
24643
24644 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24645 self.editor
24646 .update(cx, |editor, cx| {
24647 editor.remove_blocks(self.block_ids.clone(), None, cx);
24648 window.focus(&editor.focus_handle);
24649 })
24650 .log_err();
24651 }
24652
24653 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24654 let settings = ThemeSettings::get_global(cx);
24655 let text_style = TextStyle {
24656 color: if self.prompt.read(cx).read_only(cx) {
24657 cx.theme().colors().text_disabled
24658 } else {
24659 cx.theme().colors().text
24660 },
24661 font_family: settings.buffer_font.family.clone(),
24662 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24663 font_size: settings.buffer_font_size(cx).into(),
24664 font_weight: settings.buffer_font.weight,
24665 line_height: relative(settings.buffer_line_height.value()),
24666 ..Default::default()
24667 };
24668 EditorElement::new(
24669 &self.prompt,
24670 EditorStyle {
24671 background: cx.theme().colors().editor_background,
24672 local_player: cx.theme().players().local(),
24673 text: text_style,
24674 ..Default::default()
24675 },
24676 )
24677 }
24678}
24679
24680impl Render for BreakpointPromptEditor {
24681 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24682 let editor_margins = *self.editor_margins.lock();
24683 let gutter_dimensions = editor_margins.gutter;
24684 h_flex()
24685 .key_context("Editor")
24686 .bg(cx.theme().colors().editor_background)
24687 .border_y_1()
24688 .border_color(cx.theme().status().info_border)
24689 .size_full()
24690 .py(window.line_height() / 2.5)
24691 .on_action(cx.listener(Self::confirm))
24692 .on_action(cx.listener(Self::cancel))
24693 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24694 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24695 }
24696}
24697
24698impl Focusable for BreakpointPromptEditor {
24699 fn focus_handle(&self, cx: &App) -> FocusHandle {
24700 self.prompt.focus_handle(cx)
24701 }
24702}
24703
24704fn all_edits_insertions_or_deletions(
24705 edits: &Vec<(Range<Anchor>, String)>,
24706 snapshot: &MultiBufferSnapshot,
24707) -> bool {
24708 let mut all_insertions = true;
24709 let mut all_deletions = true;
24710
24711 for (range, new_text) in edits.iter() {
24712 let range_is_empty = range.to_offset(snapshot).is_empty();
24713 let text_is_empty = new_text.is_empty();
24714
24715 if range_is_empty != text_is_empty {
24716 if range_is_empty {
24717 all_deletions = false;
24718 } else {
24719 all_insertions = false;
24720 }
24721 } else {
24722 return false;
24723 }
24724
24725 if !all_insertions && !all_deletions {
24726 return false;
24727 }
24728 }
24729 all_insertions || all_deletions
24730}
24731
24732struct MissingEditPredictionKeybindingTooltip;
24733
24734impl Render for MissingEditPredictionKeybindingTooltip {
24735 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24736 ui::tooltip_container(cx, |container, cx| {
24737 container
24738 .flex_shrink_0()
24739 .max_w_80()
24740 .min_h(rems_from_px(124.))
24741 .justify_between()
24742 .child(
24743 v_flex()
24744 .flex_1()
24745 .text_ui_sm(cx)
24746 .child(Label::new("Conflict with Accept Keybinding"))
24747 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24748 )
24749 .child(
24750 h_flex()
24751 .pb_1()
24752 .gap_1()
24753 .items_end()
24754 .w_full()
24755 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24756 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24757 }))
24758 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24759 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24760 })),
24761 )
24762 })
24763 }
24764}
24765
24766#[derive(Debug, Clone, Copy, PartialEq)]
24767pub struct LineHighlight {
24768 pub background: Background,
24769 pub border: Option<gpui::Hsla>,
24770 pub include_gutter: bool,
24771 pub type_id: Option<TypeId>,
24772}
24773
24774struct LineManipulationResult {
24775 pub new_text: String,
24776 pub line_count_before: usize,
24777 pub line_count_after: usize,
24778}
24779
24780fn render_diff_hunk_controls(
24781 row: u32,
24782 status: &DiffHunkStatus,
24783 hunk_range: Range<Anchor>,
24784 is_created_file: bool,
24785 line_height: Pixels,
24786 editor: &Entity<Editor>,
24787 _window: &mut Window,
24788 cx: &mut App,
24789) -> AnyElement {
24790 h_flex()
24791 .h(line_height)
24792 .mr_1()
24793 .gap_1()
24794 .px_0p5()
24795 .pb_1()
24796 .border_x_1()
24797 .border_b_1()
24798 .border_color(cx.theme().colors().border_variant)
24799 .rounded_b_lg()
24800 .bg(cx.theme().colors().editor_background)
24801 .gap_1()
24802 .block_mouse_except_scroll()
24803 .shadow_md()
24804 .child(if status.has_secondary_hunk() {
24805 Button::new(("stage", row as u64), "Stage")
24806 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24807 .tooltip({
24808 let focus_handle = editor.focus_handle(cx);
24809 move |_window, cx| {
24810 Tooltip::for_action_in(
24811 "Stage Hunk",
24812 &::git::ToggleStaged,
24813 &focus_handle,
24814 cx,
24815 )
24816 }
24817 })
24818 .on_click({
24819 let editor = editor.clone();
24820 move |_event, _window, cx| {
24821 editor.update(cx, |editor, cx| {
24822 editor.stage_or_unstage_diff_hunks(
24823 true,
24824 vec![hunk_range.start..hunk_range.start],
24825 cx,
24826 );
24827 });
24828 }
24829 })
24830 } else {
24831 Button::new(("unstage", row as u64), "Unstage")
24832 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24833 .tooltip({
24834 let focus_handle = editor.focus_handle(cx);
24835 move |_window, cx| {
24836 Tooltip::for_action_in(
24837 "Unstage Hunk",
24838 &::git::ToggleStaged,
24839 &focus_handle,
24840 cx,
24841 )
24842 }
24843 })
24844 .on_click({
24845 let editor = editor.clone();
24846 move |_event, _window, cx| {
24847 editor.update(cx, |editor, cx| {
24848 editor.stage_or_unstage_diff_hunks(
24849 false,
24850 vec![hunk_range.start..hunk_range.start],
24851 cx,
24852 );
24853 });
24854 }
24855 })
24856 })
24857 .child(
24858 Button::new(("restore", row as u64), "Restore")
24859 .tooltip({
24860 let focus_handle = editor.focus_handle(cx);
24861 move |_window, cx| {
24862 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24863 }
24864 })
24865 .on_click({
24866 let editor = editor.clone();
24867 move |_event, window, cx| {
24868 editor.update(cx, |editor, cx| {
24869 let snapshot = editor.snapshot(window, cx);
24870 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24871 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24872 });
24873 }
24874 })
24875 .disabled(is_created_file),
24876 )
24877 .when(
24878 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24879 |el| {
24880 el.child(
24881 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24882 .shape(IconButtonShape::Square)
24883 .icon_size(IconSize::Small)
24884 // .disabled(!has_multiple_hunks)
24885 .tooltip({
24886 let focus_handle = editor.focus_handle(cx);
24887 move |_window, cx| {
24888 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24889 }
24890 })
24891 .on_click({
24892 let editor = editor.clone();
24893 move |_event, window, cx| {
24894 editor.update(cx, |editor, cx| {
24895 let snapshot = editor.snapshot(window, cx);
24896 let position =
24897 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24898 editor.go_to_hunk_before_or_after_position(
24899 &snapshot,
24900 position,
24901 Direction::Next,
24902 window,
24903 cx,
24904 );
24905 editor.expand_selected_diff_hunks(cx);
24906 });
24907 }
24908 }),
24909 )
24910 .child(
24911 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24912 .shape(IconButtonShape::Square)
24913 .icon_size(IconSize::Small)
24914 // .disabled(!has_multiple_hunks)
24915 .tooltip({
24916 let focus_handle = editor.focus_handle(cx);
24917 move |_window, cx| {
24918 Tooltip::for_action_in(
24919 "Previous Hunk",
24920 &GoToPreviousHunk,
24921 &focus_handle,
24922 cx,
24923 )
24924 }
24925 })
24926 .on_click({
24927 let editor = editor.clone();
24928 move |_event, window, cx| {
24929 editor.update(cx, |editor, cx| {
24930 let snapshot = editor.snapshot(window, cx);
24931 let point =
24932 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24933 editor.go_to_hunk_before_or_after_position(
24934 &snapshot,
24935 point,
24936 Direction::Prev,
24937 window,
24938 cx,
24939 );
24940 editor.expand_selected_diff_hunks(cx);
24941 });
24942 }
24943 }),
24944 )
24945 },
24946 )
24947 .into_any_element()
24948}
24949
24950pub fn multibuffer_context_lines(cx: &App) -> u32 {
24951 EditorSettings::try_get(cx)
24952 .map(|settings| settings.excerpt_context_lines)
24953 .unwrap_or(2)
24954 .min(32)
24955}