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 bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39pub mod tasks;
40
41#[cfg(test)]
42mod code_completion_tests;
43#[cfg(test)]
44mod edit_prediction_tests;
45#[cfg(test)]
46mod editor_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
53pub use edit_prediction::Direction;
54pub use editor_settings::{
55 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
56 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
57};
58pub use element::{
59 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
60};
61pub use git::blame::BlameRenderer;
62pub use hover_popover::hover_markdown_style;
63pub use inlays::Inlay;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use text::Bias;
72
73use ::git::{
74 Restore,
75 blame::{BlameEntry, ParsedCommitMessage},
76 status::FileStatus,
77};
78use aho_corasick::AhoCorasick;
79use anyhow::{Context as _, Result, anyhow};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
93use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
94use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
95use futures::{
96 FutureExt, StreamExt as _,
97 future::{self, Shared, join},
98 stream::FuturesUnordered,
99};
100use fuzzy::{StringMatch, StringMatchCandidate};
101use git::blame::{GitBlame, GlobalBlameRenderer};
102use gpui::{
103 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
104 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
105 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
106 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
107 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
108 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
109 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
110 div, point, prelude::*, pulsating_between, px, relative, size,
111};
112use hover_links::{HoverLink, HoveredLinkState, find_file};
113use hover_popover::{HoverState, hide_hover};
114use indent_guides::ActiveIndentGuidesState;
115use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
116use itertools::{Either, Itertools};
117use language::{
118 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
119 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
120 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
121 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
122 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
123 language_settings::{
124 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
125 language_settings,
126 },
127 point_from_lsp, point_to_lsp, text_diff_with_options,
128};
129use linked_editing_ranges::refresh_linked_ranges;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
132 LanguageServerId,
133};
134use lsp_colors::LspColorData;
135use markdown::Markdown;
136use mouse_context_menu::MouseContextMenu;
137use movement::TextLayoutDetails;
138use multi_buffer::{
139 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
140};
141use parking_lot::Mutex;
142use persistence::DB;
143use project::{
144 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
145 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
146 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
147 ProjectPath, ProjectTransaction, TaskSourceKind,
148 debugger::{
149 breakpoint_store::{
150 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
151 BreakpointStore, BreakpointStoreEvent,
152 },
153 session::{Session, SessionEvent},
154 },
155 git_store::GitStoreEvent,
156 lsp_store::{
157 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
158 OpenLspBufferHandle,
159 },
160 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
161};
162use rand::seq::SliceRandom;
163use rpc::{ErrorCode, ErrorExt, proto::PeerId};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
165use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
166use serde::{Deserialize, Serialize};
167use settings::{
168 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
169 update_settings_file,
170};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 iter::{self, Peekable},
179 mem,
180 num::NonZeroU32,
181 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
182 path::{Path, PathBuf},
183 rc::Rc,
184 sync::Arc,
185 time::{Duration, Instant},
186};
187use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
188use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
189use theme::{
190 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
191 observe_buffer_font_size_adjustment,
192};
193use ui::{
194 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
195 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
196};
197use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
198use workspace::{
199 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
200 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
201 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
202 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
203 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
204 searchable::SearchEvent,
205};
206
207use crate::{
208 code_context_menus::CompletionsMenuSource,
209 editor_settings::MultiCursorModifier,
210 hover_links::{find_url, find_url_from_range},
211 inlays::{
212 InlineValueCache,
213 inlay_hints::{LspInlayHintData, inlay_hint_settings},
214 },
215 scroll::{ScrollOffset, ScrollPixelOffset},
216 selections_collection::resolve_selections_wrapping_blocks,
217 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
218};
219
220pub const FILE_HEADER_HEIGHT: u32 = 2;
221pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
222const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
223const MAX_LINE_LEN: usize = 1024;
224const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
225const MAX_SELECTION_HISTORY_LEN: usize = 1024;
226pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
227#[doc(hidden)]
228pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
229pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
230
231pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
234pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
235
236pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
237pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
238pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
239
240pub type RenderDiffHunkControlsFn = Arc<
241 dyn Fn(
242 u32,
243 &DiffHunkStatus,
244 Range<Anchor>,
245 bool,
246 Pixels,
247 &Entity<Editor>,
248 &mut Window,
249 &mut App,
250 ) -> AnyElement,
251>;
252
253enum ReportEditorEvent {
254 Saved { auto_saved: bool },
255 EditorOpened,
256 Closed,
257}
258
259impl ReportEditorEvent {
260 pub fn event_type(&self) -> &'static str {
261 match self {
262 Self::Saved { .. } => "Editor Saved",
263 Self::EditorOpened => "Editor Opened",
264 Self::Closed => "Editor Closed",
265 }
266 }
267}
268
269pub enum ActiveDebugLine {}
270pub enum DebugStackFrameLine {}
271enum DocumentHighlightRead {}
272enum DocumentHighlightWrite {}
273enum InputComposition {}
274pub enum PendingInput {}
275enum SelectedTextHighlight {}
276
277pub enum ConflictsOuter {}
278pub enum ConflictsOurs {}
279pub enum ConflictsTheirs {}
280pub enum ConflictsOursMarker {}
281pub enum ConflictsTheirsMarker {}
282
283#[derive(Debug, Copy, Clone, PartialEq, Eq)]
284pub enum Navigated {
285 Yes,
286 No,
287}
288
289impl Navigated {
290 pub fn from_bool(yes: bool) -> Navigated {
291 if yes { Navigated::Yes } else { Navigated::No }
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, Eq)]
296enum DisplayDiffHunk {
297 Folded {
298 display_row: DisplayRow,
299 },
300 Unfolded {
301 is_created_file: bool,
302 diff_base_byte_range: Range<usize>,
303 display_row_range: Range<DisplayRow>,
304 multi_buffer_range: Range<Anchor>,
305 status: DiffHunkStatus,
306 },
307}
308
309pub enum HideMouseCursorOrigin {
310 TypingAction,
311 MovementAction,
312}
313
314pub fn init_settings(cx: &mut App) {
315 EditorSettings::register(cx);
316}
317
318pub fn init(cx: &mut App) {
319 init_settings(cx);
320
321 cx.set_global(GlobalBlameRenderer(Arc::new(())));
322
323 workspace::register_project_item::<Editor>(cx);
324 workspace::FollowableViewRegistry::register::<Editor>(cx);
325 workspace::register_serializable_item::<Editor>(cx);
326
327 cx.observe_new(
328 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
329 workspace.register_action(Editor::new_file);
330 workspace.register_action(Editor::new_file_split);
331 workspace.register_action(Editor::new_file_vertical);
332 workspace.register_action(Editor::new_file_horizontal);
333 workspace.register_action(Editor::cancel_language_server_work);
334 workspace.register_action(Editor::toggle_focus);
335 },
336 )
337 .detach();
338
339 cx.on_action(move |_: &workspace::NewFile, cx| {
340 let app_state = workspace::AppState::global(cx);
341 if let Some(app_state) = app_state.upgrade() {
342 workspace::open_new(
343 Default::default(),
344 app_state,
345 cx,
346 |workspace, window, cx| {
347 Editor::new_file(workspace, &Default::default(), window, cx)
348 },
349 )
350 .detach();
351 }
352 });
353 cx.on_action(move |_: &workspace::NewWindow, cx| {
354 let app_state = workspace::AppState::global(cx);
355 if let Some(app_state) = app_state.upgrade() {
356 workspace::open_new(
357 Default::default(),
358 app_state,
359 cx,
360 |workspace, window, cx| {
361 cx.activate(true);
362 Editor::new_file(workspace, &Default::default(), window, cx)
363 },
364 )
365 .detach();
366 }
367 });
368}
369
370pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
371 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
372}
373
374pub trait DiagnosticRenderer {
375 fn render_group(
376 &self,
377 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
378 buffer_id: BufferId,
379 snapshot: EditorSnapshot,
380 editor: WeakEntity<Editor>,
381 cx: &mut App,
382 ) -> Vec<BlockProperties<Anchor>>;
383
384 fn render_hover(
385 &self,
386 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
387 range: Range<Point>,
388 buffer_id: BufferId,
389 cx: &mut App,
390 ) -> Option<Entity<markdown::Markdown>>;
391
392 fn open_link(
393 &self,
394 editor: &mut Editor,
395 link: SharedString,
396 window: &mut Window,
397 cx: &mut Context<Editor>,
398 );
399}
400
401pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
402
403impl GlobalDiagnosticRenderer {
404 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
405 cx.try_global::<Self>().map(|g| g.0.clone())
406 }
407}
408
409impl gpui::Global for GlobalDiagnosticRenderer {}
410pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
411 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
412}
413
414pub struct SearchWithinRange;
415
416trait InvalidationRegion {
417 fn ranges(&self) -> &[Range<Anchor>];
418}
419
420#[derive(Clone, Debug, PartialEq)]
421pub enum SelectPhase {
422 Begin {
423 position: DisplayPoint,
424 add: bool,
425 click_count: usize,
426 },
427 BeginColumnar {
428 position: DisplayPoint,
429 reset: bool,
430 mode: ColumnarMode,
431 goal_column: u32,
432 },
433 Extend {
434 position: DisplayPoint,
435 click_count: usize,
436 },
437 Update {
438 position: DisplayPoint,
439 goal_column: u32,
440 scroll_delta: gpui::Point<f32>,
441 },
442 End,
443}
444
445#[derive(Clone, Debug, PartialEq)]
446pub enum ColumnarMode {
447 FromMouse,
448 FromSelection,
449}
450
451#[derive(Clone, Debug)]
452pub enum SelectMode {
453 Character,
454 Word(Range<Anchor>),
455 Line(Range<Anchor>),
456 All,
457}
458
459#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
460pub enum SizingBehavior {
461 /// The editor will layout itself using `size_full` and will include the vertical
462 /// scroll margin as requested by user settings.
463 #[default]
464 Default,
465 /// The editor will layout itself using `size_full`, but will not have any
466 /// vertical overscroll.
467 ExcludeOverscrollMargin,
468 /// The editor will request a vertical size according to its content and will be
469 /// layouted without a vertical scroll margin.
470 SizeByContent,
471}
472
473#[derive(Clone, PartialEq, Eq, Debug)]
474pub enum EditorMode {
475 SingleLine,
476 AutoHeight {
477 min_lines: usize,
478 max_lines: Option<usize>,
479 },
480 Full {
481 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
482 scale_ui_elements_with_buffer_font_size: bool,
483 /// When set to `true`, the editor will render a background for the active line.
484 show_active_line_background: bool,
485 /// Determines the sizing behavior for this editor
486 sizing_behavior: SizingBehavior,
487 },
488 Minimap {
489 parent: WeakEntity<Editor>,
490 },
491}
492
493impl EditorMode {
494 pub fn full() -> Self {
495 Self::Full {
496 scale_ui_elements_with_buffer_font_size: true,
497 show_active_line_background: true,
498 sizing_behavior: SizingBehavior::Default,
499 }
500 }
501
502 #[inline]
503 pub fn is_full(&self) -> bool {
504 matches!(self, Self::Full { .. })
505 }
506
507 #[inline]
508 pub fn is_single_line(&self) -> bool {
509 matches!(self, Self::SingleLine { .. })
510 }
511
512 #[inline]
513 fn is_minimap(&self) -> bool {
514 matches!(self, Self::Minimap { .. })
515 }
516}
517
518#[derive(Copy, Clone, Debug)]
519pub enum SoftWrap {
520 /// Prefer not to wrap at all.
521 ///
522 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
523 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
524 GitDiff,
525 /// Prefer a single line generally, unless an overly long line is encountered.
526 None,
527 /// Soft wrap lines that exceed the editor width.
528 EditorWidth,
529 /// Soft wrap lines at the preferred line length.
530 Column(u32),
531 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
532 Bounded(u32),
533}
534
535#[derive(Clone)]
536pub struct EditorStyle {
537 pub background: Hsla,
538 pub border: Hsla,
539 pub local_player: PlayerColor,
540 pub text: TextStyle,
541 pub scrollbar_width: Pixels,
542 pub syntax: Arc<SyntaxTheme>,
543 pub status: StatusColors,
544 pub inlay_hints_style: HighlightStyle,
545 pub edit_prediction_styles: EditPredictionStyles,
546 pub unnecessary_code_fade: f32,
547 pub show_underlines: bool,
548}
549
550impl Default for EditorStyle {
551 fn default() -> Self {
552 Self {
553 background: Hsla::default(),
554 border: Hsla::default(),
555 local_player: PlayerColor::default(),
556 text: TextStyle::default(),
557 scrollbar_width: Pixels::default(),
558 syntax: Default::default(),
559 // HACK: Status colors don't have a real default.
560 // We should look into removing the status colors from the editor
561 // style and retrieve them directly from the theme.
562 status: StatusColors::dark(),
563 inlay_hints_style: HighlightStyle::default(),
564 edit_prediction_styles: EditPredictionStyles {
565 insertion: HighlightStyle::default(),
566 whitespace: HighlightStyle::default(),
567 },
568 unnecessary_code_fade: Default::default(),
569 show_underlines: true,
570 }
571 }
572}
573
574pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
575 let show_background = language_settings::language_settings(None, None, cx)
576 .inlay_hints
577 .show_background;
578
579 let mut style = cx.theme().syntax().get("hint");
580
581 if style.color.is_none() {
582 style.color = Some(cx.theme().status().hint);
583 }
584
585 if !show_background {
586 style.background_color = None;
587 return style;
588 }
589
590 if style.background_color.is_none() {
591 style.background_color = Some(cx.theme().status().hint_background);
592 }
593
594 style
595}
596
597pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
598 EditPredictionStyles {
599 insertion: HighlightStyle {
600 color: Some(cx.theme().status().predictive),
601 ..HighlightStyle::default()
602 },
603 whitespace: HighlightStyle {
604 background_color: Some(cx.theme().status().created_background),
605 ..HighlightStyle::default()
606 },
607 }
608}
609
610type CompletionId = usize;
611
612pub(crate) enum EditDisplayMode {
613 TabAccept,
614 DiffPopover,
615 Inline,
616}
617
618enum EditPrediction {
619 Edit {
620 edits: Vec<(Range<Anchor>, String)>,
621 edit_preview: Option<EditPreview>,
622 display_mode: EditDisplayMode,
623 snapshot: BufferSnapshot,
624 },
625 /// Move to a specific location in the active editor
626 MoveWithin {
627 target: Anchor,
628 snapshot: BufferSnapshot,
629 },
630 /// Move to a specific location in a different editor (not the active one)
631 MoveOutside {
632 target: language::Anchor,
633 snapshot: BufferSnapshot,
634 },
635}
636
637struct EditPredictionState {
638 inlay_ids: Vec<InlayId>,
639 completion: EditPrediction,
640 completion_id: Option<SharedString>,
641 invalidation_range: Option<Range<Anchor>>,
642}
643
644enum EditPredictionSettings {
645 Disabled,
646 Enabled {
647 show_in_menu: bool,
648 preview_requires_modifier: bool,
649 },
650}
651
652enum EditPredictionHighlight {}
653
654#[derive(Debug, Clone)]
655struct InlineDiagnostic {
656 message: SharedString,
657 group_id: usize,
658 is_primary: bool,
659 start: Point,
660 severity: lsp::DiagnosticSeverity,
661}
662
663pub enum MenuEditPredictionsPolicy {
664 Never,
665 ByProvider,
666}
667
668pub enum EditPredictionPreview {
669 /// Modifier is not pressed
670 Inactive { released_too_fast: bool },
671 /// Modifier pressed
672 Active {
673 since: Instant,
674 previous_scroll_position: Option<ScrollAnchor>,
675 },
676}
677
678impl EditPredictionPreview {
679 pub fn released_too_fast(&self) -> bool {
680 match self {
681 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
682 EditPredictionPreview::Active { .. } => false,
683 }
684 }
685
686 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
687 if let EditPredictionPreview::Active {
688 previous_scroll_position,
689 ..
690 } = self
691 {
692 *previous_scroll_position = scroll_position;
693 }
694 }
695}
696
697pub struct ContextMenuOptions {
698 pub min_entries_visible: usize,
699 pub max_entries_visible: usize,
700 pub placement: Option<ContextMenuPlacement>,
701}
702
703#[derive(Debug, Clone, PartialEq, Eq)]
704pub enum ContextMenuPlacement {
705 Above,
706 Below,
707}
708
709#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
710struct EditorActionId(usize);
711
712impl EditorActionId {
713 pub fn post_inc(&mut self) -> Self {
714 let answer = self.0;
715
716 *self = Self(answer + 1);
717
718 Self(answer)
719 }
720}
721
722// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
723// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
724
725type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
726type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
727
728#[derive(Default)]
729struct ScrollbarMarkerState {
730 scrollbar_size: Size<Pixels>,
731 dirty: bool,
732 markers: Arc<[PaintQuad]>,
733 pending_refresh: Option<Task<Result<()>>>,
734}
735
736impl ScrollbarMarkerState {
737 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
738 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
739 }
740}
741
742#[derive(Clone, Copy, PartialEq, Eq)]
743pub enum MinimapVisibility {
744 Disabled,
745 Enabled {
746 /// The configuration currently present in the users settings.
747 setting_configuration: bool,
748 /// Whether to override the currently set visibility from the users setting.
749 toggle_override: bool,
750 },
751}
752
753impl MinimapVisibility {
754 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
755 if mode.is_full() {
756 Self::Enabled {
757 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
758 toggle_override: false,
759 }
760 } else {
761 Self::Disabled
762 }
763 }
764
765 fn hidden(&self) -> Self {
766 match *self {
767 Self::Enabled {
768 setting_configuration,
769 ..
770 } => Self::Enabled {
771 setting_configuration,
772 toggle_override: setting_configuration,
773 },
774 Self::Disabled => Self::Disabled,
775 }
776 }
777
778 fn disabled(&self) -> bool {
779 matches!(*self, Self::Disabled)
780 }
781
782 fn settings_visibility(&self) -> bool {
783 match *self {
784 Self::Enabled {
785 setting_configuration,
786 ..
787 } => setting_configuration,
788 _ => false,
789 }
790 }
791
792 fn visible(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 toggle_override,
797 } => setting_configuration ^ toggle_override,
798 _ => false,
799 }
800 }
801
802 fn toggle_visibility(&self) -> Self {
803 match *self {
804 Self::Enabled {
805 toggle_override,
806 setting_configuration,
807 } => Self::Enabled {
808 setting_configuration,
809 toggle_override: !toggle_override,
810 },
811 Self::Disabled => Self::Disabled,
812 }
813 }
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
817pub enum BufferSerialization {
818 All,
819 NonDirtyBuffers,
820}
821
822impl BufferSerialization {
823 fn new(restore_unsaved_buffers: bool) -> Self {
824 if restore_unsaved_buffers {
825 Self::All
826 } else {
827 Self::NonDirtyBuffers
828 }
829 }
830}
831
832#[derive(Clone, Debug)]
833struct RunnableTasks {
834 templates: Vec<(TaskSourceKind, TaskTemplate)>,
835 offset: multi_buffer::Anchor,
836 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
837 column: u32,
838 // Values of all named captures, including those starting with '_'
839 extra_variables: HashMap<String, String>,
840 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
841 context_range: Range<BufferOffset>,
842}
843
844impl RunnableTasks {
845 fn resolve<'a>(
846 &'a self,
847 cx: &'a task::TaskContext,
848 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
849 self.templates.iter().filter_map(|(kind, template)| {
850 template
851 .resolve_task(&kind.to_id_base(), cx)
852 .map(|task| (kind.clone(), task))
853 })
854 }
855}
856
857#[derive(Clone)]
858pub struct ResolvedTasks {
859 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
860 position: Anchor,
861}
862
863#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
864struct BufferOffset(usize);
865
866/// Addons allow storing per-editor state in other crates (e.g. Vim)
867pub trait Addon: 'static {
868 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
869
870 fn render_buffer_header_controls(
871 &self,
872 _: &ExcerptInfo,
873 _: &Window,
874 _: &App,
875 ) -> Option<AnyElement> {
876 None
877 }
878
879 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
880 None
881 }
882
883 fn to_any(&self) -> &dyn std::any::Any;
884
885 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
886 None
887 }
888}
889
890struct ChangeLocation {
891 current: Option<Vec<Anchor>>,
892 original: Vec<Anchor>,
893}
894impl ChangeLocation {
895 fn locations(&self) -> &[Anchor] {
896 self.current.as_ref().unwrap_or(&self.original)
897 }
898}
899
900/// A set of caret positions, registered when the editor was edited.
901pub struct ChangeList {
902 changes: Vec<ChangeLocation>,
903 /// Currently "selected" change.
904 position: Option<usize>,
905}
906
907impl ChangeList {
908 pub fn new() -> Self {
909 Self {
910 changes: Vec::new(),
911 position: None,
912 }
913 }
914
915 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
916 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
917 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
918 if self.changes.is_empty() {
919 return None;
920 }
921
922 let prev = self.position.unwrap_or(self.changes.len());
923 let next = if direction == Direction::Prev {
924 prev.saturating_sub(count)
925 } else {
926 (prev + count).min(self.changes.len() - 1)
927 };
928 self.position = Some(next);
929 self.changes.get(next).map(|change| change.locations())
930 }
931
932 /// Adds a new change to the list, resetting the change list position.
933 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
934 self.position.take();
935 if let Some(last) = self.changes.last_mut()
936 && group
937 {
938 last.current = Some(new_positions)
939 } else {
940 self.changes.push(ChangeLocation {
941 original: new_positions,
942 current: None,
943 });
944 }
945 }
946
947 pub fn last(&self) -> Option<&[Anchor]> {
948 self.changes.last().map(|change| change.locations())
949 }
950
951 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.original.as_slice())
953 }
954
955 pub fn invert_last_group(&mut self) {
956 if let Some(last) = self.changes.last_mut()
957 && let Some(current) = last.current.as_mut()
958 {
959 mem::swap(&mut last.original, current);
960 }
961 }
962}
963
964#[derive(Clone)]
965struct InlineBlamePopoverState {
966 scroll_handle: ScrollHandle,
967 commit_message: Option<ParsedCommitMessage>,
968 markdown: Entity<Markdown>,
969}
970
971struct InlineBlamePopover {
972 position: gpui::Point<Pixels>,
973 hide_task: Option<Task<()>>,
974 popover_bounds: Option<Bounds<Pixels>>,
975 popover_state: InlineBlamePopoverState,
976 keyboard_grace: bool,
977}
978
979enum SelectionDragState {
980 /// State when no drag related activity is detected.
981 None,
982 /// State when the mouse is down on a selection that is about to be dragged.
983 ReadyToDrag {
984 selection: Selection<Anchor>,
985 click_position: gpui::Point<Pixels>,
986 mouse_down_time: Instant,
987 },
988 /// State when the mouse is dragging the selection in the editor.
989 Dragging {
990 selection: Selection<Anchor>,
991 drop_cursor: Selection<Anchor>,
992 hide_drop_cursor: bool,
993 },
994}
995
996enum ColumnarSelectionState {
997 FromMouse {
998 selection_tail: Anchor,
999 display_point: Option<DisplayPoint>,
1000 },
1001 FromSelection {
1002 selection_tail: Anchor,
1003 },
1004}
1005
1006/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1007/// a breakpoint on them.
1008#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1009struct PhantomBreakpointIndicator {
1010 display_row: DisplayRow,
1011 /// There's a small debounce between hovering over the line and showing the indicator.
1012 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1013 is_active: bool,
1014 collides_with_existing_breakpoint: bool,
1015}
1016
1017/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1018///
1019/// See the [module level documentation](self) for more information.
1020pub struct Editor {
1021 focus_handle: FocusHandle,
1022 last_focused_descendant: Option<WeakFocusHandle>,
1023 /// The text buffer being edited
1024 buffer: Entity<MultiBuffer>,
1025 /// Map of how text in the buffer should be displayed.
1026 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1027 pub display_map: Entity<DisplayMap>,
1028 placeholder_display_map: Option<Entity<DisplayMap>>,
1029 pub selections: SelectionsCollection,
1030 pub scroll_manager: ScrollManager,
1031 /// When inline assist editors are linked, they all render cursors because
1032 /// typing enters text into each of them, even the ones that aren't focused.
1033 pub(crate) show_cursor_when_unfocused: bool,
1034 columnar_selection_state: Option<ColumnarSelectionState>,
1035 add_selections_state: Option<AddSelectionsState>,
1036 select_next_state: Option<SelectNextState>,
1037 select_prev_state: Option<SelectNextState>,
1038 selection_history: SelectionHistory,
1039 defer_selection_effects: bool,
1040 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1041 autoclose_regions: Vec<AutocloseRegion>,
1042 snippet_stack: InvalidationStack<SnippetState>,
1043 select_syntax_node_history: SelectSyntaxNodeHistory,
1044 ime_transaction: Option<TransactionId>,
1045 pub diagnostics_max_severity: DiagnosticSeverity,
1046 active_diagnostics: ActiveDiagnostic,
1047 show_inline_diagnostics: bool,
1048 inline_diagnostics_update: Task<()>,
1049 inline_diagnostics_enabled: bool,
1050 diagnostics_enabled: bool,
1051 word_completions_enabled: bool,
1052 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1053 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1054 hard_wrap: Option<usize>,
1055 project: Option<Entity<Project>>,
1056 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1057 completion_provider: Option<Rc<dyn CompletionProvider>>,
1058 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1059 blink_manager: Entity<BlinkManager>,
1060 show_cursor_names: bool,
1061 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1062 pub show_local_selections: bool,
1063 mode: EditorMode,
1064 show_breadcrumbs: bool,
1065 show_gutter: bool,
1066 show_scrollbars: ScrollbarAxes,
1067 minimap_visibility: MinimapVisibility,
1068 offset_content: bool,
1069 disable_expand_excerpt_buttons: bool,
1070 show_line_numbers: Option<bool>,
1071 use_relative_line_numbers: Option<bool>,
1072 show_git_diff_gutter: Option<bool>,
1073 show_code_actions: Option<bool>,
1074 show_runnables: Option<bool>,
1075 show_breakpoints: Option<bool>,
1076 show_wrap_guides: Option<bool>,
1077 show_indent_guides: Option<bool>,
1078 highlight_order: usize,
1079 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1080 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1081 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1082 scrollbar_marker_state: ScrollbarMarkerState,
1083 active_indent_guides_state: ActiveIndentGuidesState,
1084 nav_history: Option<ItemNavHistory>,
1085 context_menu: RefCell<Option<CodeContextMenu>>,
1086 context_menu_options: Option<ContextMenuOptions>,
1087 mouse_context_menu: Option<MouseContextMenu>,
1088 completion_tasks: Vec<(CompletionId, Task<()>)>,
1089 inline_blame_popover: Option<InlineBlamePopover>,
1090 inline_blame_popover_show_task: Option<Task<()>>,
1091 signature_help_state: SignatureHelpState,
1092 auto_signature_help: Option<bool>,
1093 find_all_references_task_sources: Vec<Anchor>,
1094 next_completion_id: CompletionId,
1095 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1096 code_actions_task: Option<Task<Result<()>>>,
1097 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1098 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1099 document_highlights_task: Option<Task<()>>,
1100 linked_editing_range_task: Option<Task<Option<()>>>,
1101 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1102 pending_rename: Option<RenameState>,
1103 searchable: bool,
1104 cursor_shape: CursorShape,
1105 current_line_highlight: Option<CurrentLineHighlight>,
1106 autoindent_mode: Option<AutoindentMode>,
1107 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1108 input_enabled: bool,
1109 use_modal_editing: bool,
1110 read_only: bool,
1111 leader_id: Option<CollaboratorId>,
1112 remote_id: Option<ViewId>,
1113 pub hover_state: HoverState,
1114 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1115 gutter_hovered: bool,
1116 hovered_link_state: Option<HoveredLinkState>,
1117 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1118 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1119 active_edit_prediction: Option<EditPredictionState>,
1120 /// Used to prevent flickering as the user types while the menu is open
1121 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1122 edit_prediction_settings: EditPredictionSettings,
1123 edit_predictions_hidden_for_vim_mode: bool,
1124 show_edit_predictions_override: Option<bool>,
1125 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1126 edit_prediction_preview: EditPredictionPreview,
1127 edit_prediction_indent_conflict: bool,
1128 edit_prediction_requires_modifier_in_indent_conflict: bool,
1129 next_inlay_id: usize,
1130 next_color_inlay_id: usize,
1131 _subscriptions: Vec<Subscription>,
1132 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1133 gutter_dimensions: GutterDimensions,
1134 style: Option<EditorStyle>,
1135 text_style_refinement: Option<TextStyleRefinement>,
1136 next_editor_action_id: EditorActionId,
1137 editor_actions: Rc<
1138 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1139 >,
1140 use_autoclose: bool,
1141 use_auto_surround: bool,
1142 auto_replace_emoji_shortcode: bool,
1143 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1144 show_git_blame_gutter: bool,
1145 show_git_blame_inline: bool,
1146 show_git_blame_inline_delay_task: Option<Task<()>>,
1147 git_blame_inline_enabled: bool,
1148 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1149 buffer_serialization: Option<BufferSerialization>,
1150 show_selection_menu: Option<bool>,
1151 blame: Option<Entity<GitBlame>>,
1152 blame_subscription: Option<Subscription>,
1153 custom_context_menu: Option<
1154 Box<
1155 dyn 'static
1156 + Fn(
1157 &mut Self,
1158 DisplayPoint,
1159 &mut Window,
1160 &mut Context<Self>,
1161 ) -> Option<Entity<ui::ContextMenu>>,
1162 >,
1163 >,
1164 last_bounds: Option<Bounds<Pixels>>,
1165 last_position_map: Option<Rc<PositionMap>>,
1166 expect_bounds_change: Option<Bounds<Pixels>>,
1167 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1168 tasks_update_task: Option<Task<()>>,
1169 breakpoint_store: Option<Entity<BreakpointStore>>,
1170 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1171 hovered_diff_hunk_row: Option<DisplayRow>,
1172 pull_diagnostics_task: Task<()>,
1173 in_project_search: bool,
1174 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1175 breadcrumb_header: Option<String>,
1176 focused_block: Option<FocusedBlock>,
1177 next_scroll_position: NextScrollCursorCenterTopBottom,
1178 addons: HashMap<TypeId, Box<dyn Addon>>,
1179 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1180 load_diff_task: Option<Shared<Task<()>>>,
1181 /// Whether we are temporarily displaying a diff other than git's
1182 temporary_diff_override: bool,
1183 selection_mark_mode: bool,
1184 toggle_fold_multiple_buffers: Task<()>,
1185 _scroll_cursor_center_top_bottom_task: Task<()>,
1186 serialize_selections: Task<()>,
1187 serialize_folds: Task<()>,
1188 mouse_cursor_hidden: bool,
1189 minimap: Option<Entity<Self>>,
1190 hide_mouse_mode: HideMouseMode,
1191 pub change_list: ChangeList,
1192 inline_value_cache: InlineValueCache,
1193 selection_drag_state: SelectionDragState,
1194 colors: Option<LspColorData>,
1195 post_scroll_update: Task<()>,
1196 refresh_colors_task: Task<()>,
1197 inlay_hints: Option<LspInlayHintData>,
1198 folding_newlines: Task<()>,
1199 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1200}
1201
1202fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1203 if debounce_ms > 0 {
1204 Some(Duration::from_millis(debounce_ms))
1205 } else {
1206 None
1207 }
1208}
1209
1210#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1211enum NextScrollCursorCenterTopBottom {
1212 #[default]
1213 Center,
1214 Top,
1215 Bottom,
1216}
1217
1218impl NextScrollCursorCenterTopBottom {
1219 fn next(&self) -> Self {
1220 match self {
1221 Self::Center => Self::Top,
1222 Self::Top => Self::Bottom,
1223 Self::Bottom => Self::Center,
1224 }
1225 }
1226}
1227
1228#[derive(Clone)]
1229pub struct EditorSnapshot {
1230 pub mode: EditorMode,
1231 show_gutter: bool,
1232 show_line_numbers: Option<bool>,
1233 show_git_diff_gutter: Option<bool>,
1234 show_code_actions: Option<bool>,
1235 show_runnables: Option<bool>,
1236 show_breakpoints: Option<bool>,
1237 git_blame_gutter_max_author_length: Option<usize>,
1238 pub display_snapshot: DisplaySnapshot,
1239 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1240 is_focused: bool,
1241 scroll_anchor: ScrollAnchor,
1242 ongoing_scroll: OngoingScroll,
1243 current_line_highlight: CurrentLineHighlight,
1244 gutter_hovered: bool,
1245}
1246
1247#[derive(Default, Debug, Clone, Copy)]
1248pub struct GutterDimensions {
1249 pub left_padding: Pixels,
1250 pub right_padding: Pixels,
1251 pub width: Pixels,
1252 pub margin: Pixels,
1253 pub git_blame_entries_width: Option<Pixels>,
1254}
1255
1256impl GutterDimensions {
1257 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1258 Self {
1259 margin: Self::default_gutter_margin(font_id, font_size, cx),
1260 ..Default::default()
1261 }
1262 }
1263
1264 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1265 -cx.text_system().descent(font_id, font_size)
1266 }
1267 /// The full width of the space taken up by the gutter.
1268 pub fn full_width(&self) -> Pixels {
1269 self.margin + self.width
1270 }
1271
1272 /// The width of the space reserved for the fold indicators,
1273 /// use alongside 'justify_end' and `gutter_width` to
1274 /// right align content with the line numbers
1275 pub fn fold_area_width(&self) -> Pixels {
1276 self.margin + self.right_padding
1277 }
1278}
1279
1280struct CharacterDimensions {
1281 em_width: Pixels,
1282 em_advance: Pixels,
1283 line_height: Pixels,
1284}
1285
1286#[derive(Debug)]
1287pub struct RemoteSelection {
1288 pub replica_id: ReplicaId,
1289 pub selection: Selection<Anchor>,
1290 pub cursor_shape: CursorShape,
1291 pub collaborator_id: CollaboratorId,
1292 pub line_mode: bool,
1293 pub user_name: Option<SharedString>,
1294 pub color: PlayerColor,
1295}
1296
1297#[derive(Clone, Debug)]
1298struct SelectionHistoryEntry {
1299 selections: Arc<[Selection<Anchor>]>,
1300 select_next_state: Option<SelectNextState>,
1301 select_prev_state: Option<SelectNextState>,
1302 add_selections_state: Option<AddSelectionsState>,
1303}
1304
1305#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1306enum SelectionHistoryMode {
1307 Normal,
1308 Undoing,
1309 Redoing,
1310 Skipping,
1311}
1312
1313#[derive(Clone, PartialEq, Eq, Hash)]
1314struct HoveredCursor {
1315 replica_id: ReplicaId,
1316 selection_id: usize,
1317}
1318
1319impl Default for SelectionHistoryMode {
1320 fn default() -> Self {
1321 Self::Normal
1322 }
1323}
1324
1325#[derive(Debug)]
1326/// SelectionEffects controls the side-effects of updating the selection.
1327///
1328/// The default behaviour does "what you mostly want":
1329/// - it pushes to the nav history if the cursor moved by >10 lines
1330/// - it re-triggers completion requests
1331/// - it scrolls to fit
1332///
1333/// You might want to modify these behaviours. For example when doing a "jump"
1334/// like go to definition, we always want to add to nav history; but when scrolling
1335/// in vim mode we never do.
1336///
1337/// Similarly, you might want to disable scrolling if you don't want the viewport to
1338/// move.
1339#[derive(Clone)]
1340pub struct SelectionEffects {
1341 nav_history: Option<bool>,
1342 completions: bool,
1343 scroll: Option<Autoscroll>,
1344}
1345
1346impl Default for SelectionEffects {
1347 fn default() -> Self {
1348 Self {
1349 nav_history: None,
1350 completions: true,
1351 scroll: Some(Autoscroll::fit()),
1352 }
1353 }
1354}
1355impl SelectionEffects {
1356 pub fn scroll(scroll: Autoscroll) -> Self {
1357 Self {
1358 scroll: Some(scroll),
1359 ..Default::default()
1360 }
1361 }
1362
1363 pub fn no_scroll() -> Self {
1364 Self {
1365 scroll: None,
1366 ..Default::default()
1367 }
1368 }
1369
1370 pub fn completions(self, completions: bool) -> Self {
1371 Self {
1372 completions,
1373 ..self
1374 }
1375 }
1376
1377 pub fn nav_history(self, nav_history: bool) -> Self {
1378 Self {
1379 nav_history: Some(nav_history),
1380 ..self
1381 }
1382 }
1383}
1384
1385struct DeferredSelectionEffectsState {
1386 changed: bool,
1387 effects: SelectionEffects,
1388 old_cursor_position: Anchor,
1389 history_entry: SelectionHistoryEntry,
1390}
1391
1392#[derive(Default)]
1393struct SelectionHistory {
1394 #[allow(clippy::type_complexity)]
1395 selections_by_transaction:
1396 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1397 mode: SelectionHistoryMode,
1398 undo_stack: VecDeque<SelectionHistoryEntry>,
1399 redo_stack: VecDeque<SelectionHistoryEntry>,
1400}
1401
1402impl SelectionHistory {
1403 #[track_caller]
1404 fn insert_transaction(
1405 &mut self,
1406 transaction_id: TransactionId,
1407 selections: Arc<[Selection<Anchor>]>,
1408 ) {
1409 if selections.is_empty() {
1410 log::error!(
1411 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1412 std::panic::Location::caller()
1413 );
1414 return;
1415 }
1416 self.selections_by_transaction
1417 .insert(transaction_id, (selections, None));
1418 }
1419
1420 #[allow(clippy::type_complexity)]
1421 fn transaction(
1422 &self,
1423 transaction_id: TransactionId,
1424 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1425 self.selections_by_transaction.get(&transaction_id)
1426 }
1427
1428 #[allow(clippy::type_complexity)]
1429 fn transaction_mut(
1430 &mut self,
1431 transaction_id: TransactionId,
1432 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1433 self.selections_by_transaction.get_mut(&transaction_id)
1434 }
1435
1436 fn push(&mut self, entry: SelectionHistoryEntry) {
1437 if !entry.selections.is_empty() {
1438 match self.mode {
1439 SelectionHistoryMode::Normal => {
1440 self.push_undo(entry);
1441 self.redo_stack.clear();
1442 }
1443 SelectionHistoryMode::Undoing => self.push_redo(entry),
1444 SelectionHistoryMode::Redoing => self.push_undo(entry),
1445 SelectionHistoryMode::Skipping => {}
1446 }
1447 }
1448 }
1449
1450 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1451 if self
1452 .undo_stack
1453 .back()
1454 .is_none_or(|e| e.selections != entry.selections)
1455 {
1456 self.undo_stack.push_back(entry);
1457 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1458 self.undo_stack.pop_front();
1459 }
1460 }
1461 }
1462
1463 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1464 if self
1465 .redo_stack
1466 .back()
1467 .is_none_or(|e| e.selections != entry.selections)
1468 {
1469 self.redo_stack.push_back(entry);
1470 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1471 self.redo_stack.pop_front();
1472 }
1473 }
1474 }
1475}
1476
1477#[derive(Clone, Copy)]
1478pub struct RowHighlightOptions {
1479 pub autoscroll: bool,
1480 pub include_gutter: bool,
1481}
1482
1483impl Default for RowHighlightOptions {
1484 fn default() -> Self {
1485 Self {
1486 autoscroll: Default::default(),
1487 include_gutter: true,
1488 }
1489 }
1490}
1491
1492struct RowHighlight {
1493 index: usize,
1494 range: Range<Anchor>,
1495 color: Hsla,
1496 options: RowHighlightOptions,
1497 type_id: TypeId,
1498}
1499
1500#[derive(Clone, Debug)]
1501struct AddSelectionsState {
1502 groups: Vec<AddSelectionsGroup>,
1503}
1504
1505#[derive(Clone, Debug)]
1506struct AddSelectionsGroup {
1507 above: bool,
1508 stack: Vec<usize>,
1509}
1510
1511#[derive(Clone)]
1512struct SelectNextState {
1513 query: AhoCorasick,
1514 wordwise: bool,
1515 done: bool,
1516}
1517
1518impl std::fmt::Debug for SelectNextState {
1519 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1520 f.debug_struct(std::any::type_name::<Self>())
1521 .field("wordwise", &self.wordwise)
1522 .field("done", &self.done)
1523 .finish()
1524 }
1525}
1526
1527#[derive(Debug)]
1528struct AutocloseRegion {
1529 selection_id: usize,
1530 range: Range<Anchor>,
1531 pair: BracketPair,
1532}
1533
1534#[derive(Debug)]
1535struct SnippetState {
1536 ranges: Vec<Vec<Range<Anchor>>>,
1537 active_index: usize,
1538 choices: Vec<Option<Vec<String>>>,
1539}
1540
1541#[doc(hidden)]
1542pub struct RenameState {
1543 pub range: Range<Anchor>,
1544 pub old_name: Arc<str>,
1545 pub editor: Entity<Editor>,
1546 block_id: CustomBlockId,
1547}
1548
1549struct InvalidationStack<T>(Vec<T>);
1550
1551struct RegisteredEditPredictionProvider {
1552 provider: Arc<dyn EditPredictionProviderHandle>,
1553 _subscription: Subscription,
1554}
1555
1556#[derive(Debug, PartialEq, Eq)]
1557pub struct ActiveDiagnosticGroup {
1558 pub active_range: Range<Anchor>,
1559 pub active_message: String,
1560 pub group_id: usize,
1561 pub blocks: HashSet<CustomBlockId>,
1562}
1563
1564#[derive(Debug, PartialEq, Eq)]
1565
1566pub(crate) enum ActiveDiagnostic {
1567 None,
1568 All,
1569 Group(ActiveDiagnosticGroup),
1570}
1571
1572#[derive(Serialize, Deserialize, Clone, Debug)]
1573pub struct ClipboardSelection {
1574 /// The number of bytes in this selection.
1575 pub len: usize,
1576 /// Whether this was a full-line selection.
1577 pub is_entire_line: bool,
1578 /// The indentation of the first line when this content was originally copied.
1579 pub first_line_indent: u32,
1580}
1581
1582// selections, scroll behavior, was newest selection reversed
1583type SelectSyntaxNodeHistoryState = (
1584 Box<[Selection<usize>]>,
1585 SelectSyntaxNodeScrollBehavior,
1586 bool,
1587);
1588
1589#[derive(Default)]
1590struct SelectSyntaxNodeHistory {
1591 stack: Vec<SelectSyntaxNodeHistoryState>,
1592 // disable temporarily to allow changing selections without losing the stack
1593 pub disable_clearing: bool,
1594}
1595
1596impl SelectSyntaxNodeHistory {
1597 pub fn try_clear(&mut self) {
1598 if !self.disable_clearing {
1599 self.stack.clear();
1600 }
1601 }
1602
1603 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1604 self.stack.push(selection);
1605 }
1606
1607 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1608 self.stack.pop()
1609 }
1610}
1611
1612enum SelectSyntaxNodeScrollBehavior {
1613 CursorTop,
1614 FitSelection,
1615 CursorBottom,
1616}
1617
1618#[derive(Debug)]
1619pub(crate) struct NavigationData {
1620 cursor_anchor: Anchor,
1621 cursor_position: Point,
1622 scroll_anchor: ScrollAnchor,
1623 scroll_top_row: u32,
1624}
1625
1626#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1627pub enum GotoDefinitionKind {
1628 Symbol,
1629 Declaration,
1630 Type,
1631 Implementation,
1632}
1633
1634pub enum FormatTarget {
1635 Buffers(HashSet<Entity<Buffer>>),
1636 Ranges(Vec<Range<MultiBufferPoint>>),
1637}
1638
1639pub(crate) struct FocusedBlock {
1640 id: BlockId,
1641 focus_handle: WeakFocusHandle,
1642}
1643
1644#[derive(Clone)]
1645enum JumpData {
1646 MultiBufferRow {
1647 row: MultiBufferRow,
1648 line_offset_from_top: u32,
1649 },
1650 MultiBufferPoint {
1651 excerpt_id: ExcerptId,
1652 position: Point,
1653 anchor: text::Anchor,
1654 line_offset_from_top: u32,
1655 },
1656}
1657
1658pub enum MultibufferSelectionMode {
1659 First,
1660 All,
1661}
1662
1663#[derive(Clone, Copy, Debug, Default)]
1664pub struct RewrapOptions {
1665 pub override_language_settings: bool,
1666 pub preserve_existing_whitespace: bool,
1667}
1668
1669impl Editor {
1670 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1674 }
1675
1676 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1677 let buffer = cx.new(|cx| Buffer::local("", cx));
1678 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1679 Self::new(EditorMode::full(), buffer, None, window, cx)
1680 }
1681
1682 pub fn auto_height(
1683 min_lines: usize,
1684 max_lines: usize,
1685 window: &mut Window,
1686 cx: &mut Context<Self>,
1687 ) -> Self {
1688 let buffer = cx.new(|cx| Buffer::local("", cx));
1689 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1690 Self::new(
1691 EditorMode::AutoHeight {
1692 min_lines,
1693 max_lines: Some(max_lines),
1694 },
1695 buffer,
1696 None,
1697 window,
1698 cx,
1699 )
1700 }
1701
1702 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1703 /// The editor grows as tall as needed to fit its content.
1704 pub fn auto_height_unbounded(
1705 min_lines: usize,
1706 window: &mut Window,
1707 cx: &mut Context<Self>,
1708 ) -> Self {
1709 let buffer = cx.new(|cx| Buffer::local("", cx));
1710 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1711 Self::new(
1712 EditorMode::AutoHeight {
1713 min_lines,
1714 max_lines: None,
1715 },
1716 buffer,
1717 None,
1718 window,
1719 cx,
1720 )
1721 }
1722
1723 pub fn for_buffer(
1724 buffer: Entity<Buffer>,
1725 project: Option<Entity<Project>>,
1726 window: &mut Window,
1727 cx: &mut Context<Self>,
1728 ) -> Self {
1729 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1730 Self::new(EditorMode::full(), buffer, project, window, cx)
1731 }
1732
1733 pub fn for_multibuffer(
1734 buffer: Entity<MultiBuffer>,
1735 project: Option<Entity<Project>>,
1736 window: &mut Window,
1737 cx: &mut Context<Self>,
1738 ) -> Self {
1739 Self::new(EditorMode::full(), buffer, project, window, cx)
1740 }
1741
1742 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1743 let mut clone = Self::new(
1744 self.mode.clone(),
1745 self.buffer.clone(),
1746 self.project.clone(),
1747 window,
1748 cx,
1749 );
1750 self.display_map.update(cx, |display_map, cx| {
1751 let snapshot = display_map.snapshot(cx);
1752 clone.display_map.update(cx, |display_map, cx| {
1753 display_map.set_state(&snapshot, cx);
1754 });
1755 });
1756 clone.folds_did_change(cx);
1757 clone.selections.clone_state(&self.selections);
1758 clone.scroll_manager.clone_state(&self.scroll_manager);
1759 clone.searchable = self.searchable;
1760 clone.read_only = self.read_only;
1761 clone
1762 }
1763
1764 pub fn new(
1765 mode: EditorMode,
1766 buffer: Entity<MultiBuffer>,
1767 project: Option<Entity<Project>>,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 Editor::new_internal(mode, buffer, project, None, window, cx)
1772 }
1773
1774 fn new_internal(
1775 mode: EditorMode,
1776 multi_buffer: Entity<MultiBuffer>,
1777 project: Option<Entity<Project>>,
1778 display_map: Option<Entity<DisplayMap>>,
1779 window: &mut Window,
1780 cx: &mut Context<Self>,
1781 ) -> Self {
1782 debug_assert!(
1783 display_map.is_none() || mode.is_minimap(),
1784 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1785 );
1786
1787 let full_mode = mode.is_full();
1788 let is_minimap = mode.is_minimap();
1789 let diagnostics_max_severity = if full_mode {
1790 EditorSettings::get_global(cx)
1791 .diagnostics_max_severity
1792 .unwrap_or(DiagnosticSeverity::Hint)
1793 } else {
1794 DiagnosticSeverity::Off
1795 };
1796 let style = window.text_style();
1797 let font_size = style.font_size.to_pixels(window.rem_size());
1798 let editor = cx.entity().downgrade();
1799 let fold_placeholder = FoldPlaceholder {
1800 constrain_width: false,
1801 render: Arc::new(move |fold_id, fold_range, cx| {
1802 let editor = editor.clone();
1803 div()
1804 .id(fold_id)
1805 .bg(cx.theme().colors().ghost_element_background)
1806 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1807 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1808 .rounded_xs()
1809 .size_full()
1810 .cursor_pointer()
1811 .child("⋯")
1812 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1813 .on_click(move |_, _window, cx| {
1814 editor
1815 .update(cx, |editor, cx| {
1816 editor.unfold_ranges(
1817 &[fold_range.start..fold_range.end],
1818 true,
1819 false,
1820 cx,
1821 );
1822 cx.stop_propagation();
1823 })
1824 .ok();
1825 })
1826 .into_any()
1827 }),
1828 merge_adjacent: true,
1829 ..FoldPlaceholder::default()
1830 };
1831 let display_map = display_map.unwrap_or_else(|| {
1832 cx.new(|cx| {
1833 DisplayMap::new(
1834 multi_buffer.clone(),
1835 style.font(),
1836 font_size,
1837 None,
1838 FILE_HEADER_HEIGHT,
1839 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1840 fold_placeholder,
1841 diagnostics_max_severity,
1842 cx,
1843 )
1844 })
1845 });
1846
1847 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1848
1849 let blink_manager = cx.new(|cx| {
1850 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1851 if is_minimap {
1852 blink_manager.disable(cx);
1853 }
1854 blink_manager
1855 });
1856
1857 let soft_wrap_mode_override =
1858 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1859
1860 let mut project_subscriptions = Vec::new();
1861 if full_mode && let Some(project) = project.as_ref() {
1862 project_subscriptions.push(cx.subscribe_in(
1863 project,
1864 window,
1865 |editor, _, event, window, cx| match event {
1866 project::Event::RefreshCodeLens => {
1867 // we always query lens with actions, without storing them, always refreshing them
1868 }
1869 project::Event::RefreshInlayHints {
1870 server_id,
1871 request_id,
1872 } => {
1873 editor.refresh_inlay_hints(
1874 InlayHintRefreshReason::RefreshRequested {
1875 server_id: *server_id,
1876 request_id: *request_id,
1877 },
1878 cx,
1879 );
1880 }
1881 project::Event::LanguageServerRemoved(..) => {
1882 if editor.tasks_update_task.is_none() {
1883 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1884 }
1885 editor.registered_buffers.clear();
1886 editor.register_visible_buffers(cx);
1887 }
1888 project::Event::LanguageServerAdded(..) => {
1889 if editor.tasks_update_task.is_none() {
1890 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1891 }
1892 }
1893 project::Event::SnippetEdit(id, snippet_edits) => {
1894 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1895 let focus_handle = editor.focus_handle(cx);
1896 if focus_handle.is_focused(window) {
1897 let snapshot = buffer.read(cx).snapshot();
1898 for (range, snippet) in snippet_edits {
1899 let editor_range =
1900 language::range_from_lsp(*range).to_offset(&snapshot);
1901 editor
1902 .insert_snippet(
1903 &[editor_range],
1904 snippet.clone(),
1905 window,
1906 cx,
1907 )
1908 .ok();
1909 }
1910 }
1911 }
1912 }
1913 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1914 let buffer_id = *buffer_id;
1915 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1916 editor.register_buffer(buffer_id, cx);
1917 editor.update_lsp_data(Some(buffer_id), window, cx);
1918 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1919 refresh_linked_ranges(editor, window, cx);
1920 editor.refresh_code_actions(window, cx);
1921 editor.refresh_document_highlights(cx);
1922 }
1923 }
1924
1925 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1926 let Some(workspace) = editor.workspace() else {
1927 return;
1928 };
1929 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1930 else {
1931 return;
1932 };
1933
1934 if active_editor.entity_id() == cx.entity_id() {
1935 let entity_id = cx.entity_id();
1936 workspace.update(cx, |this, cx| {
1937 this.panes_mut()
1938 .iter_mut()
1939 .filter(|pane| pane.entity_id() != entity_id)
1940 .for_each(|p| {
1941 p.update(cx, |pane, _| {
1942 pane.nav_history_mut().rename_item(
1943 entity_id,
1944 project_path.clone(),
1945 abs_path.clone().into(),
1946 );
1947 })
1948 });
1949 });
1950 let edited_buffers_already_open = {
1951 let other_editors: Vec<Entity<Editor>> = workspace
1952 .read(cx)
1953 .panes()
1954 .iter()
1955 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1956 .filter(|editor| editor.entity_id() != cx.entity_id())
1957 .collect();
1958
1959 transaction.0.keys().all(|buffer| {
1960 other_editors.iter().any(|editor| {
1961 let multi_buffer = editor.read(cx).buffer();
1962 multi_buffer.read(cx).is_singleton()
1963 && multi_buffer.read(cx).as_singleton().map_or(
1964 false,
1965 |singleton| {
1966 singleton.entity_id() == buffer.entity_id()
1967 },
1968 )
1969 })
1970 })
1971 };
1972 if !edited_buffers_already_open {
1973 let workspace = workspace.downgrade();
1974 let transaction = transaction.clone();
1975 cx.defer_in(window, move |_, window, cx| {
1976 cx.spawn_in(window, async move |editor, cx| {
1977 Self::open_project_transaction(
1978 &editor,
1979 workspace,
1980 transaction,
1981 "Rename".to_string(),
1982 cx,
1983 )
1984 .await
1985 .ok()
1986 })
1987 .detach();
1988 });
1989 }
1990 }
1991 }
1992
1993 _ => {}
1994 },
1995 ));
1996 if let Some(task_inventory) = project
1997 .read(cx)
1998 .task_store()
1999 .read(cx)
2000 .task_inventory()
2001 .cloned()
2002 {
2003 project_subscriptions.push(cx.observe_in(
2004 &task_inventory,
2005 window,
2006 |editor, _, window, cx| {
2007 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2008 },
2009 ));
2010 };
2011
2012 project_subscriptions.push(cx.subscribe_in(
2013 &project.read(cx).breakpoint_store(),
2014 window,
2015 |editor, _, event, window, cx| match event {
2016 BreakpointStoreEvent::ClearDebugLines => {
2017 editor.clear_row_highlights::<ActiveDebugLine>();
2018 editor.refresh_inline_values(cx);
2019 }
2020 BreakpointStoreEvent::SetDebugLine => {
2021 if editor.go_to_active_debug_line(window, cx) {
2022 cx.stop_propagation();
2023 }
2024
2025 editor.refresh_inline_values(cx);
2026 }
2027 _ => {}
2028 },
2029 ));
2030 let git_store = project.read(cx).git_store().clone();
2031 let project = project.clone();
2032 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2033 if let GitStoreEvent::RepositoryAdded = event {
2034 this.load_diff_task = Some(
2035 update_uncommitted_diff_for_buffer(
2036 cx.entity(),
2037 &project,
2038 this.buffer.read(cx).all_buffers(),
2039 this.buffer.clone(),
2040 cx,
2041 )
2042 .shared(),
2043 );
2044 }
2045 }));
2046 }
2047
2048 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2049
2050 let inlay_hint_settings =
2051 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2052 let focus_handle = cx.focus_handle();
2053 if !is_minimap {
2054 cx.on_focus(&focus_handle, window, Self::handle_focus)
2055 .detach();
2056 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2057 .detach();
2058 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2059 .detach();
2060 cx.on_blur(&focus_handle, window, Self::handle_blur)
2061 .detach();
2062 cx.observe_pending_input(window, Self::observe_pending_input)
2063 .detach();
2064 }
2065
2066 let show_indent_guides =
2067 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2068 Some(false)
2069 } else {
2070 None
2071 };
2072
2073 let breakpoint_store = match (&mode, project.as_ref()) {
2074 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2075 _ => None,
2076 };
2077
2078 let mut code_action_providers = Vec::new();
2079 let mut load_uncommitted_diff = None;
2080 if let Some(project) = project.clone() {
2081 load_uncommitted_diff = Some(
2082 update_uncommitted_diff_for_buffer(
2083 cx.entity(),
2084 &project,
2085 multi_buffer.read(cx).all_buffers(),
2086 multi_buffer.clone(),
2087 cx,
2088 )
2089 .shared(),
2090 );
2091 code_action_providers.push(Rc::new(project) as Rc<_>);
2092 }
2093
2094 let mut editor = Self {
2095 focus_handle,
2096 show_cursor_when_unfocused: false,
2097 last_focused_descendant: None,
2098 buffer: multi_buffer.clone(),
2099 display_map: display_map.clone(),
2100 placeholder_display_map: None,
2101 selections,
2102 scroll_manager: ScrollManager::new(cx),
2103 columnar_selection_state: None,
2104 add_selections_state: None,
2105 select_next_state: None,
2106 select_prev_state: None,
2107 selection_history: SelectionHistory::default(),
2108 defer_selection_effects: false,
2109 deferred_selection_effects_state: None,
2110 autoclose_regions: Vec::new(),
2111 snippet_stack: InvalidationStack::default(),
2112 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2113 ime_transaction: None,
2114 active_diagnostics: ActiveDiagnostic::None,
2115 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2116 inline_diagnostics_update: Task::ready(()),
2117 inline_diagnostics: Vec::new(),
2118 soft_wrap_mode_override,
2119 diagnostics_max_severity,
2120 hard_wrap: None,
2121 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2122 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2123 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2124 project,
2125 blink_manager: blink_manager.clone(),
2126 show_local_selections: true,
2127 show_scrollbars: ScrollbarAxes {
2128 horizontal: full_mode,
2129 vertical: full_mode,
2130 },
2131 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2132 offset_content: !matches!(mode, EditorMode::SingleLine),
2133 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2134 show_gutter: full_mode,
2135 show_line_numbers: (!full_mode).then_some(false),
2136 use_relative_line_numbers: None,
2137 disable_expand_excerpt_buttons: !full_mode,
2138 show_git_diff_gutter: None,
2139 show_code_actions: None,
2140 show_runnables: None,
2141 show_breakpoints: None,
2142 show_wrap_guides: None,
2143 show_indent_guides,
2144 highlight_order: 0,
2145 highlighted_rows: HashMap::default(),
2146 background_highlights: HashMap::default(),
2147 gutter_highlights: HashMap::default(),
2148 scrollbar_marker_state: ScrollbarMarkerState::default(),
2149 active_indent_guides_state: ActiveIndentGuidesState::default(),
2150 nav_history: None,
2151 context_menu: RefCell::new(None),
2152 context_menu_options: None,
2153 mouse_context_menu: None,
2154 completion_tasks: Vec::new(),
2155 inline_blame_popover: None,
2156 inline_blame_popover_show_task: None,
2157 signature_help_state: SignatureHelpState::default(),
2158 auto_signature_help: None,
2159 find_all_references_task_sources: Vec::new(),
2160 next_completion_id: 0,
2161 next_inlay_id: 0,
2162 code_action_providers,
2163 available_code_actions: None,
2164 code_actions_task: None,
2165 quick_selection_highlight_task: None,
2166 debounced_selection_highlight_task: None,
2167 document_highlights_task: None,
2168 linked_editing_range_task: None,
2169 pending_rename: None,
2170 searchable: !is_minimap,
2171 cursor_shape: EditorSettings::get_global(cx)
2172 .cursor_shape
2173 .unwrap_or_default(),
2174 current_line_highlight: None,
2175 autoindent_mode: Some(AutoindentMode::EachLine),
2176
2177 workspace: None,
2178 input_enabled: !is_minimap,
2179 use_modal_editing: full_mode,
2180 read_only: is_minimap,
2181 use_autoclose: true,
2182 use_auto_surround: true,
2183 auto_replace_emoji_shortcode: false,
2184 jsx_tag_auto_close_enabled_in_any_buffer: false,
2185 leader_id: None,
2186 remote_id: None,
2187 hover_state: HoverState::default(),
2188 pending_mouse_down: None,
2189 hovered_link_state: None,
2190 edit_prediction_provider: None,
2191 active_edit_prediction: None,
2192 stale_edit_prediction_in_menu: None,
2193 edit_prediction_preview: EditPredictionPreview::Inactive {
2194 released_too_fast: false,
2195 },
2196 inline_diagnostics_enabled: full_mode,
2197 diagnostics_enabled: full_mode,
2198 word_completions_enabled: full_mode,
2199 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2200 gutter_hovered: false,
2201 pixel_position_of_newest_cursor: None,
2202 last_bounds: None,
2203 last_position_map: None,
2204 expect_bounds_change: None,
2205 gutter_dimensions: GutterDimensions::default(),
2206 style: None,
2207 show_cursor_names: false,
2208 hovered_cursors: HashMap::default(),
2209 next_editor_action_id: EditorActionId::default(),
2210 editor_actions: Rc::default(),
2211 edit_predictions_hidden_for_vim_mode: false,
2212 show_edit_predictions_override: None,
2213 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2214 edit_prediction_settings: EditPredictionSettings::Disabled,
2215 edit_prediction_indent_conflict: false,
2216 edit_prediction_requires_modifier_in_indent_conflict: true,
2217 custom_context_menu: None,
2218 show_git_blame_gutter: false,
2219 show_git_blame_inline: false,
2220 show_selection_menu: None,
2221 show_git_blame_inline_delay_task: None,
2222 git_blame_inline_enabled: full_mode
2223 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2224 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2225 buffer_serialization: is_minimap.not().then(|| {
2226 BufferSerialization::new(
2227 ProjectSettings::get_global(cx)
2228 .session
2229 .restore_unsaved_buffers,
2230 )
2231 }),
2232 blame: None,
2233 blame_subscription: None,
2234 tasks: BTreeMap::default(),
2235
2236 breakpoint_store,
2237 gutter_breakpoint_indicator: (None, None),
2238 hovered_diff_hunk_row: None,
2239 _subscriptions: (!is_minimap)
2240 .then(|| {
2241 vec![
2242 cx.observe(&multi_buffer, Self::on_buffer_changed),
2243 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2244 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2245 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2246 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2247 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2248 cx.observe_window_activation(window, |editor, window, cx| {
2249 let active = window.is_window_active();
2250 editor.blink_manager.update(cx, |blink_manager, cx| {
2251 if active {
2252 blink_manager.enable(cx);
2253 } else {
2254 blink_manager.disable(cx);
2255 }
2256 });
2257 if active {
2258 editor.show_mouse_cursor(cx);
2259 }
2260 }),
2261 ]
2262 })
2263 .unwrap_or_default(),
2264 tasks_update_task: None,
2265 pull_diagnostics_task: Task::ready(()),
2266 colors: None,
2267 refresh_colors_task: Task::ready(()),
2268 inlay_hints: None,
2269 next_color_inlay_id: 0,
2270 post_scroll_update: Task::ready(()),
2271 linked_edit_ranges: Default::default(),
2272 in_project_search: false,
2273 previous_search_ranges: None,
2274 breadcrumb_header: None,
2275 focused_block: None,
2276 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2277 addons: HashMap::default(),
2278 registered_buffers: HashMap::default(),
2279 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2280 selection_mark_mode: false,
2281 toggle_fold_multiple_buffers: Task::ready(()),
2282 serialize_selections: Task::ready(()),
2283 serialize_folds: Task::ready(()),
2284 text_style_refinement: None,
2285 load_diff_task: load_uncommitted_diff,
2286 temporary_diff_override: false,
2287 mouse_cursor_hidden: false,
2288 minimap: None,
2289 hide_mouse_mode: EditorSettings::get_global(cx)
2290 .hide_mouse
2291 .unwrap_or_default(),
2292 change_list: ChangeList::new(),
2293 mode,
2294 selection_drag_state: SelectionDragState::None,
2295 folding_newlines: Task::ready(()),
2296 lookup_key: None,
2297 };
2298
2299 if is_minimap {
2300 return editor;
2301 }
2302
2303 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2304 editor
2305 ._subscriptions
2306 .push(cx.observe(breakpoints, |_, _, cx| {
2307 cx.notify();
2308 }));
2309 }
2310 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2311 editor._subscriptions.extend(project_subscriptions);
2312
2313 editor._subscriptions.push(cx.subscribe_in(
2314 &cx.entity(),
2315 window,
2316 |editor, _, e: &EditorEvent, window, cx| match e {
2317 EditorEvent::ScrollPositionChanged { local, .. } => {
2318 if *local {
2319 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2320 editor.inline_blame_popover.take();
2321 let new_anchor = editor.scroll_manager.anchor();
2322 let snapshot = editor.snapshot(window, cx);
2323 editor.update_restoration_data(cx, move |data| {
2324 data.scroll_position = (
2325 new_anchor.top_row(snapshot.buffer_snapshot()),
2326 new_anchor.offset,
2327 );
2328 });
2329
2330 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2331 cx.background_executor()
2332 .timer(Duration::from_millis(50))
2333 .await;
2334 editor
2335 .update_in(cx, |editor, window, cx| {
2336 editor.register_visible_buffers(cx);
2337 editor.refresh_colors_for_visible_range(None, window, cx);
2338 editor.refresh_inlay_hints(
2339 InlayHintRefreshReason::NewLinesShown,
2340 cx,
2341 );
2342 })
2343 .ok();
2344 });
2345 }
2346 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2347 cx.background_executor()
2348 .timer(Duration::from_millis(50))
2349 .await;
2350 editor
2351 .update_in(cx, |editor, window, cx| {
2352 editor.register_visible_buffers(cx);
2353 editor.refresh_colors_for_visible_range(None, window, cx);
2354 editor
2355 .refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2356 editor.colorize_brackets(cx);
2357 })
2358 .ok();
2359 });
2360 }
2361 EditorEvent::Edited { .. } => {
2362 if vim_flavor(cx).is_none() {
2363 let display_map = editor.display_snapshot(cx);
2364 let selections = editor.selections.all_adjusted_display(&display_map);
2365 let pop_state = editor
2366 .change_list
2367 .last()
2368 .map(|previous| {
2369 previous.len() == selections.len()
2370 && previous.iter().enumerate().all(|(ix, p)| {
2371 p.to_display_point(&display_map).row()
2372 == selections[ix].head().row()
2373 })
2374 })
2375 .unwrap_or(false);
2376 let new_positions = selections
2377 .into_iter()
2378 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2379 .collect();
2380 editor
2381 .change_list
2382 .push_to_change_list(pop_state, new_positions);
2383 }
2384 }
2385 _ => (),
2386 },
2387 ));
2388
2389 if let Some(dap_store) = editor
2390 .project
2391 .as_ref()
2392 .map(|project| project.read(cx).dap_store())
2393 {
2394 let weak_editor = cx.weak_entity();
2395
2396 editor
2397 ._subscriptions
2398 .push(
2399 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2400 let session_entity = cx.entity();
2401 weak_editor
2402 .update(cx, |editor, cx| {
2403 editor._subscriptions.push(
2404 cx.subscribe(&session_entity, Self::on_debug_session_event),
2405 );
2406 })
2407 .ok();
2408 }),
2409 );
2410
2411 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2412 editor
2413 ._subscriptions
2414 .push(cx.subscribe(&session, Self::on_debug_session_event));
2415 }
2416 }
2417
2418 // skip adding the initial selection to selection history
2419 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2420 editor.end_selection(window, cx);
2421 editor.selection_history.mode = SelectionHistoryMode::Normal;
2422
2423 editor.scroll_manager.show_scrollbars(window, cx);
2424 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2425
2426 if full_mode {
2427 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2428 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2429
2430 if editor.git_blame_inline_enabled {
2431 editor.start_git_blame_inline(false, window, cx);
2432 }
2433
2434 editor.go_to_active_debug_line(window, cx);
2435
2436 editor.minimap =
2437 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2438 editor.colors = Some(LspColorData::new(cx));
2439 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2440
2441 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2442 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2443 }
2444 editor.update_lsp_data(None, window, cx);
2445 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2446 }
2447
2448 editor
2449 }
2450
2451 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2452 self.selections.display_map(cx)
2453 }
2454
2455 pub fn deploy_mouse_context_menu(
2456 &mut self,
2457 position: gpui::Point<Pixels>,
2458 context_menu: Entity<ContextMenu>,
2459 window: &mut Window,
2460 cx: &mut Context<Self>,
2461 ) {
2462 self.mouse_context_menu = Some(MouseContextMenu::new(
2463 self,
2464 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2465 context_menu,
2466 window,
2467 cx,
2468 ));
2469 }
2470
2471 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2472 self.mouse_context_menu
2473 .as_ref()
2474 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2475 }
2476
2477 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2478 if self
2479 .selections
2480 .pending_anchor()
2481 .is_some_and(|pending_selection| {
2482 let snapshot = self.buffer().read(cx).snapshot(cx);
2483 pending_selection.range().includes(range, &snapshot)
2484 })
2485 {
2486 return true;
2487 }
2488
2489 self.selections
2490 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2491 .into_iter()
2492 .any(|selection| {
2493 // This is needed to cover a corner case, if we just check for an existing
2494 // selection in the fold range, having a cursor at the start of the fold
2495 // marks it as selected. Non-empty selections don't cause this.
2496 let length = selection.end - selection.start;
2497 length > 0
2498 })
2499 }
2500
2501 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2502 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2503 }
2504
2505 fn key_context_internal(
2506 &self,
2507 has_active_edit_prediction: bool,
2508 window: &mut Window,
2509 cx: &mut App,
2510 ) -> KeyContext {
2511 let mut key_context = KeyContext::new_with_defaults();
2512 key_context.add("Editor");
2513 let mode = match self.mode {
2514 EditorMode::SingleLine => "single_line",
2515 EditorMode::AutoHeight { .. } => "auto_height",
2516 EditorMode::Minimap { .. } => "minimap",
2517 EditorMode::Full { .. } => "full",
2518 };
2519
2520 if EditorSettings::jupyter_enabled(cx) {
2521 key_context.add("jupyter");
2522 }
2523
2524 key_context.set("mode", mode);
2525 if self.pending_rename.is_some() {
2526 key_context.add("renaming");
2527 }
2528
2529 match self.context_menu.borrow().as_ref() {
2530 Some(CodeContextMenu::Completions(menu)) => {
2531 if menu.visible() {
2532 key_context.add("menu");
2533 key_context.add("showing_completions");
2534 }
2535 }
2536 Some(CodeContextMenu::CodeActions(menu)) => {
2537 if menu.visible() {
2538 key_context.add("menu");
2539 key_context.add("showing_code_actions")
2540 }
2541 }
2542 None => {}
2543 }
2544
2545 if self.signature_help_state.has_multiple_signatures() {
2546 key_context.add("showing_signature_help");
2547 }
2548
2549 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2550 if !self.focus_handle(cx).contains_focused(window, cx)
2551 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2552 {
2553 for addon in self.addons.values() {
2554 addon.extend_key_context(&mut key_context, cx)
2555 }
2556 }
2557
2558 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2559 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2560 Some(
2561 file.full_path(cx)
2562 .extension()?
2563 .to_string_lossy()
2564 .into_owned(),
2565 )
2566 }) {
2567 key_context.set("extension", extension);
2568 }
2569 } else {
2570 key_context.add("multibuffer");
2571 }
2572
2573 if has_active_edit_prediction {
2574 if self.edit_prediction_in_conflict() {
2575 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2576 } else {
2577 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2578 key_context.add("copilot_suggestion");
2579 }
2580 }
2581
2582 if self.selection_mark_mode {
2583 key_context.add("selection_mode");
2584 }
2585
2586 let disjoint = self.selections.disjoint_anchors();
2587 let snapshot = self.snapshot(window, cx);
2588 let snapshot = snapshot.buffer_snapshot();
2589 if self.mode == EditorMode::SingleLine
2590 && let [selection] = disjoint
2591 && selection.start == selection.end
2592 && selection.end.to_offset(snapshot) == snapshot.len()
2593 {
2594 key_context.add("end_of_input");
2595 }
2596
2597 key_context
2598 }
2599
2600 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2601 self.last_bounds.as_ref()
2602 }
2603
2604 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2605 if self.mouse_cursor_hidden {
2606 self.mouse_cursor_hidden = false;
2607 cx.notify();
2608 }
2609 }
2610
2611 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2612 let hide_mouse_cursor = match origin {
2613 HideMouseCursorOrigin::TypingAction => {
2614 matches!(
2615 self.hide_mouse_mode,
2616 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2617 )
2618 }
2619 HideMouseCursorOrigin::MovementAction => {
2620 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2621 }
2622 };
2623 if self.mouse_cursor_hidden != hide_mouse_cursor {
2624 self.mouse_cursor_hidden = hide_mouse_cursor;
2625 cx.notify();
2626 }
2627 }
2628
2629 pub fn edit_prediction_in_conflict(&self) -> bool {
2630 if !self.show_edit_predictions_in_menu() {
2631 return false;
2632 }
2633
2634 let showing_completions = self
2635 .context_menu
2636 .borrow()
2637 .as_ref()
2638 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2639
2640 showing_completions
2641 || self.edit_prediction_requires_modifier()
2642 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2643 // bindings to insert tab characters.
2644 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2645 }
2646
2647 pub fn accept_edit_prediction_keybind(
2648 &self,
2649 accept_partial: bool,
2650 window: &mut Window,
2651 cx: &mut App,
2652 ) -> AcceptEditPredictionBinding {
2653 let key_context = self.key_context_internal(true, window, cx);
2654 let in_conflict = self.edit_prediction_in_conflict();
2655
2656 let bindings = if accept_partial {
2657 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2658 } else {
2659 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2660 };
2661
2662 // TODO: if the binding contains multiple keystrokes, display all of them, not
2663 // just the first one.
2664 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2665 !in_conflict
2666 || binding
2667 .keystrokes()
2668 .first()
2669 .is_some_and(|keystroke| keystroke.modifiers().modified())
2670 }))
2671 }
2672
2673 pub fn new_file(
2674 workspace: &mut Workspace,
2675 _: &workspace::NewFile,
2676 window: &mut Window,
2677 cx: &mut Context<Workspace>,
2678 ) {
2679 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2680 "Failed to create buffer",
2681 window,
2682 cx,
2683 |e, _, _| match e.error_code() {
2684 ErrorCode::RemoteUpgradeRequired => Some(format!(
2685 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2686 e.error_tag("required").unwrap_or("the latest version")
2687 )),
2688 _ => None,
2689 },
2690 );
2691 }
2692
2693 pub fn new_in_workspace(
2694 workspace: &mut Workspace,
2695 window: &mut Window,
2696 cx: &mut Context<Workspace>,
2697 ) -> Task<Result<Entity<Editor>>> {
2698 let project = workspace.project().clone();
2699 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2700
2701 cx.spawn_in(window, async move |workspace, cx| {
2702 let buffer = create.await?;
2703 workspace.update_in(cx, |workspace, window, cx| {
2704 let editor =
2705 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2706 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2707 editor
2708 })
2709 })
2710 }
2711
2712 fn new_file_vertical(
2713 workspace: &mut Workspace,
2714 _: &workspace::NewFileSplitVertical,
2715 window: &mut Window,
2716 cx: &mut Context<Workspace>,
2717 ) {
2718 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2719 }
2720
2721 fn new_file_horizontal(
2722 workspace: &mut Workspace,
2723 _: &workspace::NewFileSplitHorizontal,
2724 window: &mut Window,
2725 cx: &mut Context<Workspace>,
2726 ) {
2727 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2728 }
2729
2730 fn new_file_split(
2731 workspace: &mut Workspace,
2732 action: &workspace::NewFileSplit,
2733 window: &mut Window,
2734 cx: &mut Context<Workspace>,
2735 ) {
2736 Self::new_file_in_direction(workspace, action.0, window, cx)
2737 }
2738
2739 fn new_file_in_direction(
2740 workspace: &mut Workspace,
2741 direction: SplitDirection,
2742 window: &mut Window,
2743 cx: &mut Context<Workspace>,
2744 ) {
2745 let project = workspace.project().clone();
2746 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2747
2748 cx.spawn_in(window, async move |workspace, cx| {
2749 let buffer = create.await?;
2750 workspace.update_in(cx, move |workspace, window, cx| {
2751 workspace.split_item(
2752 direction,
2753 Box::new(
2754 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2755 ),
2756 window,
2757 cx,
2758 )
2759 })?;
2760 anyhow::Ok(())
2761 })
2762 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2763 match e.error_code() {
2764 ErrorCode::RemoteUpgradeRequired => Some(format!(
2765 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2766 e.error_tag("required").unwrap_or("the latest version")
2767 )),
2768 _ => None,
2769 }
2770 });
2771 }
2772
2773 pub fn leader_id(&self) -> Option<CollaboratorId> {
2774 self.leader_id
2775 }
2776
2777 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2778 &self.buffer
2779 }
2780
2781 pub fn project(&self) -> Option<&Entity<Project>> {
2782 self.project.as_ref()
2783 }
2784
2785 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2786 self.workspace.as_ref()?.0.upgrade()
2787 }
2788
2789 /// Returns the workspace serialization ID if this editor should be serialized.
2790 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2791 self.workspace
2792 .as_ref()
2793 .filter(|_| self.should_serialize_buffer())
2794 .and_then(|workspace| workspace.1)
2795 }
2796
2797 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2798 self.buffer().read(cx).title(cx)
2799 }
2800
2801 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2802 let git_blame_gutter_max_author_length = self
2803 .render_git_blame_gutter(cx)
2804 .then(|| {
2805 if let Some(blame) = self.blame.as_ref() {
2806 let max_author_length =
2807 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2808 Some(max_author_length)
2809 } else {
2810 None
2811 }
2812 })
2813 .flatten();
2814
2815 EditorSnapshot {
2816 mode: self.mode.clone(),
2817 show_gutter: self.show_gutter,
2818 show_line_numbers: self.show_line_numbers,
2819 show_git_diff_gutter: self.show_git_diff_gutter,
2820 show_code_actions: self.show_code_actions,
2821 show_runnables: self.show_runnables,
2822 show_breakpoints: self.show_breakpoints,
2823 git_blame_gutter_max_author_length,
2824 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2825 placeholder_display_snapshot: self
2826 .placeholder_display_map
2827 .as_ref()
2828 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2829 scroll_anchor: self.scroll_manager.anchor(),
2830 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2831 is_focused: self.focus_handle.is_focused(window),
2832 current_line_highlight: self
2833 .current_line_highlight
2834 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2835 gutter_hovered: self.gutter_hovered,
2836 }
2837 }
2838
2839 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2840 self.buffer.read(cx).language_at(point, cx)
2841 }
2842
2843 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2844 self.buffer.read(cx).read(cx).file_at(point).cloned()
2845 }
2846
2847 pub fn active_excerpt(
2848 &self,
2849 cx: &App,
2850 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2851 self.buffer
2852 .read(cx)
2853 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2854 }
2855
2856 pub fn mode(&self) -> &EditorMode {
2857 &self.mode
2858 }
2859
2860 pub fn set_mode(&mut self, mode: EditorMode) {
2861 self.mode = mode;
2862 }
2863
2864 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2865 self.collaboration_hub.as_deref()
2866 }
2867
2868 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2869 self.collaboration_hub = Some(hub);
2870 }
2871
2872 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2873 self.in_project_search = in_project_search;
2874 }
2875
2876 pub fn set_custom_context_menu(
2877 &mut self,
2878 f: impl 'static
2879 + Fn(
2880 &mut Self,
2881 DisplayPoint,
2882 &mut Window,
2883 &mut Context<Self>,
2884 ) -> Option<Entity<ui::ContextMenu>>,
2885 ) {
2886 self.custom_context_menu = Some(Box::new(f))
2887 }
2888
2889 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2890 self.completion_provider = provider;
2891 }
2892
2893 #[cfg(any(test, feature = "test-support"))]
2894 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2895 self.completion_provider.clone()
2896 }
2897
2898 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2899 self.semantics_provider.clone()
2900 }
2901
2902 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2903 self.semantics_provider = provider;
2904 }
2905
2906 pub fn set_edit_prediction_provider<T>(
2907 &mut self,
2908 provider: Option<Entity<T>>,
2909 window: &mut Window,
2910 cx: &mut Context<Self>,
2911 ) where
2912 T: EditPredictionProvider,
2913 {
2914 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2915 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2916 if this.focus_handle.is_focused(window) {
2917 this.update_visible_edit_prediction(window, cx);
2918 }
2919 }),
2920 provider: Arc::new(provider),
2921 });
2922 self.update_edit_prediction_settings(cx);
2923 self.refresh_edit_prediction(false, false, window, cx);
2924 }
2925
2926 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2927 self.placeholder_display_map
2928 .as_ref()
2929 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2930 }
2931
2932 pub fn set_placeholder_text(
2933 &mut self,
2934 placeholder_text: &str,
2935 window: &mut Window,
2936 cx: &mut Context<Self>,
2937 ) {
2938 let multibuffer = cx
2939 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2940
2941 let style = window.text_style();
2942
2943 self.placeholder_display_map = Some(cx.new(|cx| {
2944 DisplayMap::new(
2945 multibuffer,
2946 style.font(),
2947 style.font_size.to_pixels(window.rem_size()),
2948 None,
2949 FILE_HEADER_HEIGHT,
2950 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2951 Default::default(),
2952 DiagnosticSeverity::Off,
2953 cx,
2954 )
2955 }));
2956 cx.notify();
2957 }
2958
2959 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2960 self.cursor_shape = cursor_shape;
2961
2962 // Disrupt blink for immediate user feedback that the cursor shape has changed
2963 self.blink_manager.update(cx, BlinkManager::show_cursor);
2964
2965 cx.notify();
2966 }
2967
2968 pub fn set_current_line_highlight(
2969 &mut self,
2970 current_line_highlight: Option<CurrentLineHighlight>,
2971 ) {
2972 self.current_line_highlight = current_line_highlight;
2973 }
2974
2975 pub fn range_for_match<T: std::marker::Copy>(
2976 &self,
2977 range: &Range<T>,
2978 collapse: bool,
2979 ) -> Range<T> {
2980 if collapse {
2981 return range.start..range.start;
2982 }
2983 range.clone()
2984 }
2985
2986 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2987 if self.display_map.read(cx).clip_at_line_ends != clip {
2988 self.display_map
2989 .update(cx, |map, _| map.clip_at_line_ends = clip);
2990 }
2991 }
2992
2993 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2994 self.input_enabled = input_enabled;
2995 }
2996
2997 pub fn set_edit_predictions_hidden_for_vim_mode(
2998 &mut self,
2999 hidden: bool,
3000 window: &mut Window,
3001 cx: &mut Context<Self>,
3002 ) {
3003 if hidden != self.edit_predictions_hidden_for_vim_mode {
3004 self.edit_predictions_hidden_for_vim_mode = hidden;
3005 if hidden {
3006 self.update_visible_edit_prediction(window, cx);
3007 } else {
3008 self.refresh_edit_prediction(true, false, window, cx);
3009 }
3010 }
3011 }
3012
3013 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3014 self.menu_edit_predictions_policy = value;
3015 }
3016
3017 pub fn set_autoindent(&mut self, autoindent: bool) {
3018 if autoindent {
3019 self.autoindent_mode = Some(AutoindentMode::EachLine);
3020 } else {
3021 self.autoindent_mode = None;
3022 }
3023 }
3024
3025 pub fn read_only(&self, cx: &App) -> bool {
3026 self.read_only || self.buffer.read(cx).read_only()
3027 }
3028
3029 pub fn set_read_only(&mut self, read_only: bool) {
3030 self.read_only = read_only;
3031 }
3032
3033 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3034 self.use_autoclose = autoclose;
3035 }
3036
3037 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3038 self.use_auto_surround = auto_surround;
3039 }
3040
3041 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3042 self.auto_replace_emoji_shortcode = auto_replace;
3043 }
3044
3045 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3046 self.buffer_serialization = should_serialize.then(|| {
3047 BufferSerialization::new(
3048 ProjectSettings::get_global(cx)
3049 .session
3050 .restore_unsaved_buffers,
3051 )
3052 })
3053 }
3054
3055 fn should_serialize_buffer(&self) -> bool {
3056 self.buffer_serialization.is_some()
3057 }
3058
3059 pub fn toggle_edit_predictions(
3060 &mut self,
3061 _: &ToggleEditPrediction,
3062 window: &mut Window,
3063 cx: &mut Context<Self>,
3064 ) {
3065 if self.show_edit_predictions_override.is_some() {
3066 self.set_show_edit_predictions(None, window, cx);
3067 } else {
3068 let show_edit_predictions = !self.edit_predictions_enabled();
3069 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3070 }
3071 }
3072
3073 pub fn set_show_edit_predictions(
3074 &mut self,
3075 show_edit_predictions: Option<bool>,
3076 window: &mut Window,
3077 cx: &mut Context<Self>,
3078 ) {
3079 self.show_edit_predictions_override = show_edit_predictions;
3080 self.update_edit_prediction_settings(cx);
3081
3082 if let Some(false) = show_edit_predictions {
3083 self.discard_edit_prediction(false, cx);
3084 } else {
3085 self.refresh_edit_prediction(false, true, window, cx);
3086 }
3087 }
3088
3089 fn edit_predictions_disabled_in_scope(
3090 &self,
3091 buffer: &Entity<Buffer>,
3092 buffer_position: language::Anchor,
3093 cx: &App,
3094 ) -> bool {
3095 let snapshot = buffer.read(cx).snapshot();
3096 let settings = snapshot.settings_at(buffer_position, cx);
3097
3098 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3099 return false;
3100 };
3101
3102 scope.override_name().is_some_and(|scope_name| {
3103 settings
3104 .edit_predictions_disabled_in
3105 .iter()
3106 .any(|s| s == scope_name)
3107 })
3108 }
3109
3110 pub fn set_use_modal_editing(&mut self, to: bool) {
3111 self.use_modal_editing = to;
3112 }
3113
3114 pub fn use_modal_editing(&self) -> bool {
3115 self.use_modal_editing
3116 }
3117
3118 fn selections_did_change(
3119 &mut self,
3120 local: bool,
3121 old_cursor_position: &Anchor,
3122 effects: SelectionEffects,
3123 window: &mut Window,
3124 cx: &mut Context<Self>,
3125 ) {
3126 window.invalidate_character_coordinates();
3127
3128 // Copy selections to primary selection buffer
3129 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3130 if local {
3131 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3132 let buffer_handle = self.buffer.read(cx).read(cx);
3133
3134 let mut text = String::new();
3135 for (index, selection) in selections.iter().enumerate() {
3136 let text_for_selection = buffer_handle
3137 .text_for_range(selection.start..selection.end)
3138 .collect::<String>();
3139
3140 text.push_str(&text_for_selection);
3141 if index != selections.len() - 1 {
3142 text.push('\n');
3143 }
3144 }
3145
3146 if !text.is_empty() {
3147 cx.write_to_primary(ClipboardItem::new_string(text));
3148 }
3149 }
3150
3151 let selection_anchors = self.selections.disjoint_anchors_arc();
3152
3153 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3154 self.buffer.update(cx, |buffer, cx| {
3155 buffer.set_active_selections(
3156 &selection_anchors,
3157 self.selections.line_mode(),
3158 self.cursor_shape,
3159 cx,
3160 )
3161 });
3162 }
3163 let display_map = self
3164 .display_map
3165 .update(cx, |display_map, cx| display_map.snapshot(cx));
3166 let buffer = display_map.buffer_snapshot();
3167 if self.selections.count() == 1 {
3168 self.add_selections_state = None;
3169 }
3170 self.select_next_state = None;
3171 self.select_prev_state = None;
3172 self.select_syntax_node_history.try_clear();
3173 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3174 self.snippet_stack.invalidate(&selection_anchors, buffer);
3175 self.take_rename(false, window, cx);
3176
3177 let newest_selection = self.selections.newest_anchor();
3178 let new_cursor_position = newest_selection.head();
3179 let selection_start = newest_selection.start;
3180
3181 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3182 self.push_to_nav_history(
3183 *old_cursor_position,
3184 Some(new_cursor_position.to_point(buffer)),
3185 false,
3186 effects.nav_history == Some(true),
3187 cx,
3188 );
3189 }
3190
3191 if local {
3192 if let Some(buffer_id) = new_cursor_position.buffer_id {
3193 self.register_buffer(buffer_id, cx);
3194 }
3195
3196 let mut context_menu = self.context_menu.borrow_mut();
3197 let completion_menu = match context_menu.as_ref() {
3198 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3199 Some(CodeContextMenu::CodeActions(_)) => {
3200 *context_menu = None;
3201 None
3202 }
3203 None => None,
3204 };
3205 let completion_position = completion_menu.map(|menu| menu.initial_position);
3206 drop(context_menu);
3207
3208 if effects.completions
3209 && let Some(completion_position) = completion_position
3210 {
3211 let start_offset = selection_start.to_offset(buffer);
3212 let position_matches = start_offset == completion_position.to_offset(buffer);
3213 let continue_showing = if position_matches {
3214 if self.snippet_stack.is_empty() {
3215 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3216 == Some(CharKind::Word)
3217 } else {
3218 // Snippet choices can be shown even when the cursor is in whitespace.
3219 // Dismissing the menu with actions like backspace is handled by
3220 // invalidation regions.
3221 true
3222 }
3223 } else {
3224 false
3225 };
3226
3227 if continue_showing {
3228 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3229 } else {
3230 self.hide_context_menu(window, cx);
3231 }
3232 }
3233
3234 hide_hover(self, cx);
3235
3236 if old_cursor_position.to_display_point(&display_map).row()
3237 != new_cursor_position.to_display_point(&display_map).row()
3238 {
3239 self.available_code_actions.take();
3240 }
3241 self.refresh_code_actions(window, cx);
3242 self.refresh_document_highlights(cx);
3243 refresh_linked_ranges(self, window, cx);
3244
3245 self.refresh_selected_text_highlights(false, window, cx);
3246 self.colorize_brackets(cx);
3247 self.refresh_matching_bracket_highlights(window, cx);
3248 self.update_visible_edit_prediction(window, cx);
3249 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3250 self.inline_blame_popover.take();
3251 if self.git_blame_inline_enabled {
3252 self.start_inline_blame_timer(window, cx);
3253 }
3254 }
3255
3256 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3257 cx.emit(EditorEvent::SelectionsChanged { local });
3258
3259 let selections = &self.selections.disjoint_anchors_arc();
3260 if selections.len() == 1 {
3261 cx.emit(SearchEvent::ActiveMatchChanged)
3262 }
3263 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3264 let inmemory_selections = selections
3265 .iter()
3266 .map(|s| {
3267 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3268 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3269 })
3270 .collect();
3271 self.update_restoration_data(cx, |data| {
3272 data.selections = inmemory_selections;
3273 });
3274
3275 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3276 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3277 {
3278 let snapshot = self.buffer().read(cx).snapshot(cx);
3279 let selections = selections.clone();
3280 let background_executor = cx.background_executor().clone();
3281 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3282 self.serialize_selections = cx.background_spawn(async move {
3283 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3284 let db_selections = selections
3285 .iter()
3286 .map(|selection| {
3287 (
3288 selection.start.to_offset(&snapshot),
3289 selection.end.to_offset(&snapshot),
3290 )
3291 })
3292 .collect();
3293
3294 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3295 .await
3296 .with_context(|| {
3297 format!(
3298 "persisting editor selections for editor {editor_id}, \
3299 workspace {workspace_id:?}"
3300 )
3301 })
3302 .log_err();
3303 });
3304 }
3305 }
3306
3307 cx.notify();
3308 }
3309
3310 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3311 use text::ToOffset as _;
3312 use text::ToPoint as _;
3313
3314 if self.mode.is_minimap()
3315 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3316 {
3317 return;
3318 }
3319
3320 if !self.buffer().read(cx).is_singleton() {
3321 return;
3322 }
3323
3324 let display_snapshot = self
3325 .display_map
3326 .update(cx, |display_map, cx| display_map.snapshot(cx));
3327 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3328 return;
3329 };
3330 let inmemory_folds = display_snapshot
3331 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3332 .map(|fold| {
3333 fold.range.start.text_anchor.to_point(&snapshot)
3334 ..fold.range.end.text_anchor.to_point(&snapshot)
3335 })
3336 .collect();
3337 self.update_restoration_data(cx, |data| {
3338 data.folds = inmemory_folds;
3339 });
3340
3341 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3342 return;
3343 };
3344 let background_executor = cx.background_executor().clone();
3345 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3346 let db_folds = display_snapshot
3347 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3348 .map(|fold| {
3349 (
3350 fold.range.start.text_anchor.to_offset(&snapshot),
3351 fold.range.end.text_anchor.to_offset(&snapshot),
3352 )
3353 })
3354 .collect();
3355 self.serialize_folds = cx.background_spawn(async move {
3356 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3357 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3358 .await
3359 .with_context(|| {
3360 format!(
3361 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3362 )
3363 })
3364 .log_err();
3365 });
3366 }
3367
3368 pub fn sync_selections(
3369 &mut self,
3370 other: Entity<Editor>,
3371 cx: &mut Context<Self>,
3372 ) -> gpui::Subscription {
3373 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3374 if !other_selections.is_empty() {
3375 self.selections.change_with(cx, |selections| {
3376 selections.select_anchors(other_selections);
3377 });
3378 }
3379
3380 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3381 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3382 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3383 if other_selections.is_empty() {
3384 return;
3385 }
3386 this.selections.change_with(cx, |selections| {
3387 selections.select_anchors(other_selections);
3388 });
3389 }
3390 });
3391
3392 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3393 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3394 let these_selections = this.selections.disjoint_anchors().to_vec();
3395 if these_selections.is_empty() {
3396 return;
3397 }
3398 other.update(cx, |other_editor, cx| {
3399 other_editor.selections.change_with(cx, |selections| {
3400 selections.select_anchors(these_selections);
3401 })
3402 });
3403 }
3404 });
3405
3406 Subscription::join(other_subscription, this_subscription)
3407 }
3408
3409 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3410 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3411 /// effects of selection change occur at the end of the transaction.
3412 pub fn change_selections<R>(
3413 &mut self,
3414 effects: SelectionEffects,
3415 window: &mut Window,
3416 cx: &mut Context<Self>,
3417 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3418 ) -> R {
3419 if let Some(state) = &mut self.deferred_selection_effects_state {
3420 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3421 state.effects.completions = effects.completions;
3422 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3423 let (changed, result) = self.selections.change_with(cx, change);
3424 state.changed |= changed;
3425 return result;
3426 }
3427 let mut state = DeferredSelectionEffectsState {
3428 changed: false,
3429 effects,
3430 old_cursor_position: self.selections.newest_anchor().head(),
3431 history_entry: SelectionHistoryEntry {
3432 selections: self.selections.disjoint_anchors_arc(),
3433 select_next_state: self.select_next_state.clone(),
3434 select_prev_state: self.select_prev_state.clone(),
3435 add_selections_state: self.add_selections_state.clone(),
3436 },
3437 };
3438 let (changed, result) = self.selections.change_with(cx, change);
3439 state.changed = state.changed || changed;
3440 if self.defer_selection_effects {
3441 self.deferred_selection_effects_state = Some(state);
3442 } else {
3443 self.apply_selection_effects(state, window, cx);
3444 }
3445 result
3446 }
3447
3448 /// Defers the effects of selection change, so that the effects of multiple calls to
3449 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3450 /// to selection history and the state of popovers based on selection position aren't
3451 /// erroneously updated.
3452 pub fn with_selection_effects_deferred<R>(
3453 &mut self,
3454 window: &mut Window,
3455 cx: &mut Context<Self>,
3456 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3457 ) -> R {
3458 let already_deferred = self.defer_selection_effects;
3459 self.defer_selection_effects = true;
3460 let result = update(self, window, cx);
3461 if !already_deferred {
3462 self.defer_selection_effects = false;
3463 if let Some(state) = self.deferred_selection_effects_state.take() {
3464 self.apply_selection_effects(state, window, cx);
3465 }
3466 }
3467 result
3468 }
3469
3470 fn apply_selection_effects(
3471 &mut self,
3472 state: DeferredSelectionEffectsState,
3473 window: &mut Window,
3474 cx: &mut Context<Self>,
3475 ) {
3476 if state.changed {
3477 self.selection_history.push(state.history_entry);
3478
3479 if let Some(autoscroll) = state.effects.scroll {
3480 self.request_autoscroll(autoscroll, cx);
3481 }
3482
3483 let old_cursor_position = &state.old_cursor_position;
3484
3485 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3486
3487 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3488 self.show_signature_help(&ShowSignatureHelp, window, cx);
3489 }
3490 }
3491 }
3492
3493 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3494 where
3495 I: IntoIterator<Item = (Range<S>, T)>,
3496 S: ToOffset,
3497 T: Into<Arc<str>>,
3498 {
3499 if self.read_only(cx) {
3500 return;
3501 }
3502
3503 self.buffer
3504 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3505 }
3506
3507 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3508 where
3509 I: IntoIterator<Item = (Range<S>, T)>,
3510 S: ToOffset,
3511 T: Into<Arc<str>>,
3512 {
3513 if self.read_only(cx) {
3514 return;
3515 }
3516
3517 self.buffer.update(cx, |buffer, cx| {
3518 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3519 });
3520 }
3521
3522 pub fn edit_with_block_indent<I, S, T>(
3523 &mut self,
3524 edits: I,
3525 original_indent_columns: Vec<Option<u32>>,
3526 cx: &mut Context<Self>,
3527 ) where
3528 I: IntoIterator<Item = (Range<S>, T)>,
3529 S: ToOffset,
3530 T: Into<Arc<str>>,
3531 {
3532 if self.read_only(cx) {
3533 return;
3534 }
3535
3536 self.buffer.update(cx, |buffer, cx| {
3537 buffer.edit(
3538 edits,
3539 Some(AutoindentMode::Block {
3540 original_indent_columns,
3541 }),
3542 cx,
3543 )
3544 });
3545 }
3546
3547 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3548 self.hide_context_menu(window, cx);
3549
3550 match phase {
3551 SelectPhase::Begin {
3552 position,
3553 add,
3554 click_count,
3555 } => self.begin_selection(position, add, click_count, window, cx),
3556 SelectPhase::BeginColumnar {
3557 position,
3558 goal_column,
3559 reset,
3560 mode,
3561 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3562 SelectPhase::Extend {
3563 position,
3564 click_count,
3565 } => self.extend_selection(position, click_count, window, cx),
3566 SelectPhase::Update {
3567 position,
3568 goal_column,
3569 scroll_delta,
3570 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3571 SelectPhase::End => self.end_selection(window, cx),
3572 }
3573 }
3574
3575 fn extend_selection(
3576 &mut self,
3577 position: DisplayPoint,
3578 click_count: usize,
3579 window: &mut Window,
3580 cx: &mut Context<Self>,
3581 ) {
3582 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3583 let tail = self.selections.newest::<usize>(&display_map).tail();
3584 let click_count = click_count.max(match self.selections.select_mode() {
3585 SelectMode::Character => 1,
3586 SelectMode::Word(_) => 2,
3587 SelectMode::Line(_) => 3,
3588 SelectMode::All => 4,
3589 });
3590 self.begin_selection(position, false, click_count, window, cx);
3591
3592 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3593
3594 let current_selection = match self.selections.select_mode() {
3595 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3596 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3597 };
3598
3599 let mut pending_selection = self
3600 .selections
3601 .pending_anchor()
3602 .cloned()
3603 .expect("extend_selection not called with pending selection");
3604
3605 if pending_selection
3606 .start
3607 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3608 == Ordering::Greater
3609 {
3610 pending_selection.start = current_selection.start;
3611 }
3612 if pending_selection
3613 .end
3614 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3615 == Ordering::Less
3616 {
3617 pending_selection.end = current_selection.end;
3618 pending_selection.reversed = true;
3619 }
3620
3621 let mut pending_mode = self.selections.pending_mode().unwrap();
3622 match &mut pending_mode {
3623 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3624 _ => {}
3625 }
3626
3627 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3628 SelectionEffects::scroll(Autoscroll::fit())
3629 } else {
3630 SelectionEffects::no_scroll()
3631 };
3632
3633 self.change_selections(effects, window, cx, |s| {
3634 s.set_pending(pending_selection.clone(), pending_mode);
3635 s.set_is_extending(true);
3636 });
3637 }
3638
3639 fn begin_selection(
3640 &mut self,
3641 position: DisplayPoint,
3642 add: bool,
3643 click_count: usize,
3644 window: &mut Window,
3645 cx: &mut Context<Self>,
3646 ) {
3647 if !self.focus_handle.is_focused(window) {
3648 self.last_focused_descendant = None;
3649 window.focus(&self.focus_handle);
3650 }
3651
3652 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3653 let buffer = display_map.buffer_snapshot();
3654 let position = display_map.clip_point(position, Bias::Left);
3655
3656 let start;
3657 let end;
3658 let mode;
3659 let mut auto_scroll;
3660 match click_count {
3661 1 => {
3662 start = buffer.anchor_before(position.to_point(&display_map));
3663 end = start;
3664 mode = SelectMode::Character;
3665 auto_scroll = true;
3666 }
3667 2 => {
3668 let position = display_map
3669 .clip_point(position, Bias::Left)
3670 .to_offset(&display_map, Bias::Left);
3671 let (range, _) = buffer.surrounding_word(position, None);
3672 start = buffer.anchor_before(range.start);
3673 end = buffer.anchor_before(range.end);
3674 mode = SelectMode::Word(start..end);
3675 auto_scroll = true;
3676 }
3677 3 => {
3678 let position = display_map
3679 .clip_point(position, Bias::Left)
3680 .to_point(&display_map);
3681 let line_start = display_map.prev_line_boundary(position).0;
3682 let next_line_start = buffer.clip_point(
3683 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3684 Bias::Left,
3685 );
3686 start = buffer.anchor_before(line_start);
3687 end = buffer.anchor_before(next_line_start);
3688 mode = SelectMode::Line(start..end);
3689 auto_scroll = true;
3690 }
3691 _ => {
3692 start = buffer.anchor_before(0);
3693 end = buffer.anchor_before(buffer.len());
3694 mode = SelectMode::All;
3695 auto_scroll = false;
3696 }
3697 }
3698 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3699
3700 let point_to_delete: Option<usize> = {
3701 let selected_points: Vec<Selection<Point>> =
3702 self.selections.disjoint_in_range(start..end, &display_map);
3703
3704 if !add || click_count > 1 {
3705 None
3706 } else if !selected_points.is_empty() {
3707 Some(selected_points[0].id)
3708 } else {
3709 let clicked_point_already_selected =
3710 self.selections.disjoint_anchors().iter().find(|selection| {
3711 selection.start.to_point(buffer) == start.to_point(buffer)
3712 || selection.end.to_point(buffer) == end.to_point(buffer)
3713 });
3714
3715 clicked_point_already_selected.map(|selection| selection.id)
3716 }
3717 };
3718
3719 let selections_count = self.selections.count();
3720 let effects = if auto_scroll {
3721 SelectionEffects::default()
3722 } else {
3723 SelectionEffects::no_scroll()
3724 };
3725
3726 self.change_selections(effects, window, cx, |s| {
3727 if let Some(point_to_delete) = point_to_delete {
3728 s.delete(point_to_delete);
3729
3730 if selections_count == 1 {
3731 s.set_pending_anchor_range(start..end, mode);
3732 }
3733 } else {
3734 if !add {
3735 s.clear_disjoint();
3736 }
3737
3738 s.set_pending_anchor_range(start..end, mode);
3739 }
3740 });
3741 }
3742
3743 fn begin_columnar_selection(
3744 &mut self,
3745 position: DisplayPoint,
3746 goal_column: u32,
3747 reset: bool,
3748 mode: ColumnarMode,
3749 window: &mut Window,
3750 cx: &mut Context<Self>,
3751 ) {
3752 if !self.focus_handle.is_focused(window) {
3753 self.last_focused_descendant = None;
3754 window.focus(&self.focus_handle);
3755 }
3756
3757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3758
3759 if reset {
3760 let pointer_position = display_map
3761 .buffer_snapshot()
3762 .anchor_before(position.to_point(&display_map));
3763
3764 self.change_selections(
3765 SelectionEffects::scroll(Autoscroll::newest()),
3766 window,
3767 cx,
3768 |s| {
3769 s.clear_disjoint();
3770 s.set_pending_anchor_range(
3771 pointer_position..pointer_position,
3772 SelectMode::Character,
3773 );
3774 },
3775 );
3776 };
3777
3778 let tail = self.selections.newest::<Point>(&display_map).tail();
3779 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3780 self.columnar_selection_state = match mode {
3781 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3782 selection_tail: selection_anchor,
3783 display_point: if reset {
3784 if position.column() != goal_column {
3785 Some(DisplayPoint::new(position.row(), goal_column))
3786 } else {
3787 None
3788 }
3789 } else {
3790 None
3791 },
3792 }),
3793 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3794 selection_tail: selection_anchor,
3795 }),
3796 };
3797
3798 if !reset {
3799 self.select_columns(position, goal_column, &display_map, window, cx);
3800 }
3801 }
3802
3803 fn update_selection(
3804 &mut self,
3805 position: DisplayPoint,
3806 goal_column: u32,
3807 scroll_delta: gpui::Point<f32>,
3808 window: &mut Window,
3809 cx: &mut Context<Self>,
3810 ) {
3811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3812
3813 if self.columnar_selection_state.is_some() {
3814 self.select_columns(position, goal_column, &display_map, window, cx);
3815 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3816 let buffer = display_map.buffer_snapshot();
3817 let head;
3818 let tail;
3819 let mode = self.selections.pending_mode().unwrap();
3820 match &mode {
3821 SelectMode::Character => {
3822 head = position.to_point(&display_map);
3823 tail = pending.tail().to_point(buffer);
3824 }
3825 SelectMode::Word(original_range) => {
3826 let offset = display_map
3827 .clip_point(position, Bias::Left)
3828 .to_offset(&display_map, Bias::Left);
3829 let original_range = original_range.to_offset(buffer);
3830
3831 let head_offset = if buffer.is_inside_word(offset, None)
3832 || original_range.contains(&offset)
3833 {
3834 let (word_range, _) = buffer.surrounding_word(offset, None);
3835 if word_range.start < original_range.start {
3836 word_range.start
3837 } else {
3838 word_range.end
3839 }
3840 } else {
3841 offset
3842 };
3843
3844 head = head_offset.to_point(buffer);
3845 if head_offset <= original_range.start {
3846 tail = original_range.end.to_point(buffer);
3847 } else {
3848 tail = original_range.start.to_point(buffer);
3849 }
3850 }
3851 SelectMode::Line(original_range) => {
3852 let original_range = original_range.to_point(display_map.buffer_snapshot());
3853
3854 let position = display_map
3855 .clip_point(position, Bias::Left)
3856 .to_point(&display_map);
3857 let line_start = display_map.prev_line_boundary(position).0;
3858 let next_line_start = buffer.clip_point(
3859 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3860 Bias::Left,
3861 );
3862
3863 if line_start < original_range.start {
3864 head = line_start
3865 } else {
3866 head = next_line_start
3867 }
3868
3869 if head <= original_range.start {
3870 tail = original_range.end;
3871 } else {
3872 tail = original_range.start;
3873 }
3874 }
3875 SelectMode::All => {
3876 return;
3877 }
3878 };
3879
3880 if head < tail {
3881 pending.start = buffer.anchor_before(head);
3882 pending.end = buffer.anchor_before(tail);
3883 pending.reversed = true;
3884 } else {
3885 pending.start = buffer.anchor_before(tail);
3886 pending.end = buffer.anchor_before(head);
3887 pending.reversed = false;
3888 }
3889
3890 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3891 s.set_pending(pending.clone(), mode);
3892 });
3893 } else {
3894 log::error!("update_selection dispatched with no pending selection");
3895 return;
3896 }
3897
3898 self.apply_scroll_delta(scroll_delta, window, cx);
3899 cx.notify();
3900 }
3901
3902 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3903 self.columnar_selection_state.take();
3904 if let Some(pending_mode) = self.selections.pending_mode() {
3905 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3906 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3907 s.select(selections);
3908 s.clear_pending();
3909 if s.is_extending() {
3910 s.set_is_extending(false);
3911 } else {
3912 s.set_select_mode(pending_mode);
3913 }
3914 });
3915 }
3916 }
3917
3918 fn select_columns(
3919 &mut self,
3920 head: DisplayPoint,
3921 goal_column: u32,
3922 display_map: &DisplaySnapshot,
3923 window: &mut Window,
3924 cx: &mut Context<Self>,
3925 ) {
3926 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3927 return;
3928 };
3929
3930 let tail = match columnar_state {
3931 ColumnarSelectionState::FromMouse {
3932 selection_tail,
3933 display_point,
3934 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3935 ColumnarSelectionState::FromSelection { selection_tail } => {
3936 selection_tail.to_display_point(display_map)
3937 }
3938 };
3939
3940 let start_row = cmp::min(tail.row(), head.row());
3941 let end_row = cmp::max(tail.row(), head.row());
3942 let start_column = cmp::min(tail.column(), goal_column);
3943 let end_column = cmp::max(tail.column(), goal_column);
3944 let reversed = start_column < tail.column();
3945
3946 let selection_ranges = (start_row.0..=end_row.0)
3947 .map(DisplayRow)
3948 .filter_map(|row| {
3949 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3950 || start_column <= display_map.line_len(row))
3951 && !display_map.is_block_line(row)
3952 {
3953 let start = display_map
3954 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3955 .to_point(display_map);
3956 let end = display_map
3957 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3958 .to_point(display_map);
3959 if reversed {
3960 Some(end..start)
3961 } else {
3962 Some(start..end)
3963 }
3964 } else {
3965 None
3966 }
3967 })
3968 .collect::<Vec<_>>();
3969 if selection_ranges.is_empty() {
3970 return;
3971 }
3972
3973 let ranges = match columnar_state {
3974 ColumnarSelectionState::FromMouse { .. } => {
3975 let mut non_empty_ranges = selection_ranges
3976 .iter()
3977 .filter(|selection_range| selection_range.start != selection_range.end)
3978 .peekable();
3979 if non_empty_ranges.peek().is_some() {
3980 non_empty_ranges.cloned().collect()
3981 } else {
3982 selection_ranges
3983 }
3984 }
3985 _ => selection_ranges,
3986 };
3987
3988 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3989 s.select_ranges(ranges);
3990 });
3991 cx.notify();
3992 }
3993
3994 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3995 self.selections
3996 .all_adjusted(snapshot)
3997 .iter()
3998 .any(|selection| !selection.is_empty())
3999 }
4000
4001 pub fn has_pending_nonempty_selection(&self) -> bool {
4002 let pending_nonempty_selection = match self.selections.pending_anchor() {
4003 Some(Selection { start, end, .. }) => start != end,
4004 None => false,
4005 };
4006
4007 pending_nonempty_selection
4008 || (self.columnar_selection_state.is_some()
4009 && self.selections.disjoint_anchors().len() > 1)
4010 }
4011
4012 pub fn has_pending_selection(&self) -> bool {
4013 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4014 }
4015
4016 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4017 self.selection_mark_mode = false;
4018 self.selection_drag_state = SelectionDragState::None;
4019
4020 if self.clear_expanded_diff_hunks(cx) {
4021 cx.notify();
4022 return;
4023 }
4024 if self.dismiss_menus_and_popups(true, window, cx) {
4025 return;
4026 }
4027
4028 if self.mode.is_full()
4029 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4030 {
4031 return;
4032 }
4033
4034 cx.propagate();
4035 }
4036
4037 pub fn dismiss_menus_and_popups(
4038 &mut self,
4039 is_user_requested: bool,
4040 window: &mut Window,
4041 cx: &mut Context<Self>,
4042 ) -> bool {
4043 if self.take_rename(false, window, cx).is_some() {
4044 return true;
4045 }
4046
4047 if self.hide_blame_popover(true, cx) {
4048 return true;
4049 }
4050
4051 if hide_hover(self, cx) {
4052 return true;
4053 }
4054
4055 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4056 return true;
4057 }
4058
4059 if self.hide_context_menu(window, cx).is_some() {
4060 return true;
4061 }
4062
4063 if self.mouse_context_menu.take().is_some() {
4064 return true;
4065 }
4066
4067 if is_user_requested && self.discard_edit_prediction(true, cx) {
4068 return true;
4069 }
4070
4071 if self.snippet_stack.pop().is_some() {
4072 return true;
4073 }
4074
4075 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4076 self.dismiss_diagnostics(cx);
4077 return true;
4078 }
4079
4080 false
4081 }
4082
4083 fn linked_editing_ranges_for(
4084 &self,
4085 selection: Range<text::Anchor>,
4086 cx: &App,
4087 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4088 if self.linked_edit_ranges.is_empty() {
4089 return None;
4090 }
4091 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4092 selection.end.buffer_id.and_then(|end_buffer_id| {
4093 if selection.start.buffer_id != Some(end_buffer_id) {
4094 return None;
4095 }
4096 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4097 let snapshot = buffer.read(cx).snapshot();
4098 self.linked_edit_ranges
4099 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4100 .map(|ranges| (ranges, snapshot, buffer))
4101 })?;
4102 use text::ToOffset as TO;
4103 // find offset from the start of current range to current cursor position
4104 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4105
4106 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4107 let start_difference = start_offset - start_byte_offset;
4108 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4109 let end_difference = end_offset - start_byte_offset;
4110 // Current range has associated linked ranges.
4111 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4112 for range in linked_ranges.iter() {
4113 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4114 let end_offset = start_offset + end_difference;
4115 let start_offset = start_offset + start_difference;
4116 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4117 continue;
4118 }
4119 if self.selections.disjoint_anchor_ranges().any(|s| {
4120 if s.start.buffer_id != selection.start.buffer_id
4121 || s.end.buffer_id != selection.end.buffer_id
4122 {
4123 return false;
4124 }
4125 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4126 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4127 }) {
4128 continue;
4129 }
4130 let start = buffer_snapshot.anchor_after(start_offset);
4131 let end = buffer_snapshot.anchor_after(end_offset);
4132 linked_edits
4133 .entry(buffer.clone())
4134 .or_default()
4135 .push(start..end);
4136 }
4137 Some(linked_edits)
4138 }
4139
4140 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4141 let text: Arc<str> = text.into();
4142
4143 if self.read_only(cx) {
4144 return;
4145 }
4146
4147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4148
4149 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4150 let mut bracket_inserted = false;
4151 let mut edits = Vec::new();
4152 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4153 let mut new_selections = Vec::with_capacity(selections.len());
4154 let mut new_autoclose_regions = Vec::new();
4155 let snapshot = self.buffer.read(cx).read(cx);
4156 let mut clear_linked_edit_ranges = false;
4157
4158 for (selection, autoclose_region) in
4159 self.selections_with_autoclose_regions(selections, &snapshot)
4160 {
4161 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4162 // Determine if the inserted text matches the opening or closing
4163 // bracket of any of this language's bracket pairs.
4164 let mut bracket_pair = None;
4165 let mut is_bracket_pair_start = false;
4166 let mut is_bracket_pair_end = false;
4167 if !text.is_empty() {
4168 let mut bracket_pair_matching_end = None;
4169 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4170 // and they are removing the character that triggered IME popup.
4171 for (pair, enabled) in scope.brackets() {
4172 if !pair.close && !pair.surround {
4173 continue;
4174 }
4175
4176 if enabled && pair.start.ends_with(text.as_ref()) {
4177 let prefix_len = pair.start.len() - text.len();
4178 let preceding_text_matches_prefix = prefix_len == 0
4179 || (selection.start.column >= (prefix_len as u32)
4180 && snapshot.contains_str_at(
4181 Point::new(
4182 selection.start.row,
4183 selection.start.column - (prefix_len as u32),
4184 ),
4185 &pair.start[..prefix_len],
4186 ));
4187 if preceding_text_matches_prefix {
4188 bracket_pair = Some(pair.clone());
4189 is_bracket_pair_start = true;
4190 break;
4191 }
4192 }
4193 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4194 {
4195 // take first bracket pair matching end, but don't break in case a later bracket
4196 // pair matches start
4197 bracket_pair_matching_end = Some(pair.clone());
4198 }
4199 }
4200 if let Some(end) = bracket_pair_matching_end
4201 && bracket_pair.is_none()
4202 {
4203 bracket_pair = Some(end);
4204 is_bracket_pair_end = true;
4205 }
4206 }
4207
4208 if let Some(bracket_pair) = bracket_pair {
4209 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4210 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4211 let auto_surround =
4212 self.use_auto_surround && snapshot_settings.use_auto_surround;
4213 if selection.is_empty() {
4214 if is_bracket_pair_start {
4215 // If the inserted text is a suffix of an opening bracket and the
4216 // selection is preceded by the rest of the opening bracket, then
4217 // insert the closing bracket.
4218 let following_text_allows_autoclose = snapshot
4219 .chars_at(selection.start)
4220 .next()
4221 .is_none_or(|c| scope.should_autoclose_before(c));
4222
4223 let preceding_text_allows_autoclose = selection.start.column == 0
4224 || snapshot
4225 .reversed_chars_at(selection.start)
4226 .next()
4227 .is_none_or(|c| {
4228 bracket_pair.start != bracket_pair.end
4229 || !snapshot
4230 .char_classifier_at(selection.start)
4231 .is_word(c)
4232 });
4233
4234 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4235 && bracket_pair.start.len() == 1
4236 {
4237 let target = bracket_pair.start.chars().next().unwrap();
4238 let current_line_count = snapshot
4239 .reversed_chars_at(selection.start)
4240 .take_while(|&c| c != '\n')
4241 .filter(|&c| c == target)
4242 .count();
4243 current_line_count % 2 == 1
4244 } else {
4245 false
4246 };
4247
4248 if autoclose
4249 && bracket_pair.close
4250 && following_text_allows_autoclose
4251 && preceding_text_allows_autoclose
4252 && !is_closing_quote
4253 {
4254 let anchor = snapshot.anchor_before(selection.end);
4255 new_selections.push((selection.map(|_| anchor), text.len()));
4256 new_autoclose_regions.push((
4257 anchor,
4258 text.len(),
4259 selection.id,
4260 bracket_pair.clone(),
4261 ));
4262 edits.push((
4263 selection.range(),
4264 format!("{}{}", text, bracket_pair.end).into(),
4265 ));
4266 bracket_inserted = true;
4267 continue;
4268 }
4269 }
4270
4271 if let Some(region) = autoclose_region {
4272 // If the selection is followed by an auto-inserted closing bracket,
4273 // then don't insert that closing bracket again; just move the selection
4274 // past the closing bracket.
4275 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4276 && text.as_ref() == region.pair.end.as_str()
4277 && snapshot.contains_str_at(region.range.end, text.as_ref());
4278 if should_skip {
4279 let anchor = snapshot.anchor_after(selection.end);
4280 new_selections
4281 .push((selection.map(|_| anchor), region.pair.end.len()));
4282 continue;
4283 }
4284 }
4285
4286 let always_treat_brackets_as_autoclosed = snapshot
4287 .language_settings_at(selection.start, cx)
4288 .always_treat_brackets_as_autoclosed;
4289 if always_treat_brackets_as_autoclosed
4290 && is_bracket_pair_end
4291 && snapshot.contains_str_at(selection.end, text.as_ref())
4292 {
4293 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4294 // and the inserted text is a closing bracket and the selection is followed
4295 // by the closing bracket then move the selection past the closing bracket.
4296 let anchor = snapshot.anchor_after(selection.end);
4297 new_selections.push((selection.map(|_| anchor), text.len()));
4298 continue;
4299 }
4300 }
4301 // If an opening bracket is 1 character long and is typed while
4302 // text is selected, then surround that text with the bracket pair.
4303 else if auto_surround
4304 && bracket_pair.surround
4305 && is_bracket_pair_start
4306 && bracket_pair.start.chars().count() == 1
4307 {
4308 edits.push((selection.start..selection.start, text.clone()));
4309 edits.push((
4310 selection.end..selection.end,
4311 bracket_pair.end.as_str().into(),
4312 ));
4313 bracket_inserted = true;
4314 new_selections.push((
4315 Selection {
4316 id: selection.id,
4317 start: snapshot.anchor_after(selection.start),
4318 end: snapshot.anchor_before(selection.end),
4319 reversed: selection.reversed,
4320 goal: selection.goal,
4321 },
4322 0,
4323 ));
4324 continue;
4325 }
4326 }
4327 }
4328
4329 if self.auto_replace_emoji_shortcode
4330 && selection.is_empty()
4331 && text.as_ref().ends_with(':')
4332 && let Some(possible_emoji_short_code) =
4333 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4334 && !possible_emoji_short_code.is_empty()
4335 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4336 {
4337 let emoji_shortcode_start = Point::new(
4338 selection.start.row,
4339 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4340 );
4341
4342 // Remove shortcode from buffer
4343 edits.push((
4344 emoji_shortcode_start..selection.start,
4345 "".to_string().into(),
4346 ));
4347 new_selections.push((
4348 Selection {
4349 id: selection.id,
4350 start: snapshot.anchor_after(emoji_shortcode_start),
4351 end: snapshot.anchor_before(selection.start),
4352 reversed: selection.reversed,
4353 goal: selection.goal,
4354 },
4355 0,
4356 ));
4357
4358 // Insert emoji
4359 let selection_start_anchor = snapshot.anchor_after(selection.start);
4360 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4361 edits.push((selection.start..selection.end, emoji.to_string().into()));
4362
4363 continue;
4364 }
4365
4366 // If not handling any auto-close operation, then just replace the selected
4367 // text with the given input and move the selection to the end of the
4368 // newly inserted text.
4369 let anchor = snapshot.anchor_after(selection.end);
4370 if !self.linked_edit_ranges.is_empty() {
4371 let start_anchor = snapshot.anchor_before(selection.start);
4372
4373 let is_word_char = text.chars().next().is_none_or(|char| {
4374 let classifier = snapshot
4375 .char_classifier_at(start_anchor.to_offset(&snapshot))
4376 .scope_context(Some(CharScopeContext::LinkedEdit));
4377 classifier.is_word(char)
4378 });
4379
4380 if is_word_char {
4381 if let Some(ranges) = self
4382 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4383 {
4384 for (buffer, edits) in ranges {
4385 linked_edits
4386 .entry(buffer.clone())
4387 .or_default()
4388 .extend(edits.into_iter().map(|range| (range, text.clone())));
4389 }
4390 }
4391 } else {
4392 clear_linked_edit_ranges = true;
4393 }
4394 }
4395
4396 new_selections.push((selection.map(|_| anchor), 0));
4397 edits.push((selection.start..selection.end, text.clone()));
4398 }
4399
4400 drop(snapshot);
4401
4402 self.transact(window, cx, |this, window, cx| {
4403 if clear_linked_edit_ranges {
4404 this.linked_edit_ranges.clear();
4405 }
4406 let initial_buffer_versions =
4407 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4408
4409 this.buffer.update(cx, |buffer, cx| {
4410 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4411 });
4412 for (buffer, edits) in linked_edits {
4413 buffer.update(cx, |buffer, cx| {
4414 let snapshot = buffer.snapshot();
4415 let edits = edits
4416 .into_iter()
4417 .map(|(range, text)| {
4418 use text::ToPoint as TP;
4419 let end_point = TP::to_point(&range.end, &snapshot);
4420 let start_point = TP::to_point(&range.start, &snapshot);
4421 (start_point..end_point, text)
4422 })
4423 .sorted_by_key(|(range, _)| range.start);
4424 buffer.edit(edits, None, cx);
4425 })
4426 }
4427 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4428 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4429 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4430 let new_selections =
4431 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4432 .zip(new_selection_deltas)
4433 .map(|(selection, delta)| Selection {
4434 id: selection.id,
4435 start: selection.start + delta,
4436 end: selection.end + delta,
4437 reversed: selection.reversed,
4438 goal: SelectionGoal::None,
4439 })
4440 .collect::<Vec<_>>();
4441
4442 let mut i = 0;
4443 for (position, delta, selection_id, pair) in new_autoclose_regions {
4444 let position = position.to_offset(map.buffer_snapshot()) + delta;
4445 let start = map.buffer_snapshot().anchor_before(position);
4446 let end = map.buffer_snapshot().anchor_after(position);
4447 while let Some(existing_state) = this.autoclose_regions.get(i) {
4448 match existing_state
4449 .range
4450 .start
4451 .cmp(&start, map.buffer_snapshot())
4452 {
4453 Ordering::Less => i += 1,
4454 Ordering::Greater => break,
4455 Ordering::Equal => {
4456 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4457 Ordering::Less => i += 1,
4458 Ordering::Equal => break,
4459 Ordering::Greater => break,
4460 }
4461 }
4462 }
4463 }
4464 this.autoclose_regions.insert(
4465 i,
4466 AutocloseRegion {
4467 selection_id,
4468 range: start..end,
4469 pair,
4470 },
4471 );
4472 }
4473
4474 let had_active_edit_prediction = this.has_active_edit_prediction();
4475 this.change_selections(
4476 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4477 window,
4478 cx,
4479 |s| s.select(new_selections),
4480 );
4481
4482 if !bracket_inserted
4483 && let Some(on_type_format_task) =
4484 this.trigger_on_type_formatting(text.to_string(), window, cx)
4485 {
4486 on_type_format_task.detach_and_log_err(cx);
4487 }
4488
4489 let editor_settings = EditorSettings::get_global(cx);
4490 if bracket_inserted
4491 && (editor_settings.auto_signature_help
4492 || editor_settings.show_signature_help_after_edits)
4493 {
4494 this.show_signature_help(&ShowSignatureHelp, window, cx);
4495 }
4496
4497 let trigger_in_words =
4498 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4499 if this.hard_wrap.is_some() {
4500 let latest: Range<Point> = this.selections.newest(&map).range();
4501 if latest.is_empty()
4502 && this
4503 .buffer()
4504 .read(cx)
4505 .snapshot(cx)
4506 .line_len(MultiBufferRow(latest.start.row))
4507 == latest.start.column
4508 {
4509 this.rewrap_impl(
4510 RewrapOptions {
4511 override_language_settings: true,
4512 preserve_existing_whitespace: true,
4513 },
4514 cx,
4515 )
4516 }
4517 }
4518 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4519 refresh_linked_ranges(this, window, cx);
4520 this.refresh_edit_prediction(true, false, window, cx);
4521 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4522 });
4523 }
4524
4525 fn find_possible_emoji_shortcode_at_position(
4526 snapshot: &MultiBufferSnapshot,
4527 position: Point,
4528 ) -> Option<String> {
4529 let mut chars = Vec::new();
4530 let mut found_colon = false;
4531 for char in snapshot.reversed_chars_at(position).take(100) {
4532 // Found a possible emoji shortcode in the middle of the buffer
4533 if found_colon {
4534 if char.is_whitespace() {
4535 chars.reverse();
4536 return Some(chars.iter().collect());
4537 }
4538 // If the previous character is not a whitespace, we are in the middle of a word
4539 // and we only want to complete the shortcode if the word is made up of other emojis
4540 let mut containing_word = String::new();
4541 for ch in snapshot
4542 .reversed_chars_at(position)
4543 .skip(chars.len() + 1)
4544 .take(100)
4545 {
4546 if ch.is_whitespace() {
4547 break;
4548 }
4549 containing_word.push(ch);
4550 }
4551 let containing_word = containing_word.chars().rev().collect::<String>();
4552 if util::word_consists_of_emojis(containing_word.as_str()) {
4553 chars.reverse();
4554 return Some(chars.iter().collect());
4555 }
4556 }
4557
4558 if char.is_whitespace() || !char.is_ascii() {
4559 return None;
4560 }
4561 if char == ':' {
4562 found_colon = true;
4563 } else {
4564 chars.push(char);
4565 }
4566 }
4567 // Found a possible emoji shortcode at the beginning of the buffer
4568 chars.reverse();
4569 Some(chars.iter().collect())
4570 }
4571
4572 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4574 self.transact(window, cx, |this, window, cx| {
4575 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4576 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4577 let multi_buffer = this.buffer.read(cx);
4578 let buffer = multi_buffer.snapshot(cx);
4579 selections
4580 .iter()
4581 .map(|selection| {
4582 let start_point = selection.start.to_point(&buffer);
4583 let mut existing_indent =
4584 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4585 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4586 let start = selection.start;
4587 let end = selection.end;
4588 let selection_is_empty = start == end;
4589 let language_scope = buffer.language_scope_at(start);
4590 let (
4591 comment_delimiter,
4592 doc_delimiter,
4593 insert_extra_newline,
4594 indent_on_newline,
4595 indent_on_extra_newline,
4596 ) = if let Some(language) = &language_scope {
4597 let mut insert_extra_newline =
4598 insert_extra_newline_brackets(&buffer, start..end, language)
4599 || insert_extra_newline_tree_sitter(&buffer, start..end);
4600
4601 // Comment extension on newline is allowed only for cursor selections
4602 let comment_delimiter = maybe!({
4603 if !selection_is_empty {
4604 return None;
4605 }
4606
4607 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4608 return None;
4609 }
4610
4611 let delimiters = language.line_comment_prefixes();
4612 let max_len_of_delimiter =
4613 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4614 let (snapshot, range) =
4615 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4616
4617 let num_of_whitespaces = snapshot
4618 .chars_for_range(range.clone())
4619 .take_while(|c| c.is_whitespace())
4620 .count();
4621 let comment_candidate = snapshot
4622 .chars_for_range(range.clone())
4623 .skip(num_of_whitespaces)
4624 .take(max_len_of_delimiter)
4625 .collect::<String>();
4626 let (delimiter, trimmed_len) = delimiters
4627 .iter()
4628 .filter_map(|delimiter| {
4629 let prefix = delimiter.trim_end();
4630 if comment_candidate.starts_with(prefix) {
4631 Some((delimiter, prefix.len()))
4632 } else {
4633 None
4634 }
4635 })
4636 .max_by_key(|(_, len)| *len)?;
4637
4638 if let Some(BlockCommentConfig {
4639 start: block_start, ..
4640 }) = language.block_comment()
4641 {
4642 let block_start_trimmed = block_start.trim_end();
4643 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4644 let line_content = snapshot
4645 .chars_for_range(range)
4646 .skip(num_of_whitespaces)
4647 .take(block_start_trimmed.len())
4648 .collect::<String>();
4649
4650 if line_content.starts_with(block_start_trimmed) {
4651 return None;
4652 }
4653 }
4654 }
4655
4656 let cursor_is_placed_after_comment_marker =
4657 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4658 if cursor_is_placed_after_comment_marker {
4659 Some(delimiter.clone())
4660 } else {
4661 None
4662 }
4663 });
4664
4665 let mut indent_on_newline = IndentSize::spaces(0);
4666 let mut indent_on_extra_newline = IndentSize::spaces(0);
4667
4668 let doc_delimiter = maybe!({
4669 if !selection_is_empty {
4670 return None;
4671 }
4672
4673 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4674 return None;
4675 }
4676
4677 let BlockCommentConfig {
4678 start: start_tag,
4679 end: end_tag,
4680 prefix: delimiter,
4681 tab_size: len,
4682 } = language.documentation_comment()?;
4683 let is_within_block_comment = buffer
4684 .language_scope_at(start_point)
4685 .is_some_and(|scope| scope.override_name() == Some("comment"));
4686 if !is_within_block_comment {
4687 return None;
4688 }
4689
4690 let (snapshot, range) =
4691 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4692
4693 let num_of_whitespaces = snapshot
4694 .chars_for_range(range.clone())
4695 .take_while(|c| c.is_whitespace())
4696 .count();
4697
4698 // 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.
4699 let column = start_point.column;
4700 let cursor_is_after_start_tag = {
4701 let start_tag_len = start_tag.len();
4702 let start_tag_line = snapshot
4703 .chars_for_range(range.clone())
4704 .skip(num_of_whitespaces)
4705 .take(start_tag_len)
4706 .collect::<String>();
4707 if start_tag_line.starts_with(start_tag.as_ref()) {
4708 num_of_whitespaces + start_tag_len <= column as usize
4709 } else {
4710 false
4711 }
4712 };
4713
4714 let cursor_is_after_delimiter = {
4715 let delimiter_trim = delimiter.trim_end();
4716 let delimiter_line = snapshot
4717 .chars_for_range(range.clone())
4718 .skip(num_of_whitespaces)
4719 .take(delimiter_trim.len())
4720 .collect::<String>();
4721 if delimiter_line.starts_with(delimiter_trim) {
4722 num_of_whitespaces + delimiter_trim.len() <= column as usize
4723 } else {
4724 false
4725 }
4726 };
4727
4728 let cursor_is_before_end_tag_if_exists = {
4729 let mut char_position = 0u32;
4730 let mut end_tag_offset = None;
4731
4732 'outer: for chunk in snapshot.text_for_range(range) {
4733 if let Some(byte_pos) = chunk.find(&**end_tag) {
4734 let chars_before_match =
4735 chunk[..byte_pos].chars().count() as u32;
4736 end_tag_offset =
4737 Some(char_position + chars_before_match);
4738 break 'outer;
4739 }
4740 char_position += chunk.chars().count() as u32;
4741 }
4742
4743 if let Some(end_tag_offset) = end_tag_offset {
4744 let cursor_is_before_end_tag = column <= end_tag_offset;
4745 if cursor_is_after_start_tag {
4746 if cursor_is_before_end_tag {
4747 insert_extra_newline = true;
4748 }
4749 let cursor_is_at_start_of_end_tag =
4750 column == end_tag_offset;
4751 if cursor_is_at_start_of_end_tag {
4752 indent_on_extra_newline.len = *len;
4753 }
4754 }
4755 cursor_is_before_end_tag
4756 } else {
4757 true
4758 }
4759 };
4760
4761 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4762 && cursor_is_before_end_tag_if_exists
4763 {
4764 if cursor_is_after_start_tag {
4765 indent_on_newline.len = *len;
4766 }
4767 Some(delimiter.clone())
4768 } else {
4769 None
4770 }
4771 });
4772
4773 (
4774 comment_delimiter,
4775 doc_delimiter,
4776 insert_extra_newline,
4777 indent_on_newline,
4778 indent_on_extra_newline,
4779 )
4780 } else {
4781 (
4782 None,
4783 None,
4784 false,
4785 IndentSize::default(),
4786 IndentSize::default(),
4787 )
4788 };
4789
4790 let prevent_auto_indent = doc_delimiter.is_some();
4791 let delimiter = comment_delimiter.or(doc_delimiter);
4792
4793 let capacity_for_delimiter =
4794 delimiter.as_deref().map(str::len).unwrap_or_default();
4795 let mut new_text = String::with_capacity(
4796 1 + capacity_for_delimiter
4797 + existing_indent.len as usize
4798 + indent_on_newline.len as usize
4799 + indent_on_extra_newline.len as usize,
4800 );
4801 new_text.push('\n');
4802 new_text.extend(existing_indent.chars());
4803 new_text.extend(indent_on_newline.chars());
4804
4805 if let Some(delimiter) = &delimiter {
4806 new_text.push_str(delimiter);
4807 }
4808
4809 if insert_extra_newline {
4810 new_text.push('\n');
4811 new_text.extend(existing_indent.chars());
4812 new_text.extend(indent_on_extra_newline.chars());
4813 }
4814
4815 let anchor = buffer.anchor_after(end);
4816 let new_selection = selection.map(|_| anchor);
4817 (
4818 ((start..end, new_text), prevent_auto_indent),
4819 (insert_extra_newline, new_selection),
4820 )
4821 })
4822 .unzip()
4823 };
4824
4825 let mut auto_indent_edits = Vec::new();
4826 let mut edits = Vec::new();
4827 for (edit, prevent_auto_indent) in edits_with_flags {
4828 if prevent_auto_indent {
4829 edits.push(edit);
4830 } else {
4831 auto_indent_edits.push(edit);
4832 }
4833 }
4834 if !edits.is_empty() {
4835 this.edit(edits, cx);
4836 }
4837 if !auto_indent_edits.is_empty() {
4838 this.edit_with_autoindent(auto_indent_edits, cx);
4839 }
4840
4841 let buffer = this.buffer.read(cx).snapshot(cx);
4842 let new_selections = selection_info
4843 .into_iter()
4844 .map(|(extra_newline_inserted, new_selection)| {
4845 let mut cursor = new_selection.end.to_point(&buffer);
4846 if extra_newline_inserted {
4847 cursor.row -= 1;
4848 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4849 }
4850 new_selection.map(|_| cursor)
4851 })
4852 .collect();
4853
4854 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4855 this.refresh_edit_prediction(true, false, window, cx);
4856 });
4857 }
4858
4859 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4860 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4861
4862 let buffer = self.buffer.read(cx);
4863 let snapshot = buffer.snapshot(cx);
4864
4865 let mut edits = Vec::new();
4866 let mut rows = Vec::new();
4867
4868 for (rows_inserted, selection) in self
4869 .selections
4870 .all_adjusted(&self.display_snapshot(cx))
4871 .into_iter()
4872 .enumerate()
4873 {
4874 let cursor = selection.head();
4875 let row = cursor.row;
4876
4877 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4878
4879 let newline = "\n".to_string();
4880 edits.push((start_of_line..start_of_line, newline));
4881
4882 rows.push(row + rows_inserted as u32);
4883 }
4884
4885 self.transact(window, cx, |editor, window, cx| {
4886 editor.edit(edits, cx);
4887
4888 editor.change_selections(Default::default(), window, cx, |s| {
4889 let mut index = 0;
4890 s.move_cursors_with(|map, _, _| {
4891 let row = rows[index];
4892 index += 1;
4893
4894 let point = Point::new(row, 0);
4895 let boundary = map.next_line_boundary(point).1;
4896 let clipped = map.clip_point(boundary, Bias::Left);
4897
4898 (clipped, SelectionGoal::None)
4899 });
4900 });
4901
4902 let mut indent_edits = Vec::new();
4903 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4904 for row in rows {
4905 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4906 for (row, indent) in indents {
4907 if indent.len == 0 {
4908 continue;
4909 }
4910
4911 let text = match indent.kind {
4912 IndentKind::Space => " ".repeat(indent.len as usize),
4913 IndentKind::Tab => "\t".repeat(indent.len as usize),
4914 };
4915 let point = Point::new(row.0, 0);
4916 indent_edits.push((point..point, text));
4917 }
4918 }
4919 editor.edit(indent_edits, cx);
4920 });
4921 }
4922
4923 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4924 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4925
4926 let buffer = self.buffer.read(cx);
4927 let snapshot = buffer.snapshot(cx);
4928
4929 let mut edits = Vec::new();
4930 let mut rows = Vec::new();
4931 let mut rows_inserted = 0;
4932
4933 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4934 let cursor = selection.head();
4935 let row = cursor.row;
4936
4937 let point = Point::new(row + 1, 0);
4938 let start_of_line = snapshot.clip_point(point, Bias::Left);
4939
4940 let newline = "\n".to_string();
4941 edits.push((start_of_line..start_of_line, newline));
4942
4943 rows_inserted += 1;
4944 rows.push(row + rows_inserted);
4945 }
4946
4947 self.transact(window, cx, |editor, window, cx| {
4948 editor.edit(edits, cx);
4949
4950 editor.change_selections(Default::default(), window, cx, |s| {
4951 let mut index = 0;
4952 s.move_cursors_with(|map, _, _| {
4953 let row = rows[index];
4954 index += 1;
4955
4956 let point = Point::new(row, 0);
4957 let boundary = map.next_line_boundary(point).1;
4958 let clipped = map.clip_point(boundary, Bias::Left);
4959
4960 (clipped, SelectionGoal::None)
4961 });
4962 });
4963
4964 let mut indent_edits = Vec::new();
4965 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4966 for row in rows {
4967 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4968 for (row, indent) in indents {
4969 if indent.len == 0 {
4970 continue;
4971 }
4972
4973 let text = match indent.kind {
4974 IndentKind::Space => " ".repeat(indent.len as usize),
4975 IndentKind::Tab => "\t".repeat(indent.len as usize),
4976 };
4977 let point = Point::new(row.0, 0);
4978 indent_edits.push((point..point, text));
4979 }
4980 }
4981 editor.edit(indent_edits, cx);
4982 });
4983 }
4984
4985 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4986 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4987 original_indent_columns: Vec::new(),
4988 });
4989 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4990 }
4991
4992 fn insert_with_autoindent_mode(
4993 &mut self,
4994 text: &str,
4995 autoindent_mode: Option<AutoindentMode>,
4996 window: &mut Window,
4997 cx: &mut Context<Self>,
4998 ) {
4999 if self.read_only(cx) {
5000 return;
5001 }
5002
5003 let text: Arc<str> = text.into();
5004 self.transact(window, cx, |this, window, cx| {
5005 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5006 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5007 let anchors = {
5008 let snapshot = buffer.read(cx);
5009 old_selections
5010 .iter()
5011 .map(|s| {
5012 let anchor = snapshot.anchor_after(s.head());
5013 s.map(|_| anchor)
5014 })
5015 .collect::<Vec<_>>()
5016 };
5017 buffer.edit(
5018 old_selections
5019 .iter()
5020 .map(|s| (s.start..s.end, text.clone())),
5021 autoindent_mode,
5022 cx,
5023 );
5024 anchors
5025 });
5026
5027 this.change_selections(Default::default(), window, cx, |s| {
5028 s.select_anchors(selection_anchors);
5029 });
5030
5031 cx.notify();
5032 });
5033 }
5034
5035 fn trigger_completion_on_input(
5036 &mut self,
5037 text: &str,
5038 trigger_in_words: bool,
5039 window: &mut Window,
5040 cx: &mut Context<Self>,
5041 ) {
5042 let completions_source = self
5043 .context_menu
5044 .borrow()
5045 .as_ref()
5046 .and_then(|menu| match menu {
5047 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5048 CodeContextMenu::CodeActions(_) => None,
5049 });
5050
5051 match completions_source {
5052 Some(CompletionsMenuSource::Words { .. }) => {
5053 self.open_or_update_completions_menu(
5054 Some(CompletionsMenuSource::Words {
5055 ignore_threshold: false,
5056 }),
5057 None,
5058 window,
5059 cx,
5060 );
5061 }
5062 Some(CompletionsMenuSource::Normal)
5063 | Some(CompletionsMenuSource::SnippetChoices)
5064 | None
5065 if self.is_completion_trigger(
5066 text,
5067 trigger_in_words,
5068 completions_source.is_some(),
5069 cx,
5070 ) =>
5071 {
5072 self.show_completions(
5073 &ShowCompletions {
5074 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5075 },
5076 window,
5077 cx,
5078 )
5079 }
5080 _ => {
5081 self.hide_context_menu(window, cx);
5082 }
5083 }
5084 }
5085
5086 fn is_completion_trigger(
5087 &self,
5088 text: &str,
5089 trigger_in_words: bool,
5090 menu_is_open: bool,
5091 cx: &mut Context<Self>,
5092 ) -> bool {
5093 let position = self.selections.newest_anchor().head();
5094 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5095 return false;
5096 };
5097
5098 if let Some(completion_provider) = &self.completion_provider {
5099 completion_provider.is_completion_trigger(
5100 &buffer,
5101 position.text_anchor,
5102 text,
5103 trigger_in_words,
5104 menu_is_open,
5105 cx,
5106 )
5107 } else {
5108 false
5109 }
5110 }
5111
5112 /// If any empty selections is touching the start of its innermost containing autoclose
5113 /// region, expand it to select the brackets.
5114 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5115 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5116 let buffer = self.buffer.read(cx).read(cx);
5117 let new_selections = self
5118 .selections_with_autoclose_regions(selections, &buffer)
5119 .map(|(mut selection, region)| {
5120 if !selection.is_empty() {
5121 return selection;
5122 }
5123
5124 if let Some(region) = region {
5125 let mut range = region.range.to_offset(&buffer);
5126 if selection.start == range.start && range.start >= region.pair.start.len() {
5127 range.start -= region.pair.start.len();
5128 if buffer.contains_str_at(range.start, ®ion.pair.start)
5129 && buffer.contains_str_at(range.end, ®ion.pair.end)
5130 {
5131 range.end += region.pair.end.len();
5132 selection.start = range.start;
5133 selection.end = range.end;
5134
5135 return selection;
5136 }
5137 }
5138 }
5139
5140 let always_treat_brackets_as_autoclosed = buffer
5141 .language_settings_at(selection.start, cx)
5142 .always_treat_brackets_as_autoclosed;
5143
5144 if !always_treat_brackets_as_autoclosed {
5145 return selection;
5146 }
5147
5148 if let Some(scope) = buffer.language_scope_at(selection.start) {
5149 for (pair, enabled) in scope.brackets() {
5150 if !enabled || !pair.close {
5151 continue;
5152 }
5153
5154 if buffer.contains_str_at(selection.start, &pair.end) {
5155 let pair_start_len = pair.start.len();
5156 if buffer.contains_str_at(
5157 selection.start.saturating_sub(pair_start_len),
5158 &pair.start,
5159 ) {
5160 selection.start -= pair_start_len;
5161 selection.end += pair.end.len();
5162
5163 return selection;
5164 }
5165 }
5166 }
5167 }
5168
5169 selection
5170 })
5171 .collect();
5172
5173 drop(buffer);
5174 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5175 selections.select(new_selections)
5176 });
5177 }
5178
5179 /// Iterate the given selections, and for each one, find the smallest surrounding
5180 /// autoclose region. This uses the ordering of the selections and the autoclose
5181 /// regions to avoid repeated comparisons.
5182 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5183 &'a self,
5184 selections: impl IntoIterator<Item = Selection<D>>,
5185 buffer: &'a MultiBufferSnapshot,
5186 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5187 let mut i = 0;
5188 let mut regions = self.autoclose_regions.as_slice();
5189 selections.into_iter().map(move |selection| {
5190 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5191
5192 let mut enclosing = None;
5193 while let Some(pair_state) = regions.get(i) {
5194 if pair_state.range.end.to_offset(buffer) < range.start {
5195 regions = ®ions[i + 1..];
5196 i = 0;
5197 } else if pair_state.range.start.to_offset(buffer) > range.end {
5198 break;
5199 } else {
5200 if pair_state.selection_id == selection.id {
5201 enclosing = Some(pair_state);
5202 }
5203 i += 1;
5204 }
5205 }
5206
5207 (selection, enclosing)
5208 })
5209 }
5210
5211 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5212 fn invalidate_autoclose_regions(
5213 &mut self,
5214 mut selections: &[Selection<Anchor>],
5215 buffer: &MultiBufferSnapshot,
5216 ) {
5217 self.autoclose_regions.retain(|state| {
5218 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5219 return false;
5220 }
5221
5222 let mut i = 0;
5223 while let Some(selection) = selections.get(i) {
5224 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5225 selections = &selections[1..];
5226 continue;
5227 }
5228 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5229 break;
5230 }
5231 if selection.id == state.selection_id {
5232 return true;
5233 } else {
5234 i += 1;
5235 }
5236 }
5237 false
5238 });
5239 }
5240
5241 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5242 let offset = position.to_offset(buffer);
5243 let (word_range, kind) =
5244 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5245 if offset > word_range.start && kind == Some(CharKind::Word) {
5246 Some(
5247 buffer
5248 .text_for_range(word_range.start..offset)
5249 .collect::<String>(),
5250 )
5251 } else {
5252 None
5253 }
5254 }
5255
5256 pub fn visible_excerpts(
5257 &self,
5258 cx: &mut Context<Editor>,
5259 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5260 let Some(project) = self.project() else {
5261 return HashMap::default();
5262 };
5263 let project = project.read(cx);
5264 let multi_buffer = self.buffer().read(cx);
5265 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5266 let multi_buffer_visible_start = self
5267 .scroll_manager
5268 .anchor()
5269 .anchor
5270 .to_point(&multi_buffer_snapshot);
5271 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5272 multi_buffer_visible_start
5273 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5274 Bias::Left,
5275 );
5276 multi_buffer_snapshot
5277 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5278 .into_iter()
5279 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5280 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5281 let buffer_file = project::File::from_dyn(buffer.file())?;
5282 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5283 let worktree_entry = buffer_worktree
5284 .read(cx)
5285 .entry_for_id(buffer_file.project_entry_id()?)?;
5286 if worktree_entry.is_ignored {
5287 None
5288 } else {
5289 Some((
5290 excerpt_id,
5291 (
5292 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5293 buffer.version().clone(),
5294 excerpt_visible_range,
5295 ),
5296 ))
5297 }
5298 })
5299 .collect()
5300 }
5301
5302 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5303 TextLayoutDetails {
5304 text_system: window.text_system().clone(),
5305 editor_style: self.style.clone().unwrap(),
5306 rem_size: window.rem_size(),
5307 scroll_anchor: self.scroll_manager.anchor(),
5308 visible_rows: self.visible_line_count(),
5309 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5310 }
5311 }
5312
5313 fn trigger_on_type_formatting(
5314 &self,
5315 input: String,
5316 window: &mut Window,
5317 cx: &mut Context<Self>,
5318 ) -> Option<Task<Result<()>>> {
5319 if input.len() != 1 {
5320 return None;
5321 }
5322
5323 let project = self.project()?;
5324 let position = self.selections.newest_anchor().head();
5325 let (buffer, buffer_position) = self
5326 .buffer
5327 .read(cx)
5328 .text_anchor_for_position(position, cx)?;
5329
5330 let settings = language_settings::language_settings(
5331 buffer
5332 .read(cx)
5333 .language_at(buffer_position)
5334 .map(|l| l.name()),
5335 buffer.read(cx).file(),
5336 cx,
5337 );
5338 if !settings.use_on_type_format {
5339 return None;
5340 }
5341
5342 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5343 // hence we do LSP request & edit on host side only — add formats to host's history.
5344 let push_to_lsp_host_history = true;
5345 // If this is not the host, append its history with new edits.
5346 let push_to_client_history = project.read(cx).is_via_collab();
5347
5348 let on_type_formatting = project.update(cx, |project, cx| {
5349 project.on_type_format(
5350 buffer.clone(),
5351 buffer_position,
5352 input,
5353 push_to_lsp_host_history,
5354 cx,
5355 )
5356 });
5357 Some(cx.spawn_in(window, async move |editor, cx| {
5358 if let Some(transaction) = on_type_formatting.await? {
5359 if push_to_client_history {
5360 buffer
5361 .update(cx, |buffer, _| {
5362 buffer.push_transaction(transaction, Instant::now());
5363 buffer.finalize_last_transaction();
5364 })
5365 .ok();
5366 }
5367 editor.update(cx, |editor, cx| {
5368 editor.refresh_document_highlights(cx);
5369 })?;
5370 }
5371 Ok(())
5372 }))
5373 }
5374
5375 pub fn show_word_completions(
5376 &mut self,
5377 _: &ShowWordCompletions,
5378 window: &mut Window,
5379 cx: &mut Context<Self>,
5380 ) {
5381 self.open_or_update_completions_menu(
5382 Some(CompletionsMenuSource::Words {
5383 ignore_threshold: true,
5384 }),
5385 None,
5386 window,
5387 cx,
5388 );
5389 }
5390
5391 pub fn show_completions(
5392 &mut self,
5393 options: &ShowCompletions,
5394 window: &mut Window,
5395 cx: &mut Context<Self>,
5396 ) {
5397 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5398 }
5399
5400 fn open_or_update_completions_menu(
5401 &mut self,
5402 requested_source: Option<CompletionsMenuSource>,
5403 trigger: Option<&str>,
5404 window: &mut Window,
5405 cx: &mut Context<Self>,
5406 ) {
5407 if self.pending_rename.is_some() {
5408 return;
5409 }
5410
5411 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5412
5413 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5414 // inserted and selected. To handle that case, the start of the selection is used so that
5415 // the menu starts with all choices.
5416 let position = self
5417 .selections
5418 .newest_anchor()
5419 .start
5420 .bias_right(&multibuffer_snapshot);
5421 if position.diff_base_anchor.is_some() {
5422 return;
5423 }
5424 let buffer_position = multibuffer_snapshot.anchor_before(position);
5425 let Some(buffer) = buffer_position
5426 .buffer_id
5427 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5428 else {
5429 return;
5430 };
5431 let buffer_snapshot = buffer.read(cx).snapshot();
5432
5433 let query: Option<Arc<String>> =
5434 Self::completion_query(&multibuffer_snapshot, buffer_position)
5435 .map(|query| query.into());
5436
5437 drop(multibuffer_snapshot);
5438
5439 // Hide the current completions menu when query is empty. Without this, cached
5440 // completions from before the trigger char may be reused (#32774).
5441 if query.is_none() {
5442 let menu_is_open = matches!(
5443 self.context_menu.borrow().as_ref(),
5444 Some(CodeContextMenu::Completions(_))
5445 );
5446 if menu_is_open {
5447 self.hide_context_menu(window, cx);
5448 }
5449 }
5450
5451 let mut ignore_word_threshold = false;
5452 let provider = match requested_source {
5453 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5454 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5455 ignore_word_threshold = ignore_threshold;
5456 None
5457 }
5458 Some(CompletionsMenuSource::SnippetChoices) => {
5459 log::error!("bug: SnippetChoices requested_source is not handled");
5460 None
5461 }
5462 };
5463
5464 let sort_completions = provider
5465 .as_ref()
5466 .is_some_and(|provider| provider.sort_completions());
5467
5468 let filter_completions = provider
5469 .as_ref()
5470 .is_none_or(|provider| provider.filter_completions());
5471
5472 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5473 if filter_completions {
5474 menu.filter(query.clone(), provider.clone(), window, cx);
5475 }
5476 // When `is_incomplete` is false, no need to re-query completions when the current query
5477 // is a suffix of the initial query.
5478 if !menu.is_incomplete {
5479 // If the new query is a suffix of the old query (typing more characters) and
5480 // the previous result was complete, the existing completions can be filtered.
5481 //
5482 // Note that this is always true for snippet completions.
5483 let query_matches = match (&menu.initial_query, &query) {
5484 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5485 (None, _) => true,
5486 _ => false,
5487 };
5488 if query_matches {
5489 let position_matches = if menu.initial_position == position {
5490 true
5491 } else {
5492 let snapshot = self.buffer.read(cx).read(cx);
5493 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5494 };
5495 if position_matches {
5496 return;
5497 }
5498 }
5499 }
5500 };
5501
5502 let trigger_kind = match trigger {
5503 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5504 CompletionTriggerKind::TRIGGER_CHARACTER
5505 }
5506 _ => CompletionTriggerKind::INVOKED,
5507 };
5508 let completion_context = CompletionContext {
5509 trigger_character: trigger.and_then(|trigger| {
5510 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5511 Some(String::from(trigger))
5512 } else {
5513 None
5514 }
5515 }),
5516 trigger_kind,
5517 };
5518
5519 let Anchor {
5520 excerpt_id: buffer_excerpt_id,
5521 text_anchor: buffer_position,
5522 ..
5523 } = buffer_position;
5524
5525 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5526 buffer_snapshot.surrounding_word(buffer_position, None)
5527 {
5528 let word_to_exclude = buffer_snapshot
5529 .text_for_range(word_range.clone())
5530 .collect::<String>();
5531 (
5532 buffer_snapshot.anchor_before(word_range.start)
5533 ..buffer_snapshot.anchor_after(buffer_position),
5534 Some(word_to_exclude),
5535 )
5536 } else {
5537 (buffer_position..buffer_position, None)
5538 };
5539
5540 let language = buffer_snapshot
5541 .language_at(buffer_position)
5542 .map(|language| language.name());
5543
5544 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5545 .completions
5546 .clone();
5547
5548 let show_completion_documentation = buffer_snapshot
5549 .settings_at(buffer_position, cx)
5550 .show_completion_documentation;
5551
5552 // The document can be large, so stay in reasonable bounds when searching for words,
5553 // otherwise completion pop-up might be slow to appear.
5554 const WORD_LOOKUP_ROWS: u32 = 5_000;
5555 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5556 let min_word_search = buffer_snapshot.clip_point(
5557 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5558 Bias::Left,
5559 );
5560 let max_word_search = buffer_snapshot.clip_point(
5561 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5562 Bias::Right,
5563 );
5564 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5565 ..buffer_snapshot.point_to_offset(max_word_search);
5566
5567 let skip_digits = query
5568 .as_ref()
5569 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5570
5571 let omit_word_completions = !self.word_completions_enabled
5572 || (!ignore_word_threshold
5573 && match &query {
5574 Some(query) => query.chars().count() < completion_settings.words_min_length,
5575 None => completion_settings.words_min_length != 0,
5576 });
5577
5578 let (mut words, provider_responses) = match &provider {
5579 Some(provider) => {
5580 let provider_responses = provider.completions(
5581 buffer_excerpt_id,
5582 &buffer,
5583 buffer_position,
5584 completion_context,
5585 window,
5586 cx,
5587 );
5588
5589 let words = match (omit_word_completions, completion_settings.words) {
5590 (true, _) | (_, WordsCompletionMode::Disabled) => {
5591 Task::ready(BTreeMap::default())
5592 }
5593 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5594 .background_spawn(async move {
5595 buffer_snapshot.words_in_range(WordsQuery {
5596 fuzzy_contents: None,
5597 range: word_search_range,
5598 skip_digits,
5599 })
5600 }),
5601 };
5602
5603 (words, provider_responses)
5604 }
5605 None => {
5606 let words = if omit_word_completions {
5607 Task::ready(BTreeMap::default())
5608 } else {
5609 cx.background_spawn(async move {
5610 buffer_snapshot.words_in_range(WordsQuery {
5611 fuzzy_contents: None,
5612 range: word_search_range,
5613 skip_digits,
5614 })
5615 })
5616 };
5617 (words, Task::ready(Ok(Vec::new())))
5618 }
5619 };
5620
5621 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5622
5623 let id = post_inc(&mut self.next_completion_id);
5624 let task = cx.spawn_in(window, async move |editor, cx| {
5625 let Ok(()) = editor.update(cx, |this, _| {
5626 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5627 }) else {
5628 return;
5629 };
5630
5631 // TODO: Ideally completions from different sources would be selectively re-queried, so
5632 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5633 let mut completions = Vec::new();
5634 let mut is_incomplete = false;
5635 let mut display_options: Option<CompletionDisplayOptions> = None;
5636 if let Some(provider_responses) = provider_responses.await.log_err()
5637 && !provider_responses.is_empty()
5638 {
5639 for response in provider_responses {
5640 completions.extend(response.completions);
5641 is_incomplete = is_incomplete || response.is_incomplete;
5642 match display_options.as_mut() {
5643 None => {
5644 display_options = Some(response.display_options);
5645 }
5646 Some(options) => options.merge(&response.display_options),
5647 }
5648 }
5649 if completion_settings.words == WordsCompletionMode::Fallback {
5650 words = Task::ready(BTreeMap::default());
5651 }
5652 }
5653 let display_options = display_options.unwrap_or_default();
5654
5655 let mut words = words.await;
5656 if let Some(word_to_exclude) = &word_to_exclude {
5657 words.remove(word_to_exclude);
5658 }
5659 for lsp_completion in &completions {
5660 words.remove(&lsp_completion.new_text);
5661 }
5662 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5663 replace_range: word_replace_range.clone(),
5664 new_text: word.clone(),
5665 label: CodeLabel::plain(word, None),
5666 icon_path: None,
5667 documentation: None,
5668 source: CompletionSource::BufferWord {
5669 word_range,
5670 resolved: false,
5671 },
5672 insert_text_mode: Some(InsertTextMode::AS_IS),
5673 confirm: None,
5674 }));
5675
5676 let menu = if completions.is_empty() {
5677 None
5678 } else {
5679 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5680 let languages = editor
5681 .workspace
5682 .as_ref()
5683 .and_then(|(workspace, _)| workspace.upgrade())
5684 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5685 let menu = CompletionsMenu::new(
5686 id,
5687 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5688 sort_completions,
5689 show_completion_documentation,
5690 position,
5691 query.clone(),
5692 is_incomplete,
5693 buffer.clone(),
5694 completions.into(),
5695 display_options,
5696 snippet_sort_order,
5697 languages,
5698 language,
5699 cx,
5700 );
5701
5702 let query = if filter_completions { query } else { None };
5703 let matches_task = if let Some(query) = query {
5704 menu.do_async_filtering(query, cx)
5705 } else {
5706 Task::ready(menu.unfiltered_matches())
5707 };
5708 (menu, matches_task)
5709 }) else {
5710 return;
5711 };
5712
5713 let matches = matches_task.await;
5714
5715 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5716 // Newer menu already set, so exit.
5717 if let Some(CodeContextMenu::Completions(prev_menu)) =
5718 editor.context_menu.borrow().as_ref()
5719 && prev_menu.id > id
5720 {
5721 return;
5722 };
5723
5724 // Only valid to take prev_menu because it the new menu is immediately set
5725 // below, or the menu is hidden.
5726 if let Some(CodeContextMenu::Completions(prev_menu)) =
5727 editor.context_menu.borrow_mut().take()
5728 {
5729 let position_matches =
5730 if prev_menu.initial_position == menu.initial_position {
5731 true
5732 } else {
5733 let snapshot = editor.buffer.read(cx).read(cx);
5734 prev_menu.initial_position.to_offset(&snapshot)
5735 == menu.initial_position.to_offset(&snapshot)
5736 };
5737 if position_matches {
5738 // Preserve markdown cache before `set_filter_results` because it will
5739 // try to populate the documentation cache.
5740 menu.preserve_markdown_cache(prev_menu);
5741 }
5742 };
5743
5744 menu.set_filter_results(matches, provider, window, cx);
5745 }) else {
5746 return;
5747 };
5748
5749 menu.visible().then_some(menu)
5750 };
5751
5752 editor
5753 .update_in(cx, |editor, window, cx| {
5754 if editor.focus_handle.is_focused(window)
5755 && let Some(menu) = menu
5756 {
5757 *editor.context_menu.borrow_mut() =
5758 Some(CodeContextMenu::Completions(menu));
5759
5760 crate::hover_popover::hide_hover(editor, cx);
5761 if editor.show_edit_predictions_in_menu() {
5762 editor.update_visible_edit_prediction(window, cx);
5763 } else {
5764 editor.discard_edit_prediction(false, cx);
5765 }
5766
5767 cx.notify();
5768 return;
5769 }
5770
5771 if editor.completion_tasks.len() <= 1 {
5772 // If there are no more completion tasks and the last menu was empty, we should hide it.
5773 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5774 // If it was already hidden and we don't show edit predictions in the menu,
5775 // we should also show the edit prediction when available.
5776 if was_hidden && editor.show_edit_predictions_in_menu() {
5777 editor.update_visible_edit_prediction(window, cx);
5778 }
5779 }
5780 })
5781 .ok();
5782 });
5783
5784 self.completion_tasks.push((id, task));
5785 }
5786
5787 #[cfg(feature = "test-support")]
5788 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5789 let menu = self.context_menu.borrow();
5790 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5791 let completions = menu.completions.borrow();
5792 Some(completions.to_vec())
5793 } else {
5794 None
5795 }
5796 }
5797
5798 pub fn with_completions_menu_matching_id<R>(
5799 &self,
5800 id: CompletionId,
5801 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5802 ) -> R {
5803 let mut context_menu = self.context_menu.borrow_mut();
5804 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5805 return f(None);
5806 };
5807 if completions_menu.id != id {
5808 return f(None);
5809 }
5810 f(Some(completions_menu))
5811 }
5812
5813 pub fn confirm_completion(
5814 &mut self,
5815 action: &ConfirmCompletion,
5816 window: &mut Window,
5817 cx: &mut Context<Self>,
5818 ) -> Option<Task<Result<()>>> {
5819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5820 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5821 }
5822
5823 pub fn confirm_completion_insert(
5824 &mut self,
5825 _: &ConfirmCompletionInsert,
5826 window: &mut Window,
5827 cx: &mut Context<Self>,
5828 ) -> Option<Task<Result<()>>> {
5829 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5830 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5831 }
5832
5833 pub fn confirm_completion_replace(
5834 &mut self,
5835 _: &ConfirmCompletionReplace,
5836 window: &mut Window,
5837 cx: &mut Context<Self>,
5838 ) -> Option<Task<Result<()>>> {
5839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5840 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5841 }
5842
5843 pub fn compose_completion(
5844 &mut self,
5845 action: &ComposeCompletion,
5846 window: &mut Window,
5847 cx: &mut Context<Self>,
5848 ) -> Option<Task<Result<()>>> {
5849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5850 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5851 }
5852
5853 fn do_completion(
5854 &mut self,
5855 item_ix: Option<usize>,
5856 intent: CompletionIntent,
5857 window: &mut Window,
5858 cx: &mut Context<Editor>,
5859 ) -> Option<Task<Result<()>>> {
5860 use language::ToOffset as _;
5861
5862 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5863 else {
5864 return None;
5865 };
5866
5867 let candidate_id = {
5868 let entries = completions_menu.entries.borrow();
5869 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5870 if self.show_edit_predictions_in_menu() {
5871 self.discard_edit_prediction(true, cx);
5872 }
5873 mat.candidate_id
5874 };
5875
5876 let completion = completions_menu
5877 .completions
5878 .borrow()
5879 .get(candidate_id)?
5880 .clone();
5881 cx.stop_propagation();
5882
5883 let buffer_handle = completions_menu.buffer.clone();
5884
5885 let CompletionEdit {
5886 new_text,
5887 snippet,
5888 replace_range,
5889 } = process_completion_for_edit(
5890 &completion,
5891 intent,
5892 &buffer_handle,
5893 &completions_menu.initial_position.text_anchor,
5894 cx,
5895 );
5896
5897 let buffer = buffer_handle.read(cx);
5898 let snapshot = self.buffer.read(cx).snapshot(cx);
5899 let newest_anchor = self.selections.newest_anchor();
5900 let replace_range_multibuffer = {
5901 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5902 excerpt.map_range_from_buffer(replace_range.clone())
5903 };
5904 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5905 return None;
5906 }
5907
5908 let old_text = buffer
5909 .text_for_range(replace_range.clone())
5910 .collect::<String>();
5911 let lookbehind = newest_anchor
5912 .start
5913 .text_anchor
5914 .to_offset(buffer)
5915 .saturating_sub(replace_range.start);
5916 let lookahead = replace_range
5917 .end
5918 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5919 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5920 let suffix = &old_text[lookbehind.min(old_text.len())..];
5921
5922 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5923 let mut ranges = Vec::new();
5924 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5925
5926 for selection in &selections {
5927 let range = if selection.id == newest_anchor.id {
5928 replace_range_multibuffer.clone()
5929 } else {
5930 let mut range = selection.range();
5931
5932 // if prefix is present, don't duplicate it
5933 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5934 range.start = range.start.saturating_sub(lookbehind);
5935
5936 // if suffix is also present, mimic the newest cursor and replace it
5937 if selection.id != newest_anchor.id
5938 && snapshot.contains_str_at(range.end, suffix)
5939 {
5940 range.end += lookahead;
5941 }
5942 }
5943 range
5944 };
5945
5946 ranges.push(range.clone());
5947
5948 if !self.linked_edit_ranges.is_empty() {
5949 let start_anchor = snapshot.anchor_before(range.start);
5950 let end_anchor = snapshot.anchor_after(range.end);
5951 if let Some(ranges) = self
5952 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5953 {
5954 for (buffer, edits) in ranges {
5955 linked_edits
5956 .entry(buffer.clone())
5957 .or_default()
5958 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5959 }
5960 }
5961 }
5962 }
5963
5964 let common_prefix_len = old_text
5965 .chars()
5966 .zip(new_text.chars())
5967 .take_while(|(a, b)| a == b)
5968 .map(|(a, _)| a.len_utf8())
5969 .sum::<usize>();
5970
5971 cx.emit(EditorEvent::InputHandled {
5972 utf16_range_to_replace: None,
5973 text: new_text[common_prefix_len..].into(),
5974 });
5975
5976 self.transact(window, cx, |editor, window, cx| {
5977 if let Some(mut snippet) = snippet {
5978 snippet.text = new_text.to_string();
5979 editor
5980 .insert_snippet(&ranges, snippet, window, cx)
5981 .log_err();
5982 } else {
5983 editor.buffer.update(cx, |multi_buffer, cx| {
5984 let auto_indent = match completion.insert_text_mode {
5985 Some(InsertTextMode::AS_IS) => None,
5986 _ => editor.autoindent_mode.clone(),
5987 };
5988 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5989 multi_buffer.edit(edits, auto_indent, cx);
5990 });
5991 }
5992 for (buffer, edits) in linked_edits {
5993 buffer.update(cx, |buffer, cx| {
5994 let snapshot = buffer.snapshot();
5995 let edits = edits
5996 .into_iter()
5997 .map(|(range, text)| {
5998 use text::ToPoint as TP;
5999 let end_point = TP::to_point(&range.end, &snapshot);
6000 let start_point = TP::to_point(&range.start, &snapshot);
6001 (start_point..end_point, text)
6002 })
6003 .sorted_by_key(|(range, _)| range.start);
6004 buffer.edit(edits, None, cx);
6005 })
6006 }
6007
6008 editor.refresh_edit_prediction(true, false, window, cx);
6009 });
6010 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6011
6012 let show_new_completions_on_confirm = completion
6013 .confirm
6014 .as_ref()
6015 .is_some_and(|confirm| confirm(intent, window, cx));
6016 if show_new_completions_on_confirm {
6017 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6018 }
6019
6020 let provider = self.completion_provider.as_ref()?;
6021 drop(completion);
6022 let apply_edits = provider.apply_additional_edits_for_completion(
6023 buffer_handle,
6024 completions_menu.completions.clone(),
6025 candidate_id,
6026 true,
6027 cx,
6028 );
6029
6030 let editor_settings = EditorSettings::get_global(cx);
6031 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6032 // After the code completion is finished, users often want to know what signatures are needed.
6033 // so we should automatically call signature_help
6034 self.show_signature_help(&ShowSignatureHelp, window, cx);
6035 }
6036
6037 Some(cx.foreground_executor().spawn(async move {
6038 apply_edits.await?;
6039 Ok(())
6040 }))
6041 }
6042
6043 pub fn toggle_code_actions(
6044 &mut self,
6045 action: &ToggleCodeActions,
6046 window: &mut Window,
6047 cx: &mut Context<Self>,
6048 ) {
6049 let quick_launch = action.quick_launch;
6050 let mut context_menu = self.context_menu.borrow_mut();
6051 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6052 if code_actions.deployed_from == action.deployed_from {
6053 // Toggle if we're selecting the same one
6054 *context_menu = None;
6055 cx.notify();
6056 return;
6057 } else {
6058 // Otherwise, clear it and start a new one
6059 *context_menu = None;
6060 cx.notify();
6061 }
6062 }
6063 drop(context_menu);
6064 let snapshot = self.snapshot(window, cx);
6065 let deployed_from = action.deployed_from.clone();
6066 let action = action.clone();
6067 self.completion_tasks.clear();
6068 self.discard_edit_prediction(false, cx);
6069
6070 let multibuffer_point = match &action.deployed_from {
6071 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6072 DisplayPoint::new(*row, 0).to_point(&snapshot)
6073 }
6074 _ => self
6075 .selections
6076 .newest::<Point>(&snapshot.display_snapshot)
6077 .head(),
6078 };
6079 let Some((buffer, buffer_row)) = snapshot
6080 .buffer_snapshot()
6081 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6082 .and_then(|(buffer_snapshot, range)| {
6083 self.buffer()
6084 .read(cx)
6085 .buffer(buffer_snapshot.remote_id())
6086 .map(|buffer| (buffer, range.start.row))
6087 })
6088 else {
6089 return;
6090 };
6091 let buffer_id = buffer.read(cx).remote_id();
6092 let tasks = self
6093 .tasks
6094 .get(&(buffer_id, buffer_row))
6095 .map(|t| Arc::new(t.to_owned()));
6096
6097 if !self.focus_handle.is_focused(window) {
6098 return;
6099 }
6100 let project = self.project.clone();
6101
6102 let code_actions_task = match deployed_from {
6103 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6104 _ => self.code_actions(buffer_row, window, cx),
6105 };
6106
6107 let runnable_task = match deployed_from {
6108 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6109 _ => {
6110 let mut task_context_task = Task::ready(None);
6111 if let Some(tasks) = &tasks
6112 && let Some(project) = project
6113 {
6114 task_context_task =
6115 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6116 }
6117
6118 cx.spawn_in(window, {
6119 let buffer = buffer.clone();
6120 async move |editor, cx| {
6121 let task_context = task_context_task.await;
6122
6123 let resolved_tasks =
6124 tasks
6125 .zip(task_context.clone())
6126 .map(|(tasks, task_context)| ResolvedTasks {
6127 templates: tasks.resolve(&task_context).collect(),
6128 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6129 multibuffer_point.row,
6130 tasks.column,
6131 )),
6132 });
6133 let debug_scenarios = editor
6134 .update(cx, |editor, cx| {
6135 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6136 })?
6137 .await;
6138 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6139 }
6140 })
6141 }
6142 };
6143
6144 cx.spawn_in(window, async move |editor, cx| {
6145 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6146 let code_actions = code_actions_task.await;
6147 let spawn_straight_away = quick_launch
6148 && resolved_tasks
6149 .as_ref()
6150 .is_some_and(|tasks| tasks.templates.len() == 1)
6151 && code_actions
6152 .as_ref()
6153 .is_none_or(|actions| actions.is_empty())
6154 && debug_scenarios.is_empty();
6155
6156 editor.update_in(cx, |editor, window, cx| {
6157 crate::hover_popover::hide_hover(editor, cx);
6158 let actions = CodeActionContents::new(
6159 resolved_tasks,
6160 code_actions,
6161 debug_scenarios,
6162 task_context.unwrap_or_default(),
6163 );
6164
6165 // Don't show the menu if there are no actions available
6166 if actions.is_empty() {
6167 cx.notify();
6168 return Task::ready(Ok(()));
6169 }
6170
6171 *editor.context_menu.borrow_mut() =
6172 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6173 buffer,
6174 actions,
6175 selected_item: Default::default(),
6176 scroll_handle: UniformListScrollHandle::default(),
6177 deployed_from,
6178 }));
6179 cx.notify();
6180 if spawn_straight_away
6181 && let Some(task) = editor.confirm_code_action(
6182 &ConfirmCodeAction { item_ix: Some(0) },
6183 window,
6184 cx,
6185 )
6186 {
6187 return task;
6188 }
6189
6190 Task::ready(Ok(()))
6191 })
6192 })
6193 .detach_and_log_err(cx);
6194 }
6195
6196 fn debug_scenarios(
6197 &mut self,
6198 resolved_tasks: &Option<ResolvedTasks>,
6199 buffer: &Entity<Buffer>,
6200 cx: &mut App,
6201 ) -> Task<Vec<task::DebugScenario>> {
6202 maybe!({
6203 let project = self.project()?;
6204 let dap_store = project.read(cx).dap_store();
6205 let mut scenarios = vec![];
6206 let resolved_tasks = resolved_tasks.as_ref()?;
6207 let buffer = buffer.read(cx);
6208 let language = buffer.language()?;
6209 let file = buffer.file();
6210 let debug_adapter = language_settings(language.name().into(), file, cx)
6211 .debuggers
6212 .first()
6213 .map(SharedString::from)
6214 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6215
6216 dap_store.update(cx, |dap_store, cx| {
6217 for (_, task) in &resolved_tasks.templates {
6218 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6219 task.original_task().clone(),
6220 debug_adapter.clone().into(),
6221 task.display_label().to_owned().into(),
6222 cx,
6223 );
6224 scenarios.push(maybe_scenario);
6225 }
6226 });
6227 Some(cx.background_spawn(async move {
6228 futures::future::join_all(scenarios)
6229 .await
6230 .into_iter()
6231 .flatten()
6232 .collect::<Vec<_>>()
6233 }))
6234 })
6235 .unwrap_or_else(|| Task::ready(vec![]))
6236 }
6237
6238 fn code_actions(
6239 &mut self,
6240 buffer_row: u32,
6241 window: &mut Window,
6242 cx: &mut Context<Self>,
6243 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6244 let mut task = self.code_actions_task.take();
6245 cx.spawn_in(window, async move |editor, cx| {
6246 while let Some(prev_task) = task {
6247 prev_task.await.log_err();
6248 task = editor
6249 .update(cx, |this, _| this.code_actions_task.take())
6250 .ok()?;
6251 }
6252
6253 editor
6254 .update(cx, |editor, cx| {
6255 editor
6256 .available_code_actions
6257 .clone()
6258 .and_then(|(location, code_actions)| {
6259 let snapshot = location.buffer.read(cx).snapshot();
6260 let point_range = location.range.to_point(&snapshot);
6261 let point_range = point_range.start.row..=point_range.end.row;
6262 if point_range.contains(&buffer_row) {
6263 Some(code_actions)
6264 } else {
6265 None
6266 }
6267 })
6268 })
6269 .ok()
6270 .flatten()
6271 })
6272 }
6273
6274 pub fn confirm_code_action(
6275 &mut self,
6276 action: &ConfirmCodeAction,
6277 window: &mut Window,
6278 cx: &mut Context<Self>,
6279 ) -> Option<Task<Result<()>>> {
6280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6281
6282 let actions_menu =
6283 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6284 menu
6285 } else {
6286 return None;
6287 };
6288
6289 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6290 let action = actions_menu.actions.get(action_ix)?;
6291 let title = action.label();
6292 let buffer = actions_menu.buffer;
6293 let workspace = self.workspace()?;
6294
6295 match action {
6296 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6297 workspace.update(cx, |workspace, cx| {
6298 workspace.schedule_resolved_task(
6299 task_source_kind,
6300 resolved_task,
6301 false,
6302 window,
6303 cx,
6304 );
6305
6306 Some(Task::ready(Ok(())))
6307 })
6308 }
6309 CodeActionsItem::CodeAction {
6310 excerpt_id,
6311 action,
6312 provider,
6313 } => {
6314 let apply_code_action =
6315 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6316 let workspace = workspace.downgrade();
6317 Some(cx.spawn_in(window, async move |editor, cx| {
6318 let project_transaction = apply_code_action.await?;
6319 Self::open_project_transaction(
6320 &editor,
6321 workspace,
6322 project_transaction,
6323 title,
6324 cx,
6325 )
6326 .await
6327 }))
6328 }
6329 CodeActionsItem::DebugScenario(scenario) => {
6330 let context = actions_menu.actions.context;
6331
6332 workspace.update(cx, |workspace, cx| {
6333 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6334 workspace.start_debug_session(
6335 scenario,
6336 context,
6337 Some(buffer),
6338 None,
6339 window,
6340 cx,
6341 );
6342 });
6343 Some(Task::ready(Ok(())))
6344 }
6345 }
6346 }
6347
6348 pub async fn open_project_transaction(
6349 editor: &WeakEntity<Editor>,
6350 workspace: WeakEntity<Workspace>,
6351 transaction: ProjectTransaction,
6352 title: String,
6353 cx: &mut AsyncWindowContext,
6354 ) -> Result<()> {
6355 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6356 cx.update(|_, cx| {
6357 entries.sort_unstable_by_key(|(buffer, _)| {
6358 buffer.read(cx).file().map(|f| f.path().clone())
6359 });
6360 })?;
6361 if entries.is_empty() {
6362 return Ok(());
6363 }
6364
6365 // If the project transaction's edits are all contained within this editor, then
6366 // avoid opening a new editor to display them.
6367
6368 if let [(buffer, transaction)] = &*entries {
6369 let excerpt = editor.update(cx, |editor, cx| {
6370 editor
6371 .buffer()
6372 .read(cx)
6373 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6374 })?;
6375 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6376 && excerpted_buffer == *buffer
6377 {
6378 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6379 let excerpt_range = excerpt_range.to_offset(buffer);
6380 buffer
6381 .edited_ranges_for_transaction::<usize>(transaction)
6382 .all(|range| {
6383 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6384 })
6385 })?;
6386
6387 if all_edits_within_excerpt {
6388 return Ok(());
6389 }
6390 }
6391 }
6392
6393 let mut ranges_to_highlight = Vec::new();
6394 let excerpt_buffer = cx.new(|cx| {
6395 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6396 for (buffer_handle, transaction) in &entries {
6397 let edited_ranges = buffer_handle
6398 .read(cx)
6399 .edited_ranges_for_transaction::<Point>(transaction)
6400 .collect::<Vec<_>>();
6401 let (ranges, _) = multibuffer.set_excerpts_for_path(
6402 PathKey::for_buffer(buffer_handle, cx),
6403 buffer_handle.clone(),
6404 edited_ranges,
6405 multibuffer_context_lines(cx),
6406 cx,
6407 );
6408
6409 ranges_to_highlight.extend(ranges);
6410 }
6411 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6412 multibuffer
6413 })?;
6414
6415 workspace.update_in(cx, |workspace, window, cx| {
6416 let project = workspace.project().clone();
6417 let editor =
6418 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6419 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6420 editor.update(cx, |editor, cx| {
6421 editor.highlight_background::<Self>(
6422 &ranges_to_highlight,
6423 |theme| theme.colors().editor_highlighted_line_background,
6424 cx,
6425 );
6426 });
6427 })?;
6428
6429 Ok(())
6430 }
6431
6432 pub fn clear_code_action_providers(&mut self) {
6433 self.code_action_providers.clear();
6434 self.available_code_actions.take();
6435 }
6436
6437 pub fn add_code_action_provider(
6438 &mut self,
6439 provider: Rc<dyn CodeActionProvider>,
6440 window: &mut Window,
6441 cx: &mut Context<Self>,
6442 ) {
6443 if self
6444 .code_action_providers
6445 .iter()
6446 .any(|existing_provider| existing_provider.id() == provider.id())
6447 {
6448 return;
6449 }
6450
6451 self.code_action_providers.push(provider);
6452 self.refresh_code_actions(window, cx);
6453 }
6454
6455 pub fn remove_code_action_provider(
6456 &mut self,
6457 id: Arc<str>,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 self.code_action_providers
6462 .retain(|provider| provider.id() != id);
6463 self.refresh_code_actions(window, cx);
6464 }
6465
6466 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6467 !self.code_action_providers.is_empty()
6468 && EditorSettings::get_global(cx).toolbar.code_actions
6469 }
6470
6471 pub fn has_available_code_actions(&self) -> bool {
6472 self.available_code_actions
6473 .as_ref()
6474 .is_some_and(|(_, actions)| !actions.is_empty())
6475 }
6476
6477 fn render_inline_code_actions(
6478 &self,
6479 icon_size: ui::IconSize,
6480 display_row: DisplayRow,
6481 is_active: bool,
6482 cx: &mut Context<Self>,
6483 ) -> AnyElement {
6484 let show_tooltip = !self.context_menu_visible();
6485 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6486 .icon_size(icon_size)
6487 .shape(ui::IconButtonShape::Square)
6488 .icon_color(ui::Color::Hidden)
6489 .toggle_state(is_active)
6490 .when(show_tooltip, |this| {
6491 this.tooltip({
6492 let focus_handle = self.focus_handle.clone();
6493 move |_window, cx| {
6494 Tooltip::for_action_in(
6495 "Toggle Code Actions",
6496 &ToggleCodeActions {
6497 deployed_from: None,
6498 quick_launch: false,
6499 },
6500 &focus_handle,
6501 cx,
6502 )
6503 }
6504 })
6505 })
6506 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6507 window.focus(&editor.focus_handle(cx));
6508 editor.toggle_code_actions(
6509 &crate::actions::ToggleCodeActions {
6510 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6511 display_row,
6512 )),
6513 quick_launch: false,
6514 },
6515 window,
6516 cx,
6517 );
6518 }))
6519 .into_any_element()
6520 }
6521
6522 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6523 &self.context_menu
6524 }
6525
6526 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6527 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6528 cx.background_executor()
6529 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6530 .await;
6531
6532 let (start_buffer, start, _, end, newest_selection) = this
6533 .update(cx, |this, cx| {
6534 let newest_selection = this.selections.newest_anchor().clone();
6535 if newest_selection.head().diff_base_anchor.is_some() {
6536 return None;
6537 }
6538 let display_snapshot = this.display_snapshot(cx);
6539 let newest_selection_adjusted =
6540 this.selections.newest_adjusted(&display_snapshot);
6541 let buffer = this.buffer.read(cx);
6542
6543 let (start_buffer, start) =
6544 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6545 let (end_buffer, end) =
6546 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6547
6548 Some((start_buffer, start, end_buffer, end, newest_selection))
6549 })?
6550 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6551 .context(
6552 "Expected selection to lie in a single buffer when refreshing code actions",
6553 )?;
6554 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6555 let providers = this.code_action_providers.clone();
6556 let tasks = this
6557 .code_action_providers
6558 .iter()
6559 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6560 .collect::<Vec<_>>();
6561 (providers, tasks)
6562 })?;
6563
6564 let mut actions = Vec::new();
6565 for (provider, provider_actions) in
6566 providers.into_iter().zip(future::join_all(tasks).await)
6567 {
6568 if let Some(provider_actions) = provider_actions.log_err() {
6569 actions.extend(provider_actions.into_iter().map(|action| {
6570 AvailableCodeAction {
6571 excerpt_id: newest_selection.start.excerpt_id,
6572 action,
6573 provider: provider.clone(),
6574 }
6575 }));
6576 }
6577 }
6578
6579 this.update(cx, |this, cx| {
6580 this.available_code_actions = if actions.is_empty() {
6581 None
6582 } else {
6583 Some((
6584 Location {
6585 buffer: start_buffer,
6586 range: start..end,
6587 },
6588 actions.into(),
6589 ))
6590 };
6591 cx.notify();
6592 })
6593 }));
6594 }
6595
6596 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6597 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6598 self.show_git_blame_inline = false;
6599
6600 self.show_git_blame_inline_delay_task =
6601 Some(cx.spawn_in(window, async move |this, cx| {
6602 cx.background_executor().timer(delay).await;
6603
6604 this.update(cx, |this, cx| {
6605 this.show_git_blame_inline = true;
6606 cx.notify();
6607 })
6608 .log_err();
6609 }));
6610 }
6611 }
6612
6613 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6614 let snapshot = self.snapshot(window, cx);
6615 let cursor = self
6616 .selections
6617 .newest::<Point>(&snapshot.display_snapshot)
6618 .head();
6619 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6620 else {
6621 return;
6622 };
6623
6624 let Some(blame) = self.blame.as_ref() else {
6625 return;
6626 };
6627
6628 let row_info = RowInfo {
6629 buffer_id: Some(buffer.remote_id()),
6630 buffer_row: Some(point.row),
6631 ..Default::default()
6632 };
6633 let Some((buffer, blame_entry)) = blame
6634 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6635 .flatten()
6636 else {
6637 return;
6638 };
6639
6640 let anchor = self.selections.newest_anchor().head();
6641 let position = self.to_pixel_point(anchor, &snapshot, window);
6642 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6643 self.show_blame_popover(
6644 buffer,
6645 &blame_entry,
6646 position + last_bounds.origin,
6647 true,
6648 cx,
6649 );
6650 };
6651 }
6652
6653 fn show_blame_popover(
6654 &mut self,
6655 buffer: BufferId,
6656 blame_entry: &BlameEntry,
6657 position: gpui::Point<Pixels>,
6658 ignore_timeout: bool,
6659 cx: &mut Context<Self>,
6660 ) {
6661 if let Some(state) = &mut self.inline_blame_popover {
6662 state.hide_task.take();
6663 } else {
6664 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6665 let blame_entry = blame_entry.clone();
6666 let show_task = cx.spawn(async move |editor, cx| {
6667 if !ignore_timeout {
6668 cx.background_executor()
6669 .timer(std::time::Duration::from_millis(blame_popover_delay))
6670 .await;
6671 }
6672 editor
6673 .update(cx, |editor, cx| {
6674 editor.inline_blame_popover_show_task.take();
6675 let Some(blame) = editor.blame.as_ref() else {
6676 return;
6677 };
6678 let blame = blame.read(cx);
6679 let details = blame.details_for_entry(buffer, &blame_entry);
6680 let markdown = cx.new(|cx| {
6681 Markdown::new(
6682 details
6683 .as_ref()
6684 .map(|message| message.message.clone())
6685 .unwrap_or_default(),
6686 None,
6687 None,
6688 cx,
6689 )
6690 });
6691 editor.inline_blame_popover = Some(InlineBlamePopover {
6692 position,
6693 hide_task: None,
6694 popover_bounds: None,
6695 popover_state: InlineBlamePopoverState {
6696 scroll_handle: ScrollHandle::new(),
6697 commit_message: details,
6698 markdown,
6699 },
6700 keyboard_grace: ignore_timeout,
6701 });
6702 cx.notify();
6703 })
6704 .ok();
6705 });
6706 self.inline_blame_popover_show_task = Some(show_task);
6707 }
6708 }
6709
6710 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6711 self.inline_blame_popover_show_task.take();
6712 if let Some(state) = &mut self.inline_blame_popover {
6713 let hide_task = cx.spawn(async move |editor, cx| {
6714 if !ignore_timeout {
6715 cx.background_executor()
6716 .timer(std::time::Duration::from_millis(100))
6717 .await;
6718 }
6719 editor
6720 .update(cx, |editor, cx| {
6721 editor.inline_blame_popover.take();
6722 cx.notify();
6723 })
6724 .ok();
6725 });
6726 state.hide_task = Some(hide_task);
6727 true
6728 } else {
6729 false
6730 }
6731 }
6732
6733 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6734 if self.pending_rename.is_some() {
6735 return None;
6736 }
6737
6738 let provider = self.semantics_provider.clone()?;
6739 let buffer = self.buffer.read(cx);
6740 let newest_selection = self.selections.newest_anchor().clone();
6741 let cursor_position = newest_selection.head();
6742 let (cursor_buffer, cursor_buffer_position) =
6743 buffer.text_anchor_for_position(cursor_position, cx)?;
6744 let (tail_buffer, tail_buffer_position) =
6745 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6746 if cursor_buffer != tail_buffer {
6747 return None;
6748 }
6749
6750 let snapshot = cursor_buffer.read(cx).snapshot();
6751 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6752 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6753 if start_word_range != end_word_range {
6754 self.document_highlights_task.take();
6755 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6756 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6757 return None;
6758 }
6759
6760 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6761 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6762 cx.background_executor()
6763 .timer(Duration::from_millis(debounce))
6764 .await;
6765
6766 let highlights = if let Some(highlights) = cx
6767 .update(|cx| {
6768 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6769 })
6770 .ok()
6771 .flatten()
6772 {
6773 highlights.await.log_err()
6774 } else {
6775 None
6776 };
6777
6778 if let Some(highlights) = highlights {
6779 this.update(cx, |this, cx| {
6780 if this.pending_rename.is_some() {
6781 return;
6782 }
6783
6784 let buffer = this.buffer.read(cx);
6785 if buffer
6786 .text_anchor_for_position(cursor_position, cx)
6787 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6788 {
6789 return;
6790 }
6791
6792 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6793 let mut write_ranges = Vec::new();
6794 let mut read_ranges = Vec::new();
6795 for highlight in highlights {
6796 let buffer_id = cursor_buffer.read(cx).remote_id();
6797 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6798 {
6799 let start = highlight
6800 .range
6801 .start
6802 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6803 let end = highlight
6804 .range
6805 .end
6806 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6807 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6808 continue;
6809 }
6810
6811 let range =
6812 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6813 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6814 write_ranges.push(range);
6815 } else {
6816 read_ranges.push(range);
6817 }
6818 }
6819 }
6820
6821 this.highlight_background::<DocumentHighlightRead>(
6822 &read_ranges,
6823 |theme| theme.colors().editor_document_highlight_read_background,
6824 cx,
6825 );
6826 this.highlight_background::<DocumentHighlightWrite>(
6827 &write_ranges,
6828 |theme| theme.colors().editor_document_highlight_write_background,
6829 cx,
6830 );
6831 cx.notify();
6832 })
6833 .log_err();
6834 }
6835 }));
6836 None
6837 }
6838
6839 fn prepare_highlight_query_from_selection(
6840 &mut self,
6841 window: &Window,
6842 cx: &mut Context<Editor>,
6843 ) -> Option<(String, Range<Anchor>)> {
6844 if matches!(self.mode, EditorMode::SingleLine) {
6845 return None;
6846 }
6847 if !EditorSettings::get_global(cx).selection_highlight {
6848 return None;
6849 }
6850 if self.selections.count() != 1 || self.selections.line_mode() {
6851 return None;
6852 }
6853 let snapshot = self.snapshot(window, cx);
6854 let selection = self.selections.newest::<Point>(&snapshot);
6855 // If the selection spans multiple rows OR it is empty
6856 if selection.start.row != selection.end.row
6857 || selection.start.column == selection.end.column
6858 {
6859 return None;
6860 }
6861 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6862 let query = snapshot
6863 .buffer_snapshot()
6864 .text_for_range(selection_anchor_range.clone())
6865 .collect::<String>();
6866 if query.trim().is_empty() {
6867 return None;
6868 }
6869 Some((query, selection_anchor_range))
6870 }
6871
6872 fn update_selection_occurrence_highlights(
6873 &mut self,
6874 query_text: String,
6875 query_range: Range<Anchor>,
6876 multi_buffer_range_to_query: Range<Point>,
6877 use_debounce: bool,
6878 window: &mut Window,
6879 cx: &mut Context<Editor>,
6880 ) -> Task<()> {
6881 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6882 cx.spawn_in(window, async move |editor, cx| {
6883 if use_debounce {
6884 cx.background_executor()
6885 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6886 .await;
6887 }
6888 let match_task = cx.background_spawn(async move {
6889 let buffer_ranges = multi_buffer_snapshot
6890 .range_to_buffer_ranges(multi_buffer_range_to_query)
6891 .into_iter()
6892 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6893 let mut match_ranges = Vec::new();
6894 let Ok(regex) = project::search::SearchQuery::text(
6895 query_text.clone(),
6896 false,
6897 false,
6898 false,
6899 Default::default(),
6900 Default::default(),
6901 false,
6902 None,
6903 ) else {
6904 return Vec::default();
6905 };
6906 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6907 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6908 match_ranges.extend(
6909 regex
6910 .search(buffer_snapshot, Some(search_range.clone()))
6911 .await
6912 .into_iter()
6913 .filter_map(|match_range| {
6914 let match_start = buffer_snapshot
6915 .anchor_after(search_range.start + match_range.start);
6916 let match_end = buffer_snapshot
6917 .anchor_before(search_range.start + match_range.end);
6918 let match_anchor_range = Anchor::range_in_buffer(
6919 excerpt_id,
6920 buffer_snapshot.remote_id(),
6921 match_start..match_end,
6922 );
6923 (match_anchor_range != query_range).then_some(match_anchor_range)
6924 }),
6925 );
6926 }
6927 match_ranges
6928 });
6929 let match_ranges = match_task.await;
6930 editor
6931 .update_in(cx, |editor, _, cx| {
6932 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6933 if !match_ranges.is_empty() {
6934 editor.highlight_background::<SelectedTextHighlight>(
6935 &match_ranges,
6936 |theme| theme.colors().editor_document_highlight_bracket_background,
6937 cx,
6938 )
6939 }
6940 })
6941 .log_err();
6942 })
6943 }
6944
6945 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6946 struct NewlineFold;
6947 let type_id = std::any::TypeId::of::<NewlineFold>();
6948 if !self.mode.is_single_line() {
6949 return;
6950 }
6951 let snapshot = self.snapshot(window, cx);
6952 if snapshot.buffer_snapshot().max_point().row == 0 {
6953 return;
6954 }
6955 let task = cx.background_spawn(async move {
6956 let new_newlines = snapshot
6957 .buffer_chars_at(0)
6958 .filter_map(|(c, i)| {
6959 if c == '\n' {
6960 Some(
6961 snapshot.buffer_snapshot().anchor_after(i)
6962 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6963 )
6964 } else {
6965 None
6966 }
6967 })
6968 .collect::<Vec<_>>();
6969 let existing_newlines = snapshot
6970 .folds_in_range(0..snapshot.buffer_snapshot().len())
6971 .filter_map(|fold| {
6972 if fold.placeholder.type_tag == Some(type_id) {
6973 Some(fold.range.start..fold.range.end)
6974 } else {
6975 None
6976 }
6977 })
6978 .collect::<Vec<_>>();
6979
6980 (new_newlines, existing_newlines)
6981 });
6982 self.folding_newlines = cx.spawn(async move |this, cx| {
6983 let (new_newlines, existing_newlines) = task.await;
6984 if new_newlines == existing_newlines {
6985 return;
6986 }
6987 let placeholder = FoldPlaceholder {
6988 render: Arc::new(move |_, _, cx| {
6989 div()
6990 .bg(cx.theme().status().hint_background)
6991 .border_b_1()
6992 .size_full()
6993 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6994 .border_color(cx.theme().status().hint)
6995 .child("\\n")
6996 .into_any()
6997 }),
6998 constrain_width: false,
6999 merge_adjacent: false,
7000 type_tag: Some(type_id),
7001 };
7002 let creases = new_newlines
7003 .into_iter()
7004 .map(|range| Crease::simple(range, placeholder.clone()))
7005 .collect();
7006 this.update(cx, |this, cx| {
7007 this.display_map.update(cx, |display_map, cx| {
7008 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7009 display_map.fold(creases, cx);
7010 });
7011 })
7012 .ok();
7013 });
7014 }
7015
7016 fn refresh_selected_text_highlights(
7017 &mut self,
7018 on_buffer_edit: bool,
7019 window: &mut Window,
7020 cx: &mut Context<Editor>,
7021 ) {
7022 let Some((query_text, query_range)) =
7023 self.prepare_highlight_query_from_selection(window, cx)
7024 else {
7025 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7026 self.quick_selection_highlight_task.take();
7027 self.debounced_selection_highlight_task.take();
7028 return;
7029 };
7030 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7031 if on_buffer_edit
7032 || self
7033 .quick_selection_highlight_task
7034 .as_ref()
7035 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7036 {
7037 let multi_buffer_visible_start = self
7038 .scroll_manager
7039 .anchor()
7040 .anchor
7041 .to_point(&multi_buffer_snapshot);
7042 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7043 multi_buffer_visible_start
7044 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7045 Bias::Left,
7046 );
7047 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7048 self.quick_selection_highlight_task = Some((
7049 query_range.clone(),
7050 self.update_selection_occurrence_highlights(
7051 query_text.clone(),
7052 query_range.clone(),
7053 multi_buffer_visible_range,
7054 false,
7055 window,
7056 cx,
7057 ),
7058 ));
7059 }
7060 if on_buffer_edit
7061 || self
7062 .debounced_selection_highlight_task
7063 .as_ref()
7064 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7065 {
7066 let multi_buffer_start = multi_buffer_snapshot
7067 .anchor_before(0)
7068 .to_point(&multi_buffer_snapshot);
7069 let multi_buffer_end = multi_buffer_snapshot
7070 .anchor_after(multi_buffer_snapshot.len())
7071 .to_point(&multi_buffer_snapshot);
7072 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7073 self.debounced_selection_highlight_task = Some((
7074 query_range.clone(),
7075 self.update_selection_occurrence_highlights(
7076 query_text,
7077 query_range,
7078 multi_buffer_full_range,
7079 true,
7080 window,
7081 cx,
7082 ),
7083 ));
7084 }
7085 }
7086
7087 pub fn refresh_edit_prediction(
7088 &mut self,
7089 debounce: bool,
7090 user_requested: bool,
7091 window: &mut Window,
7092 cx: &mut Context<Self>,
7093 ) -> Option<()> {
7094 if DisableAiSettings::get_global(cx).disable_ai {
7095 return None;
7096 }
7097
7098 let provider = self.edit_prediction_provider()?;
7099 let cursor = self.selections.newest_anchor().head();
7100 let (buffer, cursor_buffer_position) =
7101 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7102
7103 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7104 self.discard_edit_prediction(false, cx);
7105 return None;
7106 }
7107
7108 self.update_visible_edit_prediction(window, cx);
7109
7110 if !user_requested
7111 && (!self.should_show_edit_predictions()
7112 || !self.is_focused(window)
7113 || buffer.read(cx).is_empty())
7114 {
7115 self.discard_edit_prediction(false, cx);
7116 return None;
7117 }
7118
7119 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7120 Some(())
7121 }
7122
7123 fn show_edit_predictions_in_menu(&self) -> bool {
7124 match self.edit_prediction_settings {
7125 EditPredictionSettings::Disabled => false,
7126 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7127 }
7128 }
7129
7130 pub fn edit_predictions_enabled(&self) -> bool {
7131 match self.edit_prediction_settings {
7132 EditPredictionSettings::Disabled => false,
7133 EditPredictionSettings::Enabled { .. } => true,
7134 }
7135 }
7136
7137 fn edit_prediction_requires_modifier(&self) -> bool {
7138 match self.edit_prediction_settings {
7139 EditPredictionSettings::Disabled => false,
7140 EditPredictionSettings::Enabled {
7141 preview_requires_modifier,
7142 ..
7143 } => preview_requires_modifier,
7144 }
7145 }
7146
7147 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7148 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7149 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7150 self.discard_edit_prediction(false, cx);
7151 } else {
7152 let selection = self.selections.newest_anchor();
7153 let cursor = selection.head();
7154
7155 if let Some((buffer, cursor_buffer_position)) =
7156 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7157 {
7158 self.edit_prediction_settings =
7159 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7160 }
7161 }
7162 }
7163
7164 fn edit_prediction_settings_at_position(
7165 &self,
7166 buffer: &Entity<Buffer>,
7167 buffer_position: language::Anchor,
7168 cx: &App,
7169 ) -> EditPredictionSettings {
7170 if !self.mode.is_full()
7171 || !self.show_edit_predictions_override.unwrap_or(true)
7172 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7173 {
7174 return EditPredictionSettings::Disabled;
7175 }
7176
7177 let buffer = buffer.read(cx);
7178
7179 let file = buffer.file();
7180
7181 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7182 return EditPredictionSettings::Disabled;
7183 };
7184
7185 let by_provider = matches!(
7186 self.menu_edit_predictions_policy,
7187 MenuEditPredictionsPolicy::ByProvider
7188 );
7189
7190 let show_in_menu = by_provider
7191 && self
7192 .edit_prediction_provider
7193 .as_ref()
7194 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7195
7196 let preview_requires_modifier =
7197 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7198
7199 EditPredictionSettings::Enabled {
7200 show_in_menu,
7201 preview_requires_modifier,
7202 }
7203 }
7204
7205 fn should_show_edit_predictions(&self) -> bool {
7206 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7207 }
7208
7209 pub fn edit_prediction_preview_is_active(&self) -> bool {
7210 matches!(
7211 self.edit_prediction_preview,
7212 EditPredictionPreview::Active { .. }
7213 )
7214 }
7215
7216 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7217 let cursor = self.selections.newest_anchor().head();
7218 if let Some((buffer, cursor_position)) =
7219 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7220 {
7221 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7222 } else {
7223 false
7224 }
7225 }
7226
7227 pub fn supports_minimap(&self, cx: &App) -> bool {
7228 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7229 }
7230
7231 fn edit_predictions_enabled_in_buffer(
7232 &self,
7233 buffer: &Entity<Buffer>,
7234 buffer_position: language::Anchor,
7235 cx: &App,
7236 ) -> bool {
7237 maybe!({
7238 if self.read_only(cx) {
7239 return Some(false);
7240 }
7241 let provider = self.edit_prediction_provider()?;
7242 if !provider.is_enabled(buffer, buffer_position, cx) {
7243 return Some(false);
7244 }
7245 let buffer = buffer.read(cx);
7246 let Some(file) = buffer.file() else {
7247 return Some(true);
7248 };
7249 let settings = all_language_settings(Some(file), cx);
7250 Some(settings.edit_predictions_enabled_for_file(file, cx))
7251 })
7252 .unwrap_or(false)
7253 }
7254
7255 fn cycle_edit_prediction(
7256 &mut self,
7257 direction: Direction,
7258 window: &mut Window,
7259 cx: &mut Context<Self>,
7260 ) -> Option<()> {
7261 let provider = self.edit_prediction_provider()?;
7262 let cursor = self.selections.newest_anchor().head();
7263 let (buffer, cursor_buffer_position) =
7264 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7265 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7266 return None;
7267 }
7268
7269 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7270 self.update_visible_edit_prediction(window, cx);
7271
7272 Some(())
7273 }
7274
7275 pub fn show_edit_prediction(
7276 &mut self,
7277 _: &ShowEditPrediction,
7278 window: &mut Window,
7279 cx: &mut Context<Self>,
7280 ) {
7281 if !self.has_active_edit_prediction() {
7282 self.refresh_edit_prediction(false, true, window, cx);
7283 return;
7284 }
7285
7286 self.update_visible_edit_prediction(window, cx);
7287 }
7288
7289 pub fn display_cursor_names(
7290 &mut self,
7291 _: &DisplayCursorNames,
7292 window: &mut Window,
7293 cx: &mut Context<Self>,
7294 ) {
7295 self.show_cursor_names(window, cx);
7296 }
7297
7298 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7299 self.show_cursor_names = true;
7300 cx.notify();
7301 cx.spawn_in(window, async move |this, cx| {
7302 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7303 this.update(cx, |this, cx| {
7304 this.show_cursor_names = false;
7305 cx.notify()
7306 })
7307 .ok()
7308 })
7309 .detach();
7310 }
7311
7312 pub fn next_edit_prediction(
7313 &mut self,
7314 _: &NextEditPrediction,
7315 window: &mut Window,
7316 cx: &mut Context<Self>,
7317 ) {
7318 if self.has_active_edit_prediction() {
7319 self.cycle_edit_prediction(Direction::Next, window, cx);
7320 } else {
7321 let is_copilot_disabled = self
7322 .refresh_edit_prediction(false, true, window, cx)
7323 .is_none();
7324 if is_copilot_disabled {
7325 cx.propagate();
7326 }
7327 }
7328 }
7329
7330 pub fn previous_edit_prediction(
7331 &mut self,
7332 _: &PreviousEditPrediction,
7333 window: &mut Window,
7334 cx: &mut Context<Self>,
7335 ) {
7336 if self.has_active_edit_prediction() {
7337 self.cycle_edit_prediction(Direction::Prev, window, cx);
7338 } else {
7339 let is_copilot_disabled = self
7340 .refresh_edit_prediction(false, true, window, cx)
7341 .is_none();
7342 if is_copilot_disabled {
7343 cx.propagate();
7344 }
7345 }
7346 }
7347
7348 pub fn accept_edit_prediction(
7349 &mut self,
7350 _: &AcceptEditPrediction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 if self.show_edit_predictions_in_menu() {
7355 self.hide_context_menu(window, cx);
7356 }
7357
7358 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7359 return;
7360 };
7361
7362 match &active_edit_prediction.completion {
7363 EditPrediction::MoveWithin { target, .. } => {
7364 let target = *target;
7365
7366 if let Some(position_map) = &self.last_position_map {
7367 if position_map
7368 .visible_row_range
7369 .contains(&target.to_display_point(&position_map.snapshot).row())
7370 || !self.edit_prediction_requires_modifier()
7371 {
7372 self.unfold_ranges(&[target..target], true, false, cx);
7373 // Note that this is also done in vim's handler of the Tab action.
7374 self.change_selections(
7375 SelectionEffects::scroll(Autoscroll::newest()),
7376 window,
7377 cx,
7378 |selections| {
7379 selections.select_anchor_ranges([target..target]);
7380 },
7381 );
7382 self.clear_row_highlights::<EditPredictionPreview>();
7383
7384 self.edit_prediction_preview
7385 .set_previous_scroll_position(None);
7386 } else {
7387 self.edit_prediction_preview
7388 .set_previous_scroll_position(Some(
7389 position_map.snapshot.scroll_anchor,
7390 ));
7391
7392 self.highlight_rows::<EditPredictionPreview>(
7393 target..target,
7394 cx.theme().colors().editor_highlighted_line_background,
7395 RowHighlightOptions {
7396 autoscroll: true,
7397 ..Default::default()
7398 },
7399 cx,
7400 );
7401 self.request_autoscroll(Autoscroll::fit(), cx);
7402 }
7403 }
7404 }
7405 EditPrediction::MoveOutside { snapshot, target } => {
7406 if let Some(workspace) = self.workspace() {
7407 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7408 .detach_and_log_err(cx);
7409 }
7410 }
7411 EditPrediction::Edit { edits, .. } => {
7412 self.report_edit_prediction_event(
7413 active_edit_prediction.completion_id.clone(),
7414 true,
7415 cx,
7416 );
7417
7418 if let Some(provider) = self.edit_prediction_provider() {
7419 provider.accept(cx);
7420 }
7421
7422 // Store the transaction ID and selections before applying the edit
7423 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7424
7425 let snapshot = self.buffer.read(cx).snapshot(cx);
7426 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7427
7428 self.buffer.update(cx, |buffer, cx| {
7429 buffer.edit(edits.iter().cloned(), None, cx)
7430 });
7431
7432 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7433 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7434 });
7435
7436 let selections = self.selections.disjoint_anchors_arc();
7437 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7438 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7439 if has_new_transaction {
7440 self.selection_history
7441 .insert_transaction(transaction_id_now, selections);
7442 }
7443 }
7444
7445 self.update_visible_edit_prediction(window, cx);
7446 if self.active_edit_prediction.is_none() {
7447 self.refresh_edit_prediction(true, true, window, cx);
7448 }
7449
7450 cx.notify();
7451 }
7452 }
7453
7454 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7455 }
7456
7457 pub fn accept_partial_edit_prediction(
7458 &mut self,
7459 _: &AcceptPartialEditPrediction,
7460 window: &mut Window,
7461 cx: &mut Context<Self>,
7462 ) {
7463 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7464 return;
7465 };
7466 if self.selections.count() != 1 {
7467 return;
7468 }
7469
7470 match &active_edit_prediction.completion {
7471 EditPrediction::MoveWithin { target, .. } => {
7472 let target = *target;
7473 self.change_selections(
7474 SelectionEffects::scroll(Autoscroll::newest()),
7475 window,
7476 cx,
7477 |selections| {
7478 selections.select_anchor_ranges([target..target]);
7479 },
7480 );
7481 }
7482 EditPrediction::MoveOutside { snapshot, target } => {
7483 if let Some(workspace) = self.workspace() {
7484 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7485 .detach_and_log_err(cx);
7486 }
7487 }
7488 EditPrediction::Edit { edits, .. } => {
7489 self.report_edit_prediction_event(
7490 active_edit_prediction.completion_id.clone(),
7491 true,
7492 cx,
7493 );
7494
7495 // Find an insertion that starts at the cursor position.
7496 let snapshot = self.buffer.read(cx).snapshot(cx);
7497 let cursor_offset = self
7498 .selections
7499 .newest::<usize>(&self.display_snapshot(cx))
7500 .head();
7501 let insertion = edits.iter().find_map(|(range, text)| {
7502 let range = range.to_offset(&snapshot);
7503 if range.is_empty() && range.start == cursor_offset {
7504 Some(text)
7505 } else {
7506 None
7507 }
7508 });
7509
7510 if let Some(text) = insertion {
7511 let mut partial_completion = text
7512 .chars()
7513 .by_ref()
7514 .take_while(|c| c.is_alphabetic())
7515 .collect::<String>();
7516 if partial_completion.is_empty() {
7517 partial_completion = text
7518 .chars()
7519 .by_ref()
7520 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7521 .collect::<String>();
7522 }
7523
7524 cx.emit(EditorEvent::InputHandled {
7525 utf16_range_to_replace: None,
7526 text: partial_completion.clone().into(),
7527 });
7528
7529 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7530
7531 self.refresh_edit_prediction(true, true, window, cx);
7532 cx.notify();
7533 } else {
7534 self.accept_edit_prediction(&Default::default(), window, cx);
7535 }
7536 }
7537 }
7538 }
7539
7540 fn discard_edit_prediction(
7541 &mut self,
7542 should_report_edit_prediction_event: bool,
7543 cx: &mut Context<Self>,
7544 ) -> bool {
7545 if should_report_edit_prediction_event {
7546 let completion_id = self
7547 .active_edit_prediction
7548 .as_ref()
7549 .and_then(|active_completion| active_completion.completion_id.clone());
7550
7551 self.report_edit_prediction_event(completion_id, false, cx);
7552 }
7553
7554 if let Some(provider) = self.edit_prediction_provider() {
7555 provider.discard(cx);
7556 }
7557
7558 self.take_active_edit_prediction(cx)
7559 }
7560
7561 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7562 let Some(provider) = self.edit_prediction_provider() else {
7563 return;
7564 };
7565
7566 let Some((_, buffer, _)) = self
7567 .buffer
7568 .read(cx)
7569 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7570 else {
7571 return;
7572 };
7573
7574 let extension = buffer
7575 .read(cx)
7576 .file()
7577 .and_then(|file| Some(file.path().extension()?.to_string()));
7578
7579 let event_type = match accepted {
7580 true => "Edit Prediction Accepted",
7581 false => "Edit Prediction Discarded",
7582 };
7583 telemetry::event!(
7584 event_type,
7585 provider = provider.name(),
7586 prediction_id = id,
7587 suggestion_accepted = accepted,
7588 file_extension = extension,
7589 );
7590 }
7591
7592 fn open_editor_at_anchor(
7593 snapshot: &language::BufferSnapshot,
7594 target: language::Anchor,
7595 workspace: &Entity<Workspace>,
7596 window: &mut Window,
7597 cx: &mut App,
7598 ) -> Task<Result<()>> {
7599 workspace.update(cx, |workspace, cx| {
7600 let path = snapshot.file().map(|file| file.full_path(cx));
7601 let Some(path) =
7602 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7603 else {
7604 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7605 };
7606 let target = text::ToPoint::to_point(&target, snapshot);
7607 let item = workspace.open_path(path, None, true, window, cx);
7608 window.spawn(cx, async move |cx| {
7609 let Some(editor) = item.await?.downcast::<Editor>() else {
7610 return Ok(());
7611 };
7612 editor
7613 .update_in(cx, |editor, window, cx| {
7614 editor.go_to_singleton_buffer_point(target, window, cx);
7615 })
7616 .ok();
7617 anyhow::Ok(())
7618 })
7619 })
7620 }
7621
7622 pub fn has_active_edit_prediction(&self) -> bool {
7623 self.active_edit_prediction.is_some()
7624 }
7625
7626 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7627 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7628 return false;
7629 };
7630
7631 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7632 self.clear_highlights::<EditPredictionHighlight>(cx);
7633 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7634 true
7635 }
7636
7637 /// Returns true when we're displaying the edit prediction popover below the cursor
7638 /// like we are not previewing and the LSP autocomplete menu is visible
7639 /// or we are in `when_holding_modifier` mode.
7640 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7641 if self.edit_prediction_preview_is_active()
7642 || !self.show_edit_predictions_in_menu()
7643 || !self.edit_predictions_enabled()
7644 {
7645 return false;
7646 }
7647
7648 if self.has_visible_completions_menu() {
7649 return true;
7650 }
7651
7652 has_completion && self.edit_prediction_requires_modifier()
7653 }
7654
7655 fn handle_modifiers_changed(
7656 &mut self,
7657 modifiers: Modifiers,
7658 position_map: &PositionMap,
7659 window: &mut Window,
7660 cx: &mut Context<Self>,
7661 ) {
7662 // Ensure that the edit prediction preview is updated, even when not
7663 // enabled, if there's an active edit prediction preview.
7664 if self.show_edit_predictions_in_menu()
7665 || matches!(
7666 self.edit_prediction_preview,
7667 EditPredictionPreview::Active { .. }
7668 )
7669 {
7670 self.update_edit_prediction_preview(&modifiers, window, cx);
7671 }
7672
7673 self.update_selection_mode(&modifiers, position_map, window, cx);
7674
7675 let mouse_position = window.mouse_position();
7676 if !position_map.text_hitbox.is_hovered(window) {
7677 return;
7678 }
7679
7680 self.update_hovered_link(
7681 position_map.point_for_position(mouse_position),
7682 &position_map.snapshot,
7683 modifiers,
7684 window,
7685 cx,
7686 )
7687 }
7688
7689 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7690 match EditorSettings::get_global(cx).multi_cursor_modifier {
7691 MultiCursorModifier::Alt => modifiers.secondary(),
7692 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7693 }
7694 }
7695
7696 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7697 match EditorSettings::get_global(cx).multi_cursor_modifier {
7698 MultiCursorModifier::Alt => modifiers.alt,
7699 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7700 }
7701 }
7702
7703 fn columnar_selection_mode(
7704 modifiers: &Modifiers,
7705 cx: &mut Context<Self>,
7706 ) -> Option<ColumnarMode> {
7707 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7708 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7709 Some(ColumnarMode::FromMouse)
7710 } else if Self::is_alt_pressed(modifiers, cx) {
7711 Some(ColumnarMode::FromSelection)
7712 } else {
7713 None
7714 }
7715 } else {
7716 None
7717 }
7718 }
7719
7720 fn update_selection_mode(
7721 &mut self,
7722 modifiers: &Modifiers,
7723 position_map: &PositionMap,
7724 window: &mut Window,
7725 cx: &mut Context<Self>,
7726 ) {
7727 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7728 return;
7729 };
7730 if self.selections.pending_anchor().is_none() {
7731 return;
7732 }
7733
7734 let mouse_position = window.mouse_position();
7735 let point_for_position = position_map.point_for_position(mouse_position);
7736 let position = point_for_position.previous_valid;
7737
7738 self.select(
7739 SelectPhase::BeginColumnar {
7740 position,
7741 reset: false,
7742 mode,
7743 goal_column: point_for_position.exact_unclipped.column(),
7744 },
7745 window,
7746 cx,
7747 );
7748 }
7749
7750 fn update_edit_prediction_preview(
7751 &mut self,
7752 modifiers: &Modifiers,
7753 window: &mut Window,
7754 cx: &mut Context<Self>,
7755 ) {
7756 let mut modifiers_held = false;
7757 if let Some(accept_keystroke) = self
7758 .accept_edit_prediction_keybind(false, window, cx)
7759 .keystroke()
7760 {
7761 modifiers_held = modifiers_held
7762 || (accept_keystroke.modifiers() == modifiers
7763 && accept_keystroke.modifiers().modified());
7764 };
7765 if let Some(accept_partial_keystroke) = self
7766 .accept_edit_prediction_keybind(true, window, cx)
7767 .keystroke()
7768 {
7769 modifiers_held = modifiers_held
7770 || (accept_partial_keystroke.modifiers() == modifiers
7771 && accept_partial_keystroke.modifiers().modified());
7772 }
7773
7774 if modifiers_held {
7775 if matches!(
7776 self.edit_prediction_preview,
7777 EditPredictionPreview::Inactive { .. }
7778 ) {
7779 self.edit_prediction_preview = EditPredictionPreview::Active {
7780 previous_scroll_position: None,
7781 since: Instant::now(),
7782 };
7783
7784 self.update_visible_edit_prediction(window, cx);
7785 cx.notify();
7786 }
7787 } else if let EditPredictionPreview::Active {
7788 previous_scroll_position,
7789 since,
7790 } = self.edit_prediction_preview
7791 {
7792 if let (Some(previous_scroll_position), Some(position_map)) =
7793 (previous_scroll_position, self.last_position_map.as_ref())
7794 {
7795 self.set_scroll_position(
7796 previous_scroll_position
7797 .scroll_position(&position_map.snapshot.display_snapshot),
7798 window,
7799 cx,
7800 );
7801 }
7802
7803 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7804 released_too_fast: since.elapsed() < Duration::from_millis(200),
7805 };
7806 self.clear_row_highlights::<EditPredictionPreview>();
7807 self.update_visible_edit_prediction(window, cx);
7808 cx.notify();
7809 }
7810 }
7811
7812 fn update_visible_edit_prediction(
7813 &mut self,
7814 _window: &mut Window,
7815 cx: &mut Context<Self>,
7816 ) -> Option<()> {
7817 if DisableAiSettings::get_global(cx).disable_ai {
7818 return None;
7819 }
7820
7821 if self.ime_transaction.is_some() {
7822 self.discard_edit_prediction(false, cx);
7823 return None;
7824 }
7825
7826 let selection = self.selections.newest_anchor();
7827 let cursor = selection.head();
7828 let multibuffer = self.buffer.read(cx).snapshot(cx);
7829 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7830 let excerpt_id = cursor.excerpt_id;
7831
7832 let show_in_menu = self.show_edit_predictions_in_menu();
7833 let completions_menu_has_precedence = !show_in_menu
7834 && (self.context_menu.borrow().is_some()
7835 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7836
7837 if completions_menu_has_precedence
7838 || !offset_selection.is_empty()
7839 || self
7840 .active_edit_prediction
7841 .as_ref()
7842 .is_some_and(|completion| {
7843 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7844 return false;
7845 };
7846 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7847 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7848 !invalidation_range.contains(&offset_selection.head())
7849 })
7850 {
7851 self.discard_edit_prediction(false, cx);
7852 return None;
7853 }
7854
7855 self.take_active_edit_prediction(cx);
7856 let Some(provider) = self.edit_prediction_provider() else {
7857 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7858 return None;
7859 };
7860
7861 let (buffer, cursor_buffer_position) =
7862 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7863
7864 self.edit_prediction_settings =
7865 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7866
7867 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7868
7869 if self.edit_prediction_indent_conflict {
7870 let cursor_point = cursor.to_point(&multibuffer);
7871
7872 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7873
7874 if let Some((_, indent)) = indents.iter().next()
7875 && indent.len == cursor_point.column
7876 {
7877 self.edit_prediction_indent_conflict = false;
7878 }
7879 }
7880
7881 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7882
7883 let (completion_id, edits, edit_preview) = match edit_prediction {
7884 edit_prediction::EditPrediction::Local {
7885 id,
7886 edits,
7887 edit_preview,
7888 } => (id, edits, edit_preview),
7889 edit_prediction::EditPrediction::Jump {
7890 id,
7891 snapshot,
7892 target,
7893 } => {
7894 self.stale_edit_prediction_in_menu = None;
7895 self.active_edit_prediction = Some(EditPredictionState {
7896 inlay_ids: vec![],
7897 completion: EditPrediction::MoveOutside { snapshot, target },
7898 completion_id: id,
7899 invalidation_range: None,
7900 });
7901 cx.notify();
7902 return Some(());
7903 }
7904 };
7905
7906 let edits = edits
7907 .into_iter()
7908 .flat_map(|(range, new_text)| {
7909 Some((
7910 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7911 new_text,
7912 ))
7913 })
7914 .collect::<Vec<_>>();
7915 if edits.is_empty() {
7916 return None;
7917 }
7918
7919 let first_edit_start = edits.first().unwrap().0.start;
7920 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7921 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7922
7923 let last_edit_end = edits.last().unwrap().0.end;
7924 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7925 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7926
7927 let cursor_row = cursor.to_point(&multibuffer).row;
7928
7929 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7930
7931 let mut inlay_ids = Vec::new();
7932 let invalidation_row_range;
7933 let move_invalidation_row_range = if cursor_row < edit_start_row {
7934 Some(cursor_row..edit_end_row)
7935 } else if cursor_row > edit_end_row {
7936 Some(edit_start_row..cursor_row)
7937 } else {
7938 None
7939 };
7940 let supports_jump = self
7941 .edit_prediction_provider
7942 .as_ref()
7943 .map(|provider| provider.provider.supports_jump_to_edit())
7944 .unwrap_or(true);
7945
7946 let is_move = supports_jump
7947 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7948 let completion = if is_move {
7949 invalidation_row_range =
7950 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7951 let target = first_edit_start;
7952 EditPrediction::MoveWithin { target, snapshot }
7953 } else {
7954 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7955 && !self.edit_predictions_hidden_for_vim_mode;
7956
7957 if show_completions_in_buffer {
7958 if edits
7959 .iter()
7960 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7961 {
7962 let mut inlays = Vec::new();
7963 for (range, new_text) in &edits {
7964 let inlay = Inlay::edit_prediction(
7965 post_inc(&mut self.next_inlay_id),
7966 range.start,
7967 new_text.as_str(),
7968 );
7969 inlay_ids.push(inlay.id);
7970 inlays.push(inlay);
7971 }
7972
7973 self.splice_inlays(&[], inlays, cx);
7974 } else {
7975 let background_color = cx.theme().status().deleted_background;
7976 self.highlight_text::<EditPredictionHighlight>(
7977 edits.iter().map(|(range, _)| range.clone()).collect(),
7978 HighlightStyle {
7979 background_color: Some(background_color),
7980 ..Default::default()
7981 },
7982 cx,
7983 );
7984 }
7985 }
7986
7987 invalidation_row_range = edit_start_row..edit_end_row;
7988
7989 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7990 if provider.show_tab_accept_marker() {
7991 EditDisplayMode::TabAccept
7992 } else {
7993 EditDisplayMode::Inline
7994 }
7995 } else {
7996 EditDisplayMode::DiffPopover
7997 };
7998
7999 EditPrediction::Edit {
8000 edits,
8001 edit_preview,
8002 display_mode,
8003 snapshot,
8004 }
8005 };
8006
8007 let invalidation_range = multibuffer
8008 .anchor_before(Point::new(invalidation_row_range.start, 0))
8009 ..multibuffer.anchor_after(Point::new(
8010 invalidation_row_range.end,
8011 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8012 ));
8013
8014 self.stale_edit_prediction_in_menu = None;
8015 self.active_edit_prediction = Some(EditPredictionState {
8016 inlay_ids,
8017 completion,
8018 completion_id,
8019 invalidation_range: Some(invalidation_range),
8020 });
8021
8022 cx.notify();
8023
8024 Some(())
8025 }
8026
8027 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8028 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8029 }
8030
8031 fn clear_tasks(&mut self) {
8032 self.tasks.clear()
8033 }
8034
8035 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8036 if self.tasks.insert(key, value).is_some() {
8037 // This case should hopefully be rare, but just in case...
8038 log::error!(
8039 "multiple different run targets found on a single line, only the last target will be rendered"
8040 )
8041 }
8042 }
8043
8044 /// Get all display points of breakpoints that will be rendered within editor
8045 ///
8046 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8047 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8048 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8049 fn active_breakpoints(
8050 &self,
8051 range: Range<DisplayRow>,
8052 window: &mut Window,
8053 cx: &mut Context<Self>,
8054 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8055 let mut breakpoint_display_points = HashMap::default();
8056
8057 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8058 return breakpoint_display_points;
8059 };
8060
8061 let snapshot = self.snapshot(window, cx);
8062
8063 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8064 let Some(project) = self.project() else {
8065 return breakpoint_display_points;
8066 };
8067
8068 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8069 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8070
8071 for (buffer_snapshot, range, excerpt_id) in
8072 multi_buffer_snapshot.range_to_buffer_ranges(range)
8073 {
8074 let Some(buffer) = project
8075 .read(cx)
8076 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8077 else {
8078 continue;
8079 };
8080 let breakpoints = breakpoint_store.read(cx).breakpoints(
8081 &buffer,
8082 Some(
8083 buffer_snapshot.anchor_before(range.start)
8084 ..buffer_snapshot.anchor_after(range.end),
8085 ),
8086 buffer_snapshot,
8087 cx,
8088 );
8089 for (breakpoint, state) in breakpoints {
8090 let multi_buffer_anchor =
8091 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8092 let position = multi_buffer_anchor
8093 .to_point(&multi_buffer_snapshot)
8094 .to_display_point(&snapshot);
8095
8096 breakpoint_display_points.insert(
8097 position.row(),
8098 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8099 );
8100 }
8101 }
8102
8103 breakpoint_display_points
8104 }
8105
8106 fn breakpoint_context_menu(
8107 &self,
8108 anchor: Anchor,
8109 window: &mut Window,
8110 cx: &mut Context<Self>,
8111 ) -> Entity<ui::ContextMenu> {
8112 let weak_editor = cx.weak_entity();
8113 let focus_handle = self.focus_handle(cx);
8114
8115 let row = self
8116 .buffer
8117 .read(cx)
8118 .snapshot(cx)
8119 .summary_for_anchor::<Point>(&anchor)
8120 .row;
8121
8122 let breakpoint = self
8123 .breakpoint_at_row(row, window, cx)
8124 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8125
8126 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8127 "Edit Log Breakpoint"
8128 } else {
8129 "Set Log Breakpoint"
8130 };
8131
8132 let condition_breakpoint_msg = if breakpoint
8133 .as_ref()
8134 .is_some_and(|bp| bp.1.condition.is_some())
8135 {
8136 "Edit Condition Breakpoint"
8137 } else {
8138 "Set Condition Breakpoint"
8139 };
8140
8141 let hit_condition_breakpoint_msg = if breakpoint
8142 .as_ref()
8143 .is_some_and(|bp| bp.1.hit_condition.is_some())
8144 {
8145 "Edit Hit Condition Breakpoint"
8146 } else {
8147 "Set Hit Condition Breakpoint"
8148 };
8149
8150 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8151 "Unset Breakpoint"
8152 } else {
8153 "Set Breakpoint"
8154 };
8155
8156 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8157
8158 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8159 BreakpointState::Enabled => Some("Disable"),
8160 BreakpointState::Disabled => Some("Enable"),
8161 });
8162
8163 let (anchor, breakpoint) =
8164 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8165
8166 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8167 menu.on_blur_subscription(Subscription::new(|| {}))
8168 .context(focus_handle)
8169 .when(run_to_cursor, |this| {
8170 let weak_editor = weak_editor.clone();
8171 this.entry("Run to cursor", None, move |window, cx| {
8172 weak_editor
8173 .update(cx, |editor, cx| {
8174 editor.change_selections(
8175 SelectionEffects::no_scroll(),
8176 window,
8177 cx,
8178 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8179 );
8180 })
8181 .ok();
8182
8183 window.dispatch_action(Box::new(RunToCursor), cx);
8184 })
8185 .separator()
8186 })
8187 .when_some(toggle_state_msg, |this, msg| {
8188 this.entry(msg, None, {
8189 let weak_editor = weak_editor.clone();
8190 let breakpoint = breakpoint.clone();
8191 move |_window, cx| {
8192 weak_editor
8193 .update(cx, |this, cx| {
8194 this.edit_breakpoint_at_anchor(
8195 anchor,
8196 breakpoint.as_ref().clone(),
8197 BreakpointEditAction::InvertState,
8198 cx,
8199 );
8200 })
8201 .log_err();
8202 }
8203 })
8204 })
8205 .entry(set_breakpoint_msg, None, {
8206 let weak_editor = weak_editor.clone();
8207 let breakpoint = breakpoint.clone();
8208 move |_window, cx| {
8209 weak_editor
8210 .update(cx, |this, cx| {
8211 this.edit_breakpoint_at_anchor(
8212 anchor,
8213 breakpoint.as_ref().clone(),
8214 BreakpointEditAction::Toggle,
8215 cx,
8216 );
8217 })
8218 .log_err();
8219 }
8220 })
8221 .entry(log_breakpoint_msg, None, {
8222 let breakpoint = breakpoint.clone();
8223 let weak_editor = weak_editor.clone();
8224 move |window, cx| {
8225 weak_editor
8226 .update(cx, |this, cx| {
8227 this.add_edit_breakpoint_block(
8228 anchor,
8229 breakpoint.as_ref(),
8230 BreakpointPromptEditAction::Log,
8231 window,
8232 cx,
8233 );
8234 })
8235 .log_err();
8236 }
8237 })
8238 .entry(condition_breakpoint_msg, None, {
8239 let breakpoint = breakpoint.clone();
8240 let weak_editor = weak_editor.clone();
8241 move |window, cx| {
8242 weak_editor
8243 .update(cx, |this, cx| {
8244 this.add_edit_breakpoint_block(
8245 anchor,
8246 breakpoint.as_ref(),
8247 BreakpointPromptEditAction::Condition,
8248 window,
8249 cx,
8250 );
8251 })
8252 .log_err();
8253 }
8254 })
8255 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8256 weak_editor
8257 .update(cx, |this, cx| {
8258 this.add_edit_breakpoint_block(
8259 anchor,
8260 breakpoint.as_ref(),
8261 BreakpointPromptEditAction::HitCondition,
8262 window,
8263 cx,
8264 );
8265 })
8266 .log_err();
8267 })
8268 })
8269 }
8270
8271 fn render_breakpoint(
8272 &self,
8273 position: Anchor,
8274 row: DisplayRow,
8275 breakpoint: &Breakpoint,
8276 state: Option<BreakpointSessionState>,
8277 cx: &mut Context<Self>,
8278 ) -> IconButton {
8279 let is_rejected = state.is_some_and(|s| !s.verified);
8280 // Is it a breakpoint that shows up when hovering over gutter?
8281 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8282 (false, false),
8283 |PhantomBreakpointIndicator {
8284 is_active,
8285 display_row,
8286 collides_with_existing_breakpoint,
8287 }| {
8288 (
8289 is_active && display_row == row,
8290 collides_with_existing_breakpoint,
8291 )
8292 },
8293 );
8294
8295 let (color, icon) = {
8296 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8297 (false, false) => ui::IconName::DebugBreakpoint,
8298 (true, false) => ui::IconName::DebugLogBreakpoint,
8299 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8300 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8301 };
8302
8303 let color = if is_phantom {
8304 Color::Hint
8305 } else if is_rejected {
8306 Color::Disabled
8307 } else {
8308 Color::Debugger
8309 };
8310
8311 (color, icon)
8312 };
8313
8314 let breakpoint = Arc::from(breakpoint.clone());
8315
8316 let alt_as_text = gpui::Keystroke {
8317 modifiers: Modifiers::secondary_key(),
8318 ..Default::default()
8319 };
8320 let primary_action_text = if breakpoint.is_disabled() {
8321 "Enable breakpoint"
8322 } else if is_phantom && !collides_with_existing {
8323 "Set breakpoint"
8324 } else {
8325 "Unset breakpoint"
8326 };
8327 let focus_handle = self.focus_handle.clone();
8328
8329 let meta = if is_rejected {
8330 SharedString::from("No executable code is associated with this line.")
8331 } else if collides_with_existing && !breakpoint.is_disabled() {
8332 SharedString::from(format!(
8333 "{alt_as_text}-click to disable,\nright-click for more options."
8334 ))
8335 } else {
8336 SharedString::from("Right-click for more options.")
8337 };
8338 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8339 .icon_size(IconSize::XSmall)
8340 .size(ui::ButtonSize::None)
8341 .when(is_rejected, |this| {
8342 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8343 })
8344 .icon_color(color)
8345 .style(ButtonStyle::Transparent)
8346 .on_click(cx.listener({
8347 move |editor, event: &ClickEvent, window, cx| {
8348 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8349 BreakpointEditAction::InvertState
8350 } else {
8351 BreakpointEditAction::Toggle
8352 };
8353
8354 window.focus(&editor.focus_handle(cx));
8355 editor.edit_breakpoint_at_anchor(
8356 position,
8357 breakpoint.as_ref().clone(),
8358 edit_action,
8359 cx,
8360 );
8361 }
8362 }))
8363 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8364 editor.set_breakpoint_context_menu(
8365 row,
8366 Some(position),
8367 event.position(),
8368 window,
8369 cx,
8370 );
8371 }))
8372 .tooltip(move |_window, cx| {
8373 Tooltip::with_meta_in(
8374 primary_action_text,
8375 Some(&ToggleBreakpoint),
8376 meta.clone(),
8377 &focus_handle,
8378 cx,
8379 )
8380 })
8381 }
8382
8383 fn build_tasks_context(
8384 project: &Entity<Project>,
8385 buffer: &Entity<Buffer>,
8386 buffer_row: u32,
8387 tasks: &Arc<RunnableTasks>,
8388 cx: &mut Context<Self>,
8389 ) -> Task<Option<task::TaskContext>> {
8390 let position = Point::new(buffer_row, tasks.column);
8391 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8392 let location = Location {
8393 buffer: buffer.clone(),
8394 range: range_start..range_start,
8395 };
8396 // Fill in the environmental variables from the tree-sitter captures
8397 let mut captured_task_variables = TaskVariables::default();
8398 for (capture_name, value) in tasks.extra_variables.clone() {
8399 captured_task_variables.insert(
8400 task::VariableName::Custom(capture_name.into()),
8401 value.clone(),
8402 );
8403 }
8404 project.update(cx, |project, cx| {
8405 project.task_store().update(cx, |task_store, cx| {
8406 task_store.task_context_for_location(captured_task_variables, location, cx)
8407 })
8408 })
8409 }
8410
8411 pub fn spawn_nearest_task(
8412 &mut self,
8413 action: &SpawnNearestTask,
8414 window: &mut Window,
8415 cx: &mut Context<Self>,
8416 ) {
8417 let Some((workspace, _)) = self.workspace.clone() else {
8418 return;
8419 };
8420 let Some(project) = self.project.clone() else {
8421 return;
8422 };
8423
8424 // Try to find a closest, enclosing node using tree-sitter that has a task
8425 let Some((buffer, buffer_row, tasks)) = self
8426 .find_enclosing_node_task(cx)
8427 // Or find the task that's closest in row-distance.
8428 .or_else(|| self.find_closest_task(cx))
8429 else {
8430 return;
8431 };
8432
8433 let reveal_strategy = action.reveal;
8434 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8435 cx.spawn_in(window, async move |_, cx| {
8436 let context = task_context.await?;
8437 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8438
8439 let resolved = &mut resolved_task.resolved;
8440 resolved.reveal = reveal_strategy;
8441
8442 workspace
8443 .update_in(cx, |workspace, window, cx| {
8444 workspace.schedule_resolved_task(
8445 task_source_kind,
8446 resolved_task,
8447 false,
8448 window,
8449 cx,
8450 );
8451 })
8452 .ok()
8453 })
8454 .detach();
8455 }
8456
8457 fn find_closest_task(
8458 &mut self,
8459 cx: &mut Context<Self>,
8460 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8461 let cursor_row = self
8462 .selections
8463 .newest_adjusted(&self.display_snapshot(cx))
8464 .head()
8465 .row;
8466
8467 let ((buffer_id, row), tasks) = self
8468 .tasks
8469 .iter()
8470 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8471
8472 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8473 let tasks = Arc::new(tasks.to_owned());
8474 Some((buffer, *row, tasks))
8475 }
8476
8477 fn find_enclosing_node_task(
8478 &mut self,
8479 cx: &mut Context<Self>,
8480 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8481 let snapshot = self.buffer.read(cx).snapshot(cx);
8482 let offset = self
8483 .selections
8484 .newest::<usize>(&self.display_snapshot(cx))
8485 .head();
8486 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8487 let buffer_id = excerpt.buffer().remote_id();
8488
8489 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8490 let mut cursor = layer.node().walk();
8491
8492 while cursor.goto_first_child_for_byte(offset).is_some() {
8493 if cursor.node().end_byte() == offset {
8494 cursor.goto_next_sibling();
8495 }
8496 }
8497
8498 // Ascend to the smallest ancestor that contains the range and has a task.
8499 loop {
8500 let node = cursor.node();
8501 let node_range = node.byte_range();
8502 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8503
8504 // Check if this node contains our offset
8505 if node_range.start <= offset && node_range.end >= offset {
8506 // If it contains offset, check for task
8507 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8508 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8509 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8510 }
8511 }
8512
8513 if !cursor.goto_parent() {
8514 break;
8515 }
8516 }
8517 None
8518 }
8519
8520 fn render_run_indicator(
8521 &self,
8522 _style: &EditorStyle,
8523 is_active: bool,
8524 row: DisplayRow,
8525 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8526 cx: &mut Context<Self>,
8527 ) -> IconButton {
8528 let color = Color::Muted;
8529 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8530
8531 IconButton::new(
8532 ("run_indicator", row.0 as usize),
8533 ui::IconName::PlayOutlined,
8534 )
8535 .shape(ui::IconButtonShape::Square)
8536 .icon_size(IconSize::XSmall)
8537 .icon_color(color)
8538 .toggle_state(is_active)
8539 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8540 let quick_launch = match e {
8541 ClickEvent::Keyboard(_) => true,
8542 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8543 };
8544
8545 window.focus(&editor.focus_handle(cx));
8546 editor.toggle_code_actions(
8547 &ToggleCodeActions {
8548 deployed_from: Some(CodeActionSource::RunMenu(row)),
8549 quick_launch,
8550 },
8551 window,
8552 cx,
8553 );
8554 }))
8555 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8556 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8557 }))
8558 }
8559
8560 pub fn context_menu_visible(&self) -> bool {
8561 !self.edit_prediction_preview_is_active()
8562 && self
8563 .context_menu
8564 .borrow()
8565 .as_ref()
8566 .is_some_and(|menu| menu.visible())
8567 }
8568
8569 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8570 self.context_menu
8571 .borrow()
8572 .as_ref()
8573 .map(|menu| menu.origin())
8574 }
8575
8576 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8577 self.context_menu_options = Some(options);
8578 }
8579
8580 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8581 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8582
8583 fn render_edit_prediction_popover(
8584 &mut self,
8585 text_bounds: &Bounds<Pixels>,
8586 content_origin: gpui::Point<Pixels>,
8587 right_margin: Pixels,
8588 editor_snapshot: &EditorSnapshot,
8589 visible_row_range: Range<DisplayRow>,
8590 scroll_top: ScrollOffset,
8591 scroll_bottom: ScrollOffset,
8592 line_layouts: &[LineWithInvisibles],
8593 line_height: Pixels,
8594 scroll_position: gpui::Point<ScrollOffset>,
8595 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8596 newest_selection_head: Option<DisplayPoint>,
8597 editor_width: Pixels,
8598 style: &EditorStyle,
8599 window: &mut Window,
8600 cx: &mut App,
8601 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8602 if self.mode().is_minimap() {
8603 return None;
8604 }
8605 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8606
8607 if self.edit_prediction_visible_in_cursor_popover(true) {
8608 return None;
8609 }
8610
8611 match &active_edit_prediction.completion {
8612 EditPrediction::MoveWithin { target, .. } => {
8613 let target_display_point = target.to_display_point(editor_snapshot);
8614
8615 if self.edit_prediction_requires_modifier() {
8616 if !self.edit_prediction_preview_is_active() {
8617 return None;
8618 }
8619
8620 self.render_edit_prediction_modifier_jump_popover(
8621 text_bounds,
8622 content_origin,
8623 visible_row_range,
8624 line_layouts,
8625 line_height,
8626 scroll_pixel_position,
8627 newest_selection_head,
8628 target_display_point,
8629 window,
8630 cx,
8631 )
8632 } else {
8633 self.render_edit_prediction_eager_jump_popover(
8634 text_bounds,
8635 content_origin,
8636 editor_snapshot,
8637 visible_row_range,
8638 scroll_top,
8639 scroll_bottom,
8640 line_height,
8641 scroll_pixel_position,
8642 target_display_point,
8643 editor_width,
8644 window,
8645 cx,
8646 )
8647 }
8648 }
8649 EditPrediction::Edit {
8650 display_mode: EditDisplayMode::Inline,
8651 ..
8652 } => None,
8653 EditPrediction::Edit {
8654 display_mode: EditDisplayMode::TabAccept,
8655 edits,
8656 ..
8657 } => {
8658 let range = &edits.first()?.0;
8659 let target_display_point = range.end.to_display_point(editor_snapshot);
8660
8661 self.render_edit_prediction_end_of_line_popover(
8662 "Accept",
8663 editor_snapshot,
8664 visible_row_range,
8665 target_display_point,
8666 line_height,
8667 scroll_pixel_position,
8668 content_origin,
8669 editor_width,
8670 window,
8671 cx,
8672 )
8673 }
8674 EditPrediction::Edit {
8675 edits,
8676 edit_preview,
8677 display_mode: EditDisplayMode::DiffPopover,
8678 snapshot,
8679 } => self.render_edit_prediction_diff_popover(
8680 text_bounds,
8681 content_origin,
8682 right_margin,
8683 editor_snapshot,
8684 visible_row_range,
8685 line_layouts,
8686 line_height,
8687 scroll_position,
8688 scroll_pixel_position,
8689 newest_selection_head,
8690 editor_width,
8691 style,
8692 edits,
8693 edit_preview,
8694 snapshot,
8695 window,
8696 cx,
8697 ),
8698 EditPrediction::MoveOutside { snapshot, .. } => {
8699 let file_name = snapshot
8700 .file()
8701 .map(|file| file.file_name(cx))
8702 .unwrap_or("untitled");
8703 let mut element = self
8704 .render_edit_prediction_line_popover(
8705 format!("Jump to {file_name}"),
8706 Some(IconName::ZedPredict),
8707 window,
8708 cx,
8709 )
8710 .into_any();
8711
8712 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8713 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8714 let origin_y = text_bounds.size.height - size.height - px(30.);
8715 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8716 element.prepaint_at(origin, window, cx);
8717
8718 Some((element, origin))
8719 }
8720 }
8721 }
8722
8723 fn render_edit_prediction_modifier_jump_popover(
8724 &mut self,
8725 text_bounds: &Bounds<Pixels>,
8726 content_origin: gpui::Point<Pixels>,
8727 visible_row_range: Range<DisplayRow>,
8728 line_layouts: &[LineWithInvisibles],
8729 line_height: Pixels,
8730 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8731 newest_selection_head: Option<DisplayPoint>,
8732 target_display_point: DisplayPoint,
8733 window: &mut Window,
8734 cx: &mut App,
8735 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8736 let scrolled_content_origin =
8737 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8738
8739 const SCROLL_PADDING_Y: Pixels = px(12.);
8740
8741 if target_display_point.row() < visible_row_range.start {
8742 return self.render_edit_prediction_scroll_popover(
8743 |_| SCROLL_PADDING_Y,
8744 IconName::ArrowUp,
8745 visible_row_range,
8746 line_layouts,
8747 newest_selection_head,
8748 scrolled_content_origin,
8749 window,
8750 cx,
8751 );
8752 } else if target_display_point.row() >= visible_row_range.end {
8753 return self.render_edit_prediction_scroll_popover(
8754 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8755 IconName::ArrowDown,
8756 visible_row_range,
8757 line_layouts,
8758 newest_selection_head,
8759 scrolled_content_origin,
8760 window,
8761 cx,
8762 );
8763 }
8764
8765 const POLE_WIDTH: Pixels = px(2.);
8766
8767 let line_layout =
8768 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8769 let target_column = target_display_point.column() as usize;
8770
8771 let target_x = line_layout.x_for_index(target_column);
8772 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8773 - scroll_pixel_position.y;
8774
8775 let flag_on_right = target_x < text_bounds.size.width / 2.;
8776
8777 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8778 border_color.l += 0.001;
8779
8780 let mut element = v_flex()
8781 .items_end()
8782 .when(flag_on_right, |el| el.items_start())
8783 .child(if flag_on_right {
8784 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8785 .rounded_bl(px(0.))
8786 .rounded_tl(px(0.))
8787 .border_l_2()
8788 .border_color(border_color)
8789 } else {
8790 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8791 .rounded_br(px(0.))
8792 .rounded_tr(px(0.))
8793 .border_r_2()
8794 .border_color(border_color)
8795 })
8796 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8797 .into_any();
8798
8799 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8800
8801 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8802 - point(
8803 if flag_on_right {
8804 POLE_WIDTH
8805 } else {
8806 size.width - POLE_WIDTH
8807 },
8808 size.height - line_height,
8809 );
8810
8811 origin.x = origin.x.max(content_origin.x);
8812
8813 element.prepaint_at(origin, window, cx);
8814
8815 Some((element, origin))
8816 }
8817
8818 fn render_edit_prediction_scroll_popover(
8819 &mut self,
8820 to_y: impl Fn(Size<Pixels>) -> Pixels,
8821 scroll_icon: IconName,
8822 visible_row_range: Range<DisplayRow>,
8823 line_layouts: &[LineWithInvisibles],
8824 newest_selection_head: Option<DisplayPoint>,
8825 scrolled_content_origin: gpui::Point<Pixels>,
8826 window: &mut Window,
8827 cx: &mut App,
8828 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8829 let mut element = self
8830 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8831 .into_any();
8832
8833 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8834
8835 let cursor = newest_selection_head?;
8836 let cursor_row_layout =
8837 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8838 let cursor_column = cursor.column() as usize;
8839
8840 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8841
8842 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8843
8844 element.prepaint_at(origin, window, cx);
8845 Some((element, origin))
8846 }
8847
8848 fn render_edit_prediction_eager_jump_popover(
8849 &mut self,
8850 text_bounds: &Bounds<Pixels>,
8851 content_origin: gpui::Point<Pixels>,
8852 editor_snapshot: &EditorSnapshot,
8853 visible_row_range: Range<DisplayRow>,
8854 scroll_top: ScrollOffset,
8855 scroll_bottom: ScrollOffset,
8856 line_height: Pixels,
8857 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8858 target_display_point: DisplayPoint,
8859 editor_width: Pixels,
8860 window: &mut Window,
8861 cx: &mut App,
8862 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8863 if target_display_point.row().as_f64() < scroll_top {
8864 let mut element = self
8865 .render_edit_prediction_line_popover(
8866 "Jump to Edit",
8867 Some(IconName::ArrowUp),
8868 window,
8869 cx,
8870 )
8871 .into_any();
8872
8873 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8874 let offset = point(
8875 (text_bounds.size.width - size.width) / 2.,
8876 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8877 );
8878
8879 let origin = text_bounds.origin + offset;
8880 element.prepaint_at(origin, window, cx);
8881 Some((element, origin))
8882 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8883 let mut element = self
8884 .render_edit_prediction_line_popover(
8885 "Jump to Edit",
8886 Some(IconName::ArrowDown),
8887 window,
8888 cx,
8889 )
8890 .into_any();
8891
8892 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8893 let offset = point(
8894 (text_bounds.size.width - size.width) / 2.,
8895 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8896 );
8897
8898 let origin = text_bounds.origin + offset;
8899 element.prepaint_at(origin, window, cx);
8900 Some((element, origin))
8901 } else {
8902 self.render_edit_prediction_end_of_line_popover(
8903 "Jump to Edit",
8904 editor_snapshot,
8905 visible_row_range,
8906 target_display_point,
8907 line_height,
8908 scroll_pixel_position,
8909 content_origin,
8910 editor_width,
8911 window,
8912 cx,
8913 )
8914 }
8915 }
8916
8917 fn render_edit_prediction_end_of_line_popover(
8918 self: &mut Editor,
8919 label: &'static str,
8920 editor_snapshot: &EditorSnapshot,
8921 visible_row_range: Range<DisplayRow>,
8922 target_display_point: DisplayPoint,
8923 line_height: Pixels,
8924 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8925 content_origin: gpui::Point<Pixels>,
8926 editor_width: Pixels,
8927 window: &mut Window,
8928 cx: &mut App,
8929 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8930 let target_line_end = DisplayPoint::new(
8931 target_display_point.row(),
8932 editor_snapshot.line_len(target_display_point.row()),
8933 );
8934
8935 let mut element = self
8936 .render_edit_prediction_line_popover(label, None, window, cx)
8937 .into_any();
8938
8939 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8940
8941 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8942
8943 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8944 let mut origin = start_point
8945 + line_origin
8946 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8947 origin.x = origin.x.max(content_origin.x);
8948
8949 let max_x = content_origin.x + editor_width - size.width;
8950
8951 if origin.x > max_x {
8952 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8953
8954 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8955 origin.y += offset;
8956 IconName::ArrowUp
8957 } else {
8958 origin.y -= offset;
8959 IconName::ArrowDown
8960 };
8961
8962 element = self
8963 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8964 .into_any();
8965
8966 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8967
8968 origin.x = content_origin.x + editor_width - size.width - px(2.);
8969 }
8970
8971 element.prepaint_at(origin, window, cx);
8972 Some((element, origin))
8973 }
8974
8975 fn render_edit_prediction_diff_popover(
8976 self: &Editor,
8977 text_bounds: &Bounds<Pixels>,
8978 content_origin: gpui::Point<Pixels>,
8979 right_margin: Pixels,
8980 editor_snapshot: &EditorSnapshot,
8981 visible_row_range: Range<DisplayRow>,
8982 line_layouts: &[LineWithInvisibles],
8983 line_height: Pixels,
8984 scroll_position: gpui::Point<ScrollOffset>,
8985 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8986 newest_selection_head: Option<DisplayPoint>,
8987 editor_width: Pixels,
8988 style: &EditorStyle,
8989 edits: &Vec<(Range<Anchor>, String)>,
8990 edit_preview: &Option<language::EditPreview>,
8991 snapshot: &language::BufferSnapshot,
8992 window: &mut Window,
8993 cx: &mut App,
8994 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8995 let edit_start = edits
8996 .first()
8997 .unwrap()
8998 .0
8999 .start
9000 .to_display_point(editor_snapshot);
9001 let edit_end = edits
9002 .last()
9003 .unwrap()
9004 .0
9005 .end
9006 .to_display_point(editor_snapshot);
9007
9008 let is_visible = visible_row_range.contains(&edit_start.row())
9009 || visible_row_range.contains(&edit_end.row());
9010 if !is_visible {
9011 return None;
9012 }
9013
9014 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9015 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9016 } else {
9017 // Fallback for providers without edit_preview
9018 crate::edit_prediction_fallback_text(edits, cx)
9019 };
9020
9021 let styled_text = highlighted_edits.to_styled_text(&style.text);
9022 let line_count = highlighted_edits.text.lines().count();
9023
9024 const BORDER_WIDTH: Pixels = px(1.);
9025
9026 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9027 let has_keybind = keybind.is_some();
9028
9029 let mut element = h_flex()
9030 .items_start()
9031 .child(
9032 h_flex()
9033 .bg(cx.theme().colors().editor_background)
9034 .border(BORDER_WIDTH)
9035 .shadow_xs()
9036 .border_color(cx.theme().colors().border)
9037 .rounded_l_lg()
9038 .when(line_count > 1, |el| el.rounded_br_lg())
9039 .pr_1()
9040 .child(styled_text),
9041 )
9042 .child(
9043 h_flex()
9044 .h(line_height + BORDER_WIDTH * 2.)
9045 .px_1p5()
9046 .gap_1()
9047 // Workaround: For some reason, there's a gap if we don't do this
9048 .ml(-BORDER_WIDTH)
9049 .shadow(vec![gpui::BoxShadow {
9050 color: gpui::black().opacity(0.05),
9051 offset: point(px(1.), px(1.)),
9052 blur_radius: px(2.),
9053 spread_radius: px(0.),
9054 }])
9055 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9056 .border(BORDER_WIDTH)
9057 .border_color(cx.theme().colors().border)
9058 .rounded_r_lg()
9059 .id("edit_prediction_diff_popover_keybind")
9060 .when(!has_keybind, |el| {
9061 let status_colors = cx.theme().status();
9062
9063 el.bg(status_colors.error_background)
9064 .border_color(status_colors.error.opacity(0.6))
9065 .child(Icon::new(IconName::Info).color(Color::Error))
9066 .cursor_default()
9067 .hoverable_tooltip(move |_window, cx| {
9068 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9069 })
9070 })
9071 .children(keybind),
9072 )
9073 .into_any();
9074
9075 let longest_row =
9076 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9077 let longest_line_width = if visible_row_range.contains(&longest_row) {
9078 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9079 } else {
9080 layout_line(
9081 longest_row,
9082 editor_snapshot,
9083 style,
9084 editor_width,
9085 |_| false,
9086 window,
9087 cx,
9088 )
9089 .width
9090 };
9091
9092 let viewport_bounds =
9093 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9094 right: -right_margin,
9095 ..Default::default()
9096 });
9097
9098 let x_after_longest = Pixels::from(
9099 ScrollPixelOffset::from(
9100 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9101 ) - scroll_pixel_position.x,
9102 );
9103
9104 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9105
9106 // Fully visible if it can be displayed within the window (allow overlapping other
9107 // panes). However, this is only allowed if the popover starts within text_bounds.
9108 let can_position_to_the_right = x_after_longest < text_bounds.right()
9109 && x_after_longest + element_bounds.width < viewport_bounds.right();
9110
9111 let mut origin = if can_position_to_the_right {
9112 point(
9113 x_after_longest,
9114 text_bounds.origin.y
9115 + Pixels::from(
9116 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9117 - scroll_pixel_position.y,
9118 ),
9119 )
9120 } else {
9121 let cursor_row = newest_selection_head.map(|head| head.row());
9122 let above_edit = edit_start
9123 .row()
9124 .0
9125 .checked_sub(line_count as u32)
9126 .map(DisplayRow);
9127 let below_edit = Some(edit_end.row() + 1);
9128 let above_cursor =
9129 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9130 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9131
9132 // Place the edit popover adjacent to the edit if there is a location
9133 // available that is onscreen and does not obscure the cursor. Otherwise,
9134 // place it adjacent to the cursor.
9135 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9136 .into_iter()
9137 .flatten()
9138 .find(|&start_row| {
9139 let end_row = start_row + line_count as u32;
9140 visible_row_range.contains(&start_row)
9141 && visible_row_range.contains(&end_row)
9142 && cursor_row
9143 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9144 })?;
9145
9146 content_origin
9147 + point(
9148 Pixels::from(-scroll_pixel_position.x),
9149 Pixels::from(
9150 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9151 ),
9152 )
9153 };
9154
9155 origin.x -= BORDER_WIDTH;
9156
9157 window.defer_draw(element, origin, 1);
9158
9159 // Do not return an element, since it will already be drawn due to defer_draw.
9160 None
9161 }
9162
9163 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9164 px(30.)
9165 }
9166
9167 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9168 if self.read_only(cx) {
9169 cx.theme().players().read_only()
9170 } else {
9171 self.style.as_ref().unwrap().local_player
9172 }
9173 }
9174
9175 fn render_edit_prediction_accept_keybind(
9176 &self,
9177 window: &mut Window,
9178 cx: &mut App,
9179 ) -> Option<AnyElement> {
9180 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9181 let accept_keystroke = accept_binding.keystroke()?;
9182
9183 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9184
9185 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9186 Color::Accent
9187 } else {
9188 Color::Muted
9189 };
9190
9191 h_flex()
9192 .px_0p5()
9193 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9194 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9195 .text_size(TextSize::XSmall.rems(cx))
9196 .child(h_flex().children(ui::render_modifiers(
9197 accept_keystroke.modifiers(),
9198 PlatformStyle::platform(),
9199 Some(modifiers_color),
9200 Some(IconSize::XSmall.rems().into()),
9201 true,
9202 )))
9203 .when(is_platform_style_mac, |parent| {
9204 parent.child(accept_keystroke.key().to_string())
9205 })
9206 .when(!is_platform_style_mac, |parent| {
9207 parent.child(
9208 Key::new(
9209 util::capitalize(accept_keystroke.key()),
9210 Some(Color::Default),
9211 )
9212 .size(Some(IconSize::XSmall.rems().into())),
9213 )
9214 })
9215 .into_any()
9216 .into()
9217 }
9218
9219 fn render_edit_prediction_line_popover(
9220 &self,
9221 label: impl Into<SharedString>,
9222 icon: Option<IconName>,
9223 window: &mut Window,
9224 cx: &mut App,
9225 ) -> Stateful<Div> {
9226 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9227
9228 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9229 let has_keybind = keybind.is_some();
9230
9231 h_flex()
9232 .id("ep-line-popover")
9233 .py_0p5()
9234 .pl_1()
9235 .pr(padding_right)
9236 .gap_1()
9237 .rounded_md()
9238 .border_1()
9239 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9240 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9241 .shadow_xs()
9242 .when(!has_keybind, |el| {
9243 let status_colors = cx.theme().status();
9244
9245 el.bg(status_colors.error_background)
9246 .border_color(status_colors.error.opacity(0.6))
9247 .pl_2()
9248 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9249 .cursor_default()
9250 .hoverable_tooltip(move |_window, cx| {
9251 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9252 })
9253 })
9254 .children(keybind)
9255 .child(
9256 Label::new(label)
9257 .size(LabelSize::Small)
9258 .when(!has_keybind, |el| {
9259 el.color(cx.theme().status().error.into()).strikethrough()
9260 }),
9261 )
9262 .when(!has_keybind, |el| {
9263 el.child(
9264 h_flex().ml_1().child(
9265 Icon::new(IconName::Info)
9266 .size(IconSize::Small)
9267 .color(cx.theme().status().error.into()),
9268 ),
9269 )
9270 })
9271 .when_some(icon, |element, icon| {
9272 element.child(
9273 div()
9274 .mt(px(1.5))
9275 .child(Icon::new(icon).size(IconSize::Small)),
9276 )
9277 })
9278 }
9279
9280 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9281 let accent_color = cx.theme().colors().text_accent;
9282 let editor_bg_color = cx.theme().colors().editor_background;
9283 editor_bg_color.blend(accent_color.opacity(0.1))
9284 }
9285
9286 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9287 let accent_color = cx.theme().colors().text_accent;
9288 let editor_bg_color = cx.theme().colors().editor_background;
9289 editor_bg_color.blend(accent_color.opacity(0.6))
9290 }
9291 fn get_prediction_provider_icon_name(
9292 provider: &Option<RegisteredEditPredictionProvider>,
9293 ) -> IconName {
9294 match provider {
9295 Some(provider) => match provider.provider.name() {
9296 "copilot" => IconName::Copilot,
9297 "supermaven" => IconName::Supermaven,
9298 _ => IconName::ZedPredict,
9299 },
9300 None => IconName::ZedPredict,
9301 }
9302 }
9303
9304 fn render_edit_prediction_cursor_popover(
9305 &self,
9306 min_width: Pixels,
9307 max_width: Pixels,
9308 cursor_point: Point,
9309 style: &EditorStyle,
9310 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9311 _window: &Window,
9312 cx: &mut Context<Editor>,
9313 ) -> Option<AnyElement> {
9314 let provider = self.edit_prediction_provider.as_ref()?;
9315 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9316
9317 let is_refreshing = provider.provider.is_refreshing(cx);
9318
9319 fn pending_completion_container(icon: IconName) -> Div {
9320 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9321 }
9322
9323 let completion = match &self.active_edit_prediction {
9324 Some(prediction) => {
9325 if !self.has_visible_completions_menu() {
9326 const RADIUS: Pixels = px(6.);
9327 const BORDER_WIDTH: Pixels = px(1.);
9328
9329 return Some(
9330 h_flex()
9331 .elevation_2(cx)
9332 .border(BORDER_WIDTH)
9333 .border_color(cx.theme().colors().border)
9334 .when(accept_keystroke.is_none(), |el| {
9335 el.border_color(cx.theme().status().error)
9336 })
9337 .rounded(RADIUS)
9338 .rounded_tl(px(0.))
9339 .overflow_hidden()
9340 .child(div().px_1p5().child(match &prediction.completion {
9341 EditPrediction::MoveWithin { target, snapshot } => {
9342 use text::ToPoint as _;
9343 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9344 {
9345 Icon::new(IconName::ZedPredictDown)
9346 } else {
9347 Icon::new(IconName::ZedPredictUp)
9348 }
9349 }
9350 EditPrediction::MoveOutside { .. } => {
9351 // TODO [zeta2] custom icon for external jump?
9352 Icon::new(provider_icon)
9353 }
9354 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9355 }))
9356 .child(
9357 h_flex()
9358 .gap_1()
9359 .py_1()
9360 .px_2()
9361 .rounded_r(RADIUS - BORDER_WIDTH)
9362 .border_l_1()
9363 .border_color(cx.theme().colors().border)
9364 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9365 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9366 el.child(
9367 Label::new("Hold")
9368 .size(LabelSize::Small)
9369 .when(accept_keystroke.is_none(), |el| {
9370 el.strikethrough()
9371 })
9372 .line_height_style(LineHeightStyle::UiLabel),
9373 )
9374 })
9375 .id("edit_prediction_cursor_popover_keybind")
9376 .when(accept_keystroke.is_none(), |el| {
9377 let status_colors = cx.theme().status();
9378
9379 el.bg(status_colors.error_background)
9380 .border_color(status_colors.error.opacity(0.6))
9381 .child(Icon::new(IconName::Info).color(Color::Error))
9382 .cursor_default()
9383 .hoverable_tooltip(move |_window, cx| {
9384 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9385 .into()
9386 })
9387 })
9388 .when_some(
9389 accept_keystroke.as_ref(),
9390 |el, accept_keystroke| {
9391 el.child(h_flex().children(ui::render_modifiers(
9392 accept_keystroke.modifiers(),
9393 PlatformStyle::platform(),
9394 Some(Color::Default),
9395 Some(IconSize::XSmall.rems().into()),
9396 false,
9397 )))
9398 },
9399 ),
9400 )
9401 .into_any(),
9402 );
9403 }
9404
9405 self.render_edit_prediction_cursor_popover_preview(
9406 prediction,
9407 cursor_point,
9408 style,
9409 cx,
9410 )?
9411 }
9412
9413 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9414 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9415 stale_completion,
9416 cursor_point,
9417 style,
9418 cx,
9419 )?,
9420
9421 None => pending_completion_container(provider_icon)
9422 .child(Label::new("...").size(LabelSize::Small)),
9423 },
9424
9425 None => pending_completion_container(provider_icon)
9426 .child(Label::new("...").size(LabelSize::Small)),
9427 };
9428
9429 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9430 completion
9431 .with_animation(
9432 "loading-completion",
9433 Animation::new(Duration::from_secs(2))
9434 .repeat()
9435 .with_easing(pulsating_between(0.4, 0.8)),
9436 |label, delta| label.opacity(delta),
9437 )
9438 .into_any_element()
9439 } else {
9440 completion.into_any_element()
9441 };
9442
9443 let has_completion = self.active_edit_prediction.is_some();
9444
9445 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9446 Some(
9447 h_flex()
9448 .min_w(min_width)
9449 .max_w(max_width)
9450 .flex_1()
9451 .elevation_2(cx)
9452 .border_color(cx.theme().colors().border)
9453 .child(
9454 div()
9455 .flex_1()
9456 .py_1()
9457 .px_2()
9458 .overflow_hidden()
9459 .child(completion),
9460 )
9461 .when_some(accept_keystroke, |el, accept_keystroke| {
9462 if !accept_keystroke.modifiers().modified() {
9463 return el;
9464 }
9465
9466 el.child(
9467 h_flex()
9468 .h_full()
9469 .border_l_1()
9470 .rounded_r_lg()
9471 .border_color(cx.theme().colors().border)
9472 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9473 .gap_1()
9474 .py_1()
9475 .px_2()
9476 .child(
9477 h_flex()
9478 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9479 .when(is_platform_style_mac, |parent| parent.gap_1())
9480 .child(h_flex().children(ui::render_modifiers(
9481 accept_keystroke.modifiers(),
9482 PlatformStyle::platform(),
9483 Some(if !has_completion {
9484 Color::Muted
9485 } else {
9486 Color::Default
9487 }),
9488 None,
9489 false,
9490 ))),
9491 )
9492 .child(Label::new("Preview").into_any_element())
9493 .opacity(if has_completion { 1.0 } else { 0.4 }),
9494 )
9495 })
9496 .into_any(),
9497 )
9498 }
9499
9500 fn render_edit_prediction_cursor_popover_preview(
9501 &self,
9502 completion: &EditPredictionState,
9503 cursor_point: Point,
9504 style: &EditorStyle,
9505 cx: &mut Context<Editor>,
9506 ) -> Option<Div> {
9507 use text::ToPoint as _;
9508
9509 fn render_relative_row_jump(
9510 prefix: impl Into<String>,
9511 current_row: u32,
9512 target_row: u32,
9513 ) -> Div {
9514 let (row_diff, arrow) = if target_row < current_row {
9515 (current_row - target_row, IconName::ArrowUp)
9516 } else {
9517 (target_row - current_row, IconName::ArrowDown)
9518 };
9519
9520 h_flex()
9521 .child(
9522 Label::new(format!("{}{}", prefix.into(), row_diff))
9523 .color(Color::Muted)
9524 .size(LabelSize::Small),
9525 )
9526 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9527 }
9528
9529 let supports_jump = self
9530 .edit_prediction_provider
9531 .as_ref()
9532 .map(|provider| provider.provider.supports_jump_to_edit())
9533 .unwrap_or(true);
9534
9535 match &completion.completion {
9536 EditPrediction::MoveWithin {
9537 target, snapshot, ..
9538 } => {
9539 if !supports_jump {
9540 return None;
9541 }
9542
9543 Some(
9544 h_flex()
9545 .px_2()
9546 .gap_2()
9547 .flex_1()
9548 .child(
9549 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9550 Icon::new(IconName::ZedPredictDown)
9551 } else {
9552 Icon::new(IconName::ZedPredictUp)
9553 },
9554 )
9555 .child(Label::new("Jump to Edit")),
9556 )
9557 }
9558 EditPrediction::MoveOutside { snapshot, .. } => {
9559 let file_name = snapshot
9560 .file()
9561 .map(|file| file.file_name(cx))
9562 .unwrap_or("untitled");
9563 Some(
9564 h_flex()
9565 .px_2()
9566 .gap_2()
9567 .flex_1()
9568 .child(Icon::new(IconName::ZedPredict))
9569 .child(Label::new(format!("Jump to {file_name}"))),
9570 )
9571 }
9572 EditPrediction::Edit {
9573 edits,
9574 edit_preview,
9575 snapshot,
9576 display_mode: _,
9577 } => {
9578 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9579
9580 let (highlighted_edits, has_more_lines) =
9581 if let Some(edit_preview) = edit_preview.as_ref() {
9582 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9583 .first_line_preview()
9584 } else {
9585 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9586 };
9587
9588 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9589 .with_default_highlights(&style.text, highlighted_edits.highlights);
9590
9591 let preview = h_flex()
9592 .gap_1()
9593 .min_w_16()
9594 .child(styled_text)
9595 .when(has_more_lines, |parent| parent.child("…"));
9596
9597 let left = if supports_jump && first_edit_row != cursor_point.row {
9598 render_relative_row_jump("", cursor_point.row, first_edit_row)
9599 .into_any_element()
9600 } else {
9601 let icon_name =
9602 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9603 Icon::new(icon_name).into_any_element()
9604 };
9605
9606 Some(
9607 h_flex()
9608 .h_full()
9609 .flex_1()
9610 .gap_2()
9611 .pr_1()
9612 .overflow_x_hidden()
9613 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9614 .child(left)
9615 .child(preview),
9616 )
9617 }
9618 }
9619 }
9620
9621 pub fn render_context_menu(
9622 &self,
9623 style: &EditorStyle,
9624 max_height_in_lines: u32,
9625 window: &mut Window,
9626 cx: &mut Context<Editor>,
9627 ) -> Option<AnyElement> {
9628 let menu = self.context_menu.borrow();
9629 let menu = menu.as_ref()?;
9630 if !menu.visible() {
9631 return None;
9632 };
9633 Some(menu.render(style, max_height_in_lines, window, cx))
9634 }
9635
9636 fn render_context_menu_aside(
9637 &mut self,
9638 max_size: Size<Pixels>,
9639 window: &mut Window,
9640 cx: &mut Context<Editor>,
9641 ) -> Option<AnyElement> {
9642 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9643 if menu.visible() {
9644 menu.render_aside(max_size, window, cx)
9645 } else {
9646 None
9647 }
9648 })
9649 }
9650
9651 fn hide_context_menu(
9652 &mut self,
9653 window: &mut Window,
9654 cx: &mut Context<Self>,
9655 ) -> Option<CodeContextMenu> {
9656 cx.notify();
9657 self.completion_tasks.clear();
9658 let context_menu = self.context_menu.borrow_mut().take();
9659 self.stale_edit_prediction_in_menu.take();
9660 self.update_visible_edit_prediction(window, cx);
9661 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9662 && let Some(completion_provider) = &self.completion_provider
9663 {
9664 completion_provider.selection_changed(None, window, cx);
9665 }
9666 context_menu
9667 }
9668
9669 fn show_snippet_choices(
9670 &mut self,
9671 choices: &Vec<String>,
9672 selection: Range<Anchor>,
9673 cx: &mut Context<Self>,
9674 ) {
9675 let Some((_, buffer, _)) = self
9676 .buffer()
9677 .read(cx)
9678 .excerpt_containing(selection.start, cx)
9679 else {
9680 return;
9681 };
9682 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9683 else {
9684 return;
9685 };
9686 if buffer != end_buffer {
9687 log::error!("expected anchor range to have matching buffer IDs");
9688 return;
9689 }
9690
9691 let id = post_inc(&mut self.next_completion_id);
9692 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9693 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9694 CompletionsMenu::new_snippet_choices(
9695 id,
9696 true,
9697 choices,
9698 selection,
9699 buffer,
9700 snippet_sort_order,
9701 ),
9702 ));
9703 }
9704
9705 pub fn insert_snippet(
9706 &mut self,
9707 insertion_ranges: &[Range<usize>],
9708 snippet: Snippet,
9709 window: &mut Window,
9710 cx: &mut Context<Self>,
9711 ) -> Result<()> {
9712 struct Tabstop<T> {
9713 is_end_tabstop: bool,
9714 ranges: Vec<Range<T>>,
9715 choices: Option<Vec<String>>,
9716 }
9717
9718 let tabstops = self.buffer.update(cx, |buffer, cx| {
9719 let snippet_text: Arc<str> = snippet.text.clone().into();
9720 let edits = insertion_ranges
9721 .iter()
9722 .cloned()
9723 .map(|range| (range, snippet_text.clone()));
9724 let autoindent_mode = AutoindentMode::Block {
9725 original_indent_columns: Vec::new(),
9726 };
9727 buffer.edit(edits, Some(autoindent_mode), cx);
9728
9729 let snapshot = &*buffer.read(cx);
9730 let snippet = &snippet;
9731 snippet
9732 .tabstops
9733 .iter()
9734 .map(|tabstop| {
9735 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9736 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9737 });
9738 let mut tabstop_ranges = tabstop
9739 .ranges
9740 .iter()
9741 .flat_map(|tabstop_range| {
9742 let mut delta = 0_isize;
9743 insertion_ranges.iter().map(move |insertion_range| {
9744 let insertion_start = insertion_range.start as isize + delta;
9745 delta +=
9746 snippet.text.len() as isize - insertion_range.len() as isize;
9747
9748 let start = ((insertion_start + tabstop_range.start) as usize)
9749 .min(snapshot.len());
9750 let end = ((insertion_start + tabstop_range.end) as usize)
9751 .min(snapshot.len());
9752 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9753 })
9754 })
9755 .collect::<Vec<_>>();
9756 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9757
9758 Tabstop {
9759 is_end_tabstop,
9760 ranges: tabstop_ranges,
9761 choices: tabstop.choices.clone(),
9762 }
9763 })
9764 .collect::<Vec<_>>()
9765 });
9766 if let Some(tabstop) = tabstops.first() {
9767 self.change_selections(Default::default(), window, cx, |s| {
9768 // Reverse order so that the first range is the newest created selection.
9769 // Completions will use it and autoscroll will prioritize it.
9770 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9771 });
9772
9773 if let Some(choices) = &tabstop.choices
9774 && let Some(selection) = tabstop.ranges.first()
9775 {
9776 self.show_snippet_choices(choices, selection.clone(), cx)
9777 }
9778
9779 // If we're already at the last tabstop and it's at the end of the snippet,
9780 // we're done, we don't need to keep the state around.
9781 if !tabstop.is_end_tabstop {
9782 let choices = tabstops
9783 .iter()
9784 .map(|tabstop| tabstop.choices.clone())
9785 .collect();
9786
9787 let ranges = tabstops
9788 .into_iter()
9789 .map(|tabstop| tabstop.ranges)
9790 .collect::<Vec<_>>();
9791
9792 self.snippet_stack.push(SnippetState {
9793 active_index: 0,
9794 ranges,
9795 choices,
9796 });
9797 }
9798
9799 // Check whether the just-entered snippet ends with an auto-closable bracket.
9800 if self.autoclose_regions.is_empty() {
9801 let snapshot = self.buffer.read(cx).snapshot(cx);
9802 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9803 let selection_head = selection.head();
9804 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9805 continue;
9806 };
9807
9808 let mut bracket_pair = None;
9809 let max_lookup_length = scope
9810 .brackets()
9811 .map(|(pair, _)| {
9812 pair.start
9813 .as_str()
9814 .chars()
9815 .count()
9816 .max(pair.end.as_str().chars().count())
9817 })
9818 .max();
9819 if let Some(max_lookup_length) = max_lookup_length {
9820 let next_text = snapshot
9821 .chars_at(selection_head)
9822 .take(max_lookup_length)
9823 .collect::<String>();
9824 let prev_text = snapshot
9825 .reversed_chars_at(selection_head)
9826 .take(max_lookup_length)
9827 .collect::<String>();
9828
9829 for (pair, enabled) in scope.brackets() {
9830 if enabled
9831 && pair.close
9832 && prev_text.starts_with(pair.start.as_str())
9833 && next_text.starts_with(pair.end.as_str())
9834 {
9835 bracket_pair = Some(pair.clone());
9836 break;
9837 }
9838 }
9839 }
9840
9841 if let Some(pair) = bracket_pair {
9842 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9843 let autoclose_enabled =
9844 self.use_autoclose && snapshot_settings.use_autoclose;
9845 if autoclose_enabled {
9846 let start = snapshot.anchor_after(selection_head);
9847 let end = snapshot.anchor_after(selection_head);
9848 self.autoclose_regions.push(AutocloseRegion {
9849 selection_id: selection.id,
9850 range: start..end,
9851 pair,
9852 });
9853 }
9854 }
9855 }
9856 }
9857 }
9858 Ok(())
9859 }
9860
9861 pub fn move_to_next_snippet_tabstop(
9862 &mut self,
9863 window: &mut Window,
9864 cx: &mut Context<Self>,
9865 ) -> bool {
9866 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9867 }
9868
9869 pub fn move_to_prev_snippet_tabstop(
9870 &mut self,
9871 window: &mut Window,
9872 cx: &mut Context<Self>,
9873 ) -> bool {
9874 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9875 }
9876
9877 pub fn move_to_snippet_tabstop(
9878 &mut self,
9879 bias: Bias,
9880 window: &mut Window,
9881 cx: &mut Context<Self>,
9882 ) -> bool {
9883 if let Some(mut snippet) = self.snippet_stack.pop() {
9884 match bias {
9885 Bias::Left => {
9886 if snippet.active_index > 0 {
9887 snippet.active_index -= 1;
9888 } else {
9889 self.snippet_stack.push(snippet);
9890 return false;
9891 }
9892 }
9893 Bias::Right => {
9894 if snippet.active_index + 1 < snippet.ranges.len() {
9895 snippet.active_index += 1;
9896 } else {
9897 self.snippet_stack.push(snippet);
9898 return false;
9899 }
9900 }
9901 }
9902 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9903 self.change_selections(Default::default(), window, cx, |s| {
9904 // Reverse order so that the first range is the newest created selection.
9905 // Completions will use it and autoscroll will prioritize it.
9906 s.select_ranges(current_ranges.iter().rev().cloned())
9907 });
9908
9909 if let Some(choices) = &snippet.choices[snippet.active_index]
9910 && let Some(selection) = current_ranges.first()
9911 {
9912 self.show_snippet_choices(choices, selection.clone(), cx);
9913 }
9914
9915 // If snippet state is not at the last tabstop, push it back on the stack
9916 if snippet.active_index + 1 < snippet.ranges.len() {
9917 self.snippet_stack.push(snippet);
9918 }
9919 return true;
9920 }
9921 }
9922
9923 false
9924 }
9925
9926 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9927 self.transact(window, cx, |this, window, cx| {
9928 this.select_all(&SelectAll, window, cx);
9929 this.insert("", window, cx);
9930 });
9931 }
9932
9933 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9934 if self.read_only(cx) {
9935 return;
9936 }
9937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9938 self.transact(window, cx, |this, window, cx| {
9939 this.select_autoclose_pair(window, cx);
9940
9941 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9942
9943 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9944 if !this.linked_edit_ranges.is_empty() {
9945 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9946 let snapshot = this.buffer.read(cx).snapshot(cx);
9947
9948 for selection in selections.iter() {
9949 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9950 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9951 if selection_start.buffer_id != selection_end.buffer_id {
9952 continue;
9953 }
9954 if let Some(ranges) =
9955 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9956 {
9957 for (buffer, entries) in ranges {
9958 linked_ranges.entry(buffer).or_default().extend(entries);
9959 }
9960 }
9961 }
9962 }
9963
9964 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9965 for selection in &mut selections {
9966 if selection.is_empty() {
9967 let old_head = selection.head();
9968 let mut new_head =
9969 movement::left(&display_map, old_head.to_display_point(&display_map))
9970 .to_point(&display_map);
9971 if let Some((buffer, line_buffer_range)) = display_map
9972 .buffer_snapshot()
9973 .buffer_line_for_row(MultiBufferRow(old_head.row))
9974 {
9975 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9976 let indent_len = match indent_size.kind {
9977 IndentKind::Space => {
9978 buffer.settings_at(line_buffer_range.start, cx).tab_size
9979 }
9980 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9981 };
9982 if old_head.column <= indent_size.len && old_head.column > 0 {
9983 let indent_len = indent_len.get();
9984 new_head = cmp::min(
9985 new_head,
9986 MultiBufferPoint::new(
9987 old_head.row,
9988 ((old_head.column - 1) / indent_len) * indent_len,
9989 ),
9990 );
9991 }
9992 }
9993
9994 selection.set_head(new_head, SelectionGoal::None);
9995 }
9996 }
9997
9998 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9999 this.insert("", window, cx);
10000 let empty_str: Arc<str> = Arc::from("");
10001 for (buffer, edits) in linked_ranges {
10002 let snapshot = buffer.read(cx).snapshot();
10003 use text::ToPoint as TP;
10004
10005 let edits = edits
10006 .into_iter()
10007 .map(|range| {
10008 let end_point = TP::to_point(&range.end, &snapshot);
10009 let mut start_point = TP::to_point(&range.start, &snapshot);
10010
10011 if end_point == start_point {
10012 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10013 .saturating_sub(1);
10014 start_point =
10015 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10016 };
10017
10018 (start_point..end_point, empty_str.clone())
10019 })
10020 .sorted_by_key(|(range, _)| range.start)
10021 .collect::<Vec<_>>();
10022 buffer.update(cx, |this, cx| {
10023 this.edit(edits, None, cx);
10024 })
10025 }
10026 this.refresh_edit_prediction(true, false, window, cx);
10027 refresh_linked_ranges(this, window, cx);
10028 });
10029 }
10030
10031 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10032 if self.read_only(cx) {
10033 return;
10034 }
10035 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10036 self.transact(window, cx, |this, window, cx| {
10037 this.change_selections(Default::default(), window, cx, |s| {
10038 s.move_with(|map, selection| {
10039 if selection.is_empty() {
10040 let cursor = movement::right(map, selection.head());
10041 selection.end = cursor;
10042 selection.reversed = true;
10043 selection.goal = SelectionGoal::None;
10044 }
10045 })
10046 });
10047 this.insert("", window, cx);
10048 this.refresh_edit_prediction(true, false, window, cx);
10049 });
10050 }
10051
10052 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10053 if self.mode.is_single_line() {
10054 cx.propagate();
10055 return;
10056 }
10057
10058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10059 if self.move_to_prev_snippet_tabstop(window, cx) {
10060 return;
10061 }
10062 self.outdent(&Outdent, window, cx);
10063 }
10064
10065 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10066 if self.mode.is_single_line() {
10067 cx.propagate();
10068 return;
10069 }
10070
10071 if self.move_to_next_snippet_tabstop(window, cx) {
10072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10073 return;
10074 }
10075 if self.read_only(cx) {
10076 return;
10077 }
10078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10079 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10080 let buffer = self.buffer.read(cx);
10081 let snapshot = buffer.snapshot(cx);
10082 let rows_iter = selections.iter().map(|s| s.head().row);
10083 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10084
10085 let has_some_cursor_in_whitespace = selections
10086 .iter()
10087 .filter(|selection| selection.is_empty())
10088 .any(|selection| {
10089 let cursor = selection.head();
10090 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10091 cursor.column < current_indent.len
10092 });
10093
10094 let mut edits = Vec::new();
10095 let mut prev_edited_row = 0;
10096 let mut row_delta = 0;
10097 for selection in &mut selections {
10098 if selection.start.row != prev_edited_row {
10099 row_delta = 0;
10100 }
10101 prev_edited_row = selection.end.row;
10102
10103 // If the selection is non-empty, then increase the indentation of the selected lines.
10104 if !selection.is_empty() {
10105 row_delta =
10106 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10107 continue;
10108 }
10109
10110 let cursor = selection.head();
10111 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10112 if let Some(suggested_indent) =
10113 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10114 {
10115 // Don't do anything if already at suggested indent
10116 // and there is any other cursor which is not
10117 if has_some_cursor_in_whitespace
10118 && cursor.column == current_indent.len
10119 && current_indent.len == suggested_indent.len
10120 {
10121 continue;
10122 }
10123
10124 // Adjust line and move cursor to suggested indent
10125 // if cursor is not at suggested indent
10126 if cursor.column < suggested_indent.len
10127 && cursor.column <= current_indent.len
10128 && current_indent.len <= suggested_indent.len
10129 {
10130 selection.start = Point::new(cursor.row, suggested_indent.len);
10131 selection.end = selection.start;
10132 if row_delta == 0 {
10133 edits.extend(Buffer::edit_for_indent_size_adjustment(
10134 cursor.row,
10135 current_indent,
10136 suggested_indent,
10137 ));
10138 row_delta = suggested_indent.len - current_indent.len;
10139 }
10140 continue;
10141 }
10142
10143 // If current indent is more than suggested indent
10144 // only move cursor to current indent and skip indent
10145 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10146 selection.start = Point::new(cursor.row, current_indent.len);
10147 selection.end = selection.start;
10148 continue;
10149 }
10150 }
10151
10152 // Otherwise, insert a hard or soft tab.
10153 let settings = buffer.language_settings_at(cursor, cx);
10154 let tab_size = if settings.hard_tabs {
10155 IndentSize::tab()
10156 } else {
10157 let tab_size = settings.tab_size.get();
10158 let indent_remainder = snapshot
10159 .text_for_range(Point::new(cursor.row, 0)..cursor)
10160 .flat_map(str::chars)
10161 .fold(row_delta % tab_size, |counter: u32, c| {
10162 if c == '\t' {
10163 0
10164 } else {
10165 (counter + 1) % tab_size
10166 }
10167 });
10168
10169 let chars_to_next_tab_stop = tab_size - indent_remainder;
10170 IndentSize::spaces(chars_to_next_tab_stop)
10171 };
10172 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10173 selection.end = selection.start;
10174 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10175 row_delta += tab_size.len;
10176 }
10177
10178 self.transact(window, cx, |this, window, cx| {
10179 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10180 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10181 this.refresh_edit_prediction(true, false, window, cx);
10182 });
10183 }
10184
10185 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10186 if self.read_only(cx) {
10187 return;
10188 }
10189 if self.mode.is_single_line() {
10190 cx.propagate();
10191 return;
10192 }
10193
10194 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10195 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10196 let mut prev_edited_row = 0;
10197 let mut row_delta = 0;
10198 let mut edits = Vec::new();
10199 let buffer = self.buffer.read(cx);
10200 let snapshot = buffer.snapshot(cx);
10201 for selection in &mut selections {
10202 if selection.start.row != prev_edited_row {
10203 row_delta = 0;
10204 }
10205 prev_edited_row = selection.end.row;
10206
10207 row_delta =
10208 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10209 }
10210
10211 self.transact(window, cx, |this, window, cx| {
10212 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10213 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10214 });
10215 }
10216
10217 fn indent_selection(
10218 buffer: &MultiBuffer,
10219 snapshot: &MultiBufferSnapshot,
10220 selection: &mut Selection<Point>,
10221 edits: &mut Vec<(Range<Point>, String)>,
10222 delta_for_start_row: u32,
10223 cx: &App,
10224 ) -> u32 {
10225 let settings = buffer.language_settings_at(selection.start, cx);
10226 let tab_size = settings.tab_size.get();
10227 let indent_kind = if settings.hard_tabs {
10228 IndentKind::Tab
10229 } else {
10230 IndentKind::Space
10231 };
10232 let mut start_row = selection.start.row;
10233 let mut end_row = selection.end.row + 1;
10234
10235 // If a selection ends at the beginning of a line, don't indent
10236 // that last line.
10237 if selection.end.column == 0 && selection.end.row > selection.start.row {
10238 end_row -= 1;
10239 }
10240
10241 // Avoid re-indenting a row that has already been indented by a
10242 // previous selection, but still update this selection's column
10243 // to reflect that indentation.
10244 if delta_for_start_row > 0 {
10245 start_row += 1;
10246 selection.start.column += delta_for_start_row;
10247 if selection.end.row == selection.start.row {
10248 selection.end.column += delta_for_start_row;
10249 }
10250 }
10251
10252 let mut delta_for_end_row = 0;
10253 let has_multiple_rows = start_row + 1 != end_row;
10254 for row in start_row..end_row {
10255 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10256 let indent_delta = match (current_indent.kind, indent_kind) {
10257 (IndentKind::Space, IndentKind::Space) => {
10258 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10259 IndentSize::spaces(columns_to_next_tab_stop)
10260 }
10261 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10262 (_, IndentKind::Tab) => IndentSize::tab(),
10263 };
10264
10265 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10266 0
10267 } else {
10268 selection.start.column
10269 };
10270 let row_start = Point::new(row, start);
10271 edits.push((
10272 row_start..row_start,
10273 indent_delta.chars().collect::<String>(),
10274 ));
10275
10276 // Update this selection's endpoints to reflect the indentation.
10277 if row == selection.start.row {
10278 selection.start.column += indent_delta.len;
10279 }
10280 if row == selection.end.row {
10281 selection.end.column += indent_delta.len;
10282 delta_for_end_row = indent_delta.len;
10283 }
10284 }
10285
10286 if selection.start.row == selection.end.row {
10287 delta_for_start_row + delta_for_end_row
10288 } else {
10289 delta_for_end_row
10290 }
10291 }
10292
10293 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10294 if self.read_only(cx) {
10295 return;
10296 }
10297 if self.mode.is_single_line() {
10298 cx.propagate();
10299 return;
10300 }
10301
10302 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10304 let selections = self.selections.all::<Point>(&display_map);
10305 let mut deletion_ranges = Vec::new();
10306 let mut last_outdent = None;
10307 {
10308 let buffer = self.buffer.read(cx);
10309 let snapshot = buffer.snapshot(cx);
10310 for selection in &selections {
10311 let settings = buffer.language_settings_at(selection.start, cx);
10312 let tab_size = settings.tab_size.get();
10313 let mut rows = selection.spanned_rows(false, &display_map);
10314
10315 // Avoid re-outdenting a row that has already been outdented by a
10316 // previous selection.
10317 if let Some(last_row) = last_outdent
10318 && last_row == rows.start
10319 {
10320 rows.start = rows.start.next_row();
10321 }
10322 let has_multiple_rows = rows.len() > 1;
10323 for row in rows.iter_rows() {
10324 let indent_size = snapshot.indent_size_for_line(row);
10325 if indent_size.len > 0 {
10326 let deletion_len = match indent_size.kind {
10327 IndentKind::Space => {
10328 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10329 if columns_to_prev_tab_stop == 0 {
10330 tab_size
10331 } else {
10332 columns_to_prev_tab_stop
10333 }
10334 }
10335 IndentKind::Tab => 1,
10336 };
10337 let start = if has_multiple_rows
10338 || deletion_len > selection.start.column
10339 || indent_size.len < selection.start.column
10340 {
10341 0
10342 } else {
10343 selection.start.column - deletion_len
10344 };
10345 deletion_ranges.push(
10346 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10347 );
10348 last_outdent = Some(row);
10349 }
10350 }
10351 }
10352 }
10353
10354 self.transact(window, cx, |this, window, cx| {
10355 this.buffer.update(cx, |buffer, cx| {
10356 let empty_str: Arc<str> = Arc::default();
10357 buffer.edit(
10358 deletion_ranges
10359 .into_iter()
10360 .map(|range| (range, empty_str.clone())),
10361 None,
10362 cx,
10363 );
10364 });
10365 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10366 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10367 });
10368 }
10369
10370 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10371 if self.read_only(cx) {
10372 return;
10373 }
10374 if self.mode.is_single_line() {
10375 cx.propagate();
10376 return;
10377 }
10378
10379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10380 let selections = self
10381 .selections
10382 .all::<usize>(&self.display_snapshot(cx))
10383 .into_iter()
10384 .map(|s| s.range());
10385
10386 self.transact(window, cx, |this, window, cx| {
10387 this.buffer.update(cx, |buffer, cx| {
10388 buffer.autoindent_ranges(selections, cx);
10389 });
10390 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10391 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10392 });
10393 }
10394
10395 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10396 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10398 let selections = self.selections.all::<Point>(&display_map);
10399
10400 let mut new_cursors = Vec::new();
10401 let mut edit_ranges = Vec::new();
10402 let mut selections = selections.iter().peekable();
10403 while let Some(selection) = selections.next() {
10404 let mut rows = selection.spanned_rows(false, &display_map);
10405
10406 // Accumulate contiguous regions of rows that we want to delete.
10407 while let Some(next_selection) = selections.peek() {
10408 let next_rows = next_selection.spanned_rows(false, &display_map);
10409 if next_rows.start <= rows.end {
10410 rows.end = next_rows.end;
10411 selections.next().unwrap();
10412 } else {
10413 break;
10414 }
10415 }
10416
10417 let buffer = display_map.buffer_snapshot();
10418 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10419 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10420 // If there's a line after the range, delete the \n from the end of the row range
10421 (
10422 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10423 rows.end,
10424 )
10425 } else {
10426 // If there isn't a line after the range, delete the \n from the line before the
10427 // start of the row range
10428 edit_start = edit_start.saturating_sub(1);
10429 (buffer.len(), rows.start.previous_row())
10430 };
10431
10432 let text_layout_details = self.text_layout_details(window);
10433 let x = display_map.x_for_display_point(
10434 selection.head().to_display_point(&display_map),
10435 &text_layout_details,
10436 );
10437 let row = Point::new(target_row.0, 0)
10438 .to_display_point(&display_map)
10439 .row();
10440 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10441
10442 new_cursors.push((
10443 selection.id,
10444 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10445 SelectionGoal::None,
10446 ));
10447 edit_ranges.push(edit_start..edit_end);
10448 }
10449
10450 self.transact(window, cx, |this, window, cx| {
10451 let buffer = this.buffer.update(cx, |buffer, cx| {
10452 let empty_str: Arc<str> = Arc::default();
10453 buffer.edit(
10454 edit_ranges
10455 .into_iter()
10456 .map(|range| (range, empty_str.clone())),
10457 None,
10458 cx,
10459 );
10460 buffer.snapshot(cx)
10461 });
10462 let new_selections = new_cursors
10463 .into_iter()
10464 .map(|(id, cursor, goal)| {
10465 let cursor = cursor.to_point(&buffer);
10466 Selection {
10467 id,
10468 start: cursor,
10469 end: cursor,
10470 reversed: false,
10471 goal,
10472 }
10473 })
10474 .collect();
10475
10476 this.change_selections(Default::default(), window, cx, |s| {
10477 s.select(new_selections);
10478 });
10479 });
10480 }
10481
10482 pub fn join_lines_impl(
10483 &mut self,
10484 insert_whitespace: bool,
10485 window: &mut Window,
10486 cx: &mut Context<Self>,
10487 ) {
10488 if self.read_only(cx) {
10489 return;
10490 }
10491 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10492 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10493 let start = MultiBufferRow(selection.start.row);
10494 // Treat single line selections as if they include the next line. Otherwise this action
10495 // would do nothing for single line selections individual cursors.
10496 let end = if selection.start.row == selection.end.row {
10497 MultiBufferRow(selection.start.row + 1)
10498 } else {
10499 MultiBufferRow(selection.end.row)
10500 };
10501
10502 if let Some(last_row_range) = row_ranges.last_mut()
10503 && start <= last_row_range.end
10504 {
10505 last_row_range.end = end;
10506 continue;
10507 }
10508 row_ranges.push(start..end);
10509 }
10510
10511 let snapshot = self.buffer.read(cx).snapshot(cx);
10512 let mut cursor_positions = Vec::new();
10513 for row_range in &row_ranges {
10514 let anchor = snapshot.anchor_before(Point::new(
10515 row_range.end.previous_row().0,
10516 snapshot.line_len(row_range.end.previous_row()),
10517 ));
10518 cursor_positions.push(anchor..anchor);
10519 }
10520
10521 self.transact(window, cx, |this, window, cx| {
10522 for row_range in row_ranges.into_iter().rev() {
10523 for row in row_range.iter_rows().rev() {
10524 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10525 let next_line_row = row.next_row();
10526 let indent = snapshot.indent_size_for_line(next_line_row);
10527 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10528
10529 let replace =
10530 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10531 " "
10532 } else {
10533 ""
10534 };
10535
10536 this.buffer.update(cx, |buffer, cx| {
10537 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10538 });
10539 }
10540 }
10541
10542 this.change_selections(Default::default(), window, cx, |s| {
10543 s.select_anchor_ranges(cursor_positions)
10544 });
10545 });
10546 }
10547
10548 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10550 self.join_lines_impl(true, window, cx);
10551 }
10552
10553 pub fn sort_lines_case_sensitive(
10554 &mut self,
10555 _: &SortLinesCaseSensitive,
10556 window: &mut Window,
10557 cx: &mut Context<Self>,
10558 ) {
10559 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10560 }
10561
10562 pub fn sort_lines_by_length(
10563 &mut self,
10564 _: &SortLinesByLength,
10565 window: &mut Window,
10566 cx: &mut Context<Self>,
10567 ) {
10568 self.manipulate_immutable_lines(window, cx, |lines| {
10569 lines.sort_by_key(|&line| line.chars().count())
10570 })
10571 }
10572
10573 pub fn sort_lines_case_insensitive(
10574 &mut self,
10575 _: &SortLinesCaseInsensitive,
10576 window: &mut Window,
10577 cx: &mut Context<Self>,
10578 ) {
10579 self.manipulate_immutable_lines(window, cx, |lines| {
10580 lines.sort_by_key(|line| line.to_lowercase())
10581 })
10582 }
10583
10584 pub fn unique_lines_case_insensitive(
10585 &mut self,
10586 _: &UniqueLinesCaseInsensitive,
10587 window: &mut Window,
10588 cx: &mut Context<Self>,
10589 ) {
10590 self.manipulate_immutable_lines(window, cx, |lines| {
10591 let mut seen = HashSet::default();
10592 lines.retain(|line| seen.insert(line.to_lowercase()));
10593 })
10594 }
10595
10596 pub fn unique_lines_case_sensitive(
10597 &mut self,
10598 _: &UniqueLinesCaseSensitive,
10599 window: &mut Window,
10600 cx: &mut Context<Self>,
10601 ) {
10602 self.manipulate_immutable_lines(window, cx, |lines| {
10603 let mut seen = HashSet::default();
10604 lines.retain(|line| seen.insert(*line));
10605 })
10606 }
10607
10608 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10609 let snapshot = self.buffer.read(cx).snapshot(cx);
10610 for selection in self.selections.disjoint_anchors_arc().iter() {
10611 if snapshot
10612 .language_at(selection.start)
10613 .and_then(|lang| lang.config().wrap_characters.as_ref())
10614 .is_some()
10615 {
10616 return true;
10617 }
10618 }
10619 false
10620 }
10621
10622 fn wrap_selections_in_tag(
10623 &mut self,
10624 _: &WrapSelectionsInTag,
10625 window: &mut Window,
10626 cx: &mut Context<Self>,
10627 ) {
10628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10629
10630 let snapshot = self.buffer.read(cx).snapshot(cx);
10631
10632 let mut edits = Vec::new();
10633 let mut boundaries = Vec::new();
10634
10635 for selection in self
10636 .selections
10637 .all_adjusted(&self.display_snapshot(cx))
10638 .iter()
10639 {
10640 let Some(wrap_config) = snapshot
10641 .language_at(selection.start)
10642 .and_then(|lang| lang.config().wrap_characters.clone())
10643 else {
10644 continue;
10645 };
10646
10647 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10648 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10649
10650 let start_before = snapshot.anchor_before(selection.start);
10651 let end_after = snapshot.anchor_after(selection.end);
10652
10653 edits.push((start_before..start_before, open_tag));
10654 edits.push((end_after..end_after, close_tag));
10655
10656 boundaries.push((
10657 start_before,
10658 end_after,
10659 wrap_config.start_prefix.len(),
10660 wrap_config.end_suffix.len(),
10661 ));
10662 }
10663
10664 if edits.is_empty() {
10665 return;
10666 }
10667
10668 self.transact(window, cx, |this, window, cx| {
10669 let buffer = this.buffer.update(cx, |buffer, cx| {
10670 buffer.edit(edits, None, cx);
10671 buffer.snapshot(cx)
10672 });
10673
10674 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10675 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10676 boundaries.into_iter()
10677 {
10678 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10679 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10680 new_selections.push(open_offset..open_offset);
10681 new_selections.push(close_offset..close_offset);
10682 }
10683
10684 this.change_selections(Default::default(), window, cx, |s| {
10685 s.select_ranges(new_selections);
10686 });
10687
10688 this.request_autoscroll(Autoscroll::fit(), cx);
10689 });
10690 }
10691
10692 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10693 let Some(project) = self.project.clone() else {
10694 return;
10695 };
10696 self.reload(project, window, cx)
10697 .detach_and_notify_err(window, cx);
10698 }
10699
10700 pub fn restore_file(
10701 &mut self,
10702 _: &::git::RestoreFile,
10703 window: &mut Window,
10704 cx: &mut Context<Self>,
10705 ) {
10706 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10707 let mut buffer_ids = HashSet::default();
10708 let snapshot = self.buffer().read(cx).snapshot(cx);
10709 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10710 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10711 }
10712
10713 let buffer = self.buffer().read(cx);
10714 let ranges = buffer_ids
10715 .into_iter()
10716 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10717 .collect::<Vec<_>>();
10718
10719 self.restore_hunks_in_ranges(ranges, window, cx);
10720 }
10721
10722 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10724 let selections = self
10725 .selections
10726 .all(&self.display_snapshot(cx))
10727 .into_iter()
10728 .map(|s| s.range())
10729 .collect();
10730 self.restore_hunks_in_ranges(selections, window, cx);
10731 }
10732
10733 pub fn restore_hunks_in_ranges(
10734 &mut self,
10735 ranges: Vec<Range<Point>>,
10736 window: &mut Window,
10737 cx: &mut Context<Editor>,
10738 ) {
10739 let mut revert_changes = HashMap::default();
10740 let chunk_by = self
10741 .snapshot(window, cx)
10742 .hunks_for_ranges(ranges)
10743 .into_iter()
10744 .chunk_by(|hunk| hunk.buffer_id);
10745 for (buffer_id, hunks) in &chunk_by {
10746 let hunks = hunks.collect::<Vec<_>>();
10747 for hunk in &hunks {
10748 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10749 }
10750 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10751 }
10752 drop(chunk_by);
10753 if !revert_changes.is_empty() {
10754 self.transact(window, cx, |editor, window, cx| {
10755 editor.restore(revert_changes, window, cx);
10756 });
10757 }
10758 }
10759
10760 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10761 if let Some(status) = self
10762 .addons
10763 .iter()
10764 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10765 {
10766 return Some(status);
10767 }
10768 self.project
10769 .as_ref()?
10770 .read(cx)
10771 .status_for_buffer_id(buffer_id, cx)
10772 }
10773
10774 pub fn open_active_item_in_terminal(
10775 &mut self,
10776 _: &OpenInTerminal,
10777 window: &mut Window,
10778 cx: &mut Context<Self>,
10779 ) {
10780 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10781 let project_path = buffer.read(cx).project_path(cx)?;
10782 let project = self.project()?.read(cx);
10783 let entry = project.entry_for_path(&project_path, cx)?;
10784 let parent = match &entry.canonical_path {
10785 Some(canonical_path) => canonical_path.to_path_buf(),
10786 None => project.absolute_path(&project_path, cx)?,
10787 }
10788 .parent()?
10789 .to_path_buf();
10790 Some(parent)
10791 }) {
10792 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10793 }
10794 }
10795
10796 fn set_breakpoint_context_menu(
10797 &mut self,
10798 display_row: DisplayRow,
10799 position: Option<Anchor>,
10800 clicked_point: gpui::Point<Pixels>,
10801 window: &mut Window,
10802 cx: &mut Context<Self>,
10803 ) {
10804 let source = self
10805 .buffer
10806 .read(cx)
10807 .snapshot(cx)
10808 .anchor_before(Point::new(display_row.0, 0u32));
10809
10810 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10811
10812 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10813 self,
10814 source,
10815 clicked_point,
10816 context_menu,
10817 window,
10818 cx,
10819 );
10820 }
10821
10822 fn add_edit_breakpoint_block(
10823 &mut self,
10824 anchor: Anchor,
10825 breakpoint: &Breakpoint,
10826 edit_action: BreakpointPromptEditAction,
10827 window: &mut Window,
10828 cx: &mut Context<Self>,
10829 ) {
10830 let weak_editor = cx.weak_entity();
10831 let bp_prompt = cx.new(|cx| {
10832 BreakpointPromptEditor::new(
10833 weak_editor,
10834 anchor,
10835 breakpoint.clone(),
10836 edit_action,
10837 window,
10838 cx,
10839 )
10840 });
10841
10842 let height = bp_prompt.update(cx, |this, cx| {
10843 this.prompt
10844 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10845 });
10846 let cloned_prompt = bp_prompt.clone();
10847 let blocks = vec![BlockProperties {
10848 style: BlockStyle::Sticky,
10849 placement: BlockPlacement::Above(anchor),
10850 height: Some(height),
10851 render: Arc::new(move |cx| {
10852 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10853 cloned_prompt.clone().into_any_element()
10854 }),
10855 priority: 0,
10856 }];
10857
10858 let focus_handle = bp_prompt.focus_handle(cx);
10859 window.focus(&focus_handle);
10860
10861 let block_ids = self.insert_blocks(blocks, None, cx);
10862 bp_prompt.update(cx, |prompt, _| {
10863 prompt.add_block_ids(block_ids);
10864 });
10865 }
10866
10867 pub(crate) fn breakpoint_at_row(
10868 &self,
10869 row: u32,
10870 window: &mut Window,
10871 cx: &mut Context<Self>,
10872 ) -> Option<(Anchor, Breakpoint)> {
10873 let snapshot = self.snapshot(window, cx);
10874 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10875
10876 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10877 }
10878
10879 pub(crate) fn breakpoint_at_anchor(
10880 &self,
10881 breakpoint_position: Anchor,
10882 snapshot: &EditorSnapshot,
10883 cx: &mut Context<Self>,
10884 ) -> Option<(Anchor, Breakpoint)> {
10885 let buffer = self
10886 .buffer
10887 .read(cx)
10888 .buffer_for_anchor(breakpoint_position, cx)?;
10889
10890 let enclosing_excerpt = breakpoint_position.excerpt_id;
10891 let buffer_snapshot = buffer.read(cx).snapshot();
10892
10893 let row = buffer_snapshot
10894 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10895 .row;
10896
10897 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10898 let anchor_end = snapshot
10899 .buffer_snapshot()
10900 .anchor_after(Point::new(row, line_len));
10901
10902 self.breakpoint_store
10903 .as_ref()?
10904 .read_with(cx, |breakpoint_store, cx| {
10905 breakpoint_store
10906 .breakpoints(
10907 &buffer,
10908 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10909 &buffer_snapshot,
10910 cx,
10911 )
10912 .next()
10913 .and_then(|(bp, _)| {
10914 let breakpoint_row = buffer_snapshot
10915 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10916 .row;
10917
10918 if breakpoint_row == row {
10919 snapshot
10920 .buffer_snapshot()
10921 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10922 .map(|position| (position, bp.bp.clone()))
10923 } else {
10924 None
10925 }
10926 })
10927 })
10928 }
10929
10930 pub fn edit_log_breakpoint(
10931 &mut self,
10932 _: &EditLogBreakpoint,
10933 window: &mut Window,
10934 cx: &mut Context<Self>,
10935 ) {
10936 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10937 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10938 message: None,
10939 state: BreakpointState::Enabled,
10940 condition: None,
10941 hit_condition: None,
10942 });
10943
10944 self.add_edit_breakpoint_block(
10945 anchor,
10946 &breakpoint,
10947 BreakpointPromptEditAction::Log,
10948 window,
10949 cx,
10950 );
10951 }
10952 }
10953
10954 fn breakpoints_at_cursors(
10955 &self,
10956 window: &mut Window,
10957 cx: &mut Context<Self>,
10958 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10959 let snapshot = self.snapshot(window, cx);
10960 let cursors = self
10961 .selections
10962 .disjoint_anchors_arc()
10963 .iter()
10964 .map(|selection| {
10965 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10966
10967 let breakpoint_position = self
10968 .breakpoint_at_row(cursor_position.row, window, cx)
10969 .map(|bp| bp.0)
10970 .unwrap_or_else(|| {
10971 snapshot
10972 .display_snapshot
10973 .buffer_snapshot()
10974 .anchor_after(Point::new(cursor_position.row, 0))
10975 });
10976
10977 let breakpoint = self
10978 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10979 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10980
10981 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10982 })
10983 // 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.
10984 .collect::<HashMap<Anchor, _>>();
10985
10986 cursors.into_iter().collect()
10987 }
10988
10989 pub fn enable_breakpoint(
10990 &mut self,
10991 _: &crate::actions::EnableBreakpoint,
10992 window: &mut Window,
10993 cx: &mut Context<Self>,
10994 ) {
10995 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10996 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10997 continue;
10998 };
10999 self.edit_breakpoint_at_anchor(
11000 anchor,
11001 breakpoint,
11002 BreakpointEditAction::InvertState,
11003 cx,
11004 );
11005 }
11006 }
11007
11008 pub fn disable_breakpoint(
11009 &mut self,
11010 _: &crate::actions::DisableBreakpoint,
11011 window: &mut Window,
11012 cx: &mut Context<Self>,
11013 ) {
11014 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11015 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11016 continue;
11017 };
11018 self.edit_breakpoint_at_anchor(
11019 anchor,
11020 breakpoint,
11021 BreakpointEditAction::InvertState,
11022 cx,
11023 );
11024 }
11025 }
11026
11027 pub fn toggle_breakpoint(
11028 &mut self,
11029 _: &crate::actions::ToggleBreakpoint,
11030 window: &mut Window,
11031 cx: &mut Context<Self>,
11032 ) {
11033 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11034 if let Some(breakpoint) = breakpoint {
11035 self.edit_breakpoint_at_anchor(
11036 anchor,
11037 breakpoint,
11038 BreakpointEditAction::Toggle,
11039 cx,
11040 );
11041 } else {
11042 self.edit_breakpoint_at_anchor(
11043 anchor,
11044 Breakpoint::new_standard(),
11045 BreakpointEditAction::Toggle,
11046 cx,
11047 );
11048 }
11049 }
11050 }
11051
11052 pub fn edit_breakpoint_at_anchor(
11053 &mut self,
11054 breakpoint_position: Anchor,
11055 breakpoint: Breakpoint,
11056 edit_action: BreakpointEditAction,
11057 cx: &mut Context<Self>,
11058 ) {
11059 let Some(breakpoint_store) = &self.breakpoint_store else {
11060 return;
11061 };
11062
11063 let Some(buffer) = self
11064 .buffer
11065 .read(cx)
11066 .buffer_for_anchor(breakpoint_position, cx)
11067 else {
11068 return;
11069 };
11070
11071 breakpoint_store.update(cx, |breakpoint_store, cx| {
11072 breakpoint_store.toggle_breakpoint(
11073 buffer,
11074 BreakpointWithPosition {
11075 position: breakpoint_position.text_anchor,
11076 bp: breakpoint,
11077 },
11078 edit_action,
11079 cx,
11080 );
11081 });
11082
11083 cx.notify();
11084 }
11085
11086 #[cfg(any(test, feature = "test-support"))]
11087 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11088 self.breakpoint_store.clone()
11089 }
11090
11091 pub fn prepare_restore_change(
11092 &self,
11093 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11094 hunk: &MultiBufferDiffHunk,
11095 cx: &mut App,
11096 ) -> Option<()> {
11097 if hunk.is_created_file() {
11098 return None;
11099 }
11100 let buffer = self.buffer.read(cx);
11101 let diff = buffer.diff_for(hunk.buffer_id)?;
11102 let buffer = buffer.buffer(hunk.buffer_id)?;
11103 let buffer = buffer.read(cx);
11104 let original_text = diff
11105 .read(cx)
11106 .base_text()
11107 .as_rope()
11108 .slice(hunk.diff_base_byte_range.clone());
11109 let buffer_snapshot = buffer.snapshot();
11110 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11111 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11112 probe
11113 .0
11114 .start
11115 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11116 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11117 }) {
11118 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11119 Some(())
11120 } else {
11121 None
11122 }
11123 }
11124
11125 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11126 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11127 }
11128
11129 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11130 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11131 }
11132
11133 fn manipulate_lines<M>(
11134 &mut self,
11135 window: &mut Window,
11136 cx: &mut Context<Self>,
11137 mut manipulate: M,
11138 ) where
11139 M: FnMut(&str) -> LineManipulationResult,
11140 {
11141 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11142
11143 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11144 let buffer = self.buffer.read(cx).snapshot(cx);
11145
11146 let mut edits = Vec::new();
11147
11148 let selections = self.selections.all::<Point>(&display_map);
11149 let mut selections = selections.iter().peekable();
11150 let mut contiguous_row_selections = Vec::new();
11151 let mut new_selections = Vec::new();
11152 let mut added_lines = 0;
11153 let mut removed_lines = 0;
11154
11155 while let Some(selection) = selections.next() {
11156 let (start_row, end_row) = consume_contiguous_rows(
11157 &mut contiguous_row_selections,
11158 selection,
11159 &display_map,
11160 &mut selections,
11161 );
11162
11163 let start_point = Point::new(start_row.0, 0);
11164 let end_point = Point::new(
11165 end_row.previous_row().0,
11166 buffer.line_len(end_row.previous_row()),
11167 );
11168 let text = buffer
11169 .text_for_range(start_point..end_point)
11170 .collect::<String>();
11171
11172 let LineManipulationResult {
11173 new_text,
11174 line_count_before,
11175 line_count_after,
11176 } = manipulate(&text);
11177
11178 edits.push((start_point..end_point, new_text));
11179
11180 // Selections must change based on added and removed line count
11181 let start_row =
11182 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11183 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11184 new_selections.push(Selection {
11185 id: selection.id,
11186 start: start_row,
11187 end: end_row,
11188 goal: SelectionGoal::None,
11189 reversed: selection.reversed,
11190 });
11191
11192 if line_count_after > line_count_before {
11193 added_lines += line_count_after - line_count_before;
11194 } else if line_count_before > line_count_after {
11195 removed_lines += line_count_before - line_count_after;
11196 }
11197 }
11198
11199 self.transact(window, cx, |this, window, cx| {
11200 let buffer = this.buffer.update(cx, |buffer, cx| {
11201 buffer.edit(edits, None, cx);
11202 buffer.snapshot(cx)
11203 });
11204
11205 // Recalculate offsets on newly edited buffer
11206 let new_selections = new_selections
11207 .iter()
11208 .map(|s| {
11209 let start_point = Point::new(s.start.0, 0);
11210 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11211 Selection {
11212 id: s.id,
11213 start: buffer.point_to_offset(start_point),
11214 end: buffer.point_to_offset(end_point),
11215 goal: s.goal,
11216 reversed: s.reversed,
11217 }
11218 })
11219 .collect();
11220
11221 this.change_selections(Default::default(), window, cx, |s| {
11222 s.select(new_selections);
11223 });
11224
11225 this.request_autoscroll(Autoscroll::fit(), cx);
11226 });
11227 }
11228
11229 fn manipulate_immutable_lines<Fn>(
11230 &mut self,
11231 window: &mut Window,
11232 cx: &mut Context<Self>,
11233 mut callback: Fn,
11234 ) where
11235 Fn: FnMut(&mut Vec<&str>),
11236 {
11237 self.manipulate_lines(window, cx, |text| {
11238 let mut lines: Vec<&str> = text.split('\n').collect();
11239 let line_count_before = lines.len();
11240
11241 callback(&mut lines);
11242
11243 LineManipulationResult {
11244 new_text: lines.join("\n"),
11245 line_count_before,
11246 line_count_after: lines.len(),
11247 }
11248 });
11249 }
11250
11251 fn manipulate_mutable_lines<Fn>(
11252 &mut self,
11253 window: &mut Window,
11254 cx: &mut Context<Self>,
11255 mut callback: Fn,
11256 ) where
11257 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11258 {
11259 self.manipulate_lines(window, cx, |text| {
11260 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11261 let line_count_before = lines.len();
11262
11263 callback(&mut lines);
11264
11265 LineManipulationResult {
11266 new_text: lines.join("\n"),
11267 line_count_before,
11268 line_count_after: lines.len(),
11269 }
11270 });
11271 }
11272
11273 pub fn convert_indentation_to_spaces(
11274 &mut self,
11275 _: &ConvertIndentationToSpaces,
11276 window: &mut Window,
11277 cx: &mut Context<Self>,
11278 ) {
11279 let settings = self.buffer.read(cx).language_settings(cx);
11280 let tab_size = settings.tab_size.get() as usize;
11281
11282 self.manipulate_mutable_lines(window, cx, |lines| {
11283 // Allocates a reasonably sized scratch buffer once for the whole loop
11284 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11285 // Avoids recomputing spaces that could be inserted many times
11286 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11287 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11288 .collect();
11289
11290 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11291 let mut chars = line.as_ref().chars();
11292 let mut col = 0;
11293 let mut changed = false;
11294
11295 for ch in chars.by_ref() {
11296 match ch {
11297 ' ' => {
11298 reindented_line.push(' ');
11299 col += 1;
11300 }
11301 '\t' => {
11302 // \t are converted to spaces depending on the current column
11303 let spaces_len = tab_size - (col % tab_size);
11304 reindented_line.extend(&space_cache[spaces_len - 1]);
11305 col += spaces_len;
11306 changed = true;
11307 }
11308 _ => {
11309 // If we dont append before break, the character is consumed
11310 reindented_line.push(ch);
11311 break;
11312 }
11313 }
11314 }
11315
11316 if !changed {
11317 reindented_line.clear();
11318 continue;
11319 }
11320 // Append the rest of the line and replace old reference with new one
11321 reindented_line.extend(chars);
11322 *line = Cow::Owned(reindented_line.clone());
11323 reindented_line.clear();
11324 }
11325 });
11326 }
11327
11328 pub fn convert_indentation_to_tabs(
11329 &mut self,
11330 _: &ConvertIndentationToTabs,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 let settings = self.buffer.read(cx).language_settings(cx);
11335 let tab_size = settings.tab_size.get() as usize;
11336
11337 self.manipulate_mutable_lines(window, cx, |lines| {
11338 // Allocates a reasonably sized buffer once for the whole loop
11339 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11340 // Avoids recomputing spaces that could be inserted many times
11341 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11342 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11343 .collect();
11344
11345 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11346 let mut chars = line.chars();
11347 let mut spaces_count = 0;
11348 let mut first_non_indent_char = None;
11349 let mut changed = false;
11350
11351 for ch in chars.by_ref() {
11352 match ch {
11353 ' ' => {
11354 // Keep track of spaces. Append \t when we reach tab_size
11355 spaces_count += 1;
11356 changed = true;
11357 if spaces_count == tab_size {
11358 reindented_line.push('\t');
11359 spaces_count = 0;
11360 }
11361 }
11362 '\t' => {
11363 reindented_line.push('\t');
11364 spaces_count = 0;
11365 }
11366 _ => {
11367 // Dont append it yet, we might have remaining spaces
11368 first_non_indent_char = Some(ch);
11369 break;
11370 }
11371 }
11372 }
11373
11374 if !changed {
11375 reindented_line.clear();
11376 continue;
11377 }
11378 // Remaining spaces that didn't make a full tab stop
11379 if spaces_count > 0 {
11380 reindented_line.extend(&space_cache[spaces_count - 1]);
11381 }
11382 // If we consume an extra character that was not indentation, add it back
11383 if let Some(extra_char) = first_non_indent_char {
11384 reindented_line.push(extra_char);
11385 }
11386 // Append the rest of the line and replace old reference with new one
11387 reindented_line.extend(chars);
11388 *line = Cow::Owned(reindented_line.clone());
11389 reindented_line.clear();
11390 }
11391 });
11392 }
11393
11394 pub fn convert_to_upper_case(
11395 &mut self,
11396 _: &ConvertToUpperCase,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 self.manipulate_text(window, cx, |text| text.to_uppercase())
11401 }
11402
11403 pub fn convert_to_lower_case(
11404 &mut self,
11405 _: &ConvertToLowerCase,
11406 window: &mut Window,
11407 cx: &mut Context<Self>,
11408 ) {
11409 self.manipulate_text(window, cx, |text| text.to_lowercase())
11410 }
11411
11412 pub fn convert_to_title_case(
11413 &mut self,
11414 _: &ConvertToTitleCase,
11415 window: &mut Window,
11416 cx: &mut Context<Self>,
11417 ) {
11418 self.manipulate_text(window, cx, |text| {
11419 text.split('\n')
11420 .map(|line| line.to_case(Case::Title))
11421 .join("\n")
11422 })
11423 }
11424
11425 pub fn convert_to_snake_case(
11426 &mut self,
11427 _: &ConvertToSnakeCase,
11428 window: &mut Window,
11429 cx: &mut Context<Self>,
11430 ) {
11431 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11432 }
11433
11434 pub fn convert_to_kebab_case(
11435 &mut self,
11436 _: &ConvertToKebabCase,
11437 window: &mut Window,
11438 cx: &mut Context<Self>,
11439 ) {
11440 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11441 }
11442
11443 pub fn convert_to_upper_camel_case(
11444 &mut self,
11445 _: &ConvertToUpperCamelCase,
11446 window: &mut Window,
11447 cx: &mut Context<Self>,
11448 ) {
11449 self.manipulate_text(window, cx, |text| {
11450 text.split('\n')
11451 .map(|line| line.to_case(Case::UpperCamel))
11452 .join("\n")
11453 })
11454 }
11455
11456 pub fn convert_to_lower_camel_case(
11457 &mut self,
11458 _: &ConvertToLowerCamelCase,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11463 }
11464
11465 pub fn convert_to_opposite_case(
11466 &mut self,
11467 _: &ConvertToOppositeCase,
11468 window: &mut Window,
11469 cx: &mut Context<Self>,
11470 ) {
11471 self.manipulate_text(window, cx, |text| {
11472 text.chars()
11473 .fold(String::with_capacity(text.len()), |mut t, c| {
11474 if c.is_uppercase() {
11475 t.extend(c.to_lowercase());
11476 } else {
11477 t.extend(c.to_uppercase());
11478 }
11479 t
11480 })
11481 })
11482 }
11483
11484 pub fn convert_to_sentence_case(
11485 &mut self,
11486 _: &ConvertToSentenceCase,
11487 window: &mut Window,
11488 cx: &mut Context<Self>,
11489 ) {
11490 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11491 }
11492
11493 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11494 self.manipulate_text(window, cx, |text| {
11495 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11496 if has_upper_case_characters {
11497 text.to_lowercase()
11498 } else {
11499 text.to_uppercase()
11500 }
11501 })
11502 }
11503
11504 pub fn convert_to_rot13(
11505 &mut self,
11506 _: &ConvertToRot13,
11507 window: &mut Window,
11508 cx: &mut Context<Self>,
11509 ) {
11510 self.manipulate_text(window, cx, |text| {
11511 text.chars()
11512 .map(|c| match c {
11513 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11514 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11515 _ => c,
11516 })
11517 .collect()
11518 })
11519 }
11520
11521 pub fn convert_to_rot47(
11522 &mut self,
11523 _: &ConvertToRot47,
11524 window: &mut Window,
11525 cx: &mut Context<Self>,
11526 ) {
11527 self.manipulate_text(window, cx, |text| {
11528 text.chars()
11529 .map(|c| {
11530 let code_point = c as u32;
11531 if code_point >= 33 && code_point <= 126 {
11532 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11533 }
11534 c
11535 })
11536 .collect()
11537 })
11538 }
11539
11540 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11541 where
11542 Fn: FnMut(&str) -> String,
11543 {
11544 let buffer = self.buffer.read(cx).snapshot(cx);
11545
11546 let mut new_selections = Vec::new();
11547 let mut edits = Vec::new();
11548 let mut selection_adjustment = 0i32;
11549
11550 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11551 let selection_is_empty = selection.is_empty();
11552
11553 let (start, end) = if selection_is_empty {
11554 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11555 (word_range.start, word_range.end)
11556 } else {
11557 (
11558 buffer.point_to_offset(selection.start),
11559 buffer.point_to_offset(selection.end),
11560 )
11561 };
11562
11563 let text = buffer.text_for_range(start..end).collect::<String>();
11564 let old_length = text.len() as i32;
11565 let text = callback(&text);
11566
11567 new_selections.push(Selection {
11568 start: (start as i32 - selection_adjustment) as usize,
11569 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11570 goal: SelectionGoal::None,
11571 id: selection.id,
11572 reversed: selection.reversed,
11573 });
11574
11575 selection_adjustment += old_length - text.len() as i32;
11576
11577 edits.push((start..end, text));
11578 }
11579
11580 self.transact(window, cx, |this, window, cx| {
11581 this.buffer.update(cx, |buffer, cx| {
11582 buffer.edit(edits, None, cx);
11583 });
11584
11585 this.change_selections(Default::default(), window, cx, |s| {
11586 s.select(new_selections);
11587 });
11588
11589 this.request_autoscroll(Autoscroll::fit(), cx);
11590 });
11591 }
11592
11593 pub fn move_selection_on_drop(
11594 &mut self,
11595 selection: &Selection<Anchor>,
11596 target: DisplayPoint,
11597 is_cut: bool,
11598 window: &mut Window,
11599 cx: &mut Context<Self>,
11600 ) {
11601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11602 let buffer = display_map.buffer_snapshot();
11603 let mut edits = Vec::new();
11604 let insert_point = display_map
11605 .clip_point(target, Bias::Left)
11606 .to_point(&display_map);
11607 let text = buffer
11608 .text_for_range(selection.start..selection.end)
11609 .collect::<String>();
11610 if is_cut {
11611 edits.push(((selection.start..selection.end), String::new()));
11612 }
11613 let insert_anchor = buffer.anchor_before(insert_point);
11614 edits.push(((insert_anchor..insert_anchor), text));
11615 let last_edit_start = insert_anchor.bias_left(buffer);
11616 let last_edit_end = insert_anchor.bias_right(buffer);
11617 self.transact(window, cx, |this, window, cx| {
11618 this.buffer.update(cx, |buffer, cx| {
11619 buffer.edit(edits, None, cx);
11620 });
11621 this.change_selections(Default::default(), window, cx, |s| {
11622 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11623 });
11624 });
11625 }
11626
11627 pub fn clear_selection_drag_state(&mut self) {
11628 self.selection_drag_state = SelectionDragState::None;
11629 }
11630
11631 pub fn duplicate(
11632 &mut self,
11633 upwards: bool,
11634 whole_lines: bool,
11635 window: &mut Window,
11636 cx: &mut Context<Self>,
11637 ) {
11638 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11639
11640 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11641 let buffer = display_map.buffer_snapshot();
11642 let selections = self.selections.all::<Point>(&display_map);
11643
11644 let mut edits = Vec::new();
11645 let mut selections_iter = selections.iter().peekable();
11646 while let Some(selection) = selections_iter.next() {
11647 let mut rows = selection.spanned_rows(false, &display_map);
11648 // duplicate line-wise
11649 if whole_lines || selection.start == selection.end {
11650 // Avoid duplicating the same lines twice.
11651 while let Some(next_selection) = selections_iter.peek() {
11652 let next_rows = next_selection.spanned_rows(false, &display_map);
11653 if next_rows.start < rows.end {
11654 rows.end = next_rows.end;
11655 selections_iter.next().unwrap();
11656 } else {
11657 break;
11658 }
11659 }
11660
11661 // Copy the text from the selected row region and splice it either at the start
11662 // or end of the region.
11663 let start = Point::new(rows.start.0, 0);
11664 let end = Point::new(
11665 rows.end.previous_row().0,
11666 buffer.line_len(rows.end.previous_row()),
11667 );
11668
11669 let mut text = buffer.text_for_range(start..end).collect::<String>();
11670
11671 let insert_location = if upwards {
11672 // When duplicating upward, we need to insert before the current line.
11673 // If we're on the last line and it doesn't end with a newline,
11674 // we need to add a newline before the duplicated content.
11675 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11676 && buffer.max_point().column > 0
11677 && !text.ends_with('\n');
11678
11679 if needs_leading_newline {
11680 text.insert(0, '\n');
11681 end
11682 } else {
11683 text.push('\n');
11684 Point::new(rows.start.0, 0)
11685 }
11686 } else {
11687 text.push('\n');
11688 start
11689 };
11690 edits.push((insert_location..insert_location, text));
11691 } else {
11692 // duplicate character-wise
11693 let start = selection.start;
11694 let end = selection.end;
11695 let text = buffer.text_for_range(start..end).collect::<String>();
11696 edits.push((selection.end..selection.end, text));
11697 }
11698 }
11699
11700 self.transact(window, cx, |this, window, cx| {
11701 this.buffer.update(cx, |buffer, cx| {
11702 buffer.edit(edits, None, cx);
11703 });
11704
11705 // When duplicating upward with whole lines, move the cursor to the duplicated line
11706 if upwards && whole_lines {
11707 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11708
11709 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11710 let mut new_ranges = Vec::new();
11711 let selections = s.all::<Point>(&display_map);
11712 let mut selections_iter = selections.iter().peekable();
11713
11714 while let Some(first_selection) = selections_iter.next() {
11715 // Group contiguous selections together to find the total row span
11716 let mut group_selections = vec![first_selection];
11717 let mut rows = first_selection.spanned_rows(false, &display_map);
11718
11719 while let Some(next_selection) = selections_iter.peek() {
11720 let next_rows = next_selection.spanned_rows(false, &display_map);
11721 if next_rows.start < rows.end {
11722 rows.end = next_rows.end;
11723 group_selections.push(selections_iter.next().unwrap());
11724 } else {
11725 break;
11726 }
11727 }
11728
11729 let row_count = rows.end.0 - rows.start.0;
11730
11731 // Move all selections in this group up by the total number of duplicated rows
11732 for selection in group_selections {
11733 let new_start = Point::new(
11734 selection.start.row.saturating_sub(row_count),
11735 selection.start.column,
11736 );
11737
11738 let new_end = Point::new(
11739 selection.end.row.saturating_sub(row_count),
11740 selection.end.column,
11741 );
11742
11743 new_ranges.push(new_start..new_end);
11744 }
11745 }
11746
11747 s.select_ranges(new_ranges);
11748 });
11749 }
11750
11751 this.request_autoscroll(Autoscroll::fit(), cx);
11752 });
11753 }
11754
11755 pub fn duplicate_line_up(
11756 &mut self,
11757 _: &DuplicateLineUp,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 self.duplicate(true, true, window, cx);
11762 }
11763
11764 pub fn duplicate_line_down(
11765 &mut self,
11766 _: &DuplicateLineDown,
11767 window: &mut Window,
11768 cx: &mut Context<Self>,
11769 ) {
11770 self.duplicate(false, true, window, cx);
11771 }
11772
11773 pub fn duplicate_selection(
11774 &mut self,
11775 _: &DuplicateSelection,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 self.duplicate(false, false, window, cx);
11780 }
11781
11782 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11783 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11784 if self.mode.is_single_line() {
11785 cx.propagate();
11786 return;
11787 }
11788
11789 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11790 let buffer = self.buffer.read(cx).snapshot(cx);
11791
11792 let mut edits = Vec::new();
11793 let mut unfold_ranges = Vec::new();
11794 let mut refold_creases = Vec::new();
11795
11796 let selections = self.selections.all::<Point>(&display_map);
11797 let mut selections = selections.iter().peekable();
11798 let mut contiguous_row_selections = Vec::new();
11799 let mut new_selections = Vec::new();
11800
11801 while let Some(selection) = selections.next() {
11802 // Find all the selections that span a contiguous row range
11803 let (start_row, end_row) = consume_contiguous_rows(
11804 &mut contiguous_row_selections,
11805 selection,
11806 &display_map,
11807 &mut selections,
11808 );
11809
11810 // Move the text spanned by the row range to be before the line preceding the row range
11811 if start_row.0 > 0 {
11812 let range_to_move = Point::new(
11813 start_row.previous_row().0,
11814 buffer.line_len(start_row.previous_row()),
11815 )
11816 ..Point::new(
11817 end_row.previous_row().0,
11818 buffer.line_len(end_row.previous_row()),
11819 );
11820 let insertion_point = display_map
11821 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11822 .0;
11823
11824 // Don't move lines across excerpts
11825 if buffer
11826 .excerpt_containing(insertion_point..range_to_move.end)
11827 .is_some()
11828 {
11829 let text = buffer
11830 .text_for_range(range_to_move.clone())
11831 .flat_map(|s| s.chars())
11832 .skip(1)
11833 .chain(['\n'])
11834 .collect::<String>();
11835
11836 edits.push((
11837 buffer.anchor_after(range_to_move.start)
11838 ..buffer.anchor_before(range_to_move.end),
11839 String::new(),
11840 ));
11841 let insertion_anchor = buffer.anchor_after(insertion_point);
11842 edits.push((insertion_anchor..insertion_anchor, text));
11843
11844 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11845
11846 // Move selections up
11847 new_selections.extend(contiguous_row_selections.drain(..).map(
11848 |mut selection| {
11849 selection.start.row -= row_delta;
11850 selection.end.row -= row_delta;
11851 selection
11852 },
11853 ));
11854
11855 // Move folds up
11856 unfold_ranges.push(range_to_move.clone());
11857 for fold in display_map.folds_in_range(
11858 buffer.anchor_before(range_to_move.start)
11859 ..buffer.anchor_after(range_to_move.end),
11860 ) {
11861 let mut start = fold.range.start.to_point(&buffer);
11862 let mut end = fold.range.end.to_point(&buffer);
11863 start.row -= row_delta;
11864 end.row -= row_delta;
11865 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11866 }
11867 }
11868 }
11869
11870 // If we didn't move line(s), preserve the existing selections
11871 new_selections.append(&mut contiguous_row_selections);
11872 }
11873
11874 self.transact(window, cx, |this, window, cx| {
11875 this.unfold_ranges(&unfold_ranges, true, true, cx);
11876 this.buffer.update(cx, |buffer, cx| {
11877 for (range, text) in edits {
11878 buffer.edit([(range, text)], None, cx);
11879 }
11880 });
11881 this.fold_creases(refold_creases, true, window, cx);
11882 this.change_selections(Default::default(), window, cx, |s| {
11883 s.select(new_selections);
11884 })
11885 });
11886 }
11887
11888 pub fn move_line_down(
11889 &mut self,
11890 _: &MoveLineDown,
11891 window: &mut Window,
11892 cx: &mut Context<Self>,
11893 ) {
11894 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11895 if self.mode.is_single_line() {
11896 cx.propagate();
11897 return;
11898 }
11899
11900 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11901 let buffer = self.buffer.read(cx).snapshot(cx);
11902
11903 let mut edits = Vec::new();
11904 let mut unfold_ranges = Vec::new();
11905 let mut refold_creases = Vec::new();
11906
11907 let selections = self.selections.all::<Point>(&display_map);
11908 let mut selections = selections.iter().peekable();
11909 let mut contiguous_row_selections = Vec::new();
11910 let mut new_selections = Vec::new();
11911
11912 while let Some(selection) = selections.next() {
11913 // Find all the selections that span a contiguous row range
11914 let (start_row, end_row) = consume_contiguous_rows(
11915 &mut contiguous_row_selections,
11916 selection,
11917 &display_map,
11918 &mut selections,
11919 );
11920
11921 // Move the text spanned by the row range to be after the last line of the row range
11922 if end_row.0 <= buffer.max_point().row {
11923 let range_to_move =
11924 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11925 let insertion_point = display_map
11926 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11927 .0;
11928
11929 // Don't move lines across excerpt boundaries
11930 if buffer
11931 .excerpt_containing(range_to_move.start..insertion_point)
11932 .is_some()
11933 {
11934 let mut text = String::from("\n");
11935 text.extend(buffer.text_for_range(range_to_move.clone()));
11936 text.pop(); // Drop trailing newline
11937 edits.push((
11938 buffer.anchor_after(range_to_move.start)
11939 ..buffer.anchor_before(range_to_move.end),
11940 String::new(),
11941 ));
11942 let insertion_anchor = buffer.anchor_after(insertion_point);
11943 edits.push((insertion_anchor..insertion_anchor, text));
11944
11945 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11946
11947 // Move selections down
11948 new_selections.extend(contiguous_row_selections.drain(..).map(
11949 |mut selection| {
11950 selection.start.row += row_delta;
11951 selection.end.row += row_delta;
11952 selection
11953 },
11954 ));
11955
11956 // Move folds down
11957 unfold_ranges.push(range_to_move.clone());
11958 for fold in display_map.folds_in_range(
11959 buffer.anchor_before(range_to_move.start)
11960 ..buffer.anchor_after(range_to_move.end),
11961 ) {
11962 let mut start = fold.range.start.to_point(&buffer);
11963 let mut end = fold.range.end.to_point(&buffer);
11964 start.row += row_delta;
11965 end.row += row_delta;
11966 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11967 }
11968 }
11969 }
11970
11971 // If we didn't move line(s), preserve the existing selections
11972 new_selections.append(&mut contiguous_row_selections);
11973 }
11974
11975 self.transact(window, cx, |this, window, cx| {
11976 this.unfold_ranges(&unfold_ranges, true, true, cx);
11977 this.buffer.update(cx, |buffer, cx| {
11978 for (range, text) in edits {
11979 buffer.edit([(range, text)], None, cx);
11980 }
11981 });
11982 this.fold_creases(refold_creases, true, window, cx);
11983 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11984 });
11985 }
11986
11987 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11988 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11989 let text_layout_details = &self.text_layout_details(window);
11990 self.transact(window, cx, |this, window, cx| {
11991 let edits = this.change_selections(Default::default(), window, cx, |s| {
11992 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11993 s.move_with(|display_map, selection| {
11994 if !selection.is_empty() {
11995 return;
11996 }
11997
11998 let mut head = selection.head();
11999 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12000 if head.column() == display_map.line_len(head.row()) {
12001 transpose_offset = display_map
12002 .buffer_snapshot()
12003 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12004 }
12005
12006 if transpose_offset == 0 {
12007 return;
12008 }
12009
12010 *head.column_mut() += 1;
12011 head = display_map.clip_point(head, Bias::Right);
12012 let goal = SelectionGoal::HorizontalPosition(
12013 display_map
12014 .x_for_display_point(head, text_layout_details)
12015 .into(),
12016 );
12017 selection.collapse_to(head, goal);
12018
12019 let transpose_start = display_map
12020 .buffer_snapshot()
12021 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12022 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12023 let transpose_end = display_map
12024 .buffer_snapshot()
12025 .clip_offset(transpose_offset + 1, Bias::Right);
12026 if let Some(ch) = display_map
12027 .buffer_snapshot()
12028 .chars_at(transpose_start)
12029 .next()
12030 {
12031 edits.push((transpose_start..transpose_offset, String::new()));
12032 edits.push((transpose_end..transpose_end, ch.to_string()));
12033 }
12034 }
12035 });
12036 edits
12037 });
12038 this.buffer
12039 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12040 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12041 this.change_selections(Default::default(), window, cx, |s| {
12042 s.select(selections);
12043 });
12044 });
12045 }
12046
12047 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12048 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12049 if self.mode.is_single_line() {
12050 cx.propagate();
12051 return;
12052 }
12053
12054 self.rewrap_impl(RewrapOptions::default(), cx)
12055 }
12056
12057 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12058 let buffer = self.buffer.read(cx).snapshot(cx);
12059 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12060
12061 #[derive(Clone, Debug, PartialEq)]
12062 enum CommentFormat {
12063 /// single line comment, with prefix for line
12064 Line(String),
12065 /// single line within a block comment, with prefix for line
12066 BlockLine(String),
12067 /// a single line of a block comment that includes the initial delimiter
12068 BlockCommentWithStart(BlockCommentConfig),
12069 /// a single line of a block comment that includes the ending delimiter
12070 BlockCommentWithEnd(BlockCommentConfig),
12071 }
12072
12073 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12074 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12075 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12076 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12077 .peekable();
12078
12079 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12080 row
12081 } else {
12082 return Vec::new();
12083 };
12084
12085 let language_settings = buffer.language_settings_at(selection.head(), cx);
12086 let language_scope = buffer.language_scope_at(selection.head());
12087
12088 let indent_and_prefix_for_row =
12089 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12090 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12091 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12092 &language_scope
12093 {
12094 let indent_end = Point::new(row, indent.len);
12095 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12096 let line_text_after_indent = buffer
12097 .text_for_range(indent_end..line_end)
12098 .collect::<String>();
12099
12100 let is_within_comment_override = buffer
12101 .language_scope_at(indent_end)
12102 .is_some_and(|scope| scope.override_name() == Some("comment"));
12103 let comment_delimiters = if is_within_comment_override {
12104 // we are within a comment syntax node, but we don't
12105 // yet know what kind of comment: block, doc or line
12106 match (
12107 language_scope.documentation_comment(),
12108 language_scope.block_comment(),
12109 ) {
12110 (Some(config), _) | (_, Some(config))
12111 if buffer.contains_str_at(indent_end, &config.start) =>
12112 {
12113 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12114 }
12115 (Some(config), _) | (_, Some(config))
12116 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12117 {
12118 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12119 }
12120 (Some(config), _) | (_, Some(config))
12121 if buffer.contains_str_at(indent_end, &config.prefix) =>
12122 {
12123 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12124 }
12125 (_, _) => language_scope
12126 .line_comment_prefixes()
12127 .iter()
12128 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12129 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12130 }
12131 } else {
12132 // we not in an overridden comment node, but we may
12133 // be within a non-overridden line comment node
12134 language_scope
12135 .line_comment_prefixes()
12136 .iter()
12137 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12138 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12139 };
12140
12141 let rewrap_prefix = language_scope
12142 .rewrap_prefixes()
12143 .iter()
12144 .find_map(|prefix_regex| {
12145 prefix_regex.find(&line_text_after_indent).map(|mat| {
12146 if mat.start() == 0 {
12147 Some(mat.as_str().to_string())
12148 } else {
12149 None
12150 }
12151 })
12152 })
12153 .flatten();
12154 (comment_delimiters, rewrap_prefix)
12155 } else {
12156 (None, None)
12157 };
12158 (indent, comment_prefix, rewrap_prefix)
12159 };
12160
12161 let mut ranges = Vec::new();
12162 let from_empty_selection = selection.is_empty();
12163
12164 let mut current_range_start = first_row;
12165 let mut prev_row = first_row;
12166 let (
12167 mut current_range_indent,
12168 mut current_range_comment_delimiters,
12169 mut current_range_rewrap_prefix,
12170 ) = indent_and_prefix_for_row(first_row);
12171
12172 for row in non_blank_rows_iter.skip(1) {
12173 let has_paragraph_break = row > prev_row + 1;
12174
12175 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12176 indent_and_prefix_for_row(row);
12177
12178 let has_indent_change = row_indent != current_range_indent;
12179 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12180
12181 let has_boundary_change = has_comment_change
12182 || row_rewrap_prefix.is_some()
12183 || (has_indent_change && current_range_comment_delimiters.is_some());
12184
12185 if has_paragraph_break || has_boundary_change {
12186 ranges.push((
12187 language_settings.clone(),
12188 Point::new(current_range_start, 0)
12189 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12190 current_range_indent,
12191 current_range_comment_delimiters.clone(),
12192 current_range_rewrap_prefix.clone(),
12193 from_empty_selection,
12194 ));
12195 current_range_start = row;
12196 current_range_indent = row_indent;
12197 current_range_comment_delimiters = row_comment_delimiters;
12198 current_range_rewrap_prefix = row_rewrap_prefix;
12199 }
12200 prev_row = row;
12201 }
12202
12203 ranges.push((
12204 language_settings.clone(),
12205 Point::new(current_range_start, 0)
12206 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12207 current_range_indent,
12208 current_range_comment_delimiters,
12209 current_range_rewrap_prefix,
12210 from_empty_selection,
12211 ));
12212
12213 ranges
12214 });
12215
12216 let mut edits = Vec::new();
12217 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12218
12219 for (
12220 language_settings,
12221 wrap_range,
12222 mut indent_size,
12223 comment_prefix,
12224 rewrap_prefix,
12225 from_empty_selection,
12226 ) in wrap_ranges
12227 {
12228 let mut start_row = wrap_range.start.row;
12229 let mut end_row = wrap_range.end.row;
12230
12231 // Skip selections that overlap with a range that has already been rewrapped.
12232 let selection_range = start_row..end_row;
12233 if rewrapped_row_ranges
12234 .iter()
12235 .any(|range| range.overlaps(&selection_range))
12236 {
12237 continue;
12238 }
12239
12240 let tab_size = language_settings.tab_size;
12241
12242 let (line_prefix, inside_comment) = match &comment_prefix {
12243 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12244 (Some(prefix.as_str()), true)
12245 }
12246 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12247 (Some(prefix.as_ref()), true)
12248 }
12249 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12250 start: _,
12251 end: _,
12252 prefix,
12253 tab_size,
12254 })) => {
12255 indent_size.len += tab_size;
12256 (Some(prefix.as_ref()), true)
12257 }
12258 None => (None, false),
12259 };
12260 let indent_prefix = indent_size.chars().collect::<String>();
12261 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12262
12263 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12264 RewrapBehavior::InComments => inside_comment,
12265 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12266 RewrapBehavior::Anywhere => true,
12267 };
12268
12269 let should_rewrap = options.override_language_settings
12270 || allow_rewrap_based_on_language
12271 || self.hard_wrap.is_some();
12272 if !should_rewrap {
12273 continue;
12274 }
12275
12276 if from_empty_selection {
12277 'expand_upwards: while start_row > 0 {
12278 let prev_row = start_row - 1;
12279 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12280 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12281 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12282 {
12283 start_row = prev_row;
12284 } else {
12285 break 'expand_upwards;
12286 }
12287 }
12288
12289 'expand_downwards: while end_row < buffer.max_point().row {
12290 let next_row = end_row + 1;
12291 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12292 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12293 && !buffer.is_line_blank(MultiBufferRow(next_row))
12294 {
12295 end_row = next_row;
12296 } else {
12297 break 'expand_downwards;
12298 }
12299 }
12300 }
12301
12302 let start = Point::new(start_row, 0);
12303 let start_offset = ToOffset::to_offset(&start, &buffer);
12304 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12305 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12306 let mut first_line_delimiter = None;
12307 let mut last_line_delimiter = None;
12308 let Some(lines_without_prefixes) = selection_text
12309 .lines()
12310 .enumerate()
12311 .map(|(ix, line)| {
12312 let line_trimmed = line.trim_start();
12313 if rewrap_prefix.is_some() && ix > 0 {
12314 Ok(line_trimmed)
12315 } else if let Some(
12316 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12317 start,
12318 prefix,
12319 end,
12320 tab_size,
12321 })
12322 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12323 start,
12324 prefix,
12325 end,
12326 tab_size,
12327 }),
12328 ) = &comment_prefix
12329 {
12330 let line_trimmed = line_trimmed
12331 .strip_prefix(start.as_ref())
12332 .map(|s| {
12333 let mut indent_size = indent_size;
12334 indent_size.len -= tab_size;
12335 let indent_prefix: String = indent_size.chars().collect();
12336 first_line_delimiter = Some((indent_prefix, start));
12337 s.trim_start()
12338 })
12339 .unwrap_or(line_trimmed);
12340 let line_trimmed = line_trimmed
12341 .strip_suffix(end.as_ref())
12342 .map(|s| {
12343 last_line_delimiter = Some(end);
12344 s.trim_end()
12345 })
12346 .unwrap_or(line_trimmed);
12347 let line_trimmed = line_trimmed
12348 .strip_prefix(prefix.as_ref())
12349 .unwrap_or(line_trimmed);
12350 Ok(line_trimmed)
12351 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12352 line_trimmed.strip_prefix(prefix).with_context(|| {
12353 format!("line did not start with prefix {prefix:?}: {line:?}")
12354 })
12355 } else {
12356 line_trimmed
12357 .strip_prefix(&line_prefix.trim_start())
12358 .with_context(|| {
12359 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12360 })
12361 }
12362 })
12363 .collect::<Result<Vec<_>, _>>()
12364 .log_err()
12365 else {
12366 continue;
12367 };
12368
12369 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12370 buffer
12371 .language_settings_at(Point::new(start_row, 0), cx)
12372 .preferred_line_length as usize
12373 });
12374
12375 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12376 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12377 } else {
12378 line_prefix.clone()
12379 };
12380
12381 let wrapped_text = {
12382 let mut wrapped_text = wrap_with_prefix(
12383 line_prefix,
12384 subsequent_lines_prefix,
12385 lines_without_prefixes.join("\n"),
12386 wrap_column,
12387 tab_size,
12388 options.preserve_existing_whitespace,
12389 );
12390
12391 if let Some((indent, delimiter)) = first_line_delimiter {
12392 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12393 }
12394 if let Some(last_line) = last_line_delimiter {
12395 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12396 }
12397
12398 wrapped_text
12399 };
12400
12401 // TODO: should always use char-based diff while still supporting cursor behavior that
12402 // matches vim.
12403 let mut diff_options = DiffOptions::default();
12404 if options.override_language_settings {
12405 diff_options.max_word_diff_len = 0;
12406 diff_options.max_word_diff_line_count = 0;
12407 } else {
12408 diff_options.max_word_diff_len = usize::MAX;
12409 diff_options.max_word_diff_line_count = usize::MAX;
12410 }
12411
12412 for (old_range, new_text) in
12413 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12414 {
12415 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12416 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12417 edits.push((edit_start..edit_end, new_text));
12418 }
12419
12420 rewrapped_row_ranges.push(start_row..=end_row);
12421 }
12422
12423 self.buffer
12424 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12425 }
12426
12427 pub fn cut_common(
12428 &mut self,
12429 cut_no_selection_line: bool,
12430 window: &mut Window,
12431 cx: &mut Context<Self>,
12432 ) -> ClipboardItem {
12433 let mut text = String::new();
12434 let buffer = self.buffer.read(cx).snapshot(cx);
12435 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12436 let mut clipboard_selections = Vec::with_capacity(selections.len());
12437 {
12438 let max_point = buffer.max_point();
12439 let mut is_first = true;
12440 for selection in &mut selections {
12441 let is_entire_line =
12442 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12443 if is_entire_line {
12444 selection.start = Point::new(selection.start.row, 0);
12445 if !selection.is_empty() && selection.end.column == 0 {
12446 selection.end = cmp::min(max_point, selection.end);
12447 } else {
12448 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12449 }
12450 selection.goal = SelectionGoal::None;
12451 }
12452 if is_first {
12453 is_first = false;
12454 } else {
12455 text += "\n";
12456 }
12457 let mut len = 0;
12458 for chunk in buffer.text_for_range(selection.start..selection.end) {
12459 text.push_str(chunk);
12460 len += chunk.len();
12461 }
12462 clipboard_selections.push(ClipboardSelection {
12463 len,
12464 is_entire_line,
12465 first_line_indent: buffer
12466 .indent_size_for_line(MultiBufferRow(selection.start.row))
12467 .len,
12468 });
12469 }
12470 }
12471
12472 self.transact(window, cx, |this, window, cx| {
12473 this.change_selections(Default::default(), window, cx, |s| {
12474 s.select(selections);
12475 });
12476 this.insert("", window, cx);
12477 });
12478 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12479 }
12480
12481 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12483 let item = self.cut_common(true, window, cx);
12484 cx.write_to_clipboard(item);
12485 }
12486
12487 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12489 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12490 s.move_with(|snapshot, sel| {
12491 if sel.is_empty() {
12492 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12493 }
12494 if sel.is_empty() {
12495 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12496 }
12497 });
12498 });
12499 let item = self.cut_common(false, window, cx);
12500 cx.set_global(KillRing(item))
12501 }
12502
12503 pub fn kill_ring_yank(
12504 &mut self,
12505 _: &KillRingYank,
12506 window: &mut Window,
12507 cx: &mut Context<Self>,
12508 ) {
12509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12510 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12511 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12512 (kill_ring.text().to_string(), kill_ring.metadata_json())
12513 } else {
12514 return;
12515 }
12516 } else {
12517 return;
12518 };
12519 self.do_paste(&text, metadata, false, window, cx);
12520 }
12521
12522 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12523 self.do_copy(true, cx);
12524 }
12525
12526 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12527 self.do_copy(false, cx);
12528 }
12529
12530 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12531 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12532 let buffer = self.buffer.read(cx).read(cx);
12533 let mut text = String::new();
12534
12535 let mut clipboard_selections = Vec::with_capacity(selections.len());
12536 {
12537 let max_point = buffer.max_point();
12538 let mut is_first = true;
12539 for selection in &selections {
12540 let mut start = selection.start;
12541 let mut end = selection.end;
12542 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12543 let mut add_trailing_newline = false;
12544 if is_entire_line {
12545 start = Point::new(start.row, 0);
12546 let next_line_start = Point::new(end.row + 1, 0);
12547 if next_line_start <= max_point {
12548 end = next_line_start;
12549 } else {
12550 // We're on the last line without a trailing newline.
12551 // Copy to the end of the line and add a newline afterwards.
12552 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12553 add_trailing_newline = true;
12554 }
12555 }
12556
12557 let mut trimmed_selections = Vec::new();
12558 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12559 let row = MultiBufferRow(start.row);
12560 let first_indent = buffer.indent_size_for_line(row);
12561 if first_indent.len == 0 || start.column > first_indent.len {
12562 trimmed_selections.push(start..end);
12563 } else {
12564 trimmed_selections.push(
12565 Point::new(row.0, first_indent.len)
12566 ..Point::new(row.0, buffer.line_len(row)),
12567 );
12568 for row in start.row + 1..=end.row {
12569 let mut line_len = buffer.line_len(MultiBufferRow(row));
12570 if row == end.row {
12571 line_len = end.column;
12572 }
12573 if line_len == 0 {
12574 trimmed_selections
12575 .push(Point::new(row, 0)..Point::new(row, line_len));
12576 continue;
12577 }
12578 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12579 if row_indent_size.len >= first_indent.len {
12580 trimmed_selections.push(
12581 Point::new(row, first_indent.len)..Point::new(row, line_len),
12582 );
12583 } else {
12584 trimmed_selections.clear();
12585 trimmed_selections.push(start..end);
12586 break;
12587 }
12588 }
12589 }
12590 } else {
12591 trimmed_selections.push(start..end);
12592 }
12593
12594 for trimmed_range in trimmed_selections {
12595 if is_first {
12596 is_first = false;
12597 } else {
12598 text += "\n";
12599 }
12600 let mut len = 0;
12601 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12602 text.push_str(chunk);
12603 len += chunk.len();
12604 }
12605 if add_trailing_newline {
12606 text.push('\n');
12607 len += 1;
12608 }
12609 clipboard_selections.push(ClipboardSelection {
12610 len,
12611 is_entire_line,
12612 first_line_indent: buffer
12613 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12614 .len,
12615 });
12616 }
12617 }
12618 }
12619
12620 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12621 text,
12622 clipboard_selections,
12623 ));
12624 }
12625
12626 pub fn do_paste(
12627 &mut self,
12628 text: &String,
12629 clipboard_selections: Option<Vec<ClipboardSelection>>,
12630 handle_entire_lines: bool,
12631 window: &mut Window,
12632 cx: &mut Context<Self>,
12633 ) {
12634 if self.read_only(cx) {
12635 return;
12636 }
12637
12638 let clipboard_text = Cow::Borrowed(text.as_str());
12639
12640 self.transact(window, cx, |this, window, cx| {
12641 let had_active_edit_prediction = this.has_active_edit_prediction();
12642 let display_map = this.display_snapshot(cx);
12643 let old_selections = this.selections.all::<usize>(&display_map);
12644 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12645
12646 if let Some(mut clipboard_selections) = clipboard_selections {
12647 let all_selections_were_entire_line =
12648 clipboard_selections.iter().all(|s| s.is_entire_line);
12649 let first_selection_indent_column =
12650 clipboard_selections.first().map(|s| s.first_line_indent);
12651 if clipboard_selections.len() != old_selections.len() {
12652 clipboard_selections.drain(..);
12653 }
12654 let mut auto_indent_on_paste = true;
12655
12656 this.buffer.update(cx, |buffer, cx| {
12657 let snapshot = buffer.read(cx);
12658 auto_indent_on_paste = snapshot
12659 .language_settings_at(cursor_offset, cx)
12660 .auto_indent_on_paste;
12661
12662 let mut start_offset = 0;
12663 let mut edits = Vec::new();
12664 let mut original_indent_columns = Vec::new();
12665 for (ix, selection) in old_selections.iter().enumerate() {
12666 let to_insert;
12667 let entire_line;
12668 let original_indent_column;
12669 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12670 let end_offset = start_offset + clipboard_selection.len;
12671 to_insert = &clipboard_text[start_offset..end_offset];
12672 entire_line = clipboard_selection.is_entire_line;
12673 start_offset = end_offset + 1;
12674 original_indent_column = Some(clipboard_selection.first_line_indent);
12675 } else {
12676 to_insert = &*clipboard_text;
12677 entire_line = all_selections_were_entire_line;
12678 original_indent_column = first_selection_indent_column
12679 }
12680
12681 let (range, to_insert) =
12682 if selection.is_empty() && handle_entire_lines && entire_line {
12683 // If the corresponding selection was empty when this slice of the
12684 // clipboard text was written, then the entire line containing the
12685 // selection was copied. If this selection is also currently empty,
12686 // then paste the line before the current line of the buffer.
12687 let column = selection.start.to_point(&snapshot).column as usize;
12688 let line_start = selection.start - column;
12689 (line_start..line_start, Cow::Borrowed(to_insert))
12690 } else {
12691 let language = snapshot.language_at(selection.head());
12692 let range = selection.range();
12693 if let Some(language) = language
12694 && language.name() == "Markdown".into()
12695 {
12696 edit_for_markdown_paste(
12697 &snapshot,
12698 range,
12699 to_insert,
12700 url::Url::parse(to_insert).ok(),
12701 )
12702 } else {
12703 (range, Cow::Borrowed(to_insert))
12704 }
12705 };
12706
12707 edits.push((range, to_insert));
12708 original_indent_columns.push(original_indent_column);
12709 }
12710 drop(snapshot);
12711
12712 buffer.edit(
12713 edits,
12714 if auto_indent_on_paste {
12715 Some(AutoindentMode::Block {
12716 original_indent_columns,
12717 })
12718 } else {
12719 None
12720 },
12721 cx,
12722 );
12723 });
12724
12725 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12726 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12727 } else {
12728 let url = url::Url::parse(&clipboard_text).ok();
12729
12730 let auto_indent_mode = if !clipboard_text.is_empty() {
12731 Some(AutoindentMode::Block {
12732 original_indent_columns: Vec::new(),
12733 })
12734 } else {
12735 None
12736 };
12737
12738 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12739 let snapshot = buffer.snapshot(cx);
12740
12741 let anchors = old_selections
12742 .iter()
12743 .map(|s| {
12744 let anchor = snapshot.anchor_after(s.head());
12745 s.map(|_| anchor)
12746 })
12747 .collect::<Vec<_>>();
12748
12749 let mut edits = Vec::new();
12750
12751 for selection in old_selections.iter() {
12752 let language = snapshot.language_at(selection.head());
12753 let range = selection.range();
12754
12755 let (edit_range, edit_text) = if let Some(language) = language
12756 && language.name() == "Markdown".into()
12757 {
12758 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12759 } else {
12760 (range, clipboard_text.clone())
12761 };
12762
12763 edits.push((edit_range, edit_text));
12764 }
12765
12766 drop(snapshot);
12767 buffer.edit(edits, auto_indent_mode, cx);
12768
12769 anchors
12770 });
12771
12772 this.change_selections(Default::default(), window, cx, |s| {
12773 s.select_anchors(selection_anchors);
12774 });
12775 }
12776
12777 let trigger_in_words =
12778 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12779
12780 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12781 });
12782 }
12783
12784 pub fn diff_clipboard_with_selection(
12785 &mut self,
12786 _: &DiffClipboardWithSelection,
12787 window: &mut Window,
12788 cx: &mut Context<Self>,
12789 ) {
12790 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12791
12792 if selections.is_empty() {
12793 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12794 return;
12795 };
12796
12797 let clipboard_text = match cx.read_from_clipboard() {
12798 Some(item) => match item.entries().first() {
12799 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12800 _ => None,
12801 },
12802 None => None,
12803 };
12804
12805 let Some(clipboard_text) = clipboard_text else {
12806 log::warn!("Clipboard doesn't contain text.");
12807 return;
12808 };
12809
12810 window.dispatch_action(
12811 Box::new(DiffClipboardWithSelectionData {
12812 clipboard_text,
12813 editor: cx.entity(),
12814 }),
12815 cx,
12816 );
12817 }
12818
12819 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12821 if let Some(item) = cx.read_from_clipboard() {
12822 let entries = item.entries();
12823
12824 match entries.first() {
12825 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12826 // of all the pasted entries.
12827 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12828 .do_paste(
12829 clipboard_string.text(),
12830 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12831 true,
12832 window,
12833 cx,
12834 ),
12835 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12836 }
12837 }
12838 }
12839
12840 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12841 if self.read_only(cx) {
12842 return;
12843 }
12844
12845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12846
12847 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12848 if let Some((selections, _)) =
12849 self.selection_history.transaction(transaction_id).cloned()
12850 {
12851 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12852 s.select_anchors(selections.to_vec());
12853 });
12854 } else {
12855 log::error!(
12856 "No entry in selection_history found for undo. \
12857 This may correspond to a bug where undo does not update the selection. \
12858 If this is occurring, please add details to \
12859 https://github.com/zed-industries/zed/issues/22692"
12860 );
12861 }
12862 self.request_autoscroll(Autoscroll::fit(), cx);
12863 self.unmark_text(window, cx);
12864 self.refresh_edit_prediction(true, false, window, cx);
12865 cx.emit(EditorEvent::Edited { transaction_id });
12866 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12867 }
12868 }
12869
12870 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12871 if self.read_only(cx) {
12872 return;
12873 }
12874
12875 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12876
12877 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12878 if let Some((_, Some(selections))) =
12879 self.selection_history.transaction(transaction_id).cloned()
12880 {
12881 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12882 s.select_anchors(selections.to_vec());
12883 });
12884 } else {
12885 log::error!(
12886 "No entry in selection_history found for redo. \
12887 This may correspond to a bug where undo does not update the selection. \
12888 If this is occurring, please add details to \
12889 https://github.com/zed-industries/zed/issues/22692"
12890 );
12891 }
12892 self.request_autoscroll(Autoscroll::fit(), cx);
12893 self.unmark_text(window, cx);
12894 self.refresh_edit_prediction(true, false, window, cx);
12895 cx.emit(EditorEvent::Edited { transaction_id });
12896 }
12897 }
12898
12899 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12900 self.buffer
12901 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12902 }
12903
12904 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12905 self.buffer
12906 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12907 }
12908
12909 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12910 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12911 self.change_selections(Default::default(), window, cx, |s| {
12912 s.move_with(|map, selection| {
12913 let cursor = if selection.is_empty() {
12914 movement::left(map, selection.start)
12915 } else {
12916 selection.start
12917 };
12918 selection.collapse_to(cursor, SelectionGoal::None);
12919 });
12920 })
12921 }
12922
12923 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12925 self.change_selections(Default::default(), window, cx, |s| {
12926 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12927 })
12928 }
12929
12930 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12932 self.change_selections(Default::default(), window, cx, |s| {
12933 s.move_with(|map, selection| {
12934 let cursor = if selection.is_empty() {
12935 movement::right(map, selection.end)
12936 } else {
12937 selection.end
12938 };
12939 selection.collapse_to(cursor, SelectionGoal::None)
12940 });
12941 })
12942 }
12943
12944 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12946 self.change_selections(Default::default(), window, cx, |s| {
12947 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12948 });
12949 }
12950
12951 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12952 if self.take_rename(true, window, cx).is_some() {
12953 return;
12954 }
12955
12956 if self.mode.is_single_line() {
12957 cx.propagate();
12958 return;
12959 }
12960
12961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12962
12963 let text_layout_details = &self.text_layout_details(window);
12964 let selection_count = self.selections.count();
12965 let first_selection = self.selections.first_anchor();
12966
12967 self.change_selections(Default::default(), window, cx, |s| {
12968 s.move_with(|map, selection| {
12969 if !selection.is_empty() {
12970 selection.goal = SelectionGoal::None;
12971 }
12972 let (cursor, goal) = movement::up(
12973 map,
12974 selection.start,
12975 selection.goal,
12976 false,
12977 text_layout_details,
12978 );
12979 selection.collapse_to(cursor, goal);
12980 });
12981 });
12982
12983 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12984 {
12985 cx.propagate();
12986 }
12987 }
12988
12989 pub fn move_up_by_lines(
12990 &mut self,
12991 action: &MoveUpByLines,
12992 window: &mut Window,
12993 cx: &mut Context<Self>,
12994 ) {
12995 if self.take_rename(true, window, cx).is_some() {
12996 return;
12997 }
12998
12999 if self.mode.is_single_line() {
13000 cx.propagate();
13001 return;
13002 }
13003
13004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13005
13006 let text_layout_details = &self.text_layout_details(window);
13007
13008 self.change_selections(Default::default(), window, cx, |s| {
13009 s.move_with(|map, selection| {
13010 if !selection.is_empty() {
13011 selection.goal = SelectionGoal::None;
13012 }
13013 let (cursor, goal) = movement::up_by_rows(
13014 map,
13015 selection.start,
13016 action.lines,
13017 selection.goal,
13018 false,
13019 text_layout_details,
13020 );
13021 selection.collapse_to(cursor, goal);
13022 });
13023 })
13024 }
13025
13026 pub fn move_down_by_lines(
13027 &mut self,
13028 action: &MoveDownByLines,
13029 window: &mut Window,
13030 cx: &mut Context<Self>,
13031 ) {
13032 if self.take_rename(true, window, cx).is_some() {
13033 return;
13034 }
13035
13036 if self.mode.is_single_line() {
13037 cx.propagate();
13038 return;
13039 }
13040
13041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13042
13043 let text_layout_details = &self.text_layout_details(window);
13044
13045 self.change_selections(Default::default(), window, cx, |s| {
13046 s.move_with(|map, selection| {
13047 if !selection.is_empty() {
13048 selection.goal = SelectionGoal::None;
13049 }
13050 let (cursor, goal) = movement::down_by_rows(
13051 map,
13052 selection.start,
13053 action.lines,
13054 selection.goal,
13055 false,
13056 text_layout_details,
13057 );
13058 selection.collapse_to(cursor, goal);
13059 });
13060 })
13061 }
13062
13063 pub fn select_down_by_lines(
13064 &mut self,
13065 action: &SelectDownByLines,
13066 window: &mut Window,
13067 cx: &mut Context<Self>,
13068 ) {
13069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13070 let text_layout_details = &self.text_layout_details(window);
13071 self.change_selections(Default::default(), window, cx, |s| {
13072 s.move_heads_with(|map, head, goal| {
13073 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13074 })
13075 })
13076 }
13077
13078 pub fn select_up_by_lines(
13079 &mut self,
13080 action: &SelectUpByLines,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13085 let text_layout_details = &self.text_layout_details(window);
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_heads_with(|map, head, goal| {
13088 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13089 })
13090 })
13091 }
13092
13093 pub fn select_page_up(
13094 &mut self,
13095 _: &SelectPageUp,
13096 window: &mut Window,
13097 cx: &mut Context<Self>,
13098 ) {
13099 let Some(row_count) = self.visible_row_count() else {
13100 return;
13101 };
13102
13103 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13104
13105 let text_layout_details = &self.text_layout_details(window);
13106
13107 self.change_selections(Default::default(), window, cx, |s| {
13108 s.move_heads_with(|map, head, goal| {
13109 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13110 })
13111 })
13112 }
13113
13114 pub fn move_page_up(
13115 &mut self,
13116 action: &MovePageUp,
13117 window: &mut Window,
13118 cx: &mut Context<Self>,
13119 ) {
13120 if self.take_rename(true, window, cx).is_some() {
13121 return;
13122 }
13123
13124 if self
13125 .context_menu
13126 .borrow_mut()
13127 .as_mut()
13128 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13129 .unwrap_or(false)
13130 {
13131 return;
13132 }
13133
13134 if matches!(self.mode, EditorMode::SingleLine) {
13135 cx.propagate();
13136 return;
13137 }
13138
13139 let Some(row_count) = self.visible_row_count() else {
13140 return;
13141 };
13142
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13144
13145 let effects = if action.center_cursor {
13146 SelectionEffects::scroll(Autoscroll::center())
13147 } else {
13148 SelectionEffects::default()
13149 };
13150
13151 let text_layout_details = &self.text_layout_details(window);
13152
13153 self.change_selections(effects, window, cx, |s| {
13154 s.move_with(|map, selection| {
13155 if !selection.is_empty() {
13156 selection.goal = SelectionGoal::None;
13157 }
13158 let (cursor, goal) = movement::up_by_rows(
13159 map,
13160 selection.end,
13161 row_count,
13162 selection.goal,
13163 false,
13164 text_layout_details,
13165 );
13166 selection.collapse_to(cursor, goal);
13167 });
13168 });
13169 }
13170
13171 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13173 let text_layout_details = &self.text_layout_details(window);
13174 self.change_selections(Default::default(), window, cx, |s| {
13175 s.move_heads_with(|map, head, goal| {
13176 movement::up(map, head, goal, false, text_layout_details)
13177 })
13178 })
13179 }
13180
13181 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13182 self.take_rename(true, window, cx);
13183
13184 if self.mode.is_single_line() {
13185 cx.propagate();
13186 return;
13187 }
13188
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13190
13191 let text_layout_details = &self.text_layout_details(window);
13192 let selection_count = self.selections.count();
13193 let first_selection = self.selections.first_anchor();
13194
13195 self.change_selections(Default::default(), window, cx, |s| {
13196 s.move_with(|map, selection| {
13197 if !selection.is_empty() {
13198 selection.goal = SelectionGoal::None;
13199 }
13200 let (cursor, goal) = movement::down(
13201 map,
13202 selection.end,
13203 selection.goal,
13204 false,
13205 text_layout_details,
13206 );
13207 selection.collapse_to(cursor, goal);
13208 });
13209 });
13210
13211 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13212 {
13213 cx.propagate();
13214 }
13215 }
13216
13217 pub fn select_page_down(
13218 &mut self,
13219 _: &SelectPageDown,
13220 window: &mut Window,
13221 cx: &mut Context<Self>,
13222 ) {
13223 let Some(row_count) = self.visible_row_count() else {
13224 return;
13225 };
13226
13227 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13228
13229 let text_layout_details = &self.text_layout_details(window);
13230
13231 self.change_selections(Default::default(), window, cx, |s| {
13232 s.move_heads_with(|map, head, goal| {
13233 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13234 })
13235 })
13236 }
13237
13238 pub fn move_page_down(
13239 &mut self,
13240 action: &MovePageDown,
13241 window: &mut Window,
13242 cx: &mut Context<Self>,
13243 ) {
13244 if self.take_rename(true, window, cx).is_some() {
13245 return;
13246 }
13247
13248 if self
13249 .context_menu
13250 .borrow_mut()
13251 .as_mut()
13252 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13253 .unwrap_or(false)
13254 {
13255 return;
13256 }
13257
13258 if matches!(self.mode, EditorMode::SingleLine) {
13259 cx.propagate();
13260 return;
13261 }
13262
13263 let Some(row_count) = self.visible_row_count() else {
13264 return;
13265 };
13266
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268
13269 let effects = if action.center_cursor {
13270 SelectionEffects::scroll(Autoscroll::center())
13271 } else {
13272 SelectionEffects::default()
13273 };
13274
13275 let text_layout_details = &self.text_layout_details(window);
13276 self.change_selections(effects, window, cx, |s| {
13277 s.move_with(|map, selection| {
13278 if !selection.is_empty() {
13279 selection.goal = SelectionGoal::None;
13280 }
13281 let (cursor, goal) = movement::down_by_rows(
13282 map,
13283 selection.end,
13284 row_count,
13285 selection.goal,
13286 false,
13287 text_layout_details,
13288 );
13289 selection.collapse_to(cursor, goal);
13290 });
13291 });
13292 }
13293
13294 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296 let text_layout_details = &self.text_layout_details(window);
13297 self.change_selections(Default::default(), window, cx, |s| {
13298 s.move_heads_with(|map, head, goal| {
13299 movement::down(map, head, goal, false, text_layout_details)
13300 })
13301 });
13302 }
13303
13304 pub fn context_menu_first(
13305 &mut self,
13306 _: &ContextMenuFirst,
13307 window: &mut Window,
13308 cx: &mut Context<Self>,
13309 ) {
13310 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13311 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13312 }
13313 }
13314
13315 pub fn context_menu_prev(
13316 &mut self,
13317 _: &ContextMenuPrevious,
13318 window: &mut Window,
13319 cx: &mut Context<Self>,
13320 ) {
13321 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13322 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13323 }
13324 }
13325
13326 pub fn context_menu_next(
13327 &mut self,
13328 _: &ContextMenuNext,
13329 window: &mut Window,
13330 cx: &mut Context<Self>,
13331 ) {
13332 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13333 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13334 }
13335 }
13336
13337 pub fn context_menu_last(
13338 &mut self,
13339 _: &ContextMenuLast,
13340 window: &mut Window,
13341 cx: &mut Context<Self>,
13342 ) {
13343 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13344 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13345 }
13346 }
13347
13348 pub fn signature_help_prev(
13349 &mut self,
13350 _: &SignatureHelpPrevious,
13351 _: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 if let Some(popover) = self.signature_help_state.popover_mut() {
13355 if popover.current_signature == 0 {
13356 popover.current_signature = popover.signatures.len() - 1;
13357 } else {
13358 popover.current_signature -= 1;
13359 }
13360 cx.notify();
13361 }
13362 }
13363
13364 pub fn signature_help_next(
13365 &mut self,
13366 _: &SignatureHelpNext,
13367 _: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 if let Some(popover) = self.signature_help_state.popover_mut() {
13371 if popover.current_signature + 1 == popover.signatures.len() {
13372 popover.current_signature = 0;
13373 } else {
13374 popover.current_signature += 1;
13375 }
13376 cx.notify();
13377 }
13378 }
13379
13380 pub fn move_to_previous_word_start(
13381 &mut self,
13382 _: &MoveToPreviousWordStart,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13387 self.change_selections(Default::default(), window, cx, |s| {
13388 s.move_cursors_with(|map, head, _| {
13389 (
13390 movement::previous_word_start(map, head),
13391 SelectionGoal::None,
13392 )
13393 });
13394 })
13395 }
13396
13397 pub fn move_to_previous_subword_start(
13398 &mut self,
13399 _: &MoveToPreviousSubwordStart,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13404 self.change_selections(Default::default(), window, cx, |s| {
13405 s.move_cursors_with(|map, head, _| {
13406 (
13407 movement::previous_subword_start(map, head),
13408 SelectionGoal::None,
13409 )
13410 });
13411 })
13412 }
13413
13414 pub fn select_to_previous_word_start(
13415 &mut self,
13416 _: &SelectToPreviousWordStart,
13417 window: &mut Window,
13418 cx: &mut Context<Self>,
13419 ) {
13420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13421 self.change_selections(Default::default(), window, cx, |s| {
13422 s.move_heads_with(|map, head, _| {
13423 (
13424 movement::previous_word_start(map, head),
13425 SelectionGoal::None,
13426 )
13427 });
13428 })
13429 }
13430
13431 pub fn select_to_previous_subword_start(
13432 &mut self,
13433 _: &SelectToPreviousSubwordStart,
13434 window: &mut Window,
13435 cx: &mut Context<Self>,
13436 ) {
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438 self.change_selections(Default::default(), window, cx, |s| {
13439 s.move_heads_with(|map, head, _| {
13440 (
13441 movement::previous_subword_start(map, head),
13442 SelectionGoal::None,
13443 )
13444 });
13445 })
13446 }
13447
13448 pub fn delete_to_previous_word_start(
13449 &mut self,
13450 action: &DeleteToPreviousWordStart,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13455 self.transact(window, cx, |this, window, cx| {
13456 this.select_autoclose_pair(window, cx);
13457 this.change_selections(Default::default(), window, cx, |s| {
13458 s.move_with(|map, selection| {
13459 if selection.is_empty() {
13460 let mut cursor = if action.ignore_newlines {
13461 movement::previous_word_start(map, selection.head())
13462 } else {
13463 movement::previous_word_start_or_newline(map, selection.head())
13464 };
13465 cursor = movement::adjust_greedy_deletion(
13466 map,
13467 selection.head(),
13468 cursor,
13469 action.ignore_brackets,
13470 );
13471 selection.set_head(cursor, SelectionGoal::None);
13472 }
13473 });
13474 });
13475 this.insert("", window, cx);
13476 });
13477 }
13478
13479 pub fn delete_to_previous_subword_start(
13480 &mut self,
13481 _: &DeleteToPreviousSubwordStart,
13482 window: &mut Window,
13483 cx: &mut Context<Self>,
13484 ) {
13485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13486 self.transact(window, cx, |this, window, cx| {
13487 this.select_autoclose_pair(window, cx);
13488 this.change_selections(Default::default(), window, cx, |s| {
13489 s.move_with(|map, selection| {
13490 if selection.is_empty() {
13491 let mut cursor = movement::previous_subword_start(map, selection.head());
13492 cursor =
13493 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13494 selection.set_head(cursor, SelectionGoal::None);
13495 }
13496 });
13497 });
13498 this.insert("", window, cx);
13499 });
13500 }
13501
13502 pub fn move_to_next_word_end(
13503 &mut self,
13504 _: &MoveToNextWordEnd,
13505 window: &mut Window,
13506 cx: &mut Context<Self>,
13507 ) {
13508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13509 self.change_selections(Default::default(), window, cx, |s| {
13510 s.move_cursors_with(|map, head, _| {
13511 (movement::next_word_end(map, head), SelectionGoal::None)
13512 });
13513 })
13514 }
13515
13516 pub fn move_to_next_subword_end(
13517 &mut self,
13518 _: &MoveToNextSubwordEnd,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13523 self.change_selections(Default::default(), window, cx, |s| {
13524 s.move_cursors_with(|map, head, _| {
13525 (movement::next_subword_end(map, head), SelectionGoal::None)
13526 });
13527 })
13528 }
13529
13530 pub fn select_to_next_word_end(
13531 &mut self,
13532 _: &SelectToNextWordEnd,
13533 window: &mut Window,
13534 cx: &mut Context<Self>,
13535 ) {
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13537 self.change_selections(Default::default(), window, cx, |s| {
13538 s.move_heads_with(|map, head, _| {
13539 (movement::next_word_end(map, head), SelectionGoal::None)
13540 });
13541 })
13542 }
13543
13544 pub fn select_to_next_subword_end(
13545 &mut self,
13546 _: &SelectToNextSubwordEnd,
13547 window: &mut Window,
13548 cx: &mut Context<Self>,
13549 ) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 self.change_selections(Default::default(), window, cx, |s| {
13552 s.move_heads_with(|map, head, _| {
13553 (movement::next_subword_end(map, head), SelectionGoal::None)
13554 });
13555 })
13556 }
13557
13558 pub fn delete_to_next_word_end(
13559 &mut self,
13560 action: &DeleteToNextWordEnd,
13561 window: &mut Window,
13562 cx: &mut Context<Self>,
13563 ) {
13564 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13565 self.transact(window, cx, |this, window, cx| {
13566 this.change_selections(Default::default(), window, cx, |s| {
13567 s.move_with(|map, selection| {
13568 if selection.is_empty() {
13569 let mut cursor = if action.ignore_newlines {
13570 movement::next_word_end(map, selection.head())
13571 } else {
13572 movement::next_word_end_or_newline(map, selection.head())
13573 };
13574 cursor = movement::adjust_greedy_deletion(
13575 map,
13576 selection.head(),
13577 cursor,
13578 action.ignore_brackets,
13579 );
13580 selection.set_head(cursor, SelectionGoal::None);
13581 }
13582 });
13583 });
13584 this.insert("", window, cx);
13585 });
13586 }
13587
13588 pub fn delete_to_next_subword_end(
13589 &mut self,
13590 _: &DeleteToNextSubwordEnd,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13595 self.transact(window, cx, |this, window, cx| {
13596 this.change_selections(Default::default(), window, cx, |s| {
13597 s.move_with(|map, selection| {
13598 if selection.is_empty() {
13599 let mut cursor = movement::next_subword_end(map, selection.head());
13600 cursor =
13601 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13602 selection.set_head(cursor, SelectionGoal::None);
13603 }
13604 });
13605 });
13606 this.insert("", window, cx);
13607 });
13608 }
13609
13610 pub fn move_to_beginning_of_line(
13611 &mut self,
13612 action: &MoveToBeginningOfLine,
13613 window: &mut Window,
13614 cx: &mut Context<Self>,
13615 ) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13617 self.change_selections(Default::default(), window, cx, |s| {
13618 s.move_cursors_with(|map, head, _| {
13619 (
13620 movement::indented_line_beginning(
13621 map,
13622 head,
13623 action.stop_at_soft_wraps,
13624 action.stop_at_indent,
13625 ),
13626 SelectionGoal::None,
13627 )
13628 });
13629 })
13630 }
13631
13632 pub fn select_to_beginning_of_line(
13633 &mut self,
13634 action: &SelectToBeginningOfLine,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13639 self.change_selections(Default::default(), window, cx, |s| {
13640 s.move_heads_with(|map, head, _| {
13641 (
13642 movement::indented_line_beginning(
13643 map,
13644 head,
13645 action.stop_at_soft_wraps,
13646 action.stop_at_indent,
13647 ),
13648 SelectionGoal::None,
13649 )
13650 });
13651 });
13652 }
13653
13654 pub fn delete_to_beginning_of_line(
13655 &mut self,
13656 action: &DeleteToBeginningOfLine,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13661 self.transact(window, cx, |this, window, cx| {
13662 this.change_selections(Default::default(), window, cx, |s| {
13663 s.move_with(|_, selection| {
13664 selection.reversed = true;
13665 });
13666 });
13667
13668 this.select_to_beginning_of_line(
13669 &SelectToBeginningOfLine {
13670 stop_at_soft_wraps: false,
13671 stop_at_indent: action.stop_at_indent,
13672 },
13673 window,
13674 cx,
13675 );
13676 this.backspace(&Backspace, window, cx);
13677 });
13678 }
13679
13680 pub fn move_to_end_of_line(
13681 &mut self,
13682 action: &MoveToEndOfLine,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_cursors_with(|map, head, _| {
13689 (
13690 movement::line_end(map, head, action.stop_at_soft_wraps),
13691 SelectionGoal::None,
13692 )
13693 });
13694 })
13695 }
13696
13697 pub fn select_to_end_of_line(
13698 &mut self,
13699 action: &SelectToEndOfLine,
13700 window: &mut Window,
13701 cx: &mut Context<Self>,
13702 ) {
13703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13704 self.change_selections(Default::default(), window, cx, |s| {
13705 s.move_heads_with(|map, head, _| {
13706 (
13707 movement::line_end(map, head, action.stop_at_soft_wraps),
13708 SelectionGoal::None,
13709 )
13710 });
13711 })
13712 }
13713
13714 pub fn delete_to_end_of_line(
13715 &mut self,
13716 _: &DeleteToEndOfLine,
13717 window: &mut Window,
13718 cx: &mut Context<Self>,
13719 ) {
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13721 self.transact(window, cx, |this, window, cx| {
13722 this.select_to_end_of_line(
13723 &SelectToEndOfLine {
13724 stop_at_soft_wraps: false,
13725 },
13726 window,
13727 cx,
13728 );
13729 this.delete(&Delete, window, cx);
13730 });
13731 }
13732
13733 pub fn cut_to_end_of_line(
13734 &mut self,
13735 action: &CutToEndOfLine,
13736 window: &mut Window,
13737 cx: &mut Context<Self>,
13738 ) {
13739 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13740 self.transact(window, cx, |this, window, cx| {
13741 this.select_to_end_of_line(
13742 &SelectToEndOfLine {
13743 stop_at_soft_wraps: false,
13744 },
13745 window,
13746 cx,
13747 );
13748 if !action.stop_at_newlines {
13749 this.change_selections(Default::default(), window, cx, |s| {
13750 s.move_with(|_, sel| {
13751 if sel.is_empty() {
13752 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13753 }
13754 });
13755 });
13756 }
13757 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13758 let item = this.cut_common(false, window, cx);
13759 cx.write_to_clipboard(item);
13760 });
13761 }
13762
13763 pub fn move_to_start_of_paragraph(
13764 &mut self,
13765 _: &MoveToStartOfParagraph,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if matches!(self.mode, EditorMode::SingleLine) {
13770 cx.propagate();
13771 return;
13772 }
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_with(|map, selection| {
13776 selection.collapse_to(
13777 movement::start_of_paragraph(map, selection.head(), 1),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn move_to_end_of_paragraph(
13785 &mut self,
13786 _: &MoveToEndOfParagraph,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 if matches!(self.mode, EditorMode::SingleLine) {
13791 cx.propagate();
13792 return;
13793 }
13794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.move_with(|map, selection| {
13797 selection.collapse_to(
13798 movement::end_of_paragraph(map, selection.head(), 1),
13799 SelectionGoal::None,
13800 )
13801 });
13802 })
13803 }
13804
13805 pub fn select_to_start_of_paragraph(
13806 &mut self,
13807 _: &SelectToStartOfParagraph,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 if matches!(self.mode, EditorMode::SingleLine) {
13812 cx.propagate();
13813 return;
13814 }
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 self.change_selections(Default::default(), window, cx, |s| {
13817 s.move_heads_with(|map, head, _| {
13818 (
13819 movement::start_of_paragraph(map, head, 1),
13820 SelectionGoal::None,
13821 )
13822 });
13823 })
13824 }
13825
13826 pub fn select_to_end_of_paragraph(
13827 &mut self,
13828 _: &SelectToEndOfParagraph,
13829 window: &mut Window,
13830 cx: &mut Context<Self>,
13831 ) {
13832 if matches!(self.mode, EditorMode::SingleLine) {
13833 cx.propagate();
13834 return;
13835 }
13836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13837 self.change_selections(Default::default(), window, cx, |s| {
13838 s.move_heads_with(|map, head, _| {
13839 (
13840 movement::end_of_paragraph(map, head, 1),
13841 SelectionGoal::None,
13842 )
13843 });
13844 })
13845 }
13846
13847 pub fn move_to_start_of_excerpt(
13848 &mut self,
13849 _: &MoveToStartOfExcerpt,
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::start_of_excerpt(
13862 map,
13863 selection.head(),
13864 workspace::searchable::Direction::Prev,
13865 ),
13866 SelectionGoal::None,
13867 )
13868 });
13869 })
13870 }
13871
13872 pub fn move_to_start_of_next_excerpt(
13873 &mut self,
13874 _: &MoveToStartOfNextExcerpt,
13875 window: &mut Window,
13876 cx: &mut Context<Self>,
13877 ) {
13878 if matches!(self.mode, EditorMode::SingleLine) {
13879 cx.propagate();
13880 return;
13881 }
13882
13883 self.change_selections(Default::default(), window, cx, |s| {
13884 s.move_with(|map, selection| {
13885 selection.collapse_to(
13886 movement::start_of_excerpt(
13887 map,
13888 selection.head(),
13889 workspace::searchable::Direction::Next,
13890 ),
13891 SelectionGoal::None,
13892 )
13893 });
13894 })
13895 }
13896
13897 pub fn move_to_end_of_excerpt(
13898 &mut self,
13899 _: &MoveToEndOfExcerpt,
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_with(|map, selection| {
13910 selection.collapse_to(
13911 movement::end_of_excerpt(
13912 map,
13913 selection.head(),
13914 workspace::searchable::Direction::Next,
13915 ),
13916 SelectionGoal::None,
13917 )
13918 });
13919 })
13920 }
13921
13922 pub fn move_to_end_of_previous_excerpt(
13923 &mut self,
13924 _: &MoveToEndOfPreviousExcerpt,
13925 window: &mut Window,
13926 cx: &mut Context<Self>,
13927 ) {
13928 if matches!(self.mode, EditorMode::SingleLine) {
13929 cx.propagate();
13930 return;
13931 }
13932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13933 self.change_selections(Default::default(), window, cx, |s| {
13934 s.move_with(|map, selection| {
13935 selection.collapse_to(
13936 movement::end_of_excerpt(
13937 map,
13938 selection.head(),
13939 workspace::searchable::Direction::Prev,
13940 ),
13941 SelectionGoal::None,
13942 )
13943 });
13944 })
13945 }
13946
13947 pub fn select_to_start_of_excerpt(
13948 &mut self,
13949 _: &SelectToStartOfExcerpt,
13950 window: &mut Window,
13951 cx: &mut Context<Self>,
13952 ) {
13953 if matches!(self.mode, EditorMode::SingleLine) {
13954 cx.propagate();
13955 return;
13956 }
13957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13958 self.change_selections(Default::default(), window, cx, |s| {
13959 s.move_heads_with(|map, head, _| {
13960 (
13961 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13962 SelectionGoal::None,
13963 )
13964 });
13965 })
13966 }
13967
13968 pub fn select_to_start_of_next_excerpt(
13969 &mut self,
13970 _: &SelectToStartOfNextExcerpt,
13971 window: &mut Window,
13972 cx: &mut Context<Self>,
13973 ) {
13974 if matches!(self.mode, EditorMode::SingleLine) {
13975 cx.propagate();
13976 return;
13977 }
13978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13979 self.change_selections(Default::default(), window, cx, |s| {
13980 s.move_heads_with(|map, head, _| {
13981 (
13982 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13983 SelectionGoal::None,
13984 )
13985 });
13986 })
13987 }
13988
13989 pub fn select_to_end_of_excerpt(
13990 &mut self,
13991 _: &SelectToEndOfExcerpt,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 if matches!(self.mode, EditorMode::SingleLine) {
13996 cx.propagate();
13997 return;
13998 }
13999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14000 self.change_selections(Default::default(), window, cx, |s| {
14001 s.move_heads_with(|map, head, _| {
14002 (
14003 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14004 SelectionGoal::None,
14005 )
14006 });
14007 })
14008 }
14009
14010 pub fn select_to_end_of_previous_excerpt(
14011 &mut self,
14012 _: &SelectToEndOfPreviousExcerpt,
14013 window: &mut Window,
14014 cx: &mut Context<Self>,
14015 ) {
14016 if matches!(self.mode, EditorMode::SingleLine) {
14017 cx.propagate();
14018 return;
14019 }
14020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14021 self.change_selections(Default::default(), window, cx, |s| {
14022 s.move_heads_with(|map, head, _| {
14023 (
14024 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14025 SelectionGoal::None,
14026 )
14027 });
14028 })
14029 }
14030
14031 pub fn move_to_beginning(
14032 &mut self,
14033 _: &MoveToBeginning,
14034 window: &mut Window,
14035 cx: &mut Context<Self>,
14036 ) {
14037 if matches!(self.mode, EditorMode::SingleLine) {
14038 cx.propagate();
14039 return;
14040 }
14041 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14042 self.change_selections(Default::default(), window, cx, |s| {
14043 s.select_ranges(vec![0..0]);
14044 });
14045 }
14046
14047 pub fn select_to_beginning(
14048 &mut self,
14049 _: &SelectToBeginning,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14054 selection.set_head(Point::zero(), SelectionGoal::None);
14055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14056 self.change_selections(Default::default(), window, cx, |s| {
14057 s.select(vec![selection]);
14058 });
14059 }
14060
14061 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14062 if matches!(self.mode, EditorMode::SingleLine) {
14063 cx.propagate();
14064 return;
14065 }
14066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14067 let cursor = self.buffer.read(cx).read(cx).len();
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.select_ranges(vec![cursor..cursor])
14070 });
14071 }
14072
14073 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14074 self.nav_history = nav_history;
14075 }
14076
14077 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14078 self.nav_history.as_ref()
14079 }
14080
14081 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14082 self.push_to_nav_history(
14083 self.selections.newest_anchor().head(),
14084 None,
14085 false,
14086 true,
14087 cx,
14088 );
14089 }
14090
14091 fn push_to_nav_history(
14092 &mut self,
14093 cursor_anchor: Anchor,
14094 new_position: Option<Point>,
14095 is_deactivate: bool,
14096 always: bool,
14097 cx: &mut Context<Self>,
14098 ) {
14099 if let Some(nav_history) = self.nav_history.as_mut() {
14100 let buffer = self.buffer.read(cx).read(cx);
14101 let cursor_position = cursor_anchor.to_point(&buffer);
14102 let scroll_state = self.scroll_manager.anchor();
14103 let scroll_top_row = scroll_state.top_row(&buffer);
14104 drop(buffer);
14105
14106 if let Some(new_position) = new_position {
14107 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14108 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14109 return;
14110 }
14111 }
14112
14113 nav_history.push(
14114 Some(NavigationData {
14115 cursor_anchor,
14116 cursor_position,
14117 scroll_anchor: scroll_state,
14118 scroll_top_row,
14119 }),
14120 cx,
14121 );
14122 cx.emit(EditorEvent::PushedToNavHistory {
14123 anchor: cursor_anchor,
14124 is_deactivate,
14125 })
14126 }
14127 }
14128
14129 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14131 let buffer = self.buffer.read(cx).snapshot(cx);
14132 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14133 selection.set_head(buffer.len(), SelectionGoal::None);
14134 self.change_selections(Default::default(), window, cx, |s| {
14135 s.select(vec![selection]);
14136 });
14137 }
14138
14139 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14140 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14141 let end = self.buffer.read(cx).read(cx).len();
14142 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14143 s.select_ranges(vec![0..end]);
14144 });
14145 }
14146
14147 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14148 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14149 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14150 let mut selections = self.selections.all::<Point>(&display_map);
14151 let max_point = display_map.buffer_snapshot().max_point();
14152 for selection in &mut selections {
14153 let rows = selection.spanned_rows(true, &display_map);
14154 selection.start = Point::new(rows.start.0, 0);
14155 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14156 selection.reversed = false;
14157 }
14158 self.change_selections(Default::default(), window, cx, |s| {
14159 s.select(selections);
14160 });
14161 }
14162
14163 pub fn split_selection_into_lines(
14164 &mut self,
14165 action: &SplitSelectionIntoLines,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 let selections = self
14170 .selections
14171 .all::<Point>(&self.display_snapshot(cx))
14172 .into_iter()
14173 .map(|selection| selection.start..selection.end)
14174 .collect::<Vec<_>>();
14175 self.unfold_ranges(&selections, true, true, cx);
14176
14177 let mut new_selection_ranges = Vec::new();
14178 {
14179 let buffer = self.buffer.read(cx).read(cx);
14180 for selection in selections {
14181 for row in selection.start.row..selection.end.row {
14182 let line_start = Point::new(row, 0);
14183 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14184
14185 if action.keep_selections {
14186 // Keep the selection range for each line
14187 let selection_start = if row == selection.start.row {
14188 selection.start
14189 } else {
14190 line_start
14191 };
14192 new_selection_ranges.push(selection_start..line_end);
14193 } else {
14194 // Collapse to cursor at end of line
14195 new_selection_ranges.push(line_end..line_end);
14196 }
14197 }
14198
14199 let is_multiline_selection = selection.start.row != selection.end.row;
14200 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14201 // so this action feels more ergonomic when paired with other selection operations
14202 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14203 if !should_skip_last {
14204 if action.keep_selections {
14205 if is_multiline_selection {
14206 let line_start = Point::new(selection.end.row, 0);
14207 new_selection_ranges.push(line_start..selection.end);
14208 } else {
14209 new_selection_ranges.push(selection.start..selection.end);
14210 }
14211 } else {
14212 new_selection_ranges.push(selection.end..selection.end);
14213 }
14214 }
14215 }
14216 }
14217 self.change_selections(Default::default(), window, cx, |s| {
14218 s.select_ranges(new_selection_ranges);
14219 });
14220 }
14221
14222 pub fn add_selection_above(
14223 &mut self,
14224 action: &AddSelectionAbove,
14225 window: &mut Window,
14226 cx: &mut Context<Self>,
14227 ) {
14228 self.add_selection(true, action.skip_soft_wrap, window, cx);
14229 }
14230
14231 pub fn add_selection_below(
14232 &mut self,
14233 action: &AddSelectionBelow,
14234 window: &mut Window,
14235 cx: &mut Context<Self>,
14236 ) {
14237 self.add_selection(false, action.skip_soft_wrap, window, cx);
14238 }
14239
14240 fn add_selection(
14241 &mut self,
14242 above: bool,
14243 skip_soft_wrap: bool,
14244 window: &mut Window,
14245 cx: &mut Context<Self>,
14246 ) {
14247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14248
14249 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14250 let all_selections = self.selections.all::<Point>(&display_map);
14251 let text_layout_details = self.text_layout_details(window);
14252
14253 let (mut columnar_selections, new_selections_to_columnarize) = {
14254 if let Some(state) = self.add_selections_state.as_ref() {
14255 let columnar_selection_ids: HashSet<_> = state
14256 .groups
14257 .iter()
14258 .flat_map(|group| group.stack.iter())
14259 .copied()
14260 .collect();
14261
14262 all_selections
14263 .into_iter()
14264 .partition(|s| columnar_selection_ids.contains(&s.id))
14265 } else {
14266 (Vec::new(), all_selections)
14267 }
14268 };
14269
14270 let mut state = self
14271 .add_selections_state
14272 .take()
14273 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14274
14275 for selection in new_selections_to_columnarize {
14276 let range = selection.display_range(&display_map).sorted();
14277 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14278 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14279 let positions = start_x.min(end_x)..start_x.max(end_x);
14280 let mut stack = Vec::new();
14281 for row in range.start.row().0..=range.end.row().0 {
14282 if let Some(selection) = self.selections.build_columnar_selection(
14283 &display_map,
14284 DisplayRow(row),
14285 &positions,
14286 selection.reversed,
14287 &text_layout_details,
14288 ) {
14289 stack.push(selection.id);
14290 columnar_selections.push(selection);
14291 }
14292 }
14293 if !stack.is_empty() {
14294 if above {
14295 stack.reverse();
14296 }
14297 state.groups.push(AddSelectionsGroup { above, stack });
14298 }
14299 }
14300
14301 let mut final_selections = Vec::new();
14302 let end_row = if above {
14303 DisplayRow(0)
14304 } else {
14305 display_map.max_point().row()
14306 };
14307
14308 let mut last_added_item_per_group = HashMap::default();
14309 for group in state.groups.iter_mut() {
14310 if let Some(last_id) = group.stack.last() {
14311 last_added_item_per_group.insert(*last_id, group);
14312 }
14313 }
14314
14315 for selection in columnar_selections {
14316 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14317 if above == group.above {
14318 let range = selection.display_range(&display_map).sorted();
14319 debug_assert_eq!(range.start.row(), range.end.row());
14320 let mut row = range.start.row();
14321 let positions =
14322 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14323 Pixels::from(start)..Pixels::from(end)
14324 } else {
14325 let start_x =
14326 display_map.x_for_display_point(range.start, &text_layout_details);
14327 let end_x =
14328 display_map.x_for_display_point(range.end, &text_layout_details);
14329 start_x.min(end_x)..start_x.max(end_x)
14330 };
14331
14332 let mut maybe_new_selection = None;
14333 let direction = if above { -1 } else { 1 };
14334
14335 while row != end_row {
14336 if skip_soft_wrap {
14337 row = display_map
14338 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14339 .row();
14340 } else if above {
14341 row.0 -= 1;
14342 } else {
14343 row.0 += 1;
14344 }
14345
14346 if let Some(new_selection) = self.selections.build_columnar_selection(
14347 &display_map,
14348 row,
14349 &positions,
14350 selection.reversed,
14351 &text_layout_details,
14352 ) {
14353 maybe_new_selection = Some(new_selection);
14354 break;
14355 }
14356 }
14357
14358 if let Some(new_selection) = maybe_new_selection {
14359 group.stack.push(new_selection.id);
14360 if above {
14361 final_selections.push(new_selection);
14362 final_selections.push(selection);
14363 } else {
14364 final_selections.push(selection);
14365 final_selections.push(new_selection);
14366 }
14367 } else {
14368 final_selections.push(selection);
14369 }
14370 } else {
14371 group.stack.pop();
14372 }
14373 } else {
14374 final_selections.push(selection);
14375 }
14376 }
14377
14378 self.change_selections(Default::default(), window, cx, |s| {
14379 s.select(final_selections);
14380 });
14381
14382 let final_selection_ids: HashSet<_> = self
14383 .selections
14384 .all::<Point>(&display_map)
14385 .iter()
14386 .map(|s| s.id)
14387 .collect();
14388 state.groups.retain_mut(|group| {
14389 // selections might get merged above so we remove invalid items from stacks
14390 group.stack.retain(|id| final_selection_ids.contains(id));
14391
14392 // single selection in stack can be treated as initial state
14393 group.stack.len() > 1
14394 });
14395
14396 if !state.groups.is_empty() {
14397 self.add_selections_state = Some(state);
14398 }
14399 }
14400
14401 fn select_match_ranges(
14402 &mut self,
14403 range: Range<usize>,
14404 reversed: bool,
14405 replace_newest: bool,
14406 auto_scroll: Option<Autoscroll>,
14407 window: &mut Window,
14408 cx: &mut Context<Editor>,
14409 ) {
14410 self.unfold_ranges(
14411 std::slice::from_ref(&range),
14412 false,
14413 auto_scroll.is_some(),
14414 cx,
14415 );
14416 let effects = if let Some(scroll) = auto_scroll {
14417 SelectionEffects::scroll(scroll)
14418 } else {
14419 SelectionEffects::no_scroll()
14420 };
14421 self.change_selections(effects, window, cx, |s| {
14422 if replace_newest {
14423 s.delete(s.newest_anchor().id);
14424 }
14425 if reversed {
14426 s.insert_range(range.end..range.start);
14427 } else {
14428 s.insert_range(range);
14429 }
14430 });
14431 }
14432
14433 pub fn select_next_match_internal(
14434 &mut self,
14435 display_map: &DisplaySnapshot,
14436 replace_newest: bool,
14437 autoscroll: Option<Autoscroll>,
14438 window: &mut Window,
14439 cx: &mut Context<Self>,
14440 ) -> Result<()> {
14441 let buffer = display_map.buffer_snapshot();
14442 let mut selections = self.selections.all::<usize>(&display_map);
14443 if let Some(mut select_next_state) = self.select_next_state.take() {
14444 let query = &select_next_state.query;
14445 if !select_next_state.done {
14446 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14447 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14448 let mut next_selected_range = None;
14449
14450 let bytes_after_last_selection =
14451 buffer.bytes_in_range(last_selection.end..buffer.len());
14452 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14453 let query_matches = query
14454 .stream_find_iter(bytes_after_last_selection)
14455 .map(|result| (last_selection.end, result))
14456 .chain(
14457 query
14458 .stream_find_iter(bytes_before_first_selection)
14459 .map(|result| (0, result)),
14460 );
14461
14462 for (start_offset, query_match) in query_matches {
14463 let query_match = query_match.unwrap(); // can only fail due to I/O
14464 let offset_range =
14465 start_offset + query_match.start()..start_offset + query_match.end();
14466
14467 if !select_next_state.wordwise
14468 || (!buffer.is_inside_word(offset_range.start, None)
14469 && !buffer.is_inside_word(offset_range.end, None))
14470 {
14471 let idx = selections
14472 .partition_point(|selection| selection.end <= offset_range.start);
14473 let overlaps = selections
14474 .get(idx)
14475 .map_or(false, |selection| selection.start < offset_range.end);
14476
14477 if !overlaps {
14478 next_selected_range = Some(offset_range);
14479 break;
14480 }
14481 }
14482 }
14483
14484 if let Some(next_selected_range) = next_selected_range {
14485 self.select_match_ranges(
14486 next_selected_range,
14487 last_selection.reversed,
14488 replace_newest,
14489 autoscroll,
14490 window,
14491 cx,
14492 );
14493 } else {
14494 select_next_state.done = true;
14495 }
14496 }
14497
14498 self.select_next_state = Some(select_next_state);
14499 } else {
14500 let mut only_carets = true;
14501 let mut same_text_selected = true;
14502 let mut selected_text = None;
14503
14504 let mut selections_iter = selections.iter().peekable();
14505 while let Some(selection) = selections_iter.next() {
14506 if selection.start != selection.end {
14507 only_carets = false;
14508 }
14509
14510 if same_text_selected {
14511 if selected_text.is_none() {
14512 selected_text =
14513 Some(buffer.text_for_range(selection.range()).collect::<String>());
14514 }
14515
14516 if let Some(next_selection) = selections_iter.peek() {
14517 if next_selection.range().len() == selection.range().len() {
14518 let next_selected_text = buffer
14519 .text_for_range(next_selection.range())
14520 .collect::<String>();
14521 if Some(next_selected_text) != selected_text {
14522 same_text_selected = false;
14523 selected_text = None;
14524 }
14525 } else {
14526 same_text_selected = false;
14527 selected_text = None;
14528 }
14529 }
14530 }
14531 }
14532
14533 if only_carets {
14534 for selection in &mut selections {
14535 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14536 selection.start = word_range.start;
14537 selection.end = word_range.end;
14538 selection.goal = SelectionGoal::None;
14539 selection.reversed = false;
14540 self.select_match_ranges(
14541 selection.start..selection.end,
14542 selection.reversed,
14543 replace_newest,
14544 autoscroll,
14545 window,
14546 cx,
14547 );
14548 }
14549
14550 if selections.len() == 1 {
14551 let selection = selections
14552 .last()
14553 .expect("ensured that there's only one selection");
14554 let query = buffer
14555 .text_for_range(selection.start..selection.end)
14556 .collect::<String>();
14557 let is_empty = query.is_empty();
14558 let select_state = SelectNextState {
14559 query: AhoCorasick::new(&[query])?,
14560 wordwise: true,
14561 done: is_empty,
14562 };
14563 self.select_next_state = Some(select_state);
14564 } else {
14565 self.select_next_state = None;
14566 }
14567 } else if let Some(selected_text) = selected_text {
14568 self.select_next_state = Some(SelectNextState {
14569 query: AhoCorasick::new(&[selected_text])?,
14570 wordwise: false,
14571 done: false,
14572 });
14573 self.select_next_match_internal(
14574 display_map,
14575 replace_newest,
14576 autoscroll,
14577 window,
14578 cx,
14579 )?;
14580 }
14581 }
14582 Ok(())
14583 }
14584
14585 pub fn select_all_matches(
14586 &mut self,
14587 _action: &SelectAllMatches,
14588 window: &mut Window,
14589 cx: &mut Context<Self>,
14590 ) -> Result<()> {
14591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14592
14593 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14594
14595 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14596 let Some(select_next_state) = self.select_next_state.as_mut() else {
14597 return Ok(());
14598 };
14599 if select_next_state.done {
14600 return Ok(());
14601 }
14602
14603 let mut new_selections = Vec::new();
14604
14605 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14606 let buffer = display_map.buffer_snapshot();
14607 let query_matches = select_next_state
14608 .query
14609 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14610
14611 for query_match in query_matches.into_iter() {
14612 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14613 let offset_range = if reversed {
14614 query_match.end()..query_match.start()
14615 } else {
14616 query_match.start()..query_match.end()
14617 };
14618
14619 if !select_next_state.wordwise
14620 || (!buffer.is_inside_word(offset_range.start, None)
14621 && !buffer.is_inside_word(offset_range.end, None))
14622 {
14623 new_selections.push(offset_range.start..offset_range.end);
14624 }
14625 }
14626
14627 select_next_state.done = true;
14628
14629 if new_selections.is_empty() {
14630 log::error!("bug: new_selections is empty in select_all_matches");
14631 return Ok(());
14632 }
14633
14634 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14635 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14636 selections.select_ranges(new_selections)
14637 });
14638
14639 Ok(())
14640 }
14641
14642 pub fn select_next(
14643 &mut self,
14644 action: &SelectNext,
14645 window: &mut Window,
14646 cx: &mut Context<Self>,
14647 ) -> Result<()> {
14648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14649 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14650 self.select_next_match_internal(
14651 &display_map,
14652 action.replace_newest,
14653 Some(Autoscroll::newest()),
14654 window,
14655 cx,
14656 )?;
14657 Ok(())
14658 }
14659
14660 pub fn select_previous(
14661 &mut self,
14662 action: &SelectPrevious,
14663 window: &mut Window,
14664 cx: &mut Context<Self>,
14665 ) -> Result<()> {
14666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14668 let buffer = display_map.buffer_snapshot();
14669 let mut selections = self.selections.all::<usize>(&display_map);
14670 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14671 let query = &select_prev_state.query;
14672 if !select_prev_state.done {
14673 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14674 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14675 let mut next_selected_range = None;
14676 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14677 let bytes_before_last_selection =
14678 buffer.reversed_bytes_in_range(0..last_selection.start);
14679 let bytes_after_first_selection =
14680 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14681 let query_matches = query
14682 .stream_find_iter(bytes_before_last_selection)
14683 .map(|result| (last_selection.start, result))
14684 .chain(
14685 query
14686 .stream_find_iter(bytes_after_first_selection)
14687 .map(|result| (buffer.len(), result)),
14688 );
14689 for (end_offset, query_match) in query_matches {
14690 let query_match = query_match.unwrap(); // can only fail due to I/O
14691 let offset_range =
14692 end_offset - query_match.end()..end_offset - query_match.start();
14693
14694 if !select_prev_state.wordwise
14695 || (!buffer.is_inside_word(offset_range.start, None)
14696 && !buffer.is_inside_word(offset_range.end, None))
14697 {
14698 next_selected_range = Some(offset_range);
14699 break;
14700 }
14701 }
14702
14703 if let Some(next_selected_range) = next_selected_range {
14704 self.select_match_ranges(
14705 next_selected_range,
14706 last_selection.reversed,
14707 action.replace_newest,
14708 Some(Autoscroll::newest()),
14709 window,
14710 cx,
14711 );
14712 } else {
14713 select_prev_state.done = true;
14714 }
14715 }
14716
14717 self.select_prev_state = Some(select_prev_state);
14718 } else {
14719 let mut only_carets = true;
14720 let mut same_text_selected = true;
14721 let mut selected_text = None;
14722
14723 let mut selections_iter = selections.iter().peekable();
14724 while let Some(selection) = selections_iter.next() {
14725 if selection.start != selection.end {
14726 only_carets = false;
14727 }
14728
14729 if same_text_selected {
14730 if selected_text.is_none() {
14731 selected_text =
14732 Some(buffer.text_for_range(selection.range()).collect::<String>());
14733 }
14734
14735 if let Some(next_selection) = selections_iter.peek() {
14736 if next_selection.range().len() == selection.range().len() {
14737 let next_selected_text = buffer
14738 .text_for_range(next_selection.range())
14739 .collect::<String>();
14740 if Some(next_selected_text) != selected_text {
14741 same_text_selected = false;
14742 selected_text = None;
14743 }
14744 } else {
14745 same_text_selected = false;
14746 selected_text = None;
14747 }
14748 }
14749 }
14750 }
14751
14752 if only_carets {
14753 for selection in &mut selections {
14754 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14755 selection.start = word_range.start;
14756 selection.end = word_range.end;
14757 selection.goal = SelectionGoal::None;
14758 selection.reversed = false;
14759 self.select_match_ranges(
14760 selection.start..selection.end,
14761 selection.reversed,
14762 action.replace_newest,
14763 Some(Autoscroll::newest()),
14764 window,
14765 cx,
14766 );
14767 }
14768 if selections.len() == 1 {
14769 let selection = selections
14770 .last()
14771 .expect("ensured that there's only one selection");
14772 let query = buffer
14773 .text_for_range(selection.start..selection.end)
14774 .collect::<String>();
14775 let is_empty = query.is_empty();
14776 let select_state = SelectNextState {
14777 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14778 wordwise: true,
14779 done: is_empty,
14780 };
14781 self.select_prev_state = Some(select_state);
14782 } else {
14783 self.select_prev_state = None;
14784 }
14785 } else if let Some(selected_text) = selected_text {
14786 self.select_prev_state = Some(SelectNextState {
14787 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14788 wordwise: false,
14789 done: false,
14790 });
14791 self.select_previous(action, window, cx)?;
14792 }
14793 }
14794 Ok(())
14795 }
14796
14797 pub fn find_next_match(
14798 &mut self,
14799 _: &FindNextMatch,
14800 window: &mut Window,
14801 cx: &mut Context<Self>,
14802 ) -> Result<()> {
14803 let selections = self.selections.disjoint_anchors_arc();
14804 match selections.first() {
14805 Some(first) if selections.len() >= 2 => {
14806 self.change_selections(Default::default(), window, cx, |s| {
14807 s.select_ranges([first.range()]);
14808 });
14809 }
14810 _ => self.select_next(
14811 &SelectNext {
14812 replace_newest: true,
14813 },
14814 window,
14815 cx,
14816 )?,
14817 }
14818 Ok(())
14819 }
14820
14821 pub fn find_previous_match(
14822 &mut self,
14823 _: &FindPreviousMatch,
14824 window: &mut Window,
14825 cx: &mut Context<Self>,
14826 ) -> Result<()> {
14827 let selections = self.selections.disjoint_anchors_arc();
14828 match selections.last() {
14829 Some(last) if selections.len() >= 2 => {
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.select_ranges([last.range()]);
14832 });
14833 }
14834 _ => self.select_previous(
14835 &SelectPrevious {
14836 replace_newest: true,
14837 },
14838 window,
14839 cx,
14840 )?,
14841 }
14842 Ok(())
14843 }
14844
14845 pub fn toggle_comments(
14846 &mut self,
14847 action: &ToggleComments,
14848 window: &mut Window,
14849 cx: &mut Context<Self>,
14850 ) {
14851 if self.read_only(cx) {
14852 return;
14853 }
14854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14855 let text_layout_details = &self.text_layout_details(window);
14856 self.transact(window, cx, |this, window, cx| {
14857 let mut selections = this
14858 .selections
14859 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14860 let mut edits = Vec::new();
14861 let mut selection_edit_ranges = Vec::new();
14862 let mut last_toggled_row = None;
14863 let snapshot = this.buffer.read(cx).read(cx);
14864 let empty_str: Arc<str> = Arc::default();
14865 let mut suffixes_inserted = Vec::new();
14866 let ignore_indent = action.ignore_indent;
14867
14868 fn comment_prefix_range(
14869 snapshot: &MultiBufferSnapshot,
14870 row: MultiBufferRow,
14871 comment_prefix: &str,
14872 comment_prefix_whitespace: &str,
14873 ignore_indent: bool,
14874 ) -> Range<Point> {
14875 let indent_size = if ignore_indent {
14876 0
14877 } else {
14878 snapshot.indent_size_for_line(row).len
14879 };
14880
14881 let start = Point::new(row.0, indent_size);
14882
14883 let mut line_bytes = snapshot
14884 .bytes_in_range(start..snapshot.max_point())
14885 .flatten()
14886 .copied();
14887
14888 // If this line currently begins with the line comment prefix, then record
14889 // the range containing the prefix.
14890 if line_bytes
14891 .by_ref()
14892 .take(comment_prefix.len())
14893 .eq(comment_prefix.bytes())
14894 {
14895 // Include any whitespace that matches the comment prefix.
14896 let matching_whitespace_len = line_bytes
14897 .zip(comment_prefix_whitespace.bytes())
14898 .take_while(|(a, b)| a == b)
14899 .count() as u32;
14900 let end = Point::new(
14901 start.row,
14902 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14903 );
14904 start..end
14905 } else {
14906 start..start
14907 }
14908 }
14909
14910 fn comment_suffix_range(
14911 snapshot: &MultiBufferSnapshot,
14912 row: MultiBufferRow,
14913 comment_suffix: &str,
14914 comment_suffix_has_leading_space: bool,
14915 ) -> Range<Point> {
14916 let end = Point::new(row.0, snapshot.line_len(row));
14917 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14918
14919 let mut line_end_bytes = snapshot
14920 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14921 .flatten()
14922 .copied();
14923
14924 let leading_space_len = if suffix_start_column > 0
14925 && line_end_bytes.next() == Some(b' ')
14926 && comment_suffix_has_leading_space
14927 {
14928 1
14929 } else {
14930 0
14931 };
14932
14933 // If this line currently begins with the line comment prefix, then record
14934 // the range containing the prefix.
14935 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14936 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14937 start..end
14938 } else {
14939 end..end
14940 }
14941 }
14942
14943 // TODO: Handle selections that cross excerpts
14944 for selection in &mut selections {
14945 let start_column = snapshot
14946 .indent_size_for_line(MultiBufferRow(selection.start.row))
14947 .len;
14948 let language = if let Some(language) =
14949 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14950 {
14951 language
14952 } else {
14953 continue;
14954 };
14955
14956 selection_edit_ranges.clear();
14957
14958 // If multiple selections contain a given row, avoid processing that
14959 // row more than once.
14960 let mut start_row = MultiBufferRow(selection.start.row);
14961 if last_toggled_row == Some(start_row) {
14962 start_row = start_row.next_row();
14963 }
14964 let end_row =
14965 if selection.end.row > selection.start.row && selection.end.column == 0 {
14966 MultiBufferRow(selection.end.row - 1)
14967 } else {
14968 MultiBufferRow(selection.end.row)
14969 };
14970 last_toggled_row = Some(end_row);
14971
14972 if start_row > end_row {
14973 continue;
14974 }
14975
14976 // If the language has line comments, toggle those.
14977 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14978
14979 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14980 if ignore_indent {
14981 full_comment_prefixes = full_comment_prefixes
14982 .into_iter()
14983 .map(|s| Arc::from(s.trim_end()))
14984 .collect();
14985 }
14986
14987 if !full_comment_prefixes.is_empty() {
14988 let first_prefix = full_comment_prefixes
14989 .first()
14990 .expect("prefixes is non-empty");
14991 let prefix_trimmed_lengths = full_comment_prefixes
14992 .iter()
14993 .map(|p| p.trim_end_matches(' ').len())
14994 .collect::<SmallVec<[usize; 4]>>();
14995
14996 let mut all_selection_lines_are_comments = true;
14997
14998 for row in start_row.0..=end_row.0 {
14999 let row = MultiBufferRow(row);
15000 if start_row < end_row && snapshot.is_line_blank(row) {
15001 continue;
15002 }
15003
15004 let prefix_range = full_comment_prefixes
15005 .iter()
15006 .zip(prefix_trimmed_lengths.iter().copied())
15007 .map(|(prefix, trimmed_prefix_len)| {
15008 comment_prefix_range(
15009 snapshot.deref(),
15010 row,
15011 &prefix[..trimmed_prefix_len],
15012 &prefix[trimmed_prefix_len..],
15013 ignore_indent,
15014 )
15015 })
15016 .max_by_key(|range| range.end.column - range.start.column)
15017 .expect("prefixes is non-empty");
15018
15019 if prefix_range.is_empty() {
15020 all_selection_lines_are_comments = false;
15021 }
15022
15023 selection_edit_ranges.push(prefix_range);
15024 }
15025
15026 if all_selection_lines_are_comments {
15027 edits.extend(
15028 selection_edit_ranges
15029 .iter()
15030 .cloned()
15031 .map(|range| (range, empty_str.clone())),
15032 );
15033 } else {
15034 let min_column = selection_edit_ranges
15035 .iter()
15036 .map(|range| range.start.column)
15037 .min()
15038 .unwrap_or(0);
15039 edits.extend(selection_edit_ranges.iter().map(|range| {
15040 let position = Point::new(range.start.row, min_column);
15041 (position..position, first_prefix.clone())
15042 }));
15043 }
15044 } else if let Some(BlockCommentConfig {
15045 start: full_comment_prefix,
15046 end: comment_suffix,
15047 ..
15048 }) = language.block_comment()
15049 {
15050 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15051 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15052 let prefix_range = comment_prefix_range(
15053 snapshot.deref(),
15054 start_row,
15055 comment_prefix,
15056 comment_prefix_whitespace,
15057 ignore_indent,
15058 );
15059 let suffix_range = comment_suffix_range(
15060 snapshot.deref(),
15061 end_row,
15062 comment_suffix.trim_start_matches(' '),
15063 comment_suffix.starts_with(' '),
15064 );
15065
15066 if prefix_range.is_empty() || suffix_range.is_empty() {
15067 edits.push((
15068 prefix_range.start..prefix_range.start,
15069 full_comment_prefix.clone(),
15070 ));
15071 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15072 suffixes_inserted.push((end_row, comment_suffix.len()));
15073 } else {
15074 edits.push((prefix_range, empty_str.clone()));
15075 edits.push((suffix_range, empty_str.clone()));
15076 }
15077 } else {
15078 continue;
15079 }
15080 }
15081
15082 drop(snapshot);
15083 this.buffer.update(cx, |buffer, cx| {
15084 buffer.edit(edits, None, cx);
15085 });
15086
15087 // Adjust selections so that they end before any comment suffixes that
15088 // were inserted.
15089 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15090 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15091 let snapshot = this.buffer.read(cx).read(cx);
15092 for selection in &mut selections {
15093 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15094 match row.cmp(&MultiBufferRow(selection.end.row)) {
15095 Ordering::Less => {
15096 suffixes_inserted.next();
15097 continue;
15098 }
15099 Ordering::Greater => break,
15100 Ordering::Equal => {
15101 if selection.end.column == snapshot.line_len(row) {
15102 if selection.is_empty() {
15103 selection.start.column -= suffix_len as u32;
15104 }
15105 selection.end.column -= suffix_len as u32;
15106 }
15107 break;
15108 }
15109 }
15110 }
15111 }
15112
15113 drop(snapshot);
15114 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15115
15116 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15117 let selections_on_single_row = selections.windows(2).all(|selections| {
15118 selections[0].start.row == selections[1].start.row
15119 && selections[0].end.row == selections[1].end.row
15120 && selections[0].start.row == selections[0].end.row
15121 });
15122 let selections_selecting = selections
15123 .iter()
15124 .any(|selection| selection.start != selection.end);
15125 let advance_downwards = action.advance_downwards
15126 && selections_on_single_row
15127 && !selections_selecting
15128 && !matches!(this.mode, EditorMode::SingleLine);
15129
15130 if advance_downwards {
15131 let snapshot = this.buffer.read(cx).snapshot(cx);
15132
15133 this.change_selections(Default::default(), window, cx, |s| {
15134 s.move_cursors_with(|display_snapshot, display_point, _| {
15135 let mut point = display_point.to_point(display_snapshot);
15136 point.row += 1;
15137 point = snapshot.clip_point(point, Bias::Left);
15138 let display_point = point.to_display_point(display_snapshot);
15139 let goal = SelectionGoal::HorizontalPosition(
15140 display_snapshot
15141 .x_for_display_point(display_point, text_layout_details)
15142 .into(),
15143 );
15144 (display_point, goal)
15145 })
15146 });
15147 }
15148 });
15149 }
15150
15151 pub fn select_enclosing_symbol(
15152 &mut self,
15153 _: &SelectEnclosingSymbol,
15154 window: &mut Window,
15155 cx: &mut Context<Self>,
15156 ) {
15157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15158
15159 let buffer = self.buffer.read(cx).snapshot(cx);
15160 let old_selections = self
15161 .selections
15162 .all::<usize>(&self.display_snapshot(cx))
15163 .into_boxed_slice();
15164
15165 fn update_selection(
15166 selection: &Selection<usize>,
15167 buffer_snap: &MultiBufferSnapshot,
15168 ) -> Option<Selection<usize>> {
15169 let cursor = selection.head();
15170 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15171 for symbol in symbols.iter().rev() {
15172 let start = symbol.range.start.to_offset(buffer_snap);
15173 let end = symbol.range.end.to_offset(buffer_snap);
15174 let new_range = start..end;
15175 if start < selection.start || end > selection.end {
15176 return Some(Selection {
15177 id: selection.id,
15178 start: new_range.start,
15179 end: new_range.end,
15180 goal: SelectionGoal::None,
15181 reversed: selection.reversed,
15182 });
15183 }
15184 }
15185 None
15186 }
15187
15188 let mut selected_larger_symbol = false;
15189 let new_selections = old_selections
15190 .iter()
15191 .map(|selection| match update_selection(selection, &buffer) {
15192 Some(new_selection) => {
15193 if new_selection.range() != selection.range() {
15194 selected_larger_symbol = true;
15195 }
15196 new_selection
15197 }
15198 None => selection.clone(),
15199 })
15200 .collect::<Vec<_>>();
15201
15202 if selected_larger_symbol {
15203 self.change_selections(Default::default(), window, cx, |s| {
15204 s.select(new_selections);
15205 });
15206 }
15207 }
15208
15209 pub fn select_larger_syntax_node(
15210 &mut self,
15211 _: &SelectLargerSyntaxNode,
15212 window: &mut Window,
15213 cx: &mut Context<Self>,
15214 ) {
15215 let Some(visible_row_count) = self.visible_row_count() else {
15216 return;
15217 };
15218 let old_selections: Box<[_]> = self
15219 .selections
15220 .all::<usize>(&self.display_snapshot(cx))
15221 .into();
15222 if old_selections.is_empty() {
15223 return;
15224 }
15225
15226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15227
15228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15229 let buffer = self.buffer.read(cx).snapshot(cx);
15230
15231 let mut selected_larger_node = false;
15232 let mut new_selections = old_selections
15233 .iter()
15234 .map(|selection| {
15235 let old_range = selection.start..selection.end;
15236
15237 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15238 // manually select word at selection
15239 if ["string_content", "inline"].contains(&node.kind()) {
15240 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15241 // ignore if word is already selected
15242 if !word_range.is_empty() && old_range != word_range {
15243 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15244 // only select word if start and end point belongs to same word
15245 if word_range == last_word_range {
15246 selected_larger_node = true;
15247 return Selection {
15248 id: selection.id,
15249 start: word_range.start,
15250 end: word_range.end,
15251 goal: SelectionGoal::None,
15252 reversed: selection.reversed,
15253 };
15254 }
15255 }
15256 }
15257 }
15258
15259 let mut new_range = old_range.clone();
15260 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15261 new_range = range;
15262 if !node.is_named() {
15263 continue;
15264 }
15265 if !display_map.intersects_fold(new_range.start)
15266 && !display_map.intersects_fold(new_range.end)
15267 {
15268 break;
15269 }
15270 }
15271
15272 selected_larger_node |= new_range != old_range;
15273 Selection {
15274 id: selection.id,
15275 start: new_range.start,
15276 end: new_range.end,
15277 goal: SelectionGoal::None,
15278 reversed: selection.reversed,
15279 }
15280 })
15281 .collect::<Vec<_>>();
15282
15283 if !selected_larger_node {
15284 return; // don't put this call in the history
15285 }
15286
15287 // scroll based on transformation done to the last selection created by the user
15288 let (last_old, last_new) = old_selections
15289 .last()
15290 .zip(new_selections.last().cloned())
15291 .expect("old_selections isn't empty");
15292
15293 // revert selection
15294 let is_selection_reversed = {
15295 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15296 new_selections.last_mut().expect("checked above").reversed =
15297 should_newest_selection_be_reversed;
15298 should_newest_selection_be_reversed
15299 };
15300
15301 if selected_larger_node {
15302 self.select_syntax_node_history.disable_clearing = true;
15303 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15304 s.select(new_selections.clone());
15305 });
15306 self.select_syntax_node_history.disable_clearing = false;
15307 }
15308
15309 let start_row = last_new.start.to_display_point(&display_map).row().0;
15310 let end_row = last_new.end.to_display_point(&display_map).row().0;
15311 let selection_height = end_row - start_row + 1;
15312 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15313
15314 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15315 let scroll_behavior = if fits_on_the_screen {
15316 self.request_autoscroll(Autoscroll::fit(), cx);
15317 SelectSyntaxNodeScrollBehavior::FitSelection
15318 } else if is_selection_reversed {
15319 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15320 SelectSyntaxNodeScrollBehavior::CursorTop
15321 } else {
15322 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15323 SelectSyntaxNodeScrollBehavior::CursorBottom
15324 };
15325
15326 self.select_syntax_node_history.push((
15327 old_selections,
15328 scroll_behavior,
15329 is_selection_reversed,
15330 ));
15331 }
15332
15333 pub fn select_smaller_syntax_node(
15334 &mut self,
15335 _: &SelectSmallerSyntaxNode,
15336 window: &mut Window,
15337 cx: &mut Context<Self>,
15338 ) {
15339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15340
15341 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15342 self.select_syntax_node_history.pop()
15343 {
15344 if let Some(selection) = selections.last_mut() {
15345 selection.reversed = is_selection_reversed;
15346 }
15347
15348 self.select_syntax_node_history.disable_clearing = true;
15349 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15350 s.select(selections.to_vec());
15351 });
15352 self.select_syntax_node_history.disable_clearing = false;
15353
15354 match scroll_behavior {
15355 SelectSyntaxNodeScrollBehavior::CursorTop => {
15356 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15357 }
15358 SelectSyntaxNodeScrollBehavior::FitSelection => {
15359 self.request_autoscroll(Autoscroll::fit(), cx);
15360 }
15361 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15362 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15363 }
15364 }
15365 }
15366 }
15367
15368 pub fn unwrap_syntax_node(
15369 &mut self,
15370 _: &UnwrapSyntaxNode,
15371 window: &mut Window,
15372 cx: &mut Context<Self>,
15373 ) {
15374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15375
15376 let buffer = self.buffer.read(cx).snapshot(cx);
15377 let selections = self
15378 .selections
15379 .all::<usize>(&self.display_snapshot(cx))
15380 .into_iter()
15381 // subtracting the offset requires sorting
15382 .sorted_by_key(|i| i.start);
15383
15384 let full_edits = selections
15385 .into_iter()
15386 .filter_map(|selection| {
15387 let child = if selection.is_empty()
15388 && let Some((_, ancestor_range)) =
15389 buffer.syntax_ancestor(selection.start..selection.end)
15390 {
15391 ancestor_range
15392 } else {
15393 selection.range()
15394 };
15395
15396 let mut parent = child.clone();
15397 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15398 parent = ancestor_range;
15399 if parent.start < child.start || parent.end > child.end {
15400 break;
15401 }
15402 }
15403
15404 if parent == child {
15405 return None;
15406 }
15407 let text = buffer.text_for_range(child).collect::<String>();
15408 Some((selection.id, parent, text))
15409 })
15410 .collect::<Vec<_>>();
15411 if full_edits.is_empty() {
15412 return;
15413 }
15414
15415 self.transact(window, cx, |this, window, cx| {
15416 this.buffer.update(cx, |buffer, cx| {
15417 buffer.edit(
15418 full_edits
15419 .iter()
15420 .map(|(_, p, t)| (p.clone(), t.clone()))
15421 .collect::<Vec<_>>(),
15422 None,
15423 cx,
15424 );
15425 });
15426 this.change_selections(Default::default(), window, cx, |s| {
15427 let mut offset = 0;
15428 let mut selections = vec![];
15429 for (id, parent, text) in full_edits {
15430 let start = parent.start - offset;
15431 offset += parent.len() - text.len();
15432 selections.push(Selection {
15433 id,
15434 start,
15435 end: start + text.len(),
15436 reversed: false,
15437 goal: Default::default(),
15438 });
15439 }
15440 s.select(selections);
15441 });
15442 });
15443 }
15444
15445 pub fn select_next_syntax_node(
15446 &mut self,
15447 _: &SelectNextSyntaxNode,
15448 window: &mut Window,
15449 cx: &mut Context<Self>,
15450 ) {
15451 let old_selections: Box<[_]> = self
15452 .selections
15453 .all::<usize>(&self.display_snapshot(cx))
15454 .into();
15455 if old_selections.is_empty() {
15456 return;
15457 }
15458
15459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15460
15461 let buffer = self.buffer.read(cx).snapshot(cx);
15462 let mut selected_sibling = false;
15463
15464 let new_selections = old_selections
15465 .iter()
15466 .map(|selection| {
15467 let old_range = selection.start..selection.end;
15468
15469 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15470 let new_range = node.byte_range();
15471 selected_sibling = true;
15472 Selection {
15473 id: selection.id,
15474 start: new_range.start,
15475 end: new_range.end,
15476 goal: SelectionGoal::None,
15477 reversed: selection.reversed,
15478 }
15479 } else {
15480 selection.clone()
15481 }
15482 })
15483 .collect::<Vec<_>>();
15484
15485 if selected_sibling {
15486 self.change_selections(
15487 SelectionEffects::scroll(Autoscroll::fit()),
15488 window,
15489 cx,
15490 |s| {
15491 s.select(new_selections);
15492 },
15493 );
15494 }
15495 }
15496
15497 pub fn select_prev_syntax_node(
15498 &mut self,
15499 _: &SelectPreviousSyntaxNode,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) {
15503 let old_selections: Box<[_]> = self
15504 .selections
15505 .all::<usize>(&self.display_snapshot(cx))
15506 .into();
15507 if old_selections.is_empty() {
15508 return;
15509 }
15510
15511 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15512
15513 let buffer = self.buffer.read(cx).snapshot(cx);
15514 let mut selected_sibling = false;
15515
15516 let new_selections = old_selections
15517 .iter()
15518 .map(|selection| {
15519 let old_range = selection.start..selection.end;
15520
15521 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15522 let new_range = node.byte_range();
15523 selected_sibling = true;
15524 Selection {
15525 id: selection.id,
15526 start: new_range.start,
15527 end: new_range.end,
15528 goal: SelectionGoal::None,
15529 reversed: selection.reversed,
15530 }
15531 } else {
15532 selection.clone()
15533 }
15534 })
15535 .collect::<Vec<_>>();
15536
15537 if selected_sibling {
15538 self.change_selections(
15539 SelectionEffects::scroll(Autoscroll::fit()),
15540 window,
15541 cx,
15542 |s| {
15543 s.select(new_selections);
15544 },
15545 );
15546 }
15547 }
15548
15549 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15550 if !EditorSettings::get_global(cx).gutter.runnables {
15551 self.clear_tasks();
15552 return Task::ready(());
15553 }
15554 let project = self.project().map(Entity::downgrade);
15555 let task_sources = self.lsp_task_sources(cx);
15556 let multi_buffer = self.buffer.downgrade();
15557 cx.spawn_in(window, async move |editor, cx| {
15558 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15559 let Some(project) = project.and_then(|p| p.upgrade()) else {
15560 return;
15561 };
15562 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15563 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15564 }) else {
15565 return;
15566 };
15567
15568 let hide_runnables = project
15569 .update(cx, |project, _| project.is_via_collab())
15570 .unwrap_or(true);
15571 if hide_runnables {
15572 return;
15573 }
15574 let new_rows =
15575 cx.background_spawn({
15576 let snapshot = display_snapshot.clone();
15577 async move {
15578 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15579 }
15580 })
15581 .await;
15582 let Ok(lsp_tasks) =
15583 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15584 else {
15585 return;
15586 };
15587 let lsp_tasks = lsp_tasks.await;
15588
15589 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15590 lsp_tasks
15591 .into_iter()
15592 .flat_map(|(kind, tasks)| {
15593 tasks.into_iter().filter_map(move |(location, task)| {
15594 Some((kind.clone(), location?, task))
15595 })
15596 })
15597 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15598 let buffer = location.target.buffer;
15599 let buffer_snapshot = buffer.read(cx).snapshot();
15600 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15601 |(excerpt_id, snapshot, _)| {
15602 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15603 display_snapshot
15604 .buffer_snapshot()
15605 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15606 } else {
15607 None
15608 }
15609 },
15610 );
15611 if let Some(offset) = offset {
15612 let task_buffer_range =
15613 location.target.range.to_point(&buffer_snapshot);
15614 let context_buffer_range =
15615 task_buffer_range.to_offset(&buffer_snapshot);
15616 let context_range = BufferOffset(context_buffer_range.start)
15617 ..BufferOffset(context_buffer_range.end);
15618
15619 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15620 .or_insert_with(|| RunnableTasks {
15621 templates: Vec::new(),
15622 offset,
15623 column: task_buffer_range.start.column,
15624 extra_variables: HashMap::default(),
15625 context_range,
15626 })
15627 .templates
15628 .push((kind, task.original_task().clone()));
15629 }
15630
15631 acc
15632 })
15633 }) else {
15634 return;
15635 };
15636
15637 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15638 buffer.language_settings(cx).tasks.prefer_lsp
15639 }) else {
15640 return;
15641 };
15642
15643 let rows = Self::runnable_rows(
15644 project,
15645 display_snapshot,
15646 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15647 new_rows,
15648 cx.clone(),
15649 )
15650 .await;
15651 editor
15652 .update(cx, |editor, _| {
15653 editor.clear_tasks();
15654 for (key, mut value) in rows {
15655 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15656 value.templates.extend(lsp_tasks.templates);
15657 }
15658
15659 editor.insert_tasks(key, value);
15660 }
15661 for (key, value) in lsp_tasks_by_rows {
15662 editor.insert_tasks(key, value);
15663 }
15664 })
15665 .ok();
15666 })
15667 }
15668 fn fetch_runnable_ranges(
15669 snapshot: &DisplaySnapshot,
15670 range: Range<Anchor>,
15671 ) -> Vec<language::RunnableRange> {
15672 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15673 }
15674
15675 fn runnable_rows(
15676 project: Entity<Project>,
15677 snapshot: DisplaySnapshot,
15678 prefer_lsp: bool,
15679 runnable_ranges: Vec<RunnableRange>,
15680 cx: AsyncWindowContext,
15681 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15682 cx.spawn(async move |cx| {
15683 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15684 for mut runnable in runnable_ranges {
15685 let Some(tasks) = cx
15686 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15687 .ok()
15688 else {
15689 continue;
15690 };
15691 let mut tasks = tasks.await;
15692
15693 if prefer_lsp {
15694 tasks.retain(|(task_kind, _)| {
15695 !matches!(task_kind, TaskSourceKind::Language { .. })
15696 });
15697 }
15698 if tasks.is_empty() {
15699 continue;
15700 }
15701
15702 let point = runnable
15703 .run_range
15704 .start
15705 .to_point(&snapshot.buffer_snapshot());
15706 let Some(row) = snapshot
15707 .buffer_snapshot()
15708 .buffer_line_for_row(MultiBufferRow(point.row))
15709 .map(|(_, range)| range.start.row)
15710 else {
15711 continue;
15712 };
15713
15714 let context_range =
15715 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15716 runnable_rows.push((
15717 (runnable.buffer_id, row),
15718 RunnableTasks {
15719 templates: tasks,
15720 offset: snapshot
15721 .buffer_snapshot()
15722 .anchor_before(runnable.run_range.start),
15723 context_range,
15724 column: point.column,
15725 extra_variables: runnable.extra_captures,
15726 },
15727 ));
15728 }
15729 runnable_rows
15730 })
15731 }
15732
15733 fn templates_with_tags(
15734 project: &Entity<Project>,
15735 runnable: &mut Runnable,
15736 cx: &mut App,
15737 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15738 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15739 let (worktree_id, file) = project
15740 .buffer_for_id(runnable.buffer, cx)
15741 .and_then(|buffer| buffer.read(cx).file())
15742 .map(|file| (file.worktree_id(cx), file.clone()))
15743 .unzip();
15744
15745 (
15746 project.task_store().read(cx).task_inventory().cloned(),
15747 worktree_id,
15748 file,
15749 )
15750 });
15751
15752 let tags = mem::take(&mut runnable.tags);
15753 let language = runnable.language.clone();
15754 cx.spawn(async move |cx| {
15755 let mut templates_with_tags = Vec::new();
15756 if let Some(inventory) = inventory {
15757 for RunnableTag(tag) in tags {
15758 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15759 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15760 }) else {
15761 return templates_with_tags;
15762 };
15763 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15764 move |(_, template)| {
15765 template.tags.iter().any(|source_tag| source_tag == &tag)
15766 },
15767 ));
15768 }
15769 }
15770 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15771
15772 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15773 // Strongest source wins; if we have worktree tag binding, prefer that to
15774 // global and language bindings;
15775 // if we have a global binding, prefer that to language binding.
15776 let first_mismatch = templates_with_tags
15777 .iter()
15778 .position(|(tag_source, _)| tag_source != leading_tag_source);
15779 if let Some(index) = first_mismatch {
15780 templates_with_tags.truncate(index);
15781 }
15782 }
15783
15784 templates_with_tags
15785 })
15786 }
15787
15788 pub fn move_to_enclosing_bracket(
15789 &mut self,
15790 _: &MoveToEnclosingBracket,
15791 window: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15795 self.change_selections(Default::default(), window, cx, |s| {
15796 s.move_offsets_with(|snapshot, selection| {
15797 let Some(enclosing_bracket_ranges) =
15798 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15799 else {
15800 return;
15801 };
15802
15803 let mut best_length = usize::MAX;
15804 let mut best_inside = false;
15805 let mut best_in_bracket_range = false;
15806 let mut best_destination = None;
15807 for (open, close) in enclosing_bracket_ranges {
15808 let close = close.to_inclusive();
15809 let length = close.end() - open.start;
15810 let inside = selection.start >= open.end && selection.end <= *close.start();
15811 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15812 || close.contains(&selection.head());
15813
15814 // If best is next to a bracket and current isn't, skip
15815 if !in_bracket_range && best_in_bracket_range {
15816 continue;
15817 }
15818
15819 // Prefer smaller lengths unless best is inside and current isn't
15820 if length > best_length && (best_inside || !inside) {
15821 continue;
15822 }
15823
15824 best_length = length;
15825 best_inside = inside;
15826 best_in_bracket_range = in_bracket_range;
15827 best_destination = Some(
15828 if close.contains(&selection.start) && close.contains(&selection.end) {
15829 if inside { open.end } else { open.start }
15830 } else if inside {
15831 *close.start()
15832 } else {
15833 *close.end()
15834 },
15835 );
15836 }
15837
15838 if let Some(destination) = best_destination {
15839 selection.collapse_to(destination, SelectionGoal::None);
15840 }
15841 })
15842 });
15843 }
15844
15845 pub fn undo_selection(
15846 &mut self,
15847 _: &UndoSelection,
15848 window: &mut Window,
15849 cx: &mut Context<Self>,
15850 ) {
15851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15852 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15853 self.selection_history.mode = SelectionHistoryMode::Undoing;
15854 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15855 this.end_selection(window, cx);
15856 this.change_selections(
15857 SelectionEffects::scroll(Autoscroll::newest()),
15858 window,
15859 cx,
15860 |s| s.select_anchors(entry.selections.to_vec()),
15861 );
15862 });
15863 self.selection_history.mode = SelectionHistoryMode::Normal;
15864
15865 self.select_next_state = entry.select_next_state;
15866 self.select_prev_state = entry.select_prev_state;
15867 self.add_selections_state = entry.add_selections_state;
15868 }
15869 }
15870
15871 pub fn redo_selection(
15872 &mut self,
15873 _: &RedoSelection,
15874 window: &mut Window,
15875 cx: &mut Context<Self>,
15876 ) {
15877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15878 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15879 self.selection_history.mode = SelectionHistoryMode::Redoing;
15880 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15881 this.end_selection(window, cx);
15882 this.change_selections(
15883 SelectionEffects::scroll(Autoscroll::newest()),
15884 window,
15885 cx,
15886 |s| s.select_anchors(entry.selections.to_vec()),
15887 );
15888 });
15889 self.selection_history.mode = SelectionHistoryMode::Normal;
15890
15891 self.select_next_state = entry.select_next_state;
15892 self.select_prev_state = entry.select_prev_state;
15893 self.add_selections_state = entry.add_selections_state;
15894 }
15895 }
15896
15897 pub fn expand_excerpts(
15898 &mut self,
15899 action: &ExpandExcerpts,
15900 _: &mut Window,
15901 cx: &mut Context<Self>,
15902 ) {
15903 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15904 }
15905
15906 pub fn expand_excerpts_down(
15907 &mut self,
15908 action: &ExpandExcerptsDown,
15909 _: &mut Window,
15910 cx: &mut Context<Self>,
15911 ) {
15912 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15913 }
15914
15915 pub fn expand_excerpts_up(
15916 &mut self,
15917 action: &ExpandExcerptsUp,
15918 _: &mut Window,
15919 cx: &mut Context<Self>,
15920 ) {
15921 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15922 }
15923
15924 pub fn expand_excerpts_for_direction(
15925 &mut self,
15926 lines: u32,
15927 direction: ExpandExcerptDirection,
15928
15929 cx: &mut Context<Self>,
15930 ) {
15931 let selections = self.selections.disjoint_anchors_arc();
15932
15933 let lines = if lines == 0 {
15934 EditorSettings::get_global(cx).expand_excerpt_lines
15935 } else {
15936 lines
15937 };
15938
15939 self.buffer.update(cx, |buffer, cx| {
15940 let snapshot = buffer.snapshot(cx);
15941 let mut excerpt_ids = selections
15942 .iter()
15943 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15944 .collect::<Vec<_>>();
15945 excerpt_ids.sort();
15946 excerpt_ids.dedup();
15947 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15948 })
15949 }
15950
15951 pub fn expand_excerpt(
15952 &mut self,
15953 excerpt: ExcerptId,
15954 direction: ExpandExcerptDirection,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 let current_scroll_position = self.scroll_position(cx);
15959 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15960 let mut scroll = None;
15961
15962 if direction == ExpandExcerptDirection::Down {
15963 let multi_buffer = self.buffer.read(cx);
15964 let snapshot = multi_buffer.snapshot(cx);
15965 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15966 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15967 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15968 {
15969 let buffer_snapshot = buffer.read(cx).snapshot();
15970 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15971 let last_row = buffer_snapshot.max_point().row;
15972 let lines_below = last_row.saturating_sub(excerpt_end_row);
15973 if lines_below >= lines_to_expand {
15974 scroll = Some(
15975 current_scroll_position
15976 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
15977 );
15978 }
15979 }
15980 }
15981 if direction == ExpandExcerptDirection::Up
15982 && self
15983 .buffer
15984 .read(cx)
15985 .snapshot(cx)
15986 .excerpt_before(excerpt)
15987 .is_none()
15988 {
15989 scroll = Some(current_scroll_position);
15990 }
15991
15992 self.buffer.update(cx, |buffer, cx| {
15993 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15994 });
15995
15996 if let Some(new_scroll_position) = scroll {
15997 self.set_scroll_position(new_scroll_position, window, cx);
15998 }
15999 }
16000
16001 pub fn go_to_singleton_buffer_point(
16002 &mut self,
16003 point: Point,
16004 window: &mut Window,
16005 cx: &mut Context<Self>,
16006 ) {
16007 self.go_to_singleton_buffer_range(point..point, window, cx);
16008 }
16009
16010 pub fn go_to_singleton_buffer_range(
16011 &mut self,
16012 range: Range<Point>,
16013 window: &mut Window,
16014 cx: &mut Context<Self>,
16015 ) {
16016 let multibuffer = self.buffer().read(cx);
16017 let Some(buffer) = multibuffer.as_singleton() else {
16018 return;
16019 };
16020 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16021 return;
16022 };
16023 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16024 return;
16025 };
16026 self.change_selections(
16027 SelectionEffects::default().nav_history(true),
16028 window,
16029 cx,
16030 |s| s.select_anchor_ranges([start..end]),
16031 );
16032 }
16033
16034 pub fn go_to_diagnostic(
16035 &mut self,
16036 action: &GoToDiagnostic,
16037 window: &mut Window,
16038 cx: &mut Context<Self>,
16039 ) {
16040 if !self.diagnostics_enabled() {
16041 return;
16042 }
16043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16044 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16045 }
16046
16047 pub fn go_to_prev_diagnostic(
16048 &mut self,
16049 action: &GoToPreviousDiagnostic,
16050 window: &mut Window,
16051 cx: &mut Context<Self>,
16052 ) {
16053 if !self.diagnostics_enabled() {
16054 return;
16055 }
16056 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16057 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16058 }
16059
16060 pub fn go_to_diagnostic_impl(
16061 &mut self,
16062 direction: Direction,
16063 severity: GoToDiagnosticSeverityFilter,
16064 window: &mut Window,
16065 cx: &mut Context<Self>,
16066 ) {
16067 let buffer = self.buffer.read(cx).snapshot(cx);
16068 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16069
16070 let mut active_group_id = None;
16071 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16072 && active_group.active_range.start.to_offset(&buffer) == selection.start
16073 {
16074 active_group_id = Some(active_group.group_id);
16075 }
16076
16077 fn filtered<'a>(
16078 severity: GoToDiagnosticSeverityFilter,
16079 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16080 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16081 diagnostics
16082 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16083 .filter(|entry| entry.range.start != entry.range.end)
16084 .filter(|entry| !entry.diagnostic.is_unnecessary)
16085 }
16086
16087 let before = filtered(
16088 severity,
16089 buffer
16090 .diagnostics_in_range(0..selection.start)
16091 .filter(|entry| entry.range.start <= selection.start),
16092 );
16093 let after = filtered(
16094 severity,
16095 buffer
16096 .diagnostics_in_range(selection.start..buffer.len())
16097 .filter(|entry| entry.range.start >= selection.start),
16098 );
16099
16100 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16101 if direction == Direction::Prev {
16102 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16103 {
16104 for diagnostic in prev_diagnostics.into_iter().rev() {
16105 if diagnostic.range.start != selection.start
16106 || active_group_id
16107 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16108 {
16109 found = Some(diagnostic);
16110 break 'outer;
16111 }
16112 }
16113 }
16114 } else {
16115 for diagnostic in after.chain(before) {
16116 if diagnostic.range.start != selection.start
16117 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16118 {
16119 found = Some(diagnostic);
16120 break;
16121 }
16122 }
16123 }
16124 let Some(next_diagnostic) = found else {
16125 return;
16126 };
16127
16128 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16129 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16130 return;
16131 };
16132 let snapshot = self.snapshot(window, cx);
16133 if snapshot.intersects_fold(next_diagnostic.range.start) {
16134 self.unfold_ranges(
16135 std::slice::from_ref(&next_diagnostic.range),
16136 true,
16137 false,
16138 cx,
16139 );
16140 }
16141 self.change_selections(Default::default(), window, cx, |s| {
16142 s.select_ranges(vec![
16143 next_diagnostic.range.start..next_diagnostic.range.start,
16144 ])
16145 });
16146 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16147 self.refresh_edit_prediction(false, true, window, cx);
16148 }
16149
16150 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16152 let snapshot = self.snapshot(window, cx);
16153 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16154 self.go_to_hunk_before_or_after_position(
16155 &snapshot,
16156 selection.head(),
16157 Direction::Next,
16158 window,
16159 cx,
16160 );
16161 }
16162
16163 pub fn go_to_hunk_before_or_after_position(
16164 &mut self,
16165 snapshot: &EditorSnapshot,
16166 position: Point,
16167 direction: Direction,
16168 window: &mut Window,
16169 cx: &mut Context<Editor>,
16170 ) {
16171 let row = if direction == Direction::Next {
16172 self.hunk_after_position(snapshot, position)
16173 .map(|hunk| hunk.row_range.start)
16174 } else {
16175 self.hunk_before_position(snapshot, position)
16176 };
16177
16178 if let Some(row) = row {
16179 let destination = Point::new(row.0, 0);
16180 let autoscroll = Autoscroll::center();
16181
16182 self.unfold_ranges(&[destination..destination], false, false, cx);
16183 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16184 s.select_ranges([destination..destination]);
16185 });
16186 }
16187 }
16188
16189 fn hunk_after_position(
16190 &mut self,
16191 snapshot: &EditorSnapshot,
16192 position: Point,
16193 ) -> Option<MultiBufferDiffHunk> {
16194 snapshot
16195 .buffer_snapshot()
16196 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16197 .find(|hunk| hunk.row_range.start.0 > position.row)
16198 .or_else(|| {
16199 snapshot
16200 .buffer_snapshot()
16201 .diff_hunks_in_range(Point::zero()..position)
16202 .find(|hunk| hunk.row_range.end.0 < position.row)
16203 })
16204 }
16205
16206 fn go_to_prev_hunk(
16207 &mut self,
16208 _: &GoToPreviousHunk,
16209 window: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16213 let snapshot = self.snapshot(window, cx);
16214 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16215 self.go_to_hunk_before_or_after_position(
16216 &snapshot,
16217 selection.head(),
16218 Direction::Prev,
16219 window,
16220 cx,
16221 );
16222 }
16223
16224 fn hunk_before_position(
16225 &mut self,
16226 snapshot: &EditorSnapshot,
16227 position: Point,
16228 ) -> Option<MultiBufferRow> {
16229 snapshot
16230 .buffer_snapshot()
16231 .diff_hunk_before(position)
16232 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16233 }
16234
16235 fn go_to_next_change(
16236 &mut self,
16237 _: &GoToNextChange,
16238 window: &mut Window,
16239 cx: &mut Context<Self>,
16240 ) {
16241 if let Some(selections) = self
16242 .change_list
16243 .next_change(1, Direction::Next)
16244 .map(|s| s.to_vec())
16245 {
16246 self.change_selections(Default::default(), window, cx, |s| {
16247 let map = s.display_map();
16248 s.select_display_ranges(selections.iter().map(|a| {
16249 let point = a.to_display_point(&map);
16250 point..point
16251 }))
16252 })
16253 }
16254 }
16255
16256 fn go_to_previous_change(
16257 &mut self,
16258 _: &GoToPreviousChange,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 if let Some(selections) = self
16263 .change_list
16264 .next_change(1, Direction::Prev)
16265 .map(|s| s.to_vec())
16266 {
16267 self.change_selections(Default::default(), window, cx, |s| {
16268 let map = s.display_map();
16269 s.select_display_ranges(selections.iter().map(|a| {
16270 let point = a.to_display_point(&map);
16271 point..point
16272 }))
16273 })
16274 }
16275 }
16276
16277 pub fn go_to_next_document_highlight(
16278 &mut self,
16279 _: &GoToNextDocumentHighlight,
16280 window: &mut Window,
16281 cx: &mut Context<Self>,
16282 ) {
16283 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16284 }
16285
16286 pub fn go_to_prev_document_highlight(
16287 &mut self,
16288 _: &GoToPreviousDocumentHighlight,
16289 window: &mut Window,
16290 cx: &mut Context<Self>,
16291 ) {
16292 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16293 }
16294
16295 pub fn go_to_document_highlight_before_or_after_position(
16296 &mut self,
16297 direction: Direction,
16298 window: &mut Window,
16299 cx: &mut Context<Editor>,
16300 ) {
16301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16302 let snapshot = self.snapshot(window, cx);
16303 let buffer = &snapshot.buffer_snapshot();
16304 let position = self
16305 .selections
16306 .newest::<Point>(&snapshot.display_snapshot)
16307 .head();
16308 let anchor_position = buffer.anchor_after(position);
16309
16310 // Get all document highlights (both read and write)
16311 let mut all_highlights = Vec::new();
16312
16313 if let Some((_, read_highlights)) = self
16314 .background_highlights
16315 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16316 {
16317 all_highlights.extend(read_highlights.iter());
16318 }
16319
16320 if let Some((_, write_highlights)) = self
16321 .background_highlights
16322 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16323 {
16324 all_highlights.extend(write_highlights.iter());
16325 }
16326
16327 if all_highlights.is_empty() {
16328 return;
16329 }
16330
16331 // Sort highlights by position
16332 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16333
16334 let target_highlight = match direction {
16335 Direction::Next => {
16336 // Find the first highlight after the current position
16337 all_highlights
16338 .iter()
16339 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16340 }
16341 Direction::Prev => {
16342 // Find the last highlight before the current position
16343 all_highlights
16344 .iter()
16345 .rev()
16346 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16347 }
16348 };
16349
16350 if let Some(highlight) = target_highlight {
16351 let destination = highlight.start.to_point(buffer);
16352 let autoscroll = Autoscroll::center();
16353
16354 self.unfold_ranges(&[destination..destination], false, false, cx);
16355 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16356 s.select_ranges([destination..destination]);
16357 });
16358 }
16359 }
16360
16361 fn go_to_line<T: 'static>(
16362 &mut self,
16363 position: Anchor,
16364 highlight_color: Option<Hsla>,
16365 window: &mut Window,
16366 cx: &mut Context<Self>,
16367 ) {
16368 let snapshot = self.snapshot(window, cx).display_snapshot;
16369 let position = position.to_point(&snapshot.buffer_snapshot());
16370 let start = snapshot
16371 .buffer_snapshot()
16372 .clip_point(Point::new(position.row, 0), Bias::Left);
16373 let end = start + Point::new(1, 0);
16374 let start = snapshot.buffer_snapshot().anchor_before(start);
16375 let end = snapshot.buffer_snapshot().anchor_before(end);
16376
16377 self.highlight_rows::<T>(
16378 start..end,
16379 highlight_color
16380 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16381 Default::default(),
16382 cx,
16383 );
16384
16385 if self.buffer.read(cx).is_singleton() {
16386 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16387 }
16388 }
16389
16390 pub fn go_to_definition(
16391 &mut self,
16392 _: &GoToDefinition,
16393 window: &mut Window,
16394 cx: &mut Context<Self>,
16395 ) -> Task<Result<Navigated>> {
16396 let definition =
16397 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16398 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16399 cx.spawn_in(window, async move |editor, cx| {
16400 if definition.await? == Navigated::Yes {
16401 return Ok(Navigated::Yes);
16402 }
16403 match fallback_strategy {
16404 GoToDefinitionFallback::None => Ok(Navigated::No),
16405 GoToDefinitionFallback::FindAllReferences => {
16406 match editor.update_in(cx, |editor, window, cx| {
16407 editor.find_all_references(&FindAllReferences, window, cx)
16408 })? {
16409 Some(references) => references.await,
16410 None => Ok(Navigated::No),
16411 }
16412 }
16413 }
16414 })
16415 }
16416
16417 pub fn go_to_declaration(
16418 &mut self,
16419 _: &GoToDeclaration,
16420 window: &mut Window,
16421 cx: &mut Context<Self>,
16422 ) -> Task<Result<Navigated>> {
16423 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16424 }
16425
16426 pub fn go_to_declaration_split(
16427 &mut self,
16428 _: &GoToDeclaration,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) -> Task<Result<Navigated>> {
16432 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16433 }
16434
16435 pub fn go_to_implementation(
16436 &mut self,
16437 _: &GoToImplementation,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) -> Task<Result<Navigated>> {
16441 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16442 }
16443
16444 pub fn go_to_implementation_split(
16445 &mut self,
16446 _: &GoToImplementationSplit,
16447 window: &mut Window,
16448 cx: &mut Context<Self>,
16449 ) -> Task<Result<Navigated>> {
16450 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16451 }
16452
16453 pub fn go_to_type_definition(
16454 &mut self,
16455 _: &GoToTypeDefinition,
16456 window: &mut Window,
16457 cx: &mut Context<Self>,
16458 ) -> Task<Result<Navigated>> {
16459 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16460 }
16461
16462 pub fn go_to_definition_split(
16463 &mut self,
16464 _: &GoToDefinitionSplit,
16465 window: &mut Window,
16466 cx: &mut Context<Self>,
16467 ) -> Task<Result<Navigated>> {
16468 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16469 }
16470
16471 pub fn go_to_type_definition_split(
16472 &mut self,
16473 _: &GoToTypeDefinitionSplit,
16474 window: &mut Window,
16475 cx: &mut Context<Self>,
16476 ) -> Task<Result<Navigated>> {
16477 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16478 }
16479
16480 fn go_to_definition_of_kind(
16481 &mut self,
16482 kind: GotoDefinitionKind,
16483 split: bool,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) -> Task<Result<Navigated>> {
16487 let Some(provider) = self.semantics_provider.clone() else {
16488 return Task::ready(Ok(Navigated::No));
16489 };
16490 let head = self
16491 .selections
16492 .newest::<usize>(&self.display_snapshot(cx))
16493 .head();
16494 let buffer = self.buffer.read(cx);
16495 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16496 return Task::ready(Ok(Navigated::No));
16497 };
16498 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16499 return Task::ready(Ok(Navigated::No));
16500 };
16501
16502 cx.spawn_in(window, async move |editor, cx| {
16503 let Some(definitions) = definitions.await? else {
16504 return Ok(Navigated::No);
16505 };
16506 let navigated = editor
16507 .update_in(cx, |editor, window, cx| {
16508 editor.navigate_to_hover_links(
16509 Some(kind),
16510 definitions
16511 .into_iter()
16512 .filter(|location| {
16513 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16514 })
16515 .map(HoverLink::Text)
16516 .collect::<Vec<_>>(),
16517 split,
16518 window,
16519 cx,
16520 )
16521 })?
16522 .await?;
16523 anyhow::Ok(navigated)
16524 })
16525 }
16526
16527 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16528 let selection = self.selections.newest_anchor();
16529 let head = selection.head();
16530 let tail = selection.tail();
16531
16532 let Some((buffer, start_position)) =
16533 self.buffer.read(cx).text_anchor_for_position(head, cx)
16534 else {
16535 return;
16536 };
16537
16538 let end_position = if head != tail {
16539 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16540 return;
16541 };
16542 Some(pos)
16543 } else {
16544 None
16545 };
16546
16547 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16548 let url = if let Some(end_pos) = end_position {
16549 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16550 } else {
16551 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16552 };
16553
16554 if let Some(url) = url {
16555 cx.update(|window, cx| {
16556 if parse_zed_link(&url, cx).is_some() {
16557 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16558 } else {
16559 cx.open_url(&url);
16560 }
16561 })?;
16562 }
16563
16564 anyhow::Ok(())
16565 });
16566
16567 url_finder.detach();
16568 }
16569
16570 pub fn open_selected_filename(
16571 &mut self,
16572 _: &OpenSelectedFilename,
16573 window: &mut Window,
16574 cx: &mut Context<Self>,
16575 ) {
16576 let Some(workspace) = self.workspace() else {
16577 return;
16578 };
16579
16580 let position = self.selections.newest_anchor().head();
16581
16582 let Some((buffer, buffer_position)) =
16583 self.buffer.read(cx).text_anchor_for_position(position, cx)
16584 else {
16585 return;
16586 };
16587
16588 let project = self.project.clone();
16589
16590 cx.spawn_in(window, async move |_, cx| {
16591 let result = find_file(&buffer, project, buffer_position, cx).await;
16592
16593 if let Some((_, path)) = result {
16594 workspace
16595 .update_in(cx, |workspace, window, cx| {
16596 workspace.open_resolved_path(path, window, cx)
16597 })?
16598 .await?;
16599 }
16600 anyhow::Ok(())
16601 })
16602 .detach();
16603 }
16604
16605 pub(crate) fn navigate_to_hover_links(
16606 &mut self,
16607 kind: Option<GotoDefinitionKind>,
16608 definitions: Vec<HoverLink>,
16609 split: bool,
16610 window: &mut Window,
16611 cx: &mut Context<Editor>,
16612 ) -> Task<Result<Navigated>> {
16613 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16614 let mut first_url_or_file = None;
16615 let definitions: Vec<_> = definitions
16616 .into_iter()
16617 .filter_map(|def| match def {
16618 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16619 HoverLink::InlayHint(lsp_location, server_id) => {
16620 let computation =
16621 self.compute_target_location(lsp_location, server_id, window, cx);
16622 Some(cx.background_spawn(computation))
16623 }
16624 HoverLink::Url(url) => {
16625 first_url_or_file = Some(Either::Left(url));
16626 None
16627 }
16628 HoverLink::File(path) => {
16629 first_url_or_file = Some(Either::Right(path));
16630 None
16631 }
16632 })
16633 .collect();
16634
16635 let workspace = self.workspace();
16636
16637 cx.spawn_in(window, async move |editor, cx| {
16638 let locations: Vec<Location> = future::join_all(definitions)
16639 .await
16640 .into_iter()
16641 .filter_map(|location| location.transpose())
16642 .collect::<Result<_>>()
16643 .context("location tasks")?;
16644 let mut locations = cx.update(|_, cx| {
16645 locations
16646 .into_iter()
16647 .map(|location| {
16648 let buffer = location.buffer.read(cx);
16649 (location.buffer, location.range.to_point(buffer))
16650 })
16651 .into_group_map()
16652 })?;
16653 let mut num_locations = 0;
16654 for ranges in locations.values_mut() {
16655 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16656 ranges.dedup();
16657 num_locations += ranges.len();
16658 }
16659
16660 if num_locations > 1 {
16661 let Some(workspace) = workspace else {
16662 return Ok(Navigated::No);
16663 };
16664
16665 let tab_kind = match kind {
16666 Some(GotoDefinitionKind::Implementation) => "Implementations",
16667 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16668 Some(GotoDefinitionKind::Declaration) => "Declarations",
16669 Some(GotoDefinitionKind::Type) => "Types",
16670 };
16671 let title = editor
16672 .update_in(cx, |_, _, cx| {
16673 let target = locations
16674 .iter()
16675 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16676 .map(|(buffer, location)| {
16677 buffer
16678 .read(cx)
16679 .text_for_range(location.clone())
16680 .collect::<String>()
16681 })
16682 .filter(|text| !text.contains('\n'))
16683 .unique()
16684 .take(3)
16685 .join(", ");
16686 if target.is_empty() {
16687 tab_kind.to_owned()
16688 } else {
16689 format!("{tab_kind} for {target}")
16690 }
16691 })
16692 .context("buffer title")?;
16693
16694 let opened = workspace
16695 .update_in(cx, |workspace, window, cx| {
16696 Self::open_locations_in_multibuffer(
16697 workspace,
16698 locations,
16699 title,
16700 split,
16701 MultibufferSelectionMode::First,
16702 window,
16703 cx,
16704 )
16705 })
16706 .is_ok();
16707
16708 anyhow::Ok(Navigated::from_bool(opened))
16709 } else if num_locations == 0 {
16710 // If there is one url or file, open it directly
16711 match first_url_or_file {
16712 Some(Either::Left(url)) => {
16713 cx.update(|_, cx| cx.open_url(&url))?;
16714 Ok(Navigated::Yes)
16715 }
16716 Some(Either::Right(path)) => {
16717 let Some(workspace) = workspace else {
16718 return Ok(Navigated::No);
16719 };
16720
16721 workspace
16722 .update_in(cx, |workspace, window, cx| {
16723 workspace.open_resolved_path(path, window, cx)
16724 })?
16725 .await?;
16726 Ok(Navigated::Yes)
16727 }
16728 None => Ok(Navigated::No),
16729 }
16730 } else {
16731 let Some(workspace) = workspace else {
16732 return Ok(Navigated::No);
16733 };
16734
16735 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16736 let target_range = target_ranges.first().unwrap().clone();
16737
16738 editor.update_in(cx, |editor, window, cx| {
16739 let range = target_range.to_point(target_buffer.read(cx));
16740 let range = editor.range_for_match(&range, false);
16741 let range = collapse_multiline_range(range);
16742
16743 if !split
16744 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16745 {
16746 editor.go_to_singleton_buffer_range(range, window, cx);
16747 } else {
16748 let pane = workspace.read(cx).active_pane().clone();
16749 window.defer(cx, move |window, cx| {
16750 let target_editor: Entity<Self> =
16751 workspace.update(cx, |workspace, cx| {
16752 let pane = if split {
16753 workspace.adjacent_pane(window, cx)
16754 } else {
16755 workspace.active_pane().clone()
16756 };
16757
16758 workspace.open_project_item(
16759 pane,
16760 target_buffer.clone(),
16761 true,
16762 true,
16763 window,
16764 cx,
16765 )
16766 });
16767 target_editor.update(cx, |target_editor, cx| {
16768 // When selecting a definition in a different buffer, disable the nav history
16769 // to avoid creating a history entry at the previous cursor location.
16770 pane.update(cx, |pane, _| pane.disable_history());
16771 target_editor.go_to_singleton_buffer_range(range, window, cx);
16772 pane.update(cx, |pane, _| pane.enable_history());
16773 });
16774 });
16775 }
16776 Navigated::Yes
16777 })
16778 }
16779 })
16780 }
16781
16782 fn compute_target_location(
16783 &self,
16784 lsp_location: lsp::Location,
16785 server_id: LanguageServerId,
16786 window: &mut Window,
16787 cx: &mut Context<Self>,
16788 ) -> Task<anyhow::Result<Option<Location>>> {
16789 let Some(project) = self.project.clone() else {
16790 return Task::ready(Ok(None));
16791 };
16792
16793 cx.spawn_in(window, async move |editor, cx| {
16794 let location_task = editor.update(cx, |_, cx| {
16795 project.update(cx, |project, cx| {
16796 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16797 })
16798 })?;
16799 let location = Some({
16800 let target_buffer_handle = location_task.await.context("open local buffer")?;
16801 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16802 let target_start = target_buffer
16803 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16804 let target_end = target_buffer
16805 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16806 target_buffer.anchor_after(target_start)
16807 ..target_buffer.anchor_before(target_end)
16808 })?;
16809 Location {
16810 buffer: target_buffer_handle,
16811 range,
16812 }
16813 });
16814 Ok(location)
16815 })
16816 }
16817
16818 fn go_to_next_reference(
16819 &mut self,
16820 _: &GoToNextReference,
16821 window: &mut Window,
16822 cx: &mut Context<Self>,
16823 ) {
16824 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16825 if let Some(task) = task {
16826 task.detach();
16827 };
16828 }
16829
16830 fn go_to_prev_reference(
16831 &mut self,
16832 _: &GoToPreviousReference,
16833 window: &mut Window,
16834 cx: &mut Context<Self>,
16835 ) {
16836 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16837 if let Some(task) = task {
16838 task.detach();
16839 };
16840 }
16841
16842 pub fn go_to_reference_before_or_after_position(
16843 &mut self,
16844 direction: Direction,
16845 count: usize,
16846 window: &mut Window,
16847 cx: &mut Context<Self>,
16848 ) -> Option<Task<Result<()>>> {
16849 let selection = self.selections.newest_anchor();
16850 let head = selection.head();
16851
16852 let multi_buffer = self.buffer.read(cx);
16853
16854 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16855 let workspace = self.workspace()?;
16856 let project = workspace.read(cx).project().clone();
16857 let references =
16858 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16859 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16860 let Some(locations) = references.await? else {
16861 return Ok(());
16862 };
16863
16864 if locations.is_empty() {
16865 // totally normal - the cursor may be on something which is not
16866 // a symbol (e.g. a keyword)
16867 log::info!("no references found under cursor");
16868 return Ok(());
16869 }
16870
16871 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16872
16873 let multi_buffer_snapshot =
16874 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16875
16876 let (locations, current_location_index) =
16877 multi_buffer.update(cx, |multi_buffer, cx| {
16878 let mut locations = locations
16879 .into_iter()
16880 .filter_map(|loc| {
16881 let start = multi_buffer.buffer_anchor_to_anchor(
16882 &loc.buffer,
16883 loc.range.start,
16884 cx,
16885 )?;
16886 let end = multi_buffer.buffer_anchor_to_anchor(
16887 &loc.buffer,
16888 loc.range.end,
16889 cx,
16890 )?;
16891 Some(start..end)
16892 })
16893 .collect::<Vec<_>>();
16894
16895 // There is an O(n) implementation, but given this list will be
16896 // small (usually <100 items), the extra O(log(n)) factor isn't
16897 // worth the (surprisingly large amount of) extra complexity.
16898 locations
16899 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16900
16901 let head_offset = head.to_offset(&multi_buffer_snapshot);
16902
16903 let current_location_index = locations.iter().position(|loc| {
16904 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16905 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16906 });
16907
16908 (locations, current_location_index)
16909 })?;
16910
16911 let Some(current_location_index) = current_location_index else {
16912 // This indicates something has gone wrong, because we already
16913 // handle the "no references" case above
16914 log::error!(
16915 "failed to find current reference under cursor. Total references: {}",
16916 locations.len()
16917 );
16918 return Ok(());
16919 };
16920
16921 let destination_location_index = match direction {
16922 Direction::Next => (current_location_index + count) % locations.len(),
16923 Direction::Prev => {
16924 (current_location_index + locations.len() - count % locations.len())
16925 % locations.len()
16926 }
16927 };
16928
16929 // TODO(cameron): is this needed?
16930 // the thinking is to avoid "jumping to the current location" (avoid
16931 // polluting "jumplist" in vim terms)
16932 if current_location_index == destination_location_index {
16933 return Ok(());
16934 }
16935
16936 let Range { start, end } = locations[destination_location_index];
16937
16938 editor.update_in(cx, |editor, window, cx| {
16939 let effects = SelectionEffects::default();
16940
16941 editor.unfold_ranges(&[start..end], false, false, cx);
16942 editor.change_selections(effects, window, cx, |s| {
16943 s.select_ranges([start..start]);
16944 });
16945 })?;
16946
16947 Ok(())
16948 }))
16949 }
16950
16951 pub fn find_all_references(
16952 &mut self,
16953 _: &FindAllReferences,
16954 window: &mut Window,
16955 cx: &mut Context<Self>,
16956 ) -> Option<Task<Result<Navigated>>> {
16957 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16958 let multi_buffer = self.buffer.read(cx);
16959 let head = selection.head();
16960
16961 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16962 let head_anchor = multi_buffer_snapshot.anchor_at(
16963 head,
16964 if head < selection.tail() {
16965 Bias::Right
16966 } else {
16967 Bias::Left
16968 },
16969 );
16970
16971 match self
16972 .find_all_references_task_sources
16973 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16974 {
16975 Ok(_) => {
16976 log::info!(
16977 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16978 );
16979 return None;
16980 }
16981 Err(i) => {
16982 self.find_all_references_task_sources.insert(i, head_anchor);
16983 }
16984 }
16985
16986 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16987 let workspace = self.workspace()?;
16988 let project = workspace.read(cx).project().clone();
16989 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16990 Some(cx.spawn_in(window, async move |editor, cx| {
16991 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16992 if let Ok(i) = editor
16993 .find_all_references_task_sources
16994 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16995 {
16996 editor.find_all_references_task_sources.remove(i);
16997 }
16998 });
16999
17000 let Some(locations) = references.await? else {
17001 return anyhow::Ok(Navigated::No);
17002 };
17003 let mut locations = cx.update(|_, cx| {
17004 locations
17005 .into_iter()
17006 .map(|location| {
17007 let buffer = location.buffer.read(cx);
17008 (location.buffer, location.range.to_point(buffer))
17009 })
17010 .into_group_map()
17011 })?;
17012 if locations.is_empty() {
17013 return anyhow::Ok(Navigated::No);
17014 }
17015 for ranges in locations.values_mut() {
17016 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17017 ranges.dedup();
17018 }
17019
17020 workspace.update_in(cx, |workspace, window, cx| {
17021 let target = locations
17022 .iter()
17023 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17024 .map(|(buffer, location)| {
17025 buffer
17026 .read(cx)
17027 .text_for_range(location.clone())
17028 .collect::<String>()
17029 })
17030 .filter(|text| !text.contains('\n'))
17031 .unique()
17032 .take(3)
17033 .join(", ");
17034 let title = if target.is_empty() {
17035 "References".to_owned()
17036 } else {
17037 format!("References to {target}")
17038 };
17039 Self::open_locations_in_multibuffer(
17040 workspace,
17041 locations,
17042 title,
17043 false,
17044 MultibufferSelectionMode::First,
17045 window,
17046 cx,
17047 );
17048 Navigated::Yes
17049 })
17050 }))
17051 }
17052
17053 /// Opens a multibuffer with the given project locations in it
17054 pub fn open_locations_in_multibuffer(
17055 workspace: &mut Workspace,
17056 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17057 title: String,
17058 split: bool,
17059 multibuffer_selection_mode: MultibufferSelectionMode,
17060 window: &mut Window,
17061 cx: &mut Context<Workspace>,
17062 ) {
17063 if locations.is_empty() {
17064 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17065 return;
17066 }
17067
17068 let capability = workspace.project().read(cx).capability();
17069 let mut ranges = <Vec<Range<Anchor>>>::new();
17070
17071 // a key to find existing multibuffer editors with the same set of locations
17072 // to prevent us from opening more and more multibuffer tabs for searches and the like
17073 let mut key = (title.clone(), vec![]);
17074 let excerpt_buffer = cx.new(|cx| {
17075 let key = &mut key.1;
17076 let mut multibuffer = MultiBuffer::new(capability);
17077 for (buffer, mut ranges_for_buffer) in locations {
17078 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17079 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17080 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17081 PathKey::for_buffer(&buffer, cx),
17082 buffer.clone(),
17083 ranges_for_buffer,
17084 multibuffer_context_lines(cx),
17085 cx,
17086 );
17087 ranges.extend(new_ranges)
17088 }
17089
17090 multibuffer.with_title(title)
17091 });
17092 let existing = workspace.active_pane().update(cx, |pane, cx| {
17093 pane.items()
17094 .filter_map(|item| item.downcast::<Editor>())
17095 .find(|editor| {
17096 editor
17097 .read(cx)
17098 .lookup_key
17099 .as_ref()
17100 .and_then(|it| {
17101 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17102 })
17103 .is_some_and(|it| *it == key)
17104 })
17105 });
17106 let editor = existing.unwrap_or_else(|| {
17107 cx.new(|cx| {
17108 let mut editor = Editor::for_multibuffer(
17109 excerpt_buffer,
17110 Some(workspace.project().clone()),
17111 window,
17112 cx,
17113 );
17114 editor.lookup_key = Some(Box::new(key));
17115 editor
17116 })
17117 });
17118 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17119 MultibufferSelectionMode::First => {
17120 if let Some(first_range) = ranges.first() {
17121 editor.change_selections(
17122 SelectionEffects::no_scroll(),
17123 window,
17124 cx,
17125 |selections| {
17126 selections.clear_disjoint();
17127 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17128 },
17129 );
17130 }
17131 editor.highlight_background::<Self>(
17132 &ranges,
17133 |theme| theme.colors().editor_highlighted_line_background,
17134 cx,
17135 );
17136 }
17137 MultibufferSelectionMode::All => {
17138 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17139 selections.clear_disjoint();
17140 selections.select_anchor_ranges(ranges);
17141 });
17142 }
17143 });
17144
17145 let item = Box::new(editor);
17146 let item_id = item.item_id();
17147
17148 if split {
17149 let pane = workspace.adjacent_pane(window, cx);
17150 workspace.add_item(pane, item, None, true, true, window, cx);
17151 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17152 let (preview_item_id, preview_item_idx) =
17153 workspace.active_pane().read_with(cx, |pane, _| {
17154 (pane.preview_item_id(), pane.preview_item_idx())
17155 });
17156
17157 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17158
17159 if let Some(preview_item_id) = preview_item_id {
17160 workspace.active_pane().update(cx, |pane, cx| {
17161 pane.remove_item(preview_item_id, false, false, window, cx);
17162 });
17163 }
17164 } else {
17165 workspace.add_item_to_active_pane(item, None, true, window, cx);
17166 }
17167 workspace.active_pane().update(cx, |pane, cx| {
17168 pane.set_preview_item_id(Some(item_id), cx);
17169 });
17170 }
17171
17172 pub fn rename(
17173 &mut self,
17174 _: &Rename,
17175 window: &mut Window,
17176 cx: &mut Context<Self>,
17177 ) -> Option<Task<Result<()>>> {
17178 use language::ToOffset as _;
17179
17180 let provider = self.semantics_provider.clone()?;
17181 let selection = self.selections.newest_anchor().clone();
17182 let (cursor_buffer, cursor_buffer_position) = self
17183 .buffer
17184 .read(cx)
17185 .text_anchor_for_position(selection.head(), cx)?;
17186 let (tail_buffer, cursor_buffer_position_end) = self
17187 .buffer
17188 .read(cx)
17189 .text_anchor_for_position(selection.tail(), cx)?;
17190 if tail_buffer != cursor_buffer {
17191 return None;
17192 }
17193
17194 let snapshot = cursor_buffer.read(cx).snapshot();
17195 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17196 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17197 let prepare_rename = provider
17198 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17199 .unwrap_or_else(|| Task::ready(Ok(None)));
17200 drop(snapshot);
17201
17202 Some(cx.spawn_in(window, async move |this, cx| {
17203 let rename_range = if let Some(range) = prepare_rename.await? {
17204 Some(range)
17205 } else {
17206 this.update(cx, |this, cx| {
17207 let buffer = this.buffer.read(cx).snapshot(cx);
17208 let mut buffer_highlights = this
17209 .document_highlights_for_position(selection.head(), &buffer)
17210 .filter(|highlight| {
17211 highlight.start.excerpt_id == selection.head().excerpt_id
17212 && highlight.end.excerpt_id == selection.head().excerpt_id
17213 });
17214 buffer_highlights
17215 .next()
17216 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17217 })?
17218 };
17219 if let Some(rename_range) = rename_range {
17220 this.update_in(cx, |this, window, cx| {
17221 let snapshot = cursor_buffer.read(cx).snapshot();
17222 let rename_buffer_range = rename_range.to_offset(&snapshot);
17223 let cursor_offset_in_rename_range =
17224 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17225 let cursor_offset_in_rename_range_end =
17226 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17227
17228 this.take_rename(false, window, cx);
17229 let buffer = this.buffer.read(cx).read(cx);
17230 let cursor_offset = selection.head().to_offset(&buffer);
17231 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17232 let rename_end = rename_start + rename_buffer_range.len();
17233 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17234 let mut old_highlight_id = None;
17235 let old_name: Arc<str> = buffer
17236 .chunks(rename_start..rename_end, true)
17237 .map(|chunk| {
17238 if old_highlight_id.is_none() {
17239 old_highlight_id = chunk.syntax_highlight_id;
17240 }
17241 chunk.text
17242 })
17243 .collect::<String>()
17244 .into();
17245
17246 drop(buffer);
17247
17248 // Position the selection in the rename editor so that it matches the current selection.
17249 this.show_local_selections = false;
17250 let rename_editor = cx.new(|cx| {
17251 let mut editor = Editor::single_line(window, cx);
17252 editor.buffer.update(cx, |buffer, cx| {
17253 buffer.edit([(0..0, old_name.clone())], None, cx)
17254 });
17255 let rename_selection_range = match cursor_offset_in_rename_range
17256 .cmp(&cursor_offset_in_rename_range_end)
17257 {
17258 Ordering::Equal => {
17259 editor.select_all(&SelectAll, window, cx);
17260 return editor;
17261 }
17262 Ordering::Less => {
17263 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17264 }
17265 Ordering::Greater => {
17266 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17267 }
17268 };
17269 if rename_selection_range.end > old_name.len() {
17270 editor.select_all(&SelectAll, window, cx);
17271 } else {
17272 editor.change_selections(Default::default(), window, cx, |s| {
17273 s.select_ranges([rename_selection_range]);
17274 });
17275 }
17276 editor
17277 });
17278 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17279 if e == &EditorEvent::Focused {
17280 cx.emit(EditorEvent::FocusedIn)
17281 }
17282 })
17283 .detach();
17284
17285 let write_highlights =
17286 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17287 let read_highlights =
17288 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17289 let ranges = write_highlights
17290 .iter()
17291 .flat_map(|(_, ranges)| ranges.iter())
17292 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17293 .cloned()
17294 .collect();
17295
17296 this.highlight_text::<Rename>(
17297 ranges,
17298 HighlightStyle {
17299 fade_out: Some(0.6),
17300 ..Default::default()
17301 },
17302 cx,
17303 );
17304 let rename_focus_handle = rename_editor.focus_handle(cx);
17305 window.focus(&rename_focus_handle);
17306 let block_id = this.insert_blocks(
17307 [BlockProperties {
17308 style: BlockStyle::Flex,
17309 placement: BlockPlacement::Below(range.start),
17310 height: Some(1),
17311 render: Arc::new({
17312 let rename_editor = rename_editor.clone();
17313 move |cx: &mut BlockContext| {
17314 let mut text_style = cx.editor_style.text.clone();
17315 if let Some(highlight_style) = old_highlight_id
17316 .and_then(|h| h.style(&cx.editor_style.syntax))
17317 {
17318 text_style = text_style.highlight(highlight_style);
17319 }
17320 div()
17321 .block_mouse_except_scroll()
17322 .pl(cx.anchor_x)
17323 .child(EditorElement::new(
17324 &rename_editor,
17325 EditorStyle {
17326 background: cx.theme().system().transparent,
17327 local_player: cx.editor_style.local_player,
17328 text: text_style,
17329 scrollbar_width: cx.editor_style.scrollbar_width,
17330 syntax: cx.editor_style.syntax.clone(),
17331 status: cx.editor_style.status.clone(),
17332 inlay_hints_style: HighlightStyle {
17333 font_weight: Some(FontWeight::BOLD),
17334 ..make_inlay_hints_style(cx.app)
17335 },
17336 edit_prediction_styles: make_suggestion_styles(
17337 cx.app,
17338 ),
17339 ..EditorStyle::default()
17340 },
17341 ))
17342 .into_any_element()
17343 }
17344 }),
17345 priority: 0,
17346 }],
17347 Some(Autoscroll::fit()),
17348 cx,
17349 )[0];
17350 this.pending_rename = Some(RenameState {
17351 range,
17352 old_name,
17353 editor: rename_editor,
17354 block_id,
17355 });
17356 })?;
17357 }
17358
17359 Ok(())
17360 }))
17361 }
17362
17363 pub fn confirm_rename(
17364 &mut self,
17365 _: &ConfirmRename,
17366 window: &mut Window,
17367 cx: &mut Context<Self>,
17368 ) -> Option<Task<Result<()>>> {
17369 let rename = self.take_rename(false, window, cx)?;
17370 let workspace = self.workspace()?.downgrade();
17371 let (buffer, start) = self
17372 .buffer
17373 .read(cx)
17374 .text_anchor_for_position(rename.range.start, cx)?;
17375 let (end_buffer, _) = self
17376 .buffer
17377 .read(cx)
17378 .text_anchor_for_position(rename.range.end, cx)?;
17379 if buffer != end_buffer {
17380 return None;
17381 }
17382
17383 let old_name = rename.old_name;
17384 let new_name = rename.editor.read(cx).text(cx);
17385
17386 let rename = self.semantics_provider.as_ref()?.perform_rename(
17387 &buffer,
17388 start,
17389 new_name.clone(),
17390 cx,
17391 )?;
17392
17393 Some(cx.spawn_in(window, async move |editor, cx| {
17394 let project_transaction = rename.await?;
17395 Self::open_project_transaction(
17396 &editor,
17397 workspace,
17398 project_transaction,
17399 format!("Rename: {} → {}", old_name, new_name),
17400 cx,
17401 )
17402 .await?;
17403
17404 editor.update(cx, |editor, cx| {
17405 editor.refresh_document_highlights(cx);
17406 })?;
17407 Ok(())
17408 }))
17409 }
17410
17411 fn take_rename(
17412 &mut self,
17413 moving_cursor: bool,
17414 window: &mut Window,
17415 cx: &mut Context<Self>,
17416 ) -> Option<RenameState> {
17417 let rename = self.pending_rename.take()?;
17418 if rename.editor.focus_handle(cx).is_focused(window) {
17419 window.focus(&self.focus_handle);
17420 }
17421
17422 self.remove_blocks(
17423 [rename.block_id].into_iter().collect(),
17424 Some(Autoscroll::fit()),
17425 cx,
17426 );
17427 self.clear_highlights::<Rename>(cx);
17428 self.show_local_selections = true;
17429
17430 if moving_cursor {
17431 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17432 editor
17433 .selections
17434 .newest::<usize>(&editor.display_snapshot(cx))
17435 .head()
17436 });
17437
17438 // Update the selection to match the position of the selection inside
17439 // the rename editor.
17440 let snapshot = self.buffer.read(cx).read(cx);
17441 let rename_range = rename.range.to_offset(&snapshot);
17442 let cursor_in_editor = snapshot
17443 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17444 .min(rename_range.end);
17445 drop(snapshot);
17446
17447 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17448 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17449 });
17450 } else {
17451 self.refresh_document_highlights(cx);
17452 }
17453
17454 Some(rename)
17455 }
17456
17457 pub fn pending_rename(&self) -> Option<&RenameState> {
17458 self.pending_rename.as_ref()
17459 }
17460
17461 fn format(
17462 &mut self,
17463 _: &Format,
17464 window: &mut Window,
17465 cx: &mut Context<Self>,
17466 ) -> Option<Task<Result<()>>> {
17467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17468
17469 let project = match &self.project {
17470 Some(project) => project.clone(),
17471 None => return None,
17472 };
17473
17474 Some(self.perform_format(
17475 project,
17476 FormatTrigger::Manual,
17477 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17478 window,
17479 cx,
17480 ))
17481 }
17482
17483 fn format_selections(
17484 &mut self,
17485 _: &FormatSelections,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) -> Option<Task<Result<()>>> {
17489 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17490
17491 let project = match &self.project {
17492 Some(project) => project.clone(),
17493 None => return None,
17494 };
17495
17496 let ranges = self
17497 .selections
17498 .all_adjusted(&self.display_snapshot(cx))
17499 .into_iter()
17500 .map(|selection| selection.range())
17501 .collect_vec();
17502
17503 Some(self.perform_format(
17504 project,
17505 FormatTrigger::Manual,
17506 FormatTarget::Ranges(ranges),
17507 window,
17508 cx,
17509 ))
17510 }
17511
17512 fn perform_format(
17513 &mut self,
17514 project: Entity<Project>,
17515 trigger: FormatTrigger,
17516 target: FormatTarget,
17517 window: &mut Window,
17518 cx: &mut Context<Self>,
17519 ) -> Task<Result<()>> {
17520 let buffer = self.buffer.clone();
17521 let (buffers, target) = match target {
17522 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17523 FormatTarget::Ranges(selection_ranges) => {
17524 let multi_buffer = buffer.read(cx);
17525 let snapshot = multi_buffer.read(cx);
17526 let mut buffers = HashSet::default();
17527 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17528 BTreeMap::new();
17529 for selection_range in selection_ranges {
17530 for (buffer, buffer_range, _) in
17531 snapshot.range_to_buffer_ranges(selection_range)
17532 {
17533 let buffer_id = buffer.remote_id();
17534 let start = buffer.anchor_before(buffer_range.start);
17535 let end = buffer.anchor_after(buffer_range.end);
17536 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17537 buffer_id_to_ranges
17538 .entry(buffer_id)
17539 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17540 .or_insert_with(|| vec![start..end]);
17541 }
17542 }
17543 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17544 }
17545 };
17546
17547 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17548 let selections_prev = transaction_id_prev
17549 .and_then(|transaction_id_prev| {
17550 // default to selections as they were after the last edit, if we have them,
17551 // instead of how they are now.
17552 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17553 // will take you back to where you made the last edit, instead of staying where you scrolled
17554 self.selection_history
17555 .transaction(transaction_id_prev)
17556 .map(|t| t.0.clone())
17557 })
17558 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17559
17560 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17561 let format = project.update(cx, |project, cx| {
17562 project.format(buffers, target, true, trigger, cx)
17563 });
17564
17565 cx.spawn_in(window, async move |editor, cx| {
17566 let transaction = futures::select_biased! {
17567 transaction = format.log_err().fuse() => transaction,
17568 () = timeout => {
17569 log::warn!("timed out waiting for formatting");
17570 None
17571 }
17572 };
17573
17574 buffer
17575 .update(cx, |buffer, cx| {
17576 if let Some(transaction) = transaction
17577 && !buffer.is_singleton()
17578 {
17579 buffer.push_transaction(&transaction.0, cx);
17580 }
17581 cx.notify();
17582 })
17583 .ok();
17584
17585 if let Some(transaction_id_now) =
17586 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17587 {
17588 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17589 if has_new_transaction {
17590 _ = editor.update(cx, |editor, _| {
17591 editor
17592 .selection_history
17593 .insert_transaction(transaction_id_now, selections_prev);
17594 });
17595 }
17596 }
17597
17598 Ok(())
17599 })
17600 }
17601
17602 fn organize_imports(
17603 &mut self,
17604 _: &OrganizeImports,
17605 window: &mut Window,
17606 cx: &mut Context<Self>,
17607 ) -> Option<Task<Result<()>>> {
17608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17609 let project = match &self.project {
17610 Some(project) => project.clone(),
17611 None => return None,
17612 };
17613 Some(self.perform_code_action_kind(
17614 project,
17615 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17616 window,
17617 cx,
17618 ))
17619 }
17620
17621 fn perform_code_action_kind(
17622 &mut self,
17623 project: Entity<Project>,
17624 kind: CodeActionKind,
17625 window: &mut Window,
17626 cx: &mut Context<Self>,
17627 ) -> Task<Result<()>> {
17628 let buffer = self.buffer.clone();
17629 let buffers = buffer.read(cx).all_buffers();
17630 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17631 let apply_action = project.update(cx, |project, cx| {
17632 project.apply_code_action_kind(buffers, kind, true, cx)
17633 });
17634 cx.spawn_in(window, async move |_, cx| {
17635 let transaction = futures::select_biased! {
17636 () = timeout => {
17637 log::warn!("timed out waiting for executing code action");
17638 None
17639 }
17640 transaction = apply_action.log_err().fuse() => transaction,
17641 };
17642 buffer
17643 .update(cx, |buffer, cx| {
17644 // check if we need this
17645 if let Some(transaction) = transaction
17646 && !buffer.is_singleton()
17647 {
17648 buffer.push_transaction(&transaction.0, cx);
17649 }
17650 cx.notify();
17651 })
17652 .ok();
17653 Ok(())
17654 })
17655 }
17656
17657 pub fn restart_language_server(
17658 &mut self,
17659 _: &RestartLanguageServer,
17660 _: &mut Window,
17661 cx: &mut Context<Self>,
17662 ) {
17663 if let Some(project) = self.project.clone() {
17664 self.buffer.update(cx, |multi_buffer, cx| {
17665 project.update(cx, |project, cx| {
17666 project.restart_language_servers_for_buffers(
17667 multi_buffer.all_buffers().into_iter().collect(),
17668 HashSet::default(),
17669 cx,
17670 );
17671 });
17672 })
17673 }
17674 }
17675
17676 pub fn stop_language_server(
17677 &mut self,
17678 _: &StopLanguageServer,
17679 _: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) {
17682 if let Some(project) = self.project.clone() {
17683 self.buffer.update(cx, |multi_buffer, cx| {
17684 project.update(cx, |project, cx| {
17685 project.stop_language_servers_for_buffers(
17686 multi_buffer.all_buffers().into_iter().collect(),
17687 HashSet::default(),
17688 cx,
17689 );
17690 });
17691 });
17692 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17693 }
17694 }
17695
17696 fn cancel_language_server_work(
17697 workspace: &mut Workspace,
17698 _: &actions::CancelLanguageServerWork,
17699 _: &mut Window,
17700 cx: &mut Context<Workspace>,
17701 ) {
17702 let project = workspace.project();
17703 let buffers = workspace
17704 .active_item(cx)
17705 .and_then(|item| item.act_as::<Editor>(cx))
17706 .map_or(HashSet::default(), |editor| {
17707 editor.read(cx).buffer.read(cx).all_buffers()
17708 });
17709 project.update(cx, |project, cx| {
17710 project.cancel_language_server_work_for_buffers(buffers, cx);
17711 });
17712 }
17713
17714 fn show_character_palette(
17715 &mut self,
17716 _: &ShowCharacterPalette,
17717 window: &mut Window,
17718 _: &mut Context<Self>,
17719 ) {
17720 window.show_character_palette();
17721 }
17722
17723 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17724 if !self.diagnostics_enabled() {
17725 return;
17726 }
17727
17728 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17729 let buffer = self.buffer.read(cx).snapshot(cx);
17730 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17731 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17732 let is_valid = buffer
17733 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17734 .any(|entry| {
17735 entry.diagnostic.is_primary
17736 && !entry.range.is_empty()
17737 && entry.range.start == primary_range_start
17738 && entry.diagnostic.message == active_diagnostics.active_message
17739 });
17740
17741 if !is_valid {
17742 self.dismiss_diagnostics(cx);
17743 }
17744 }
17745 }
17746
17747 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17748 match &self.active_diagnostics {
17749 ActiveDiagnostic::Group(group) => Some(group),
17750 _ => None,
17751 }
17752 }
17753
17754 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17755 if !self.diagnostics_enabled() {
17756 return;
17757 }
17758 self.dismiss_diagnostics(cx);
17759 self.active_diagnostics = ActiveDiagnostic::All;
17760 }
17761
17762 fn activate_diagnostics(
17763 &mut self,
17764 buffer_id: BufferId,
17765 diagnostic: DiagnosticEntryRef<'_, usize>,
17766 window: &mut Window,
17767 cx: &mut Context<Self>,
17768 ) {
17769 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17770 return;
17771 }
17772 self.dismiss_diagnostics(cx);
17773 let snapshot = self.snapshot(window, cx);
17774 let buffer = self.buffer.read(cx).snapshot(cx);
17775 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17776 return;
17777 };
17778
17779 let diagnostic_group = buffer
17780 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17781 .collect::<Vec<_>>();
17782
17783 let blocks =
17784 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17785
17786 let blocks = self.display_map.update(cx, |display_map, cx| {
17787 display_map.insert_blocks(blocks, cx).into_iter().collect()
17788 });
17789 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17790 active_range: buffer.anchor_before(diagnostic.range.start)
17791 ..buffer.anchor_after(diagnostic.range.end),
17792 active_message: diagnostic.diagnostic.message.clone(),
17793 group_id: diagnostic.diagnostic.group_id,
17794 blocks,
17795 });
17796 cx.notify();
17797 }
17798
17799 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17800 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17801 return;
17802 };
17803
17804 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17805 if let ActiveDiagnostic::Group(group) = prev {
17806 self.display_map.update(cx, |display_map, cx| {
17807 display_map.remove_blocks(group.blocks, cx);
17808 });
17809 cx.notify();
17810 }
17811 }
17812
17813 /// Disable inline diagnostics rendering for this editor.
17814 pub fn disable_inline_diagnostics(&mut self) {
17815 self.inline_diagnostics_enabled = false;
17816 self.inline_diagnostics_update = Task::ready(());
17817 self.inline_diagnostics.clear();
17818 }
17819
17820 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17821 self.diagnostics_enabled = false;
17822 self.dismiss_diagnostics(cx);
17823 self.inline_diagnostics_update = Task::ready(());
17824 self.inline_diagnostics.clear();
17825 }
17826
17827 pub fn disable_word_completions(&mut self) {
17828 self.word_completions_enabled = false;
17829 }
17830
17831 pub fn diagnostics_enabled(&self) -> bool {
17832 self.diagnostics_enabled && self.mode.is_full()
17833 }
17834
17835 pub fn inline_diagnostics_enabled(&self) -> bool {
17836 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17837 }
17838
17839 pub fn show_inline_diagnostics(&self) -> bool {
17840 self.show_inline_diagnostics
17841 }
17842
17843 pub fn toggle_inline_diagnostics(
17844 &mut self,
17845 _: &ToggleInlineDiagnostics,
17846 window: &mut Window,
17847 cx: &mut Context<Editor>,
17848 ) {
17849 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17850 self.refresh_inline_diagnostics(false, window, cx);
17851 }
17852
17853 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17854 self.diagnostics_max_severity = severity;
17855 self.display_map.update(cx, |display_map, _| {
17856 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17857 });
17858 }
17859
17860 pub fn toggle_diagnostics(
17861 &mut self,
17862 _: &ToggleDiagnostics,
17863 window: &mut Window,
17864 cx: &mut Context<Editor>,
17865 ) {
17866 if !self.diagnostics_enabled() {
17867 return;
17868 }
17869
17870 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17871 EditorSettings::get_global(cx)
17872 .diagnostics_max_severity
17873 .filter(|severity| severity != &DiagnosticSeverity::Off)
17874 .unwrap_or(DiagnosticSeverity::Hint)
17875 } else {
17876 DiagnosticSeverity::Off
17877 };
17878 self.set_max_diagnostics_severity(new_severity, cx);
17879 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17880 self.active_diagnostics = ActiveDiagnostic::None;
17881 self.inline_diagnostics_update = Task::ready(());
17882 self.inline_diagnostics.clear();
17883 } else {
17884 self.refresh_inline_diagnostics(false, window, cx);
17885 }
17886
17887 cx.notify();
17888 }
17889
17890 pub fn toggle_minimap(
17891 &mut self,
17892 _: &ToggleMinimap,
17893 window: &mut Window,
17894 cx: &mut Context<Editor>,
17895 ) {
17896 if self.supports_minimap(cx) {
17897 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17898 }
17899 }
17900
17901 fn refresh_inline_diagnostics(
17902 &mut self,
17903 debounce: bool,
17904 window: &mut Window,
17905 cx: &mut Context<Self>,
17906 ) {
17907 let max_severity = ProjectSettings::get_global(cx)
17908 .diagnostics
17909 .inline
17910 .max_severity
17911 .unwrap_or(self.diagnostics_max_severity);
17912
17913 if !self.inline_diagnostics_enabled()
17914 || !self.diagnostics_enabled()
17915 || !self.show_inline_diagnostics
17916 || max_severity == DiagnosticSeverity::Off
17917 {
17918 self.inline_diagnostics_update = Task::ready(());
17919 self.inline_diagnostics.clear();
17920 return;
17921 }
17922
17923 let debounce_ms = ProjectSettings::get_global(cx)
17924 .diagnostics
17925 .inline
17926 .update_debounce_ms;
17927 let debounce = if debounce && debounce_ms > 0 {
17928 Some(Duration::from_millis(debounce_ms))
17929 } else {
17930 None
17931 };
17932 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17933 if let Some(debounce) = debounce {
17934 cx.background_executor().timer(debounce).await;
17935 }
17936 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17937 editor
17938 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17939 .ok()
17940 }) else {
17941 return;
17942 };
17943
17944 let new_inline_diagnostics = cx
17945 .background_spawn(async move {
17946 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17947 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17948 let message = diagnostic_entry
17949 .diagnostic
17950 .message
17951 .split_once('\n')
17952 .map(|(line, _)| line)
17953 .map(SharedString::new)
17954 .unwrap_or_else(|| {
17955 SharedString::new(&*diagnostic_entry.diagnostic.message)
17956 });
17957 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17958 let (Ok(i) | Err(i)) = inline_diagnostics
17959 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17960 inline_diagnostics.insert(
17961 i,
17962 (
17963 start_anchor,
17964 InlineDiagnostic {
17965 message,
17966 group_id: diagnostic_entry.diagnostic.group_id,
17967 start: diagnostic_entry.range.start.to_point(&snapshot),
17968 is_primary: diagnostic_entry.diagnostic.is_primary,
17969 severity: diagnostic_entry.diagnostic.severity,
17970 },
17971 ),
17972 );
17973 }
17974 inline_diagnostics
17975 })
17976 .await;
17977
17978 editor
17979 .update(cx, |editor, cx| {
17980 editor.inline_diagnostics = new_inline_diagnostics;
17981 cx.notify();
17982 })
17983 .ok();
17984 });
17985 }
17986
17987 fn pull_diagnostics(
17988 &mut self,
17989 buffer_id: Option<BufferId>,
17990 window: &Window,
17991 cx: &mut Context<Self>,
17992 ) -> Option<()> {
17993 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17994 return None;
17995 }
17996 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17997 .diagnostics
17998 .lsp_pull_diagnostics;
17999 if !pull_diagnostics_settings.enabled {
18000 return None;
18001 }
18002 let project = self.project()?.downgrade();
18003 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18004 let mut buffers = self.buffer.read(cx).all_buffers();
18005 buffers.retain(|buffer| {
18006 let buffer_id_to_retain = buffer.read(cx).remote_id();
18007 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18008 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18009 });
18010 if buffers.is_empty() {
18011 self.pull_diagnostics_task = Task::ready(());
18012 return None;
18013 }
18014
18015 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18016 cx.background_executor().timer(debounce).await;
18017
18018 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18019 buffers
18020 .into_iter()
18021 .filter_map(|buffer| {
18022 project
18023 .update(cx, |project, cx| {
18024 project.lsp_store().update(cx, |lsp_store, cx| {
18025 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18026 })
18027 })
18028 .ok()
18029 })
18030 .collect::<FuturesUnordered<_>>()
18031 }) else {
18032 return;
18033 };
18034
18035 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18036 match pull_task {
18037 Ok(()) => {
18038 if editor
18039 .update_in(cx, |editor, window, cx| {
18040 editor.update_diagnostics_state(window, cx);
18041 })
18042 .is_err()
18043 {
18044 return;
18045 }
18046 }
18047 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18048 }
18049 }
18050 });
18051
18052 Some(())
18053 }
18054
18055 pub fn set_selections_from_remote(
18056 &mut self,
18057 selections: Vec<Selection<Anchor>>,
18058 pending_selection: Option<Selection<Anchor>>,
18059 window: &mut Window,
18060 cx: &mut Context<Self>,
18061 ) {
18062 let old_cursor_position = self.selections.newest_anchor().head();
18063 self.selections.change_with(cx, |s| {
18064 s.select_anchors(selections);
18065 if let Some(pending_selection) = pending_selection {
18066 s.set_pending(pending_selection, SelectMode::Character);
18067 } else {
18068 s.clear_pending();
18069 }
18070 });
18071 self.selections_did_change(
18072 false,
18073 &old_cursor_position,
18074 SelectionEffects::default(),
18075 window,
18076 cx,
18077 );
18078 }
18079
18080 pub fn transact(
18081 &mut self,
18082 window: &mut Window,
18083 cx: &mut Context<Self>,
18084 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18085 ) -> Option<TransactionId> {
18086 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18087 this.start_transaction_at(Instant::now(), window, cx);
18088 update(this, window, cx);
18089 this.end_transaction_at(Instant::now(), cx)
18090 })
18091 }
18092
18093 pub fn start_transaction_at(
18094 &mut self,
18095 now: Instant,
18096 window: &mut Window,
18097 cx: &mut Context<Self>,
18098 ) -> Option<TransactionId> {
18099 self.end_selection(window, cx);
18100 if let Some(tx_id) = self
18101 .buffer
18102 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18103 {
18104 self.selection_history
18105 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18106 cx.emit(EditorEvent::TransactionBegun {
18107 transaction_id: tx_id,
18108 });
18109 Some(tx_id)
18110 } else {
18111 None
18112 }
18113 }
18114
18115 pub fn end_transaction_at(
18116 &mut self,
18117 now: Instant,
18118 cx: &mut Context<Self>,
18119 ) -> Option<TransactionId> {
18120 if let Some(transaction_id) = self
18121 .buffer
18122 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18123 {
18124 if let Some((_, end_selections)) =
18125 self.selection_history.transaction_mut(transaction_id)
18126 {
18127 *end_selections = Some(self.selections.disjoint_anchors_arc());
18128 } else {
18129 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18130 }
18131
18132 cx.emit(EditorEvent::Edited { transaction_id });
18133 Some(transaction_id)
18134 } else {
18135 None
18136 }
18137 }
18138
18139 pub fn modify_transaction_selection_history(
18140 &mut self,
18141 transaction_id: TransactionId,
18142 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18143 ) -> bool {
18144 self.selection_history
18145 .transaction_mut(transaction_id)
18146 .map(modify)
18147 .is_some()
18148 }
18149
18150 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18151 if self.selection_mark_mode {
18152 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18153 s.move_with(|_, sel| {
18154 sel.collapse_to(sel.head(), SelectionGoal::None);
18155 });
18156 })
18157 }
18158 self.selection_mark_mode = true;
18159 cx.notify();
18160 }
18161
18162 pub fn swap_selection_ends(
18163 &mut self,
18164 _: &actions::SwapSelectionEnds,
18165 window: &mut Window,
18166 cx: &mut Context<Self>,
18167 ) {
18168 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18169 s.move_with(|_, sel| {
18170 if sel.start != sel.end {
18171 sel.reversed = !sel.reversed
18172 }
18173 });
18174 });
18175 self.request_autoscroll(Autoscroll::newest(), cx);
18176 cx.notify();
18177 }
18178
18179 pub fn toggle_focus(
18180 workspace: &mut Workspace,
18181 _: &actions::ToggleFocus,
18182 window: &mut Window,
18183 cx: &mut Context<Workspace>,
18184 ) {
18185 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18186 return;
18187 };
18188 workspace.activate_item(&item, true, true, window, cx);
18189 }
18190
18191 pub fn toggle_fold(
18192 &mut self,
18193 _: &actions::ToggleFold,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18198 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18199 let selection = self.selections.newest::<Point>(&display_map);
18200
18201 let range = if selection.is_empty() {
18202 let point = selection.head().to_display_point(&display_map);
18203 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18204 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18205 .to_point(&display_map);
18206 start..end
18207 } else {
18208 selection.range()
18209 };
18210 if display_map.folds_in_range(range).next().is_some() {
18211 self.unfold_lines(&Default::default(), window, cx)
18212 } else {
18213 self.fold(&Default::default(), window, cx)
18214 }
18215 } else {
18216 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18217 let buffer_ids: HashSet<_> = self
18218 .selections
18219 .disjoint_anchor_ranges()
18220 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18221 .collect();
18222
18223 let should_unfold = buffer_ids
18224 .iter()
18225 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18226
18227 for buffer_id in buffer_ids {
18228 if should_unfold {
18229 self.unfold_buffer(buffer_id, cx);
18230 } else {
18231 self.fold_buffer(buffer_id, cx);
18232 }
18233 }
18234 }
18235 }
18236
18237 pub fn toggle_fold_recursive(
18238 &mut self,
18239 _: &actions::ToggleFoldRecursive,
18240 window: &mut Window,
18241 cx: &mut Context<Self>,
18242 ) {
18243 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18244
18245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18246 let range = if selection.is_empty() {
18247 let point = selection.head().to_display_point(&display_map);
18248 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18249 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18250 .to_point(&display_map);
18251 start..end
18252 } else {
18253 selection.range()
18254 };
18255 if display_map.folds_in_range(range).next().is_some() {
18256 self.unfold_recursive(&Default::default(), window, cx)
18257 } else {
18258 self.fold_recursive(&Default::default(), window, cx)
18259 }
18260 }
18261
18262 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18263 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18264 let mut to_fold = Vec::new();
18265 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18266 let selections = self.selections.all_adjusted(&display_map);
18267
18268 for selection in selections {
18269 let range = selection.range().sorted();
18270 let buffer_start_row = range.start.row;
18271
18272 if range.start.row != range.end.row {
18273 let mut found = false;
18274 let mut row = range.start.row;
18275 while row <= range.end.row {
18276 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18277 {
18278 found = true;
18279 row = crease.range().end.row + 1;
18280 to_fold.push(crease);
18281 } else {
18282 row += 1
18283 }
18284 }
18285 if found {
18286 continue;
18287 }
18288 }
18289
18290 for row in (0..=range.start.row).rev() {
18291 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18292 && crease.range().end.row >= buffer_start_row
18293 {
18294 to_fold.push(crease);
18295 if row <= range.start.row {
18296 break;
18297 }
18298 }
18299 }
18300 }
18301
18302 self.fold_creases(to_fold, true, window, cx);
18303 } else {
18304 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18305 let buffer_ids = self
18306 .selections
18307 .disjoint_anchor_ranges()
18308 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18309 .collect::<HashSet<_>>();
18310 for buffer_id in buffer_ids {
18311 self.fold_buffer(buffer_id, cx);
18312 }
18313 }
18314 }
18315
18316 pub fn toggle_fold_all(
18317 &mut self,
18318 _: &actions::ToggleFoldAll,
18319 window: &mut Window,
18320 cx: &mut Context<Self>,
18321 ) {
18322 if self.buffer.read(cx).is_singleton() {
18323 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18324 let has_folds = display_map
18325 .folds_in_range(0..display_map.buffer_snapshot().len())
18326 .next()
18327 .is_some();
18328
18329 if has_folds {
18330 self.unfold_all(&actions::UnfoldAll, window, cx);
18331 } else {
18332 self.fold_all(&actions::FoldAll, window, cx);
18333 }
18334 } else {
18335 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18336 let should_unfold = buffer_ids
18337 .iter()
18338 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18339
18340 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18341 editor
18342 .update_in(cx, |editor, _, cx| {
18343 for buffer_id in buffer_ids {
18344 if should_unfold {
18345 editor.unfold_buffer(buffer_id, cx);
18346 } else {
18347 editor.fold_buffer(buffer_id, cx);
18348 }
18349 }
18350 })
18351 .ok();
18352 });
18353 }
18354 }
18355
18356 fn fold_at_level(
18357 &mut self,
18358 fold_at: &FoldAtLevel,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 if !self.buffer.read(cx).is_singleton() {
18363 return;
18364 }
18365
18366 let fold_at_level = fold_at.0;
18367 let snapshot = self.buffer.read(cx).snapshot(cx);
18368 let mut to_fold = Vec::new();
18369 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18370
18371 let row_ranges_to_keep: Vec<Range<u32>> = self
18372 .selections
18373 .all::<Point>(&self.display_snapshot(cx))
18374 .into_iter()
18375 .map(|sel| sel.start.row..sel.end.row)
18376 .collect();
18377
18378 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18379 while start_row < end_row {
18380 match self
18381 .snapshot(window, cx)
18382 .crease_for_buffer_row(MultiBufferRow(start_row))
18383 {
18384 Some(crease) => {
18385 let nested_start_row = crease.range().start.row + 1;
18386 let nested_end_row = crease.range().end.row;
18387
18388 if current_level < fold_at_level {
18389 stack.push((nested_start_row, nested_end_row, current_level + 1));
18390 } else if current_level == fold_at_level {
18391 // Fold iff there is no selection completely contained within the fold region
18392 if !row_ranges_to_keep.iter().any(|selection| {
18393 selection.end >= nested_start_row
18394 && selection.start <= nested_end_row
18395 }) {
18396 to_fold.push(crease);
18397 }
18398 }
18399
18400 start_row = nested_end_row + 1;
18401 }
18402 None => start_row += 1,
18403 }
18404 }
18405 }
18406
18407 self.fold_creases(to_fold, true, window, cx);
18408 }
18409
18410 pub fn fold_at_level_1(
18411 &mut self,
18412 _: &actions::FoldAtLevel1,
18413 window: &mut Window,
18414 cx: &mut Context<Self>,
18415 ) {
18416 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18417 }
18418
18419 pub fn fold_at_level_2(
18420 &mut self,
18421 _: &actions::FoldAtLevel2,
18422 window: &mut Window,
18423 cx: &mut Context<Self>,
18424 ) {
18425 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18426 }
18427
18428 pub fn fold_at_level_3(
18429 &mut self,
18430 _: &actions::FoldAtLevel3,
18431 window: &mut Window,
18432 cx: &mut Context<Self>,
18433 ) {
18434 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18435 }
18436
18437 pub fn fold_at_level_4(
18438 &mut self,
18439 _: &actions::FoldAtLevel4,
18440 window: &mut Window,
18441 cx: &mut Context<Self>,
18442 ) {
18443 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18444 }
18445
18446 pub fn fold_at_level_5(
18447 &mut self,
18448 _: &actions::FoldAtLevel5,
18449 window: &mut Window,
18450 cx: &mut Context<Self>,
18451 ) {
18452 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18453 }
18454
18455 pub fn fold_at_level_6(
18456 &mut self,
18457 _: &actions::FoldAtLevel6,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18462 }
18463
18464 pub fn fold_at_level_7(
18465 &mut self,
18466 _: &actions::FoldAtLevel7,
18467 window: &mut Window,
18468 cx: &mut Context<Self>,
18469 ) {
18470 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18471 }
18472
18473 pub fn fold_at_level_8(
18474 &mut self,
18475 _: &actions::FoldAtLevel8,
18476 window: &mut Window,
18477 cx: &mut Context<Self>,
18478 ) {
18479 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18480 }
18481
18482 pub fn fold_at_level_9(
18483 &mut self,
18484 _: &actions::FoldAtLevel9,
18485 window: &mut Window,
18486 cx: &mut Context<Self>,
18487 ) {
18488 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18489 }
18490
18491 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18492 if self.buffer.read(cx).is_singleton() {
18493 let mut fold_ranges = Vec::new();
18494 let snapshot = self.buffer.read(cx).snapshot(cx);
18495
18496 for row in 0..snapshot.max_row().0 {
18497 if let Some(foldable_range) = self
18498 .snapshot(window, cx)
18499 .crease_for_buffer_row(MultiBufferRow(row))
18500 {
18501 fold_ranges.push(foldable_range);
18502 }
18503 }
18504
18505 self.fold_creases(fold_ranges, true, window, cx);
18506 } else {
18507 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18508 editor
18509 .update_in(cx, |editor, _, cx| {
18510 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18511 editor.fold_buffer(buffer_id, cx);
18512 }
18513 })
18514 .ok();
18515 });
18516 }
18517 }
18518
18519 pub fn fold_function_bodies(
18520 &mut self,
18521 _: &actions::FoldFunctionBodies,
18522 window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 let snapshot = self.buffer.read(cx).snapshot(cx);
18526
18527 let ranges = snapshot
18528 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18529 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18530 .collect::<Vec<_>>();
18531
18532 let creases = ranges
18533 .into_iter()
18534 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18535 .collect();
18536
18537 self.fold_creases(creases, true, window, cx);
18538 }
18539
18540 pub fn fold_recursive(
18541 &mut self,
18542 _: &actions::FoldRecursive,
18543 window: &mut Window,
18544 cx: &mut Context<Self>,
18545 ) {
18546 let mut to_fold = Vec::new();
18547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18548 let selections = self.selections.all_adjusted(&display_map);
18549
18550 for selection in selections {
18551 let range = selection.range().sorted();
18552 let buffer_start_row = range.start.row;
18553
18554 if range.start.row != range.end.row {
18555 let mut found = false;
18556 for row in range.start.row..=range.end.row {
18557 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18558 found = true;
18559 to_fold.push(crease);
18560 }
18561 }
18562 if found {
18563 continue;
18564 }
18565 }
18566
18567 for row in (0..=range.start.row).rev() {
18568 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18569 if crease.range().end.row >= buffer_start_row {
18570 to_fold.push(crease);
18571 } else {
18572 break;
18573 }
18574 }
18575 }
18576 }
18577
18578 self.fold_creases(to_fold, true, window, cx);
18579 }
18580
18581 pub fn fold_at(
18582 &mut self,
18583 buffer_row: MultiBufferRow,
18584 window: &mut Window,
18585 cx: &mut Context<Self>,
18586 ) {
18587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18588
18589 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18590 let autoscroll = self
18591 .selections
18592 .all::<Point>(&display_map)
18593 .iter()
18594 .any(|selection| crease.range().overlaps(&selection.range()));
18595
18596 self.fold_creases(vec![crease], autoscroll, window, cx);
18597 }
18598 }
18599
18600 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18601 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18603 let buffer = display_map.buffer_snapshot();
18604 let selections = self.selections.all::<Point>(&display_map);
18605 let ranges = selections
18606 .iter()
18607 .map(|s| {
18608 let range = s.display_range(&display_map).sorted();
18609 let mut start = range.start.to_point(&display_map);
18610 let mut end = range.end.to_point(&display_map);
18611 start.column = 0;
18612 end.column = buffer.line_len(MultiBufferRow(end.row));
18613 start..end
18614 })
18615 .collect::<Vec<_>>();
18616
18617 self.unfold_ranges(&ranges, true, true, cx);
18618 } else {
18619 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18620 let buffer_ids = self
18621 .selections
18622 .disjoint_anchor_ranges()
18623 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18624 .collect::<HashSet<_>>();
18625 for buffer_id in buffer_ids {
18626 self.unfold_buffer(buffer_id, cx);
18627 }
18628 }
18629 }
18630
18631 pub fn unfold_recursive(
18632 &mut self,
18633 _: &UnfoldRecursive,
18634 _window: &mut Window,
18635 cx: &mut Context<Self>,
18636 ) {
18637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18638 let selections = self.selections.all::<Point>(&display_map);
18639 let ranges = selections
18640 .iter()
18641 .map(|s| {
18642 let mut range = s.display_range(&display_map).sorted();
18643 *range.start.column_mut() = 0;
18644 *range.end.column_mut() = display_map.line_len(range.end.row());
18645 let start = range.start.to_point(&display_map);
18646 let end = range.end.to_point(&display_map);
18647 start..end
18648 })
18649 .collect::<Vec<_>>();
18650
18651 self.unfold_ranges(&ranges, true, true, cx);
18652 }
18653
18654 pub fn unfold_at(
18655 &mut self,
18656 buffer_row: MultiBufferRow,
18657 _window: &mut Window,
18658 cx: &mut Context<Self>,
18659 ) {
18660 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18661
18662 let intersection_range = Point::new(buffer_row.0, 0)
18663 ..Point::new(
18664 buffer_row.0,
18665 display_map.buffer_snapshot().line_len(buffer_row),
18666 );
18667
18668 let autoscroll = self
18669 .selections
18670 .all::<Point>(&display_map)
18671 .iter()
18672 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18673
18674 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18675 }
18676
18677 pub fn unfold_all(
18678 &mut self,
18679 _: &actions::UnfoldAll,
18680 _window: &mut Window,
18681 cx: &mut Context<Self>,
18682 ) {
18683 if self.buffer.read(cx).is_singleton() {
18684 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18685 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18686 } else {
18687 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18688 editor
18689 .update(cx, |editor, cx| {
18690 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18691 editor.unfold_buffer(buffer_id, cx);
18692 }
18693 })
18694 .ok();
18695 });
18696 }
18697 }
18698
18699 pub fn fold_selected_ranges(
18700 &mut self,
18701 _: &FoldSelectedRanges,
18702 window: &mut Window,
18703 cx: &mut Context<Self>,
18704 ) {
18705 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18706 let selections = self.selections.all_adjusted(&display_map);
18707 let ranges = selections
18708 .into_iter()
18709 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18710 .collect::<Vec<_>>();
18711 self.fold_creases(ranges, true, window, cx);
18712 }
18713
18714 pub fn fold_ranges<T: ToOffset + Clone>(
18715 &mut self,
18716 ranges: Vec<Range<T>>,
18717 auto_scroll: bool,
18718 window: &mut Window,
18719 cx: &mut Context<Self>,
18720 ) {
18721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18722 let ranges = ranges
18723 .into_iter()
18724 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18725 .collect::<Vec<_>>();
18726 self.fold_creases(ranges, auto_scroll, window, cx);
18727 }
18728
18729 pub fn fold_creases<T: ToOffset + Clone>(
18730 &mut self,
18731 creases: Vec<Crease<T>>,
18732 auto_scroll: bool,
18733 _window: &mut Window,
18734 cx: &mut Context<Self>,
18735 ) {
18736 if creases.is_empty() {
18737 return;
18738 }
18739
18740 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18741
18742 if auto_scroll {
18743 self.request_autoscroll(Autoscroll::fit(), cx);
18744 }
18745
18746 cx.notify();
18747
18748 self.scrollbar_marker_state.dirty = true;
18749 self.folds_did_change(cx);
18750 }
18751
18752 /// Removes any folds whose ranges intersect any of the given ranges.
18753 pub fn unfold_ranges<T: ToOffset + Clone>(
18754 &mut self,
18755 ranges: &[Range<T>],
18756 inclusive: bool,
18757 auto_scroll: bool,
18758 cx: &mut Context<Self>,
18759 ) {
18760 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18761 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18762 });
18763 self.folds_did_change(cx);
18764 }
18765
18766 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18767 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18768 return;
18769 }
18770 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18771 self.display_map.update(cx, |display_map, cx| {
18772 display_map.fold_buffers([buffer_id], cx)
18773 });
18774 cx.emit(EditorEvent::BufferFoldToggled {
18775 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18776 folded: true,
18777 });
18778 cx.notify();
18779 }
18780
18781 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18782 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18783 return;
18784 }
18785 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18786 self.display_map.update(cx, |display_map, cx| {
18787 display_map.unfold_buffers([buffer_id], cx);
18788 });
18789 cx.emit(EditorEvent::BufferFoldToggled {
18790 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18791 folded: false,
18792 });
18793 cx.notify();
18794 }
18795
18796 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18797 self.display_map.read(cx).is_buffer_folded(buffer)
18798 }
18799
18800 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18801 self.display_map.read(cx).folded_buffers()
18802 }
18803
18804 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18805 self.display_map.update(cx, |display_map, cx| {
18806 display_map.disable_header_for_buffer(buffer_id, cx);
18807 });
18808 cx.notify();
18809 }
18810
18811 /// Removes any folds with the given ranges.
18812 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18813 &mut self,
18814 ranges: &[Range<T>],
18815 type_id: TypeId,
18816 auto_scroll: bool,
18817 cx: &mut Context<Self>,
18818 ) {
18819 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18820 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18821 });
18822 self.folds_did_change(cx);
18823 }
18824
18825 fn remove_folds_with<T: ToOffset + Clone>(
18826 &mut self,
18827 ranges: &[Range<T>],
18828 auto_scroll: bool,
18829 cx: &mut Context<Self>,
18830 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18831 ) {
18832 if ranges.is_empty() {
18833 return;
18834 }
18835
18836 let mut buffers_affected = HashSet::default();
18837 let multi_buffer = self.buffer().read(cx);
18838 for range in ranges {
18839 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18840 buffers_affected.insert(buffer.read(cx).remote_id());
18841 };
18842 }
18843
18844 self.display_map.update(cx, update);
18845
18846 if auto_scroll {
18847 self.request_autoscroll(Autoscroll::fit(), cx);
18848 }
18849
18850 cx.notify();
18851 self.scrollbar_marker_state.dirty = true;
18852 self.active_indent_guides_state.dirty = true;
18853 }
18854
18855 pub fn update_renderer_widths(
18856 &mut self,
18857 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18858 cx: &mut Context<Self>,
18859 ) -> bool {
18860 self.display_map
18861 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18862 }
18863
18864 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18865 self.display_map.read(cx).fold_placeholder.clone()
18866 }
18867
18868 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18869 self.buffer.update(cx, |buffer, cx| {
18870 buffer.set_all_diff_hunks_expanded(cx);
18871 });
18872 }
18873
18874 pub fn expand_all_diff_hunks(
18875 &mut self,
18876 _: &ExpandAllDiffHunks,
18877 _window: &mut Window,
18878 cx: &mut Context<Self>,
18879 ) {
18880 self.buffer.update(cx, |buffer, cx| {
18881 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18882 });
18883 }
18884
18885 pub fn collapse_all_diff_hunks(
18886 &mut self,
18887 _: &CollapseAllDiffHunks,
18888 _window: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 self.buffer.update(cx, |buffer, cx| {
18892 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18893 });
18894 }
18895
18896 pub fn toggle_selected_diff_hunks(
18897 &mut self,
18898 _: &ToggleSelectedDiffHunks,
18899 _window: &mut Window,
18900 cx: &mut Context<Self>,
18901 ) {
18902 let ranges: Vec<_> = self
18903 .selections
18904 .disjoint_anchors()
18905 .iter()
18906 .map(|s| s.range())
18907 .collect();
18908 self.toggle_diff_hunks_in_ranges(ranges, cx);
18909 }
18910
18911 pub fn diff_hunks_in_ranges<'a>(
18912 &'a self,
18913 ranges: &'a [Range<Anchor>],
18914 buffer: &'a MultiBufferSnapshot,
18915 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18916 ranges.iter().flat_map(move |range| {
18917 let end_excerpt_id = range.end.excerpt_id;
18918 let range = range.to_point(buffer);
18919 let mut peek_end = range.end;
18920 if range.end.row < buffer.max_row().0 {
18921 peek_end = Point::new(range.end.row + 1, 0);
18922 }
18923 buffer
18924 .diff_hunks_in_range(range.start..peek_end)
18925 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18926 })
18927 }
18928
18929 pub fn has_stageable_diff_hunks_in_ranges(
18930 &self,
18931 ranges: &[Range<Anchor>],
18932 snapshot: &MultiBufferSnapshot,
18933 ) -> bool {
18934 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18935 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18936 }
18937
18938 pub fn toggle_staged_selected_diff_hunks(
18939 &mut self,
18940 _: &::git::ToggleStaged,
18941 _: &mut Window,
18942 cx: &mut Context<Self>,
18943 ) {
18944 let snapshot = self.buffer.read(cx).snapshot(cx);
18945 let ranges: Vec<_> = self
18946 .selections
18947 .disjoint_anchors()
18948 .iter()
18949 .map(|s| s.range())
18950 .collect();
18951 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18952 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18953 }
18954
18955 pub fn set_render_diff_hunk_controls(
18956 &mut self,
18957 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18958 cx: &mut Context<Self>,
18959 ) {
18960 self.render_diff_hunk_controls = render_diff_hunk_controls;
18961 cx.notify();
18962 }
18963
18964 pub fn stage_and_next(
18965 &mut self,
18966 _: &::git::StageAndNext,
18967 window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 self.do_stage_or_unstage_and_next(true, window, cx);
18971 }
18972
18973 pub fn unstage_and_next(
18974 &mut self,
18975 _: &::git::UnstageAndNext,
18976 window: &mut Window,
18977 cx: &mut Context<Self>,
18978 ) {
18979 self.do_stage_or_unstage_and_next(false, window, cx);
18980 }
18981
18982 pub fn stage_or_unstage_diff_hunks(
18983 &mut self,
18984 stage: bool,
18985 ranges: Vec<Range<Anchor>>,
18986 cx: &mut Context<Self>,
18987 ) {
18988 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18989 cx.spawn(async move |this, cx| {
18990 task.await?;
18991 this.update(cx, |this, cx| {
18992 let snapshot = this.buffer.read(cx).snapshot(cx);
18993 let chunk_by = this
18994 .diff_hunks_in_ranges(&ranges, &snapshot)
18995 .chunk_by(|hunk| hunk.buffer_id);
18996 for (buffer_id, hunks) in &chunk_by {
18997 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18998 }
18999 })
19000 })
19001 .detach_and_log_err(cx);
19002 }
19003
19004 fn save_buffers_for_ranges_if_needed(
19005 &mut self,
19006 ranges: &[Range<Anchor>],
19007 cx: &mut Context<Editor>,
19008 ) -> Task<Result<()>> {
19009 let multibuffer = self.buffer.read(cx);
19010 let snapshot = multibuffer.read(cx);
19011 let buffer_ids: HashSet<_> = ranges
19012 .iter()
19013 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19014 .collect();
19015 drop(snapshot);
19016
19017 let mut buffers = HashSet::default();
19018 for buffer_id in buffer_ids {
19019 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19020 let buffer = buffer_entity.read(cx);
19021 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19022 {
19023 buffers.insert(buffer_entity);
19024 }
19025 }
19026 }
19027
19028 if let Some(project) = &self.project {
19029 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19030 } else {
19031 Task::ready(Ok(()))
19032 }
19033 }
19034
19035 fn do_stage_or_unstage_and_next(
19036 &mut self,
19037 stage: bool,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19042
19043 if ranges.iter().any(|range| range.start != range.end) {
19044 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19045 return;
19046 }
19047
19048 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19049 let snapshot = self.snapshot(window, cx);
19050 let position = self
19051 .selections
19052 .newest::<Point>(&snapshot.display_snapshot)
19053 .head();
19054 let mut row = snapshot
19055 .buffer_snapshot()
19056 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19057 .find(|hunk| hunk.row_range.start.0 > position.row)
19058 .map(|hunk| hunk.row_range.start);
19059
19060 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19061 // Outside of the project diff editor, wrap around to the beginning.
19062 if !all_diff_hunks_expanded {
19063 row = row.or_else(|| {
19064 snapshot
19065 .buffer_snapshot()
19066 .diff_hunks_in_range(Point::zero()..position)
19067 .find(|hunk| hunk.row_range.end.0 < position.row)
19068 .map(|hunk| hunk.row_range.start)
19069 });
19070 }
19071
19072 if let Some(row) = row {
19073 let destination = Point::new(row.0, 0);
19074 let autoscroll = Autoscroll::center();
19075
19076 self.unfold_ranges(&[destination..destination], false, false, cx);
19077 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19078 s.select_ranges([destination..destination]);
19079 });
19080 }
19081 }
19082
19083 fn do_stage_or_unstage(
19084 &self,
19085 stage: bool,
19086 buffer_id: BufferId,
19087 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19088 cx: &mut App,
19089 ) -> Option<()> {
19090 let project = self.project()?;
19091 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19092 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19093 let buffer_snapshot = buffer.read(cx).snapshot();
19094 let file_exists = buffer_snapshot
19095 .file()
19096 .is_some_and(|file| file.disk_state().exists());
19097 diff.update(cx, |diff, cx| {
19098 diff.stage_or_unstage_hunks(
19099 stage,
19100 &hunks
19101 .map(|hunk| buffer_diff::DiffHunk {
19102 buffer_range: hunk.buffer_range,
19103 diff_base_byte_range: hunk.diff_base_byte_range,
19104 secondary_status: hunk.secondary_status,
19105 range: Point::zero()..Point::zero(), // unused
19106 })
19107 .collect::<Vec<_>>(),
19108 &buffer_snapshot,
19109 file_exists,
19110 cx,
19111 )
19112 });
19113 None
19114 }
19115
19116 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19117 let ranges: Vec<_> = self
19118 .selections
19119 .disjoint_anchors()
19120 .iter()
19121 .map(|s| s.range())
19122 .collect();
19123 self.buffer
19124 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19125 }
19126
19127 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19128 self.buffer.update(cx, |buffer, cx| {
19129 let ranges = vec![Anchor::min()..Anchor::max()];
19130 if !buffer.all_diff_hunks_expanded()
19131 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19132 {
19133 buffer.collapse_diff_hunks(ranges, cx);
19134 true
19135 } else {
19136 false
19137 }
19138 })
19139 }
19140
19141 fn toggle_diff_hunks_in_ranges(
19142 &mut self,
19143 ranges: Vec<Range<Anchor>>,
19144 cx: &mut Context<Editor>,
19145 ) {
19146 self.buffer.update(cx, |buffer, cx| {
19147 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19148 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19149 })
19150 }
19151
19152 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19153 self.buffer.update(cx, |buffer, cx| {
19154 let snapshot = buffer.snapshot(cx);
19155 let excerpt_id = range.end.excerpt_id;
19156 let point_range = range.to_point(&snapshot);
19157 let expand = !buffer.single_hunk_is_expanded(range, cx);
19158 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19159 })
19160 }
19161
19162 pub(crate) fn apply_all_diff_hunks(
19163 &mut self,
19164 _: &ApplyAllDiffHunks,
19165 window: &mut Window,
19166 cx: &mut Context<Self>,
19167 ) {
19168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19169
19170 let buffers = self.buffer.read(cx).all_buffers();
19171 for branch_buffer in buffers {
19172 branch_buffer.update(cx, |branch_buffer, cx| {
19173 branch_buffer.merge_into_base(Vec::new(), cx);
19174 });
19175 }
19176
19177 if let Some(project) = self.project.clone() {
19178 self.save(
19179 SaveOptions {
19180 format: true,
19181 autosave: false,
19182 },
19183 project,
19184 window,
19185 cx,
19186 )
19187 .detach_and_log_err(cx);
19188 }
19189 }
19190
19191 pub(crate) fn apply_selected_diff_hunks(
19192 &mut self,
19193 _: &ApplyDiffHunk,
19194 window: &mut Window,
19195 cx: &mut Context<Self>,
19196 ) {
19197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19198 let snapshot = self.snapshot(window, cx);
19199 let hunks = snapshot.hunks_for_ranges(
19200 self.selections
19201 .all(&snapshot.display_snapshot)
19202 .into_iter()
19203 .map(|selection| selection.range()),
19204 );
19205 let mut ranges_by_buffer = HashMap::default();
19206 self.transact(window, cx, |editor, _window, cx| {
19207 for hunk in hunks {
19208 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19209 ranges_by_buffer
19210 .entry(buffer.clone())
19211 .or_insert_with(Vec::new)
19212 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19213 }
19214 }
19215
19216 for (buffer, ranges) in ranges_by_buffer {
19217 buffer.update(cx, |buffer, cx| {
19218 buffer.merge_into_base(ranges, cx);
19219 });
19220 }
19221 });
19222
19223 if let Some(project) = self.project.clone() {
19224 self.save(
19225 SaveOptions {
19226 format: true,
19227 autosave: false,
19228 },
19229 project,
19230 window,
19231 cx,
19232 )
19233 .detach_and_log_err(cx);
19234 }
19235 }
19236
19237 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19238 if hovered != self.gutter_hovered {
19239 self.gutter_hovered = hovered;
19240 cx.notify();
19241 }
19242 }
19243
19244 pub fn insert_blocks(
19245 &mut self,
19246 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19247 autoscroll: Option<Autoscroll>,
19248 cx: &mut Context<Self>,
19249 ) -> Vec<CustomBlockId> {
19250 let blocks = self
19251 .display_map
19252 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19253 if let Some(autoscroll) = autoscroll {
19254 self.request_autoscroll(autoscroll, cx);
19255 }
19256 cx.notify();
19257 blocks
19258 }
19259
19260 pub fn resize_blocks(
19261 &mut self,
19262 heights: HashMap<CustomBlockId, u32>,
19263 autoscroll: Option<Autoscroll>,
19264 cx: &mut Context<Self>,
19265 ) {
19266 self.display_map
19267 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19268 if let Some(autoscroll) = autoscroll {
19269 self.request_autoscroll(autoscroll, cx);
19270 }
19271 cx.notify();
19272 }
19273
19274 pub fn replace_blocks(
19275 &mut self,
19276 renderers: HashMap<CustomBlockId, RenderBlock>,
19277 autoscroll: Option<Autoscroll>,
19278 cx: &mut Context<Self>,
19279 ) {
19280 self.display_map
19281 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19282 if let Some(autoscroll) = autoscroll {
19283 self.request_autoscroll(autoscroll, cx);
19284 }
19285 cx.notify();
19286 }
19287
19288 pub fn remove_blocks(
19289 &mut self,
19290 block_ids: HashSet<CustomBlockId>,
19291 autoscroll: Option<Autoscroll>,
19292 cx: &mut Context<Self>,
19293 ) {
19294 self.display_map.update(cx, |display_map, cx| {
19295 display_map.remove_blocks(block_ids, cx)
19296 });
19297 if let Some(autoscroll) = autoscroll {
19298 self.request_autoscroll(autoscroll, cx);
19299 }
19300 cx.notify();
19301 }
19302
19303 pub fn row_for_block(
19304 &self,
19305 block_id: CustomBlockId,
19306 cx: &mut Context<Self>,
19307 ) -> Option<DisplayRow> {
19308 self.display_map
19309 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19310 }
19311
19312 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19313 self.focused_block = Some(focused_block);
19314 }
19315
19316 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19317 self.focused_block.take()
19318 }
19319
19320 pub fn insert_creases(
19321 &mut self,
19322 creases: impl IntoIterator<Item = Crease<Anchor>>,
19323 cx: &mut Context<Self>,
19324 ) -> Vec<CreaseId> {
19325 self.display_map
19326 .update(cx, |map, cx| map.insert_creases(creases, cx))
19327 }
19328
19329 pub fn remove_creases(
19330 &mut self,
19331 ids: impl IntoIterator<Item = CreaseId>,
19332 cx: &mut Context<Self>,
19333 ) -> Vec<(CreaseId, Range<Anchor>)> {
19334 self.display_map
19335 .update(cx, |map, cx| map.remove_creases(ids, cx))
19336 }
19337
19338 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19339 self.display_map
19340 .update(cx, |map, cx| map.snapshot(cx))
19341 .longest_row()
19342 }
19343
19344 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19345 self.display_map
19346 .update(cx, |map, cx| map.snapshot(cx))
19347 .max_point()
19348 }
19349
19350 pub fn text(&self, cx: &App) -> String {
19351 self.buffer.read(cx).read(cx).text()
19352 }
19353
19354 pub fn is_empty(&self, cx: &App) -> bool {
19355 self.buffer.read(cx).read(cx).is_empty()
19356 }
19357
19358 pub fn text_option(&self, cx: &App) -> Option<String> {
19359 let text = self.text(cx);
19360 let text = text.trim();
19361
19362 if text.is_empty() {
19363 return None;
19364 }
19365
19366 Some(text.to_string())
19367 }
19368
19369 pub fn set_text(
19370 &mut self,
19371 text: impl Into<Arc<str>>,
19372 window: &mut Window,
19373 cx: &mut Context<Self>,
19374 ) {
19375 self.transact(window, cx, |this, _, cx| {
19376 this.buffer
19377 .read(cx)
19378 .as_singleton()
19379 .expect("you can only call set_text on editors for singleton buffers")
19380 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19381 });
19382 }
19383
19384 pub fn display_text(&self, cx: &mut App) -> String {
19385 self.display_map
19386 .update(cx, |map, cx| map.snapshot(cx))
19387 .text()
19388 }
19389
19390 fn create_minimap(
19391 &self,
19392 minimap_settings: MinimapSettings,
19393 window: &mut Window,
19394 cx: &mut Context<Self>,
19395 ) -> Option<Entity<Self>> {
19396 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19397 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19398 }
19399
19400 fn initialize_new_minimap(
19401 &self,
19402 minimap_settings: MinimapSettings,
19403 window: &mut Window,
19404 cx: &mut Context<Self>,
19405 ) -> Entity<Self> {
19406 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19407
19408 let mut minimap = Editor::new_internal(
19409 EditorMode::Minimap {
19410 parent: cx.weak_entity(),
19411 },
19412 self.buffer.clone(),
19413 None,
19414 Some(self.display_map.clone()),
19415 window,
19416 cx,
19417 );
19418 minimap.scroll_manager.clone_state(&self.scroll_manager);
19419 minimap.set_text_style_refinement(TextStyleRefinement {
19420 font_size: Some(MINIMAP_FONT_SIZE),
19421 font_weight: Some(MINIMAP_FONT_WEIGHT),
19422 ..Default::default()
19423 });
19424 minimap.update_minimap_configuration(minimap_settings, cx);
19425 cx.new(|_| minimap)
19426 }
19427
19428 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19429 let current_line_highlight = minimap_settings
19430 .current_line_highlight
19431 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19432 self.set_current_line_highlight(Some(current_line_highlight));
19433 }
19434
19435 pub fn minimap(&self) -> Option<&Entity<Self>> {
19436 self.minimap
19437 .as_ref()
19438 .filter(|_| self.minimap_visibility.visible())
19439 }
19440
19441 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19442 let mut wrap_guides = smallvec![];
19443
19444 if self.show_wrap_guides == Some(false) {
19445 return wrap_guides;
19446 }
19447
19448 let settings = self.buffer.read(cx).language_settings(cx);
19449 if settings.show_wrap_guides {
19450 match self.soft_wrap_mode(cx) {
19451 SoftWrap::Column(soft_wrap) => {
19452 wrap_guides.push((soft_wrap as usize, true));
19453 }
19454 SoftWrap::Bounded(soft_wrap) => {
19455 wrap_guides.push((soft_wrap as usize, true));
19456 }
19457 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19458 }
19459 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19460 }
19461
19462 wrap_guides
19463 }
19464
19465 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19466 let settings = self.buffer.read(cx).language_settings(cx);
19467 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19468 match mode {
19469 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19470 SoftWrap::None
19471 }
19472 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19473 language_settings::SoftWrap::PreferredLineLength => {
19474 SoftWrap::Column(settings.preferred_line_length)
19475 }
19476 language_settings::SoftWrap::Bounded => {
19477 SoftWrap::Bounded(settings.preferred_line_length)
19478 }
19479 }
19480 }
19481
19482 pub fn set_soft_wrap_mode(
19483 &mut self,
19484 mode: language_settings::SoftWrap,
19485
19486 cx: &mut Context<Self>,
19487 ) {
19488 self.soft_wrap_mode_override = Some(mode);
19489 cx.notify();
19490 }
19491
19492 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19493 self.hard_wrap = hard_wrap;
19494 cx.notify();
19495 }
19496
19497 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19498 self.text_style_refinement = Some(style);
19499 }
19500
19501 /// called by the Element so we know what style we were most recently rendered with.
19502 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19503 // We intentionally do not inform the display map about the minimap style
19504 // so that wrapping is not recalculated and stays consistent for the editor
19505 // and its linked minimap.
19506 if !self.mode.is_minimap() {
19507 let font = style.text.font();
19508 let font_size = style.text.font_size.to_pixels(window.rem_size());
19509 let display_map = self
19510 .placeholder_display_map
19511 .as_ref()
19512 .filter(|_| self.is_empty(cx))
19513 .unwrap_or(&self.display_map);
19514
19515 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19516 }
19517 self.style = Some(style);
19518 }
19519
19520 pub fn style(&self) -> Option<&EditorStyle> {
19521 self.style.as_ref()
19522 }
19523
19524 // Called by the element. This method is not designed to be called outside of the editor
19525 // element's layout code because it does not notify when rewrapping is computed synchronously.
19526 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19527 if self.is_empty(cx) {
19528 self.placeholder_display_map
19529 .as_ref()
19530 .map_or(false, |display_map| {
19531 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19532 })
19533 } else {
19534 self.display_map
19535 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19536 }
19537 }
19538
19539 pub fn set_soft_wrap(&mut self) {
19540 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19541 }
19542
19543 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19544 if self.soft_wrap_mode_override.is_some() {
19545 self.soft_wrap_mode_override.take();
19546 } else {
19547 let soft_wrap = match self.soft_wrap_mode(cx) {
19548 SoftWrap::GitDiff => return,
19549 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19550 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19551 language_settings::SoftWrap::None
19552 }
19553 };
19554 self.soft_wrap_mode_override = Some(soft_wrap);
19555 }
19556 cx.notify();
19557 }
19558
19559 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19560 let Some(workspace) = self.workspace() else {
19561 return;
19562 };
19563 let fs = workspace.read(cx).app_state().fs.clone();
19564 let current_show = TabBarSettings::get_global(cx).show;
19565 update_settings_file(fs, cx, move |setting, _| {
19566 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19567 });
19568 }
19569
19570 pub fn toggle_indent_guides(
19571 &mut self,
19572 _: &ToggleIndentGuides,
19573 _: &mut Window,
19574 cx: &mut Context<Self>,
19575 ) {
19576 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19577 self.buffer
19578 .read(cx)
19579 .language_settings(cx)
19580 .indent_guides
19581 .enabled
19582 });
19583 self.show_indent_guides = Some(!currently_enabled);
19584 cx.notify();
19585 }
19586
19587 fn should_show_indent_guides(&self) -> Option<bool> {
19588 self.show_indent_guides
19589 }
19590
19591 pub fn toggle_line_numbers(
19592 &mut self,
19593 _: &ToggleLineNumbers,
19594 _: &mut Window,
19595 cx: &mut Context<Self>,
19596 ) {
19597 let mut editor_settings = EditorSettings::get_global(cx).clone();
19598 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19599 EditorSettings::override_global(editor_settings, cx);
19600 }
19601
19602 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19603 if let Some(show_line_numbers) = self.show_line_numbers {
19604 return show_line_numbers;
19605 }
19606 EditorSettings::get_global(cx).gutter.line_numbers
19607 }
19608
19609 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19610 match (
19611 self.use_relative_line_numbers,
19612 EditorSettings::get_global(cx).relative_line_numbers,
19613 ) {
19614 (None, setting) => setting,
19615 (Some(false), _) => RelativeLineNumbers::Disabled,
19616 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19617 (Some(true), _) => RelativeLineNumbers::Enabled,
19618 }
19619 }
19620
19621 pub fn toggle_relative_line_numbers(
19622 &mut self,
19623 _: &ToggleRelativeLineNumbers,
19624 _: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) {
19627 let is_relative = self.relative_line_numbers(cx);
19628 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19629 }
19630
19631 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19632 self.use_relative_line_numbers = is_relative;
19633 cx.notify();
19634 }
19635
19636 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19637 self.show_gutter = show_gutter;
19638 cx.notify();
19639 }
19640
19641 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19642 self.show_scrollbars = ScrollbarAxes {
19643 horizontal: show,
19644 vertical: show,
19645 };
19646 cx.notify();
19647 }
19648
19649 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19650 self.show_scrollbars.vertical = show;
19651 cx.notify();
19652 }
19653
19654 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19655 self.show_scrollbars.horizontal = show;
19656 cx.notify();
19657 }
19658
19659 pub fn set_minimap_visibility(
19660 &mut self,
19661 minimap_visibility: MinimapVisibility,
19662 window: &mut Window,
19663 cx: &mut Context<Self>,
19664 ) {
19665 if self.minimap_visibility != minimap_visibility {
19666 if minimap_visibility.visible() && self.minimap.is_none() {
19667 let minimap_settings = EditorSettings::get_global(cx).minimap;
19668 self.minimap =
19669 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19670 }
19671 self.minimap_visibility = minimap_visibility;
19672 cx.notify();
19673 }
19674 }
19675
19676 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19677 self.set_show_scrollbars(false, cx);
19678 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19679 }
19680
19681 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19682 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19683 }
19684
19685 /// Normally the text in full mode and auto height editors is padded on the
19686 /// left side by roughly half a character width for improved hit testing.
19687 ///
19688 /// Use this method to disable this for cases where this is not wanted (e.g.
19689 /// if you want to align the editor text with some other text above or below)
19690 /// or if you want to add this padding to single-line editors.
19691 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19692 self.offset_content = offset_content;
19693 cx.notify();
19694 }
19695
19696 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19697 self.show_line_numbers = Some(show_line_numbers);
19698 cx.notify();
19699 }
19700
19701 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19702 self.disable_expand_excerpt_buttons = true;
19703 cx.notify();
19704 }
19705
19706 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19707 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19708 cx.notify();
19709 }
19710
19711 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19712 self.show_code_actions = Some(show_code_actions);
19713 cx.notify();
19714 }
19715
19716 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19717 self.show_runnables = Some(show_runnables);
19718 cx.notify();
19719 }
19720
19721 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19722 self.show_breakpoints = Some(show_breakpoints);
19723 cx.notify();
19724 }
19725
19726 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19727 if self.display_map.read(cx).masked != masked {
19728 self.display_map.update(cx, |map, _| map.masked = masked);
19729 }
19730 cx.notify()
19731 }
19732
19733 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19734 self.show_wrap_guides = Some(show_wrap_guides);
19735 cx.notify();
19736 }
19737
19738 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19739 self.show_indent_guides = Some(show_indent_guides);
19740 cx.notify();
19741 }
19742
19743 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19744 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19745 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19746 && let Some(dir) = file.abs_path(cx).parent()
19747 {
19748 return Some(dir.to_owned());
19749 }
19750 }
19751
19752 None
19753 }
19754
19755 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19756 self.active_excerpt(cx)?
19757 .1
19758 .read(cx)
19759 .file()
19760 .and_then(|f| f.as_local())
19761 }
19762
19763 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19764 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19765 let buffer = buffer.read(cx);
19766 if let Some(project_path) = buffer.project_path(cx) {
19767 let project = self.project()?.read(cx);
19768 project.absolute_path(&project_path, cx)
19769 } else {
19770 buffer
19771 .file()
19772 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19773 }
19774 })
19775 }
19776
19777 pub fn reveal_in_finder(
19778 &mut self,
19779 _: &RevealInFileManager,
19780 _window: &mut Window,
19781 cx: &mut Context<Self>,
19782 ) {
19783 if let Some(target) = self.target_file(cx) {
19784 cx.reveal_path(&target.abs_path(cx));
19785 }
19786 }
19787
19788 pub fn copy_path(
19789 &mut self,
19790 _: &zed_actions::workspace::CopyPath,
19791 _window: &mut Window,
19792 cx: &mut Context<Self>,
19793 ) {
19794 if let Some(path) = self.target_file_abs_path(cx)
19795 && let Some(path) = path.to_str()
19796 {
19797 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19798 } else {
19799 cx.propagate();
19800 }
19801 }
19802
19803 pub fn copy_relative_path(
19804 &mut self,
19805 _: &zed_actions::workspace::CopyRelativePath,
19806 _window: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) {
19809 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19810 let project = self.project()?.read(cx);
19811 let path = buffer.read(cx).file()?.path();
19812 let path = path.display(project.path_style(cx));
19813 Some(path)
19814 }) {
19815 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19816 } else {
19817 cx.propagate();
19818 }
19819 }
19820
19821 /// Returns the project path for the editor's buffer, if any buffer is
19822 /// opened in the editor.
19823 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19824 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19825 buffer.read(cx).project_path(cx)
19826 } else {
19827 None
19828 }
19829 }
19830
19831 // Returns true if the editor handled a go-to-line request
19832 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19833 maybe!({
19834 let breakpoint_store = self.breakpoint_store.as_ref()?;
19835
19836 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19837 else {
19838 self.clear_row_highlights::<ActiveDebugLine>();
19839 return None;
19840 };
19841
19842 let position = active_stack_frame.position;
19843 let buffer_id = position.buffer_id?;
19844 let snapshot = self
19845 .project
19846 .as_ref()?
19847 .read(cx)
19848 .buffer_for_id(buffer_id, cx)?
19849 .read(cx)
19850 .snapshot();
19851
19852 let mut handled = false;
19853 for (id, ExcerptRange { context, .. }) in
19854 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19855 {
19856 if context.start.cmp(&position, &snapshot).is_ge()
19857 || context.end.cmp(&position, &snapshot).is_lt()
19858 {
19859 continue;
19860 }
19861 let snapshot = self.buffer.read(cx).snapshot(cx);
19862 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19863
19864 handled = true;
19865 self.clear_row_highlights::<ActiveDebugLine>();
19866
19867 self.go_to_line::<ActiveDebugLine>(
19868 multibuffer_anchor,
19869 Some(cx.theme().colors().editor_debugger_active_line_background),
19870 window,
19871 cx,
19872 );
19873
19874 cx.notify();
19875 }
19876
19877 handled.then_some(())
19878 })
19879 .is_some()
19880 }
19881
19882 pub fn copy_file_name_without_extension(
19883 &mut self,
19884 _: &CopyFileNameWithoutExtension,
19885 _: &mut Window,
19886 cx: &mut Context<Self>,
19887 ) {
19888 if let Some(file) = self.target_file(cx)
19889 && let Some(file_stem) = file.path().file_stem()
19890 {
19891 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19892 }
19893 }
19894
19895 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19896 if let Some(file) = self.target_file(cx)
19897 && let Some(name) = file.path().file_name()
19898 {
19899 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19900 }
19901 }
19902
19903 pub fn toggle_git_blame(
19904 &mut self,
19905 _: &::git::Blame,
19906 window: &mut Window,
19907 cx: &mut Context<Self>,
19908 ) {
19909 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19910
19911 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19912 self.start_git_blame(true, window, cx);
19913 }
19914
19915 cx.notify();
19916 }
19917
19918 pub fn toggle_git_blame_inline(
19919 &mut self,
19920 _: &ToggleGitBlameInline,
19921 window: &mut Window,
19922 cx: &mut Context<Self>,
19923 ) {
19924 self.toggle_git_blame_inline_internal(true, window, cx);
19925 cx.notify();
19926 }
19927
19928 pub fn open_git_blame_commit(
19929 &mut self,
19930 _: &OpenGitBlameCommit,
19931 window: &mut Window,
19932 cx: &mut Context<Self>,
19933 ) {
19934 self.open_git_blame_commit_internal(window, cx);
19935 }
19936
19937 fn open_git_blame_commit_internal(
19938 &mut self,
19939 window: &mut Window,
19940 cx: &mut Context<Self>,
19941 ) -> Option<()> {
19942 let blame = self.blame.as_ref()?;
19943 let snapshot = self.snapshot(window, cx);
19944 let cursor = self
19945 .selections
19946 .newest::<Point>(&snapshot.display_snapshot)
19947 .head();
19948 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19949 let (_, blame_entry) = blame
19950 .update(cx, |blame, cx| {
19951 blame
19952 .blame_for_rows(
19953 &[RowInfo {
19954 buffer_id: Some(buffer.remote_id()),
19955 buffer_row: Some(point.row),
19956 ..Default::default()
19957 }],
19958 cx,
19959 )
19960 .next()
19961 })
19962 .flatten()?;
19963 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19964 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19965 let workspace = self.workspace()?.downgrade();
19966 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19967 None
19968 }
19969
19970 pub fn git_blame_inline_enabled(&self) -> bool {
19971 self.git_blame_inline_enabled
19972 }
19973
19974 pub fn toggle_selection_menu(
19975 &mut self,
19976 _: &ToggleSelectionMenu,
19977 _: &mut Window,
19978 cx: &mut Context<Self>,
19979 ) {
19980 self.show_selection_menu = self
19981 .show_selection_menu
19982 .map(|show_selections_menu| !show_selections_menu)
19983 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19984
19985 cx.notify();
19986 }
19987
19988 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19989 self.show_selection_menu
19990 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19991 }
19992
19993 fn start_git_blame(
19994 &mut self,
19995 user_triggered: bool,
19996 window: &mut Window,
19997 cx: &mut Context<Self>,
19998 ) {
19999 if let Some(project) = self.project() {
20000 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20001 && buffer.read(cx).file().is_none()
20002 {
20003 return;
20004 }
20005
20006 let focused = self.focus_handle(cx).contains_focused(window, cx);
20007
20008 let project = project.clone();
20009 let blame = cx
20010 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20011 self.blame_subscription =
20012 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20013 self.blame = Some(blame);
20014 }
20015 }
20016
20017 fn toggle_git_blame_inline_internal(
20018 &mut self,
20019 user_triggered: bool,
20020 window: &mut Window,
20021 cx: &mut Context<Self>,
20022 ) {
20023 if self.git_blame_inline_enabled {
20024 self.git_blame_inline_enabled = false;
20025 self.show_git_blame_inline = false;
20026 self.show_git_blame_inline_delay_task.take();
20027 } else {
20028 self.git_blame_inline_enabled = true;
20029 self.start_git_blame_inline(user_triggered, window, cx);
20030 }
20031
20032 cx.notify();
20033 }
20034
20035 fn start_git_blame_inline(
20036 &mut self,
20037 user_triggered: bool,
20038 window: &mut Window,
20039 cx: &mut Context<Self>,
20040 ) {
20041 self.start_git_blame(user_triggered, window, cx);
20042
20043 if ProjectSettings::get_global(cx)
20044 .git
20045 .inline_blame_delay()
20046 .is_some()
20047 {
20048 self.start_inline_blame_timer(window, cx);
20049 } else {
20050 self.show_git_blame_inline = true
20051 }
20052 }
20053
20054 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20055 self.blame.as_ref()
20056 }
20057
20058 pub fn show_git_blame_gutter(&self) -> bool {
20059 self.show_git_blame_gutter
20060 }
20061
20062 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20063 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20064 }
20065
20066 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20067 self.show_git_blame_inline
20068 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20069 && !self.newest_selection_head_on_empty_line(cx)
20070 && self.has_blame_entries(cx)
20071 }
20072
20073 fn has_blame_entries(&self, cx: &App) -> bool {
20074 self.blame()
20075 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20076 }
20077
20078 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20079 let cursor_anchor = self.selections.newest_anchor().head();
20080
20081 let snapshot = self.buffer.read(cx).snapshot(cx);
20082 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20083
20084 snapshot.line_len(buffer_row) == 0
20085 }
20086
20087 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20088 let buffer_and_selection = maybe!({
20089 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20090 let selection_range = selection.range();
20091
20092 let multi_buffer = self.buffer().read(cx);
20093 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20094 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20095
20096 let (buffer, range, _) = if selection.reversed {
20097 buffer_ranges.first()
20098 } else {
20099 buffer_ranges.last()
20100 }?;
20101
20102 let selection = text::ToPoint::to_point(&range.start, buffer).row
20103 ..text::ToPoint::to_point(&range.end, buffer).row;
20104 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20105 });
20106
20107 let Some((buffer, selection)) = buffer_and_selection else {
20108 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20109 };
20110
20111 let Some(project) = self.project() else {
20112 return Task::ready(Err(anyhow!("editor does not have project")));
20113 };
20114
20115 project.update(cx, |project, cx| {
20116 project.get_permalink_to_line(&buffer, selection, cx)
20117 })
20118 }
20119
20120 pub fn copy_permalink_to_line(
20121 &mut self,
20122 _: &CopyPermalinkToLine,
20123 window: &mut Window,
20124 cx: &mut Context<Self>,
20125 ) {
20126 let permalink_task = self.get_permalink_to_line(cx);
20127 let workspace = self.workspace();
20128
20129 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20130 Ok(permalink) => {
20131 cx.update(|_, cx| {
20132 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20133 })
20134 .ok();
20135 }
20136 Err(err) => {
20137 let message = format!("Failed to copy permalink: {err}");
20138
20139 anyhow::Result::<()>::Err(err).log_err();
20140
20141 if let Some(workspace) = workspace {
20142 workspace
20143 .update_in(cx, |workspace, _, cx| {
20144 struct CopyPermalinkToLine;
20145
20146 workspace.show_toast(
20147 Toast::new(
20148 NotificationId::unique::<CopyPermalinkToLine>(),
20149 message,
20150 ),
20151 cx,
20152 )
20153 })
20154 .ok();
20155 }
20156 }
20157 })
20158 .detach();
20159 }
20160
20161 pub fn copy_file_location(
20162 &mut self,
20163 _: &CopyFileLocation,
20164 _: &mut Window,
20165 cx: &mut Context<Self>,
20166 ) {
20167 let selection = self
20168 .selections
20169 .newest::<Point>(&self.display_snapshot(cx))
20170 .start
20171 .row
20172 + 1;
20173 if let Some(file) = self.target_file(cx) {
20174 let path = file.path().display(file.path_style(cx));
20175 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20176 }
20177 }
20178
20179 pub fn open_permalink_to_line(
20180 &mut self,
20181 _: &OpenPermalinkToLine,
20182 window: &mut Window,
20183 cx: &mut Context<Self>,
20184 ) {
20185 let permalink_task = self.get_permalink_to_line(cx);
20186 let workspace = self.workspace();
20187
20188 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20189 Ok(permalink) => {
20190 cx.update(|_, cx| {
20191 cx.open_url(permalink.as_ref());
20192 })
20193 .ok();
20194 }
20195 Err(err) => {
20196 let message = format!("Failed to open permalink: {err}");
20197
20198 anyhow::Result::<()>::Err(err).log_err();
20199
20200 if let Some(workspace) = workspace {
20201 workspace
20202 .update(cx, |workspace, cx| {
20203 struct OpenPermalinkToLine;
20204
20205 workspace.show_toast(
20206 Toast::new(
20207 NotificationId::unique::<OpenPermalinkToLine>(),
20208 message,
20209 ),
20210 cx,
20211 )
20212 })
20213 .ok();
20214 }
20215 }
20216 })
20217 .detach();
20218 }
20219
20220 pub fn insert_uuid_v4(
20221 &mut self,
20222 _: &InsertUuidV4,
20223 window: &mut Window,
20224 cx: &mut Context<Self>,
20225 ) {
20226 self.insert_uuid(UuidVersion::V4, window, cx);
20227 }
20228
20229 pub fn insert_uuid_v7(
20230 &mut self,
20231 _: &InsertUuidV7,
20232 window: &mut Window,
20233 cx: &mut Context<Self>,
20234 ) {
20235 self.insert_uuid(UuidVersion::V7, window, cx);
20236 }
20237
20238 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20240 self.transact(window, cx, |this, window, cx| {
20241 let edits = this
20242 .selections
20243 .all::<Point>(&this.display_snapshot(cx))
20244 .into_iter()
20245 .map(|selection| {
20246 let uuid = match version {
20247 UuidVersion::V4 => uuid::Uuid::new_v4(),
20248 UuidVersion::V7 => uuid::Uuid::now_v7(),
20249 };
20250
20251 (selection.range(), uuid.to_string())
20252 });
20253 this.edit(edits, cx);
20254 this.refresh_edit_prediction(true, false, window, cx);
20255 });
20256 }
20257
20258 pub fn open_selections_in_multibuffer(
20259 &mut self,
20260 _: &OpenSelectionsInMultibuffer,
20261 window: &mut Window,
20262 cx: &mut Context<Self>,
20263 ) {
20264 let multibuffer = self.buffer.read(cx);
20265
20266 let Some(buffer) = multibuffer.as_singleton() else {
20267 return;
20268 };
20269
20270 let Some(workspace) = self.workspace() else {
20271 return;
20272 };
20273
20274 let title = multibuffer.title(cx).to_string();
20275
20276 let locations = self
20277 .selections
20278 .all_anchors(cx)
20279 .iter()
20280 .map(|selection| {
20281 (
20282 buffer.clone(),
20283 (selection.start.text_anchor..selection.end.text_anchor)
20284 .to_point(buffer.read(cx)),
20285 )
20286 })
20287 .into_group_map();
20288
20289 cx.spawn_in(window, async move |_, cx| {
20290 workspace.update_in(cx, |workspace, window, cx| {
20291 Self::open_locations_in_multibuffer(
20292 workspace,
20293 locations,
20294 format!("Selections for '{title}'"),
20295 false,
20296 MultibufferSelectionMode::All,
20297 window,
20298 cx,
20299 );
20300 })
20301 })
20302 .detach();
20303 }
20304
20305 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20306 /// last highlight added will be used.
20307 ///
20308 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20309 pub fn highlight_rows<T: 'static>(
20310 &mut self,
20311 range: Range<Anchor>,
20312 color: Hsla,
20313 options: RowHighlightOptions,
20314 cx: &mut Context<Self>,
20315 ) {
20316 let snapshot = self.buffer().read(cx).snapshot(cx);
20317 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20318 let ix = row_highlights.binary_search_by(|highlight| {
20319 Ordering::Equal
20320 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20321 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20322 });
20323
20324 if let Err(mut ix) = ix {
20325 let index = post_inc(&mut self.highlight_order);
20326
20327 // If this range intersects with the preceding highlight, then merge it with
20328 // the preceding highlight. Otherwise insert a new highlight.
20329 let mut merged = false;
20330 if ix > 0 {
20331 let prev_highlight = &mut row_highlights[ix - 1];
20332 if prev_highlight
20333 .range
20334 .end
20335 .cmp(&range.start, &snapshot)
20336 .is_ge()
20337 {
20338 ix -= 1;
20339 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20340 prev_highlight.range.end = range.end;
20341 }
20342 merged = true;
20343 prev_highlight.index = index;
20344 prev_highlight.color = color;
20345 prev_highlight.options = options;
20346 }
20347 }
20348
20349 if !merged {
20350 row_highlights.insert(
20351 ix,
20352 RowHighlight {
20353 range,
20354 index,
20355 color,
20356 options,
20357 type_id: TypeId::of::<T>(),
20358 },
20359 );
20360 }
20361
20362 // If any of the following highlights intersect with this one, merge them.
20363 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20364 let highlight = &row_highlights[ix];
20365 if next_highlight
20366 .range
20367 .start
20368 .cmp(&highlight.range.end, &snapshot)
20369 .is_le()
20370 {
20371 if next_highlight
20372 .range
20373 .end
20374 .cmp(&highlight.range.end, &snapshot)
20375 .is_gt()
20376 {
20377 row_highlights[ix].range.end = next_highlight.range.end;
20378 }
20379 row_highlights.remove(ix + 1);
20380 } else {
20381 break;
20382 }
20383 }
20384 }
20385 }
20386
20387 /// Remove any highlighted row ranges of the given type that intersect the
20388 /// given ranges.
20389 pub fn remove_highlighted_rows<T: 'static>(
20390 &mut self,
20391 ranges_to_remove: Vec<Range<Anchor>>,
20392 cx: &mut Context<Self>,
20393 ) {
20394 let snapshot = self.buffer().read(cx).snapshot(cx);
20395 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20396 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20397 row_highlights.retain(|highlight| {
20398 while let Some(range_to_remove) = ranges_to_remove.peek() {
20399 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20400 Ordering::Less | Ordering::Equal => {
20401 ranges_to_remove.next();
20402 }
20403 Ordering::Greater => {
20404 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20405 Ordering::Less | Ordering::Equal => {
20406 return false;
20407 }
20408 Ordering::Greater => break,
20409 }
20410 }
20411 }
20412 }
20413
20414 true
20415 })
20416 }
20417
20418 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20419 pub fn clear_row_highlights<T: 'static>(&mut self) {
20420 self.highlighted_rows.remove(&TypeId::of::<T>());
20421 }
20422
20423 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20424 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20425 self.highlighted_rows
20426 .get(&TypeId::of::<T>())
20427 .map_or(&[] as &[_], |vec| vec.as_slice())
20428 .iter()
20429 .map(|highlight| (highlight.range.clone(), highlight.color))
20430 }
20431
20432 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20433 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20434 /// Allows to ignore certain kinds of highlights.
20435 pub fn highlighted_display_rows(
20436 &self,
20437 window: &mut Window,
20438 cx: &mut App,
20439 ) -> BTreeMap<DisplayRow, LineHighlight> {
20440 let snapshot = self.snapshot(window, cx);
20441 let mut used_highlight_orders = HashMap::default();
20442 self.highlighted_rows
20443 .iter()
20444 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20445 .fold(
20446 BTreeMap::<DisplayRow, LineHighlight>::new(),
20447 |mut unique_rows, highlight| {
20448 let start = highlight.range.start.to_display_point(&snapshot);
20449 let end = highlight.range.end.to_display_point(&snapshot);
20450 let start_row = start.row().0;
20451 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20452 && end.column() == 0
20453 {
20454 end.row().0.saturating_sub(1)
20455 } else {
20456 end.row().0
20457 };
20458 for row in start_row..=end_row {
20459 let used_index =
20460 used_highlight_orders.entry(row).or_insert(highlight.index);
20461 if highlight.index >= *used_index {
20462 *used_index = highlight.index;
20463 unique_rows.insert(
20464 DisplayRow(row),
20465 LineHighlight {
20466 include_gutter: highlight.options.include_gutter,
20467 border: None,
20468 background: highlight.color.into(),
20469 type_id: Some(highlight.type_id),
20470 },
20471 );
20472 }
20473 }
20474 unique_rows
20475 },
20476 )
20477 }
20478
20479 pub fn highlighted_display_row_for_autoscroll(
20480 &self,
20481 snapshot: &DisplaySnapshot,
20482 ) -> Option<DisplayRow> {
20483 self.highlighted_rows
20484 .values()
20485 .flat_map(|highlighted_rows| highlighted_rows.iter())
20486 .filter_map(|highlight| {
20487 if highlight.options.autoscroll {
20488 Some(highlight.range.start.to_display_point(snapshot).row())
20489 } else {
20490 None
20491 }
20492 })
20493 .min()
20494 }
20495
20496 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20497 self.highlight_background::<SearchWithinRange>(
20498 ranges,
20499 |colors| colors.colors().editor_document_highlight_read_background,
20500 cx,
20501 )
20502 }
20503
20504 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20505 self.breadcrumb_header = Some(new_header);
20506 }
20507
20508 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20509 self.clear_background_highlights::<SearchWithinRange>(cx);
20510 }
20511
20512 pub fn highlight_background<T: 'static>(
20513 &mut self,
20514 ranges: &[Range<Anchor>],
20515 color_fetcher: fn(&Theme) -> Hsla,
20516 cx: &mut Context<Self>,
20517 ) {
20518 self.background_highlights.insert(
20519 HighlightKey::Type(TypeId::of::<T>()),
20520 (color_fetcher, Arc::from(ranges)),
20521 );
20522 self.scrollbar_marker_state.dirty = true;
20523 cx.notify();
20524 }
20525
20526 pub fn highlight_background_key<T: 'static>(
20527 &mut self,
20528 key: usize,
20529 ranges: &[Range<Anchor>],
20530 color_fetcher: fn(&Theme) -> Hsla,
20531 cx: &mut Context<Self>,
20532 ) {
20533 self.background_highlights.insert(
20534 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20535 (color_fetcher, Arc::from(ranges)),
20536 );
20537 self.scrollbar_marker_state.dirty = true;
20538 cx.notify();
20539 }
20540
20541 pub fn clear_background_highlights<T: 'static>(
20542 &mut self,
20543 cx: &mut Context<Self>,
20544 ) -> Option<BackgroundHighlight> {
20545 let text_highlights = self
20546 .background_highlights
20547 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20548 if !text_highlights.1.is_empty() {
20549 self.scrollbar_marker_state.dirty = true;
20550 cx.notify();
20551 }
20552 Some(text_highlights)
20553 }
20554
20555 pub fn highlight_gutter<T: 'static>(
20556 &mut self,
20557 ranges: impl Into<Vec<Range<Anchor>>>,
20558 color_fetcher: fn(&App) -> Hsla,
20559 cx: &mut Context<Self>,
20560 ) {
20561 self.gutter_highlights
20562 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20563 cx.notify();
20564 }
20565
20566 pub fn clear_gutter_highlights<T: 'static>(
20567 &mut self,
20568 cx: &mut Context<Self>,
20569 ) -> Option<GutterHighlight> {
20570 cx.notify();
20571 self.gutter_highlights.remove(&TypeId::of::<T>())
20572 }
20573
20574 pub fn insert_gutter_highlight<T: 'static>(
20575 &mut self,
20576 range: Range<Anchor>,
20577 color_fetcher: fn(&App) -> Hsla,
20578 cx: &mut Context<Self>,
20579 ) {
20580 let snapshot = self.buffer().read(cx).snapshot(cx);
20581 let mut highlights = self
20582 .gutter_highlights
20583 .remove(&TypeId::of::<T>())
20584 .map(|(_, highlights)| highlights)
20585 .unwrap_or_default();
20586 let ix = highlights.binary_search_by(|highlight| {
20587 Ordering::Equal
20588 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20589 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20590 });
20591 if let Err(ix) = ix {
20592 highlights.insert(ix, range);
20593 }
20594 self.gutter_highlights
20595 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20596 }
20597
20598 pub fn remove_gutter_highlights<T: 'static>(
20599 &mut self,
20600 ranges_to_remove: Vec<Range<Anchor>>,
20601 cx: &mut Context<Self>,
20602 ) {
20603 let snapshot = self.buffer().read(cx).snapshot(cx);
20604 let Some((color_fetcher, mut gutter_highlights)) =
20605 self.gutter_highlights.remove(&TypeId::of::<T>())
20606 else {
20607 return;
20608 };
20609 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20610 gutter_highlights.retain(|highlight| {
20611 while let Some(range_to_remove) = ranges_to_remove.peek() {
20612 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20613 Ordering::Less | Ordering::Equal => {
20614 ranges_to_remove.next();
20615 }
20616 Ordering::Greater => {
20617 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20618 Ordering::Less | Ordering::Equal => {
20619 return false;
20620 }
20621 Ordering::Greater => break,
20622 }
20623 }
20624 }
20625 }
20626
20627 true
20628 });
20629 self.gutter_highlights
20630 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20631 }
20632
20633 #[cfg(feature = "test-support")]
20634 pub fn all_text_highlights(
20635 &self,
20636 window: &mut Window,
20637 cx: &mut Context<Self>,
20638 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20639 let snapshot = self.snapshot(window, cx);
20640 self.display_map.update(cx, |display_map, _| {
20641 display_map
20642 .all_text_highlights()
20643 .map(|highlight| {
20644 let (style, ranges) = highlight.as_ref();
20645 (
20646 *style,
20647 ranges
20648 .iter()
20649 .map(|range| range.clone().to_display_points(&snapshot))
20650 .collect(),
20651 )
20652 })
20653 .collect()
20654 })
20655 }
20656
20657 #[cfg(feature = "test-support")]
20658 pub fn all_text_background_highlights(
20659 &self,
20660 window: &mut Window,
20661 cx: &mut Context<Self>,
20662 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20663 let snapshot = self.snapshot(window, cx);
20664 let buffer = &snapshot.buffer_snapshot();
20665 let start = buffer.anchor_before(0);
20666 let end = buffer.anchor_after(buffer.len());
20667 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20668 }
20669
20670 #[cfg(any(test, feature = "test-support"))]
20671 pub fn sorted_background_highlights_in_range(
20672 &self,
20673 search_range: Range<Anchor>,
20674 display_snapshot: &DisplaySnapshot,
20675 theme: &Theme,
20676 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20677 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20678 res.sort_by(|a, b| {
20679 a.0.start
20680 .cmp(&b.0.start)
20681 .then_with(|| a.0.end.cmp(&b.0.end))
20682 .then_with(|| a.1.cmp(&b.1))
20683 });
20684 res
20685 }
20686
20687 #[cfg(feature = "test-support")]
20688 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20689 let snapshot = self.buffer().read(cx).snapshot(cx);
20690
20691 let highlights = self
20692 .background_highlights
20693 .get(&HighlightKey::Type(TypeId::of::<
20694 items::BufferSearchHighlights,
20695 >()));
20696
20697 if let Some((_color, ranges)) = highlights {
20698 ranges
20699 .iter()
20700 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20701 .collect_vec()
20702 } else {
20703 vec![]
20704 }
20705 }
20706
20707 fn document_highlights_for_position<'a>(
20708 &'a self,
20709 position: Anchor,
20710 buffer: &'a MultiBufferSnapshot,
20711 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20712 let read_highlights = self
20713 .background_highlights
20714 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20715 .map(|h| &h.1);
20716 let write_highlights = self
20717 .background_highlights
20718 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20719 .map(|h| &h.1);
20720 let left_position = position.bias_left(buffer);
20721 let right_position = position.bias_right(buffer);
20722 read_highlights
20723 .into_iter()
20724 .chain(write_highlights)
20725 .flat_map(move |ranges| {
20726 let start_ix = match ranges.binary_search_by(|probe| {
20727 let cmp = probe.end.cmp(&left_position, buffer);
20728 if cmp.is_ge() {
20729 Ordering::Greater
20730 } else {
20731 Ordering::Less
20732 }
20733 }) {
20734 Ok(i) | Err(i) => i,
20735 };
20736
20737 ranges[start_ix..]
20738 .iter()
20739 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20740 })
20741 }
20742
20743 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20744 self.background_highlights
20745 .get(&HighlightKey::Type(TypeId::of::<T>()))
20746 .is_some_and(|(_, highlights)| !highlights.is_empty())
20747 }
20748
20749 /// Returns all background highlights for a given range.
20750 ///
20751 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20752 pub fn background_highlights_in_range(
20753 &self,
20754 search_range: Range<Anchor>,
20755 display_snapshot: &DisplaySnapshot,
20756 theme: &Theme,
20757 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20758 let mut results = Vec::new();
20759 for (color_fetcher, ranges) in self.background_highlights.values() {
20760 let color = color_fetcher(theme);
20761 let start_ix = match ranges.binary_search_by(|probe| {
20762 let cmp = probe
20763 .end
20764 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20765 if cmp.is_gt() {
20766 Ordering::Greater
20767 } else {
20768 Ordering::Less
20769 }
20770 }) {
20771 Ok(i) | Err(i) => i,
20772 };
20773 for range in &ranges[start_ix..] {
20774 if range
20775 .start
20776 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20777 .is_ge()
20778 {
20779 break;
20780 }
20781
20782 let start = range.start.to_display_point(display_snapshot);
20783 let end = range.end.to_display_point(display_snapshot);
20784 results.push((start..end, color))
20785 }
20786 }
20787 results
20788 }
20789
20790 pub fn gutter_highlights_in_range(
20791 &self,
20792 search_range: Range<Anchor>,
20793 display_snapshot: &DisplaySnapshot,
20794 cx: &App,
20795 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20796 let mut results = Vec::new();
20797 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20798 let color = color_fetcher(cx);
20799 let start_ix = match ranges.binary_search_by(|probe| {
20800 let cmp = probe
20801 .end
20802 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20803 if cmp.is_gt() {
20804 Ordering::Greater
20805 } else {
20806 Ordering::Less
20807 }
20808 }) {
20809 Ok(i) | Err(i) => i,
20810 };
20811 for range in &ranges[start_ix..] {
20812 if range
20813 .start
20814 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20815 .is_ge()
20816 {
20817 break;
20818 }
20819
20820 let start = range.start.to_display_point(display_snapshot);
20821 let end = range.end.to_display_point(display_snapshot);
20822 results.push((start..end, color))
20823 }
20824 }
20825 results
20826 }
20827
20828 /// Get the text ranges corresponding to the redaction query
20829 pub fn redacted_ranges(
20830 &self,
20831 search_range: Range<Anchor>,
20832 display_snapshot: &DisplaySnapshot,
20833 cx: &App,
20834 ) -> Vec<Range<DisplayPoint>> {
20835 display_snapshot
20836 .buffer_snapshot()
20837 .redacted_ranges(search_range, |file| {
20838 if let Some(file) = file {
20839 file.is_private()
20840 && EditorSettings::get(
20841 Some(SettingsLocation {
20842 worktree_id: file.worktree_id(cx),
20843 path: file.path().as_ref(),
20844 }),
20845 cx,
20846 )
20847 .redact_private_values
20848 } else {
20849 false
20850 }
20851 })
20852 .map(|range| {
20853 range.start.to_display_point(display_snapshot)
20854 ..range.end.to_display_point(display_snapshot)
20855 })
20856 .collect()
20857 }
20858
20859 pub fn highlight_text_key<T: 'static>(
20860 &mut self,
20861 key: usize,
20862 ranges: Vec<Range<Anchor>>,
20863 style: HighlightStyle,
20864 cx: &mut Context<Self>,
20865 ) {
20866 self.display_map.update(cx, |map, _| {
20867 map.highlight_text(
20868 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20869 ranges,
20870 style,
20871 );
20872 });
20873 cx.notify();
20874 }
20875
20876 pub fn highlight_text<T: 'static>(
20877 &mut self,
20878 ranges: Vec<Range<Anchor>>,
20879 style: HighlightStyle,
20880 cx: &mut Context<Self>,
20881 ) {
20882 self.display_map.update(cx, |map, _| {
20883 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20884 });
20885 cx.notify();
20886 }
20887
20888 pub fn text_highlights<'a, T: 'static>(
20889 &'a self,
20890 cx: &'a App,
20891 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20892 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20893 }
20894
20895 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20896 let cleared = self
20897 .display_map
20898 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20899 if cleared {
20900 cx.notify();
20901 }
20902 }
20903
20904 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20905 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20906 && self.focus_handle.is_focused(window)
20907 }
20908
20909 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20910 self.show_cursor_when_unfocused = is_enabled;
20911 cx.notify();
20912 }
20913
20914 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20915 cx.notify();
20916 }
20917
20918 fn on_debug_session_event(
20919 &mut self,
20920 _session: Entity<Session>,
20921 event: &SessionEvent,
20922 cx: &mut Context<Self>,
20923 ) {
20924 if let SessionEvent::InvalidateInlineValue = event {
20925 self.refresh_inline_values(cx);
20926 }
20927 }
20928
20929 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20930 let Some(project) = self.project.clone() else {
20931 return;
20932 };
20933
20934 if !self.inline_value_cache.enabled {
20935 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20936 self.splice_inlays(&inlays, Vec::new(), cx);
20937 return;
20938 }
20939
20940 let current_execution_position = self
20941 .highlighted_rows
20942 .get(&TypeId::of::<ActiveDebugLine>())
20943 .and_then(|lines| lines.last().map(|line| line.range.end));
20944
20945 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20946 let inline_values = editor
20947 .update(cx, |editor, cx| {
20948 let Some(current_execution_position) = current_execution_position else {
20949 return Some(Task::ready(Ok(Vec::new())));
20950 };
20951
20952 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20953 let snapshot = buffer.snapshot(cx);
20954
20955 let excerpt = snapshot.excerpt_containing(
20956 current_execution_position..current_execution_position,
20957 )?;
20958
20959 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20960 })?;
20961
20962 let range =
20963 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20964
20965 project.inline_values(buffer, range, cx)
20966 })
20967 .ok()
20968 .flatten()?
20969 .await
20970 .context("refreshing debugger inlays")
20971 .log_err()?;
20972
20973 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20974
20975 for (buffer_id, inline_value) in inline_values
20976 .into_iter()
20977 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20978 {
20979 buffer_inline_values
20980 .entry(buffer_id)
20981 .or_default()
20982 .push(inline_value);
20983 }
20984
20985 editor
20986 .update(cx, |editor, cx| {
20987 let snapshot = editor.buffer.read(cx).snapshot(cx);
20988 let mut new_inlays = Vec::default();
20989
20990 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20991 let buffer_id = buffer_snapshot.remote_id();
20992 buffer_inline_values
20993 .get(&buffer_id)
20994 .into_iter()
20995 .flatten()
20996 .for_each(|hint| {
20997 let inlay = Inlay::debugger(
20998 post_inc(&mut editor.next_inlay_id),
20999 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21000 hint.text(),
21001 );
21002 if !inlay.text().chars().contains(&'\n') {
21003 new_inlays.push(inlay);
21004 }
21005 });
21006 }
21007
21008 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21009 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21010
21011 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21012 })
21013 .ok()?;
21014 Some(())
21015 });
21016 }
21017
21018 fn on_buffer_event(
21019 &mut self,
21020 multibuffer: &Entity<MultiBuffer>,
21021 event: &multi_buffer::Event,
21022 window: &mut Window,
21023 cx: &mut Context<Self>,
21024 ) {
21025 match event {
21026 multi_buffer::Event::Edited { edited_buffer } => {
21027 self.scrollbar_marker_state.dirty = true;
21028 self.active_indent_guides_state.dirty = true;
21029 self.refresh_active_diagnostics(cx);
21030 self.refresh_code_actions(window, cx);
21031 self.refresh_selected_text_highlights(true, window, cx);
21032 self.refresh_single_line_folds(window, cx);
21033 self.colorize_brackets(cx);
21034 self.refresh_matching_bracket_highlights(window, cx);
21035 if self.has_active_edit_prediction() {
21036 self.update_visible_edit_prediction(window, cx);
21037 }
21038
21039 if let Some(buffer) = edited_buffer {
21040 if buffer.read(cx).file().is_none() {
21041 cx.emit(EditorEvent::TitleChanged);
21042 }
21043
21044 if self.project.is_some() {
21045 let buffer_id = buffer.read(cx).remote_id();
21046 self.register_buffer(buffer_id, cx);
21047 self.update_lsp_data(Some(buffer_id), window, cx);
21048 self.refresh_inlay_hints(
21049 InlayHintRefreshReason::BufferEdited(buffer_id),
21050 cx,
21051 );
21052 }
21053 }
21054
21055 cx.emit(EditorEvent::BufferEdited);
21056 cx.emit(SearchEvent::MatchesInvalidated);
21057
21058 let Some(project) = &self.project else { return };
21059 let (telemetry, is_via_ssh) = {
21060 let project = project.read(cx);
21061 let telemetry = project.client().telemetry().clone();
21062 let is_via_ssh = project.is_via_remote_server();
21063 (telemetry, is_via_ssh)
21064 };
21065 telemetry.log_edit_event("editor", is_via_ssh);
21066 }
21067 multi_buffer::Event::ExcerptsAdded {
21068 buffer,
21069 predecessor,
21070 excerpts,
21071 } => {
21072 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21073 let buffer_id = buffer.read(cx).remote_id();
21074 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21075 && let Some(project) = &self.project
21076 {
21077 update_uncommitted_diff_for_buffer(
21078 cx.entity(),
21079 project,
21080 [buffer.clone()],
21081 self.buffer.clone(),
21082 cx,
21083 )
21084 .detach();
21085 }
21086 self.update_lsp_data(Some(buffer_id), window, cx);
21087 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21088 self.colorize_brackets(cx);
21089 cx.emit(EditorEvent::ExcerptsAdded {
21090 buffer: buffer.clone(),
21091 predecessor: *predecessor,
21092 excerpts: excerpts.clone(),
21093 });
21094 }
21095 multi_buffer::Event::ExcerptsRemoved {
21096 ids,
21097 removed_buffer_ids,
21098 } => {
21099 if let Some(inlay_hints) = &mut self.inlay_hints {
21100 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21101 }
21102 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21103 for buffer_id in removed_buffer_ids {
21104 self.registered_buffers.remove(buffer_id);
21105 }
21106 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21107 cx.emit(EditorEvent::ExcerptsRemoved {
21108 ids: ids.clone(),
21109 removed_buffer_ids: removed_buffer_ids.clone(),
21110 });
21111 }
21112 multi_buffer::Event::ExcerptsEdited {
21113 excerpt_ids,
21114 buffer_ids,
21115 } => {
21116 self.display_map.update(cx, |map, cx| {
21117 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21118 });
21119 cx.emit(EditorEvent::ExcerptsEdited {
21120 ids: excerpt_ids.clone(),
21121 });
21122 }
21123 multi_buffer::Event::ExcerptsExpanded { ids } => {
21124 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21125 self.refresh_document_highlights(cx);
21126 self.colorize_brackets(cx);
21127 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21128 }
21129 multi_buffer::Event::Reparsed(buffer_id) => {
21130 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21131 self.colorize_brackets(cx);
21132 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21133
21134 cx.emit(EditorEvent::Reparsed(*buffer_id));
21135 }
21136 multi_buffer::Event::DiffHunksToggled => {
21137 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21138 }
21139 multi_buffer::Event::LanguageChanged(buffer_id) => {
21140 self.registered_buffers.remove(&buffer_id);
21141 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21142 cx.emit(EditorEvent::Reparsed(*buffer_id));
21143 cx.notify();
21144 }
21145 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21146 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21147 multi_buffer::Event::FileHandleChanged
21148 | multi_buffer::Event::Reloaded
21149 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21150 multi_buffer::Event::DiagnosticsUpdated => {
21151 self.update_diagnostics_state(window, cx);
21152 }
21153 _ => {}
21154 };
21155 }
21156
21157 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21158 if !self.diagnostics_enabled() {
21159 return;
21160 }
21161 self.refresh_active_diagnostics(cx);
21162 self.refresh_inline_diagnostics(true, window, cx);
21163 self.scrollbar_marker_state.dirty = true;
21164 cx.notify();
21165 }
21166
21167 pub fn start_temporary_diff_override(&mut self) {
21168 self.load_diff_task.take();
21169 self.temporary_diff_override = true;
21170 }
21171
21172 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21173 self.temporary_diff_override = false;
21174 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21175 self.buffer.update(cx, |buffer, cx| {
21176 buffer.set_all_diff_hunks_collapsed(cx);
21177 });
21178
21179 if let Some(project) = self.project.clone() {
21180 self.load_diff_task = Some(
21181 update_uncommitted_diff_for_buffer(
21182 cx.entity(),
21183 &project,
21184 self.buffer.read(cx).all_buffers(),
21185 self.buffer.clone(),
21186 cx,
21187 )
21188 .shared(),
21189 );
21190 }
21191 }
21192
21193 fn on_display_map_changed(
21194 &mut self,
21195 _: Entity<DisplayMap>,
21196 _: &mut Window,
21197 cx: &mut Context<Self>,
21198 ) {
21199 cx.notify();
21200 }
21201
21202 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21203 if self.diagnostics_enabled() {
21204 let new_severity = EditorSettings::get_global(cx)
21205 .diagnostics_max_severity
21206 .unwrap_or(DiagnosticSeverity::Hint);
21207 self.set_max_diagnostics_severity(new_severity, cx);
21208 }
21209 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21210 self.update_edit_prediction_settings(cx);
21211 self.refresh_edit_prediction(true, false, window, cx);
21212 self.refresh_inline_values(cx);
21213 self.refresh_inlay_hints(
21214 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21215 self.selections.newest_anchor().head(),
21216 &self.buffer.read(cx).snapshot(cx),
21217 cx,
21218 )),
21219 cx,
21220 );
21221
21222 let old_cursor_shape = self.cursor_shape;
21223 let old_show_breadcrumbs = self.show_breadcrumbs;
21224
21225 {
21226 let editor_settings = EditorSettings::get_global(cx);
21227 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21228 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21229 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21230 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21231 }
21232
21233 if old_cursor_shape != self.cursor_shape {
21234 cx.emit(EditorEvent::CursorShapeChanged);
21235 }
21236
21237 if old_show_breadcrumbs != self.show_breadcrumbs {
21238 cx.emit(EditorEvent::BreadcrumbsChanged);
21239 }
21240
21241 let project_settings = ProjectSettings::get_global(cx);
21242 self.buffer_serialization = self
21243 .should_serialize_buffer()
21244 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21245
21246 if self.mode.is_full() {
21247 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21248 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21249 if self.show_inline_diagnostics != show_inline_diagnostics {
21250 self.show_inline_diagnostics = show_inline_diagnostics;
21251 self.refresh_inline_diagnostics(false, window, cx);
21252 }
21253
21254 if self.git_blame_inline_enabled != inline_blame_enabled {
21255 self.toggle_git_blame_inline_internal(false, window, cx);
21256 }
21257
21258 let minimap_settings = EditorSettings::get_global(cx).minimap;
21259 if self.minimap_visibility != MinimapVisibility::Disabled {
21260 if self.minimap_visibility.settings_visibility()
21261 != minimap_settings.minimap_enabled()
21262 {
21263 self.set_minimap_visibility(
21264 MinimapVisibility::for_mode(self.mode(), cx),
21265 window,
21266 cx,
21267 );
21268 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21269 minimap_entity.update(cx, |minimap_editor, cx| {
21270 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21271 })
21272 }
21273 }
21274
21275 self.colorize_brackets(cx);
21276
21277 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21278 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21279 }) {
21280 if !inlay_splice.is_empty() {
21281 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21282 }
21283 self.refresh_colors_for_visible_range(None, window, cx);
21284 }
21285 }
21286
21287 cx.notify();
21288 }
21289
21290 pub fn set_searchable(&mut self, searchable: bool) {
21291 self.searchable = searchable;
21292 }
21293
21294 pub fn searchable(&self) -> bool {
21295 self.searchable
21296 }
21297
21298 pub fn open_excerpts_in_split(
21299 &mut self,
21300 _: &OpenExcerptsSplit,
21301 window: &mut Window,
21302 cx: &mut Context<Self>,
21303 ) {
21304 self.open_excerpts_common(None, true, window, cx)
21305 }
21306
21307 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21308 self.open_excerpts_common(None, false, window, cx)
21309 }
21310
21311 fn open_excerpts_common(
21312 &mut self,
21313 jump_data: Option<JumpData>,
21314 split: bool,
21315 window: &mut Window,
21316 cx: &mut Context<Self>,
21317 ) {
21318 let Some(workspace) = self.workspace() else {
21319 cx.propagate();
21320 return;
21321 };
21322
21323 if self.buffer.read(cx).is_singleton() {
21324 cx.propagate();
21325 return;
21326 }
21327
21328 let mut new_selections_by_buffer = HashMap::default();
21329 match &jump_data {
21330 Some(JumpData::MultiBufferPoint {
21331 excerpt_id,
21332 position,
21333 anchor,
21334 line_offset_from_top,
21335 }) => {
21336 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21337 if let Some(buffer) = multi_buffer_snapshot
21338 .buffer_id_for_excerpt(*excerpt_id)
21339 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21340 {
21341 let buffer_snapshot = buffer.read(cx).snapshot();
21342 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21343 language::ToPoint::to_point(anchor, &buffer_snapshot)
21344 } else {
21345 buffer_snapshot.clip_point(*position, Bias::Left)
21346 };
21347 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21348 new_selections_by_buffer.insert(
21349 buffer,
21350 (
21351 vec![jump_to_offset..jump_to_offset],
21352 Some(*line_offset_from_top),
21353 ),
21354 );
21355 }
21356 }
21357 Some(JumpData::MultiBufferRow {
21358 row,
21359 line_offset_from_top,
21360 }) => {
21361 let point = MultiBufferPoint::new(row.0, 0);
21362 if let Some((buffer, buffer_point, _)) =
21363 self.buffer.read(cx).point_to_buffer_point(point, cx)
21364 {
21365 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21366 new_selections_by_buffer
21367 .entry(buffer)
21368 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21369 .0
21370 .push(buffer_offset..buffer_offset)
21371 }
21372 }
21373 None => {
21374 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21375 let multi_buffer = self.buffer.read(cx);
21376 for selection in selections {
21377 for (snapshot, range, _, anchor) in multi_buffer
21378 .snapshot(cx)
21379 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21380 {
21381 if let Some(anchor) = anchor {
21382 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21383 else {
21384 continue;
21385 };
21386 let offset = text::ToOffset::to_offset(
21387 &anchor.text_anchor,
21388 &buffer_handle.read(cx).snapshot(),
21389 );
21390 let range = offset..offset;
21391 new_selections_by_buffer
21392 .entry(buffer_handle)
21393 .or_insert((Vec::new(), None))
21394 .0
21395 .push(range)
21396 } else {
21397 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21398 else {
21399 continue;
21400 };
21401 new_selections_by_buffer
21402 .entry(buffer_handle)
21403 .or_insert((Vec::new(), None))
21404 .0
21405 .push(range)
21406 }
21407 }
21408 }
21409 }
21410 }
21411
21412 new_selections_by_buffer
21413 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21414
21415 if new_selections_by_buffer.is_empty() {
21416 return;
21417 }
21418
21419 // We defer the pane interaction because we ourselves are a workspace item
21420 // and activating a new item causes the pane to call a method on us reentrantly,
21421 // which panics if we're on the stack.
21422 window.defer(cx, move |window, cx| {
21423 workspace.update(cx, |workspace, cx| {
21424 let pane = if split {
21425 workspace.adjacent_pane(window, cx)
21426 } else {
21427 workspace.active_pane().clone()
21428 };
21429
21430 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21431 let editor = buffer
21432 .read(cx)
21433 .file()
21434 .is_none()
21435 .then(|| {
21436 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21437 // so `workspace.open_project_item` will never find them, always opening a new editor.
21438 // Instead, we try to activate the existing editor in the pane first.
21439 let (editor, pane_item_index) =
21440 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21441 let editor = item.downcast::<Editor>()?;
21442 let singleton_buffer =
21443 editor.read(cx).buffer().read(cx).as_singleton()?;
21444 if singleton_buffer == buffer {
21445 Some((editor, i))
21446 } else {
21447 None
21448 }
21449 })?;
21450 pane.update(cx, |pane, cx| {
21451 pane.activate_item(pane_item_index, true, true, window, cx)
21452 });
21453 Some(editor)
21454 })
21455 .flatten()
21456 .unwrap_or_else(|| {
21457 workspace.open_project_item::<Self>(
21458 pane.clone(),
21459 buffer,
21460 true,
21461 true,
21462 window,
21463 cx,
21464 )
21465 });
21466
21467 editor.update(cx, |editor, cx| {
21468 let autoscroll = match scroll_offset {
21469 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21470 None => Autoscroll::newest(),
21471 };
21472 let nav_history = editor.nav_history.take();
21473 editor.change_selections(
21474 SelectionEffects::scroll(autoscroll),
21475 window,
21476 cx,
21477 |s| {
21478 s.select_ranges(ranges);
21479 },
21480 );
21481 editor.nav_history = nav_history;
21482 });
21483 }
21484 })
21485 });
21486 }
21487
21488 // For now, don't allow opening excerpts in buffers that aren't backed by
21489 // regular project files.
21490 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21491 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21492 }
21493
21494 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21495 let snapshot = self.buffer.read(cx).read(cx);
21496 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21497 Some(
21498 ranges
21499 .iter()
21500 .map(move |range| {
21501 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21502 })
21503 .collect(),
21504 )
21505 }
21506
21507 fn selection_replacement_ranges(
21508 &self,
21509 range: Range<OffsetUtf16>,
21510 cx: &mut App,
21511 ) -> Vec<Range<OffsetUtf16>> {
21512 let selections = self
21513 .selections
21514 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21515 let newest_selection = selections
21516 .iter()
21517 .max_by_key(|selection| selection.id)
21518 .unwrap();
21519 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21520 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21521 let snapshot = self.buffer.read(cx).read(cx);
21522 selections
21523 .into_iter()
21524 .map(|mut selection| {
21525 selection.start.0 =
21526 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21527 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21528 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21529 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21530 })
21531 .collect()
21532 }
21533
21534 fn report_editor_event(
21535 &self,
21536 reported_event: ReportEditorEvent,
21537 file_extension: Option<String>,
21538 cx: &App,
21539 ) {
21540 if cfg!(any(test, feature = "test-support")) {
21541 return;
21542 }
21543
21544 let Some(project) = &self.project else { return };
21545
21546 // If None, we are in a file without an extension
21547 let file = self
21548 .buffer
21549 .read(cx)
21550 .as_singleton()
21551 .and_then(|b| b.read(cx).file());
21552 let file_extension = file_extension.or(file
21553 .as_ref()
21554 .and_then(|file| Path::new(file.file_name(cx)).extension())
21555 .and_then(|e| e.to_str())
21556 .map(|a| a.to_string()));
21557
21558 let vim_mode = vim_flavor(cx).is_some();
21559
21560 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21561 let copilot_enabled = edit_predictions_provider
21562 == language::language_settings::EditPredictionProvider::Copilot;
21563 let copilot_enabled_for_language = self
21564 .buffer
21565 .read(cx)
21566 .language_settings(cx)
21567 .show_edit_predictions;
21568
21569 let project = project.read(cx);
21570 let event_type = reported_event.event_type();
21571
21572 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21573 telemetry::event!(
21574 event_type,
21575 type = if auto_saved {"autosave"} else {"manual"},
21576 file_extension,
21577 vim_mode,
21578 copilot_enabled,
21579 copilot_enabled_for_language,
21580 edit_predictions_provider,
21581 is_via_ssh = project.is_via_remote_server(),
21582 );
21583 } else {
21584 telemetry::event!(
21585 event_type,
21586 file_extension,
21587 vim_mode,
21588 copilot_enabled,
21589 copilot_enabled_for_language,
21590 edit_predictions_provider,
21591 is_via_ssh = project.is_via_remote_server(),
21592 );
21593 };
21594 }
21595
21596 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21597 /// with each line being an array of {text, highlight} objects.
21598 fn copy_highlight_json(
21599 &mut self,
21600 _: &CopyHighlightJson,
21601 window: &mut Window,
21602 cx: &mut Context<Self>,
21603 ) {
21604 #[derive(Serialize)]
21605 struct Chunk<'a> {
21606 text: String,
21607 highlight: Option<&'a str>,
21608 }
21609
21610 let snapshot = self.buffer.read(cx).snapshot(cx);
21611 let range = self
21612 .selected_text_range(false, window, cx)
21613 .and_then(|selection| {
21614 if selection.range.is_empty() {
21615 None
21616 } else {
21617 Some(
21618 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21619 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21620 )
21621 }
21622 })
21623 .unwrap_or_else(|| 0..snapshot.len());
21624
21625 let chunks = snapshot.chunks(range, true);
21626 let mut lines = Vec::new();
21627 let mut line: VecDeque<Chunk> = VecDeque::new();
21628
21629 let Some(style) = self.style.as_ref() else {
21630 return;
21631 };
21632
21633 for chunk in chunks {
21634 let highlight = chunk
21635 .syntax_highlight_id
21636 .and_then(|id| id.name(&style.syntax));
21637 let mut chunk_lines = chunk.text.split('\n').peekable();
21638 while let Some(text) = chunk_lines.next() {
21639 let mut merged_with_last_token = false;
21640 if let Some(last_token) = line.back_mut()
21641 && last_token.highlight == highlight
21642 {
21643 last_token.text.push_str(text);
21644 merged_with_last_token = true;
21645 }
21646
21647 if !merged_with_last_token {
21648 line.push_back(Chunk {
21649 text: text.into(),
21650 highlight,
21651 });
21652 }
21653
21654 if chunk_lines.peek().is_some() {
21655 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21656 line.pop_front();
21657 }
21658 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21659 line.pop_back();
21660 }
21661
21662 lines.push(mem::take(&mut line));
21663 }
21664 }
21665 }
21666
21667 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21668 return;
21669 };
21670 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21671 }
21672
21673 pub fn open_context_menu(
21674 &mut self,
21675 _: &OpenContextMenu,
21676 window: &mut Window,
21677 cx: &mut Context<Self>,
21678 ) {
21679 self.request_autoscroll(Autoscroll::newest(), cx);
21680 let position = self
21681 .selections
21682 .newest_display(&self.display_snapshot(cx))
21683 .start;
21684 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21685 }
21686
21687 pub fn replay_insert_event(
21688 &mut self,
21689 text: &str,
21690 relative_utf16_range: Option<Range<isize>>,
21691 window: &mut Window,
21692 cx: &mut Context<Self>,
21693 ) {
21694 if !self.input_enabled {
21695 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21696 return;
21697 }
21698 if let Some(relative_utf16_range) = relative_utf16_range {
21699 let selections = self
21700 .selections
21701 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21702 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21703 let new_ranges = selections.into_iter().map(|range| {
21704 let start = OffsetUtf16(
21705 range
21706 .head()
21707 .0
21708 .saturating_add_signed(relative_utf16_range.start),
21709 );
21710 let end = OffsetUtf16(
21711 range
21712 .head()
21713 .0
21714 .saturating_add_signed(relative_utf16_range.end),
21715 );
21716 start..end
21717 });
21718 s.select_ranges(new_ranges);
21719 });
21720 }
21721
21722 self.handle_input(text, window, cx);
21723 }
21724
21725 pub fn is_focused(&self, window: &Window) -> bool {
21726 self.focus_handle.is_focused(window)
21727 }
21728
21729 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21730 cx.emit(EditorEvent::Focused);
21731
21732 if let Some(descendant) = self
21733 .last_focused_descendant
21734 .take()
21735 .and_then(|descendant| descendant.upgrade())
21736 {
21737 window.focus(&descendant);
21738 } else {
21739 if let Some(blame) = self.blame.as_ref() {
21740 blame.update(cx, GitBlame::focus)
21741 }
21742
21743 self.blink_manager.update(cx, BlinkManager::enable);
21744 self.show_cursor_names(window, cx);
21745 self.buffer.update(cx, |buffer, cx| {
21746 buffer.finalize_last_transaction(cx);
21747 if self.leader_id.is_none() {
21748 buffer.set_active_selections(
21749 &self.selections.disjoint_anchors_arc(),
21750 self.selections.line_mode(),
21751 self.cursor_shape,
21752 cx,
21753 );
21754 }
21755 });
21756 }
21757 }
21758
21759 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21760 cx.emit(EditorEvent::FocusedIn)
21761 }
21762
21763 fn handle_focus_out(
21764 &mut self,
21765 event: FocusOutEvent,
21766 _window: &mut Window,
21767 cx: &mut Context<Self>,
21768 ) {
21769 if event.blurred != self.focus_handle {
21770 self.last_focused_descendant = Some(event.blurred);
21771 }
21772 self.selection_drag_state = SelectionDragState::None;
21773 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21774 }
21775
21776 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21777 self.blink_manager.update(cx, BlinkManager::disable);
21778 self.buffer
21779 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21780
21781 if let Some(blame) = self.blame.as_ref() {
21782 blame.update(cx, GitBlame::blur)
21783 }
21784 if !self.hover_state.focused(window, cx) {
21785 hide_hover(self, cx);
21786 }
21787 if !self
21788 .context_menu
21789 .borrow()
21790 .as_ref()
21791 .is_some_and(|context_menu| context_menu.focused(window, cx))
21792 {
21793 self.hide_context_menu(window, cx);
21794 }
21795 self.take_active_edit_prediction(cx);
21796 cx.emit(EditorEvent::Blurred);
21797 cx.notify();
21798 }
21799
21800 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21801 let mut pending: String = window
21802 .pending_input_keystrokes()
21803 .into_iter()
21804 .flatten()
21805 .filter_map(|keystroke| {
21806 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21807 keystroke.key_char.clone()
21808 } else {
21809 None
21810 }
21811 })
21812 .collect();
21813
21814 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21815 pending = "".to_string();
21816 }
21817
21818 let existing_pending = self
21819 .text_highlights::<PendingInput>(cx)
21820 .map(|(_, ranges)| ranges.to_vec());
21821 if existing_pending.is_none() && pending.is_empty() {
21822 return;
21823 }
21824 let transaction =
21825 self.transact(window, cx, |this, window, cx| {
21826 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21827 let edits = selections
21828 .iter()
21829 .map(|selection| (selection.end..selection.end, pending.clone()));
21830 this.edit(edits, cx);
21831 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21832 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21833 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21834 }));
21835 });
21836 if let Some(existing_ranges) = existing_pending {
21837 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21838 this.edit(edits, cx);
21839 }
21840 });
21841
21842 let snapshot = self.snapshot(window, cx);
21843 let ranges = self
21844 .selections
21845 .all::<usize>(&snapshot.display_snapshot)
21846 .into_iter()
21847 .map(|selection| {
21848 snapshot.buffer_snapshot().anchor_after(selection.end)
21849 ..snapshot
21850 .buffer_snapshot()
21851 .anchor_before(selection.end + pending.len())
21852 })
21853 .collect();
21854
21855 if pending.is_empty() {
21856 self.clear_highlights::<PendingInput>(cx);
21857 } else {
21858 self.highlight_text::<PendingInput>(
21859 ranges,
21860 HighlightStyle {
21861 underline: Some(UnderlineStyle {
21862 thickness: px(1.),
21863 color: None,
21864 wavy: false,
21865 }),
21866 ..Default::default()
21867 },
21868 cx,
21869 );
21870 }
21871
21872 self.ime_transaction = self.ime_transaction.or(transaction);
21873 if let Some(transaction) = self.ime_transaction {
21874 self.buffer.update(cx, |buffer, cx| {
21875 buffer.group_until_transaction(transaction, cx);
21876 });
21877 }
21878
21879 if self.text_highlights::<PendingInput>(cx).is_none() {
21880 self.ime_transaction.take();
21881 }
21882 }
21883
21884 pub fn register_action_renderer(
21885 &mut self,
21886 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21887 ) -> Subscription {
21888 let id = self.next_editor_action_id.post_inc();
21889 self.editor_actions
21890 .borrow_mut()
21891 .insert(id, Box::new(listener));
21892
21893 let editor_actions = self.editor_actions.clone();
21894 Subscription::new(move || {
21895 editor_actions.borrow_mut().remove(&id);
21896 })
21897 }
21898
21899 pub fn register_action<A: Action>(
21900 &mut self,
21901 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21902 ) -> Subscription {
21903 let id = self.next_editor_action_id.post_inc();
21904 let listener = Arc::new(listener);
21905 self.editor_actions.borrow_mut().insert(
21906 id,
21907 Box::new(move |_, window, _| {
21908 let listener = listener.clone();
21909 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21910 let action = action.downcast_ref().unwrap();
21911 if phase == DispatchPhase::Bubble {
21912 listener(action, window, cx)
21913 }
21914 })
21915 }),
21916 );
21917
21918 let editor_actions = self.editor_actions.clone();
21919 Subscription::new(move || {
21920 editor_actions.borrow_mut().remove(&id);
21921 })
21922 }
21923
21924 pub fn file_header_size(&self) -> u32 {
21925 FILE_HEADER_HEIGHT
21926 }
21927
21928 pub fn restore(
21929 &mut self,
21930 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21931 window: &mut Window,
21932 cx: &mut Context<Self>,
21933 ) {
21934 let workspace = self.workspace();
21935 let project = self.project();
21936 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21937 let mut tasks = Vec::new();
21938 for (buffer_id, changes) in revert_changes {
21939 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21940 buffer.update(cx, |buffer, cx| {
21941 buffer.edit(
21942 changes
21943 .into_iter()
21944 .map(|(range, text)| (range, text.to_string())),
21945 None,
21946 cx,
21947 );
21948 });
21949
21950 if let Some(project) =
21951 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21952 {
21953 project.update(cx, |project, cx| {
21954 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21955 })
21956 }
21957 }
21958 }
21959 tasks
21960 });
21961 cx.spawn_in(window, async move |_, cx| {
21962 for (buffer, task) in save_tasks {
21963 let result = task.await;
21964 if result.is_err() {
21965 let Some(path) = buffer
21966 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21967 .ok()
21968 else {
21969 continue;
21970 };
21971 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21972 let Some(task) = cx
21973 .update_window_entity(workspace, |workspace, window, cx| {
21974 workspace
21975 .open_path_preview(path, None, false, false, false, window, cx)
21976 })
21977 .ok()
21978 else {
21979 continue;
21980 };
21981 task.await.log_err();
21982 }
21983 }
21984 }
21985 })
21986 .detach();
21987 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21988 selections.refresh()
21989 });
21990 }
21991
21992 pub fn to_pixel_point(
21993 &self,
21994 source: multi_buffer::Anchor,
21995 editor_snapshot: &EditorSnapshot,
21996 window: &mut Window,
21997 ) -> Option<gpui::Point<Pixels>> {
21998 let source_point = source.to_display_point(editor_snapshot);
21999 self.display_to_pixel_point(source_point, editor_snapshot, window)
22000 }
22001
22002 pub fn display_to_pixel_point(
22003 &self,
22004 source: DisplayPoint,
22005 editor_snapshot: &EditorSnapshot,
22006 window: &mut Window,
22007 ) -> Option<gpui::Point<Pixels>> {
22008 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22009 let text_layout_details = self.text_layout_details(window);
22010 let scroll_top = text_layout_details
22011 .scroll_anchor
22012 .scroll_position(editor_snapshot)
22013 .y;
22014
22015 if source.row().as_f64() < scroll_top.floor() {
22016 return None;
22017 }
22018 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22019 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22020 Some(gpui::Point::new(source_x, source_y))
22021 }
22022
22023 pub fn has_visible_completions_menu(&self) -> bool {
22024 !self.edit_prediction_preview_is_active()
22025 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22026 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22027 })
22028 }
22029
22030 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22031 if self.mode.is_minimap() {
22032 return;
22033 }
22034 self.addons
22035 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22036 }
22037
22038 pub fn unregister_addon<T: Addon>(&mut self) {
22039 self.addons.remove(&std::any::TypeId::of::<T>());
22040 }
22041
22042 pub fn addon<T: Addon>(&self) -> Option<&T> {
22043 let type_id = std::any::TypeId::of::<T>();
22044 self.addons
22045 .get(&type_id)
22046 .and_then(|item| item.to_any().downcast_ref::<T>())
22047 }
22048
22049 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22050 let type_id = std::any::TypeId::of::<T>();
22051 self.addons
22052 .get_mut(&type_id)
22053 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22054 }
22055
22056 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22057 let text_layout_details = self.text_layout_details(window);
22058 let style = &text_layout_details.editor_style;
22059 let font_id = window.text_system().resolve_font(&style.text.font());
22060 let font_size = style.text.font_size.to_pixels(window.rem_size());
22061 let line_height = style.text.line_height_in_pixels(window.rem_size());
22062 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22063 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22064
22065 CharacterDimensions {
22066 em_width,
22067 em_advance,
22068 line_height,
22069 }
22070 }
22071
22072 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22073 self.load_diff_task.clone()
22074 }
22075
22076 fn read_metadata_from_db(
22077 &mut self,
22078 item_id: u64,
22079 workspace_id: WorkspaceId,
22080 window: &mut Window,
22081 cx: &mut Context<Editor>,
22082 ) {
22083 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22084 && !self.mode.is_minimap()
22085 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22086 {
22087 let buffer_snapshot = OnceCell::new();
22088
22089 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22090 && !folds.is_empty()
22091 {
22092 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22093 self.fold_ranges(
22094 folds
22095 .into_iter()
22096 .map(|(start, end)| {
22097 snapshot.clip_offset(start, Bias::Left)
22098 ..snapshot.clip_offset(end, Bias::Right)
22099 })
22100 .collect(),
22101 false,
22102 window,
22103 cx,
22104 );
22105 }
22106
22107 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22108 && !selections.is_empty()
22109 {
22110 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22111 // skip adding the initial selection to selection history
22112 self.selection_history.mode = SelectionHistoryMode::Skipping;
22113 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22114 s.select_ranges(selections.into_iter().map(|(start, end)| {
22115 snapshot.clip_offset(start, Bias::Left)
22116 ..snapshot.clip_offset(end, Bias::Right)
22117 }));
22118 });
22119 self.selection_history.mode = SelectionHistoryMode::Normal;
22120 };
22121 }
22122
22123 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22124 }
22125
22126 fn update_lsp_data(
22127 &mut self,
22128 for_buffer: Option<BufferId>,
22129 window: &mut Window,
22130 cx: &mut Context<'_, Self>,
22131 ) {
22132 self.pull_diagnostics(for_buffer, window, cx);
22133 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22134 }
22135
22136 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22137 if self.ignore_lsp_data() {
22138 return;
22139 }
22140 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22141 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22142 }
22143 }
22144
22145 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22146 if !self.registered_buffers.contains_key(&buffer_id)
22147 && let Some(project) = self.project.as_ref()
22148 {
22149 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22150 project.update(cx, |project, cx| {
22151 self.registered_buffers.insert(
22152 buffer_id,
22153 project.register_buffer_with_language_servers(&buffer, cx),
22154 );
22155 });
22156 } else {
22157 self.registered_buffers.remove(&buffer_id);
22158 }
22159 }
22160 }
22161
22162 fn ignore_lsp_data(&self) -> bool {
22163 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22164 // skip any LSP updates for it.
22165 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22166 }
22167}
22168
22169fn edit_for_markdown_paste<'a>(
22170 buffer: &MultiBufferSnapshot,
22171 range: Range<usize>,
22172 to_insert: &'a str,
22173 url: Option<url::Url>,
22174) -> (Range<usize>, Cow<'a, str>) {
22175 if url.is_none() {
22176 return (range, Cow::Borrowed(to_insert));
22177 };
22178
22179 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22180
22181 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22182 Cow::Borrowed(to_insert)
22183 } else {
22184 Cow::Owned(format!("[{old_text}]({to_insert})"))
22185 };
22186 (range, new_text)
22187}
22188
22189#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22190pub enum VimFlavor {
22191 Vim,
22192 Helix,
22193}
22194
22195pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22196 if vim_mode_setting::HelixModeSetting::try_get(cx)
22197 .map(|helix_mode| helix_mode.0)
22198 .unwrap_or(false)
22199 {
22200 Some(VimFlavor::Helix)
22201 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22202 .map(|vim_mode| vim_mode.0)
22203 .unwrap_or(false)
22204 {
22205 Some(VimFlavor::Vim)
22206 } else {
22207 None // neither vim nor helix mode
22208 }
22209}
22210
22211fn process_completion_for_edit(
22212 completion: &Completion,
22213 intent: CompletionIntent,
22214 buffer: &Entity<Buffer>,
22215 cursor_position: &text::Anchor,
22216 cx: &mut Context<Editor>,
22217) -> CompletionEdit {
22218 let buffer = buffer.read(cx);
22219 let buffer_snapshot = buffer.snapshot();
22220 let (snippet, new_text) = if completion.is_snippet() {
22221 let mut snippet_source = completion.new_text.clone();
22222 // Workaround for typescript language server issues so that methods don't expand within
22223 // strings and functions with type expressions. The previous point is used because the query
22224 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22225 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22226 let previous_point = if previous_point.column > 0 {
22227 cursor_position.to_previous_offset(&buffer_snapshot)
22228 } else {
22229 cursor_position.to_offset(&buffer_snapshot)
22230 };
22231 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22232 && scope.prefers_label_for_snippet_in_completion()
22233 && let Some(label) = completion.label()
22234 && matches!(
22235 completion.kind(),
22236 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22237 )
22238 {
22239 snippet_source = label;
22240 }
22241 match Snippet::parse(&snippet_source).log_err() {
22242 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22243 None => (None, completion.new_text.clone()),
22244 }
22245 } else {
22246 (None, completion.new_text.clone())
22247 };
22248
22249 let mut range_to_replace = {
22250 let replace_range = &completion.replace_range;
22251 if let CompletionSource::Lsp {
22252 insert_range: Some(insert_range),
22253 ..
22254 } = &completion.source
22255 {
22256 debug_assert_eq!(
22257 insert_range.start, replace_range.start,
22258 "insert_range and replace_range should start at the same position"
22259 );
22260 debug_assert!(
22261 insert_range
22262 .start
22263 .cmp(cursor_position, &buffer_snapshot)
22264 .is_le(),
22265 "insert_range should start before or at cursor position"
22266 );
22267 debug_assert!(
22268 replace_range
22269 .start
22270 .cmp(cursor_position, &buffer_snapshot)
22271 .is_le(),
22272 "replace_range should start before or at cursor position"
22273 );
22274
22275 let should_replace = match intent {
22276 CompletionIntent::CompleteWithInsert => false,
22277 CompletionIntent::CompleteWithReplace => true,
22278 CompletionIntent::Complete | CompletionIntent::Compose => {
22279 let insert_mode =
22280 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22281 .completions
22282 .lsp_insert_mode;
22283 match insert_mode {
22284 LspInsertMode::Insert => false,
22285 LspInsertMode::Replace => true,
22286 LspInsertMode::ReplaceSubsequence => {
22287 let mut text_to_replace = buffer.chars_for_range(
22288 buffer.anchor_before(replace_range.start)
22289 ..buffer.anchor_after(replace_range.end),
22290 );
22291 let mut current_needle = text_to_replace.next();
22292 for haystack_ch in completion.label.text.chars() {
22293 if let Some(needle_ch) = current_needle
22294 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22295 {
22296 current_needle = text_to_replace.next();
22297 }
22298 }
22299 current_needle.is_none()
22300 }
22301 LspInsertMode::ReplaceSuffix => {
22302 if replace_range
22303 .end
22304 .cmp(cursor_position, &buffer_snapshot)
22305 .is_gt()
22306 {
22307 let range_after_cursor = *cursor_position..replace_range.end;
22308 let text_after_cursor = buffer
22309 .text_for_range(
22310 buffer.anchor_before(range_after_cursor.start)
22311 ..buffer.anchor_after(range_after_cursor.end),
22312 )
22313 .collect::<String>()
22314 .to_ascii_lowercase();
22315 completion
22316 .label
22317 .text
22318 .to_ascii_lowercase()
22319 .ends_with(&text_after_cursor)
22320 } else {
22321 true
22322 }
22323 }
22324 }
22325 }
22326 };
22327
22328 if should_replace {
22329 replace_range.clone()
22330 } else {
22331 insert_range.clone()
22332 }
22333 } else {
22334 replace_range.clone()
22335 }
22336 };
22337
22338 if range_to_replace
22339 .end
22340 .cmp(cursor_position, &buffer_snapshot)
22341 .is_lt()
22342 {
22343 range_to_replace.end = *cursor_position;
22344 }
22345
22346 CompletionEdit {
22347 new_text,
22348 replace_range: range_to_replace.to_offset(buffer),
22349 snippet,
22350 }
22351}
22352
22353struct CompletionEdit {
22354 new_text: String,
22355 replace_range: Range<usize>,
22356 snippet: Option<Snippet>,
22357}
22358
22359fn insert_extra_newline_brackets(
22360 buffer: &MultiBufferSnapshot,
22361 range: Range<usize>,
22362 language: &language::LanguageScope,
22363) -> bool {
22364 let leading_whitespace_len = buffer
22365 .reversed_chars_at(range.start)
22366 .take_while(|c| c.is_whitespace() && *c != '\n')
22367 .map(|c| c.len_utf8())
22368 .sum::<usize>();
22369 let trailing_whitespace_len = buffer
22370 .chars_at(range.end)
22371 .take_while(|c| c.is_whitespace() && *c != '\n')
22372 .map(|c| c.len_utf8())
22373 .sum::<usize>();
22374 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22375
22376 language.brackets().any(|(pair, enabled)| {
22377 let pair_start = pair.start.trim_end();
22378 let pair_end = pair.end.trim_start();
22379
22380 enabled
22381 && pair.newline
22382 && buffer.contains_str_at(range.end, pair_end)
22383 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22384 })
22385}
22386
22387fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22388 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22389 [(buffer, range, _)] => (*buffer, range.clone()),
22390 _ => return false,
22391 };
22392 let pair = {
22393 let mut result: Option<BracketMatch> = None;
22394
22395 for pair in buffer
22396 .all_bracket_ranges(range.clone())
22397 .into_iter()
22398 .filter(move |pair| {
22399 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22400 })
22401 {
22402 let len = pair.close_range.end - pair.open_range.start;
22403
22404 if let Some(existing) = &result {
22405 let existing_len = existing.close_range.end - existing.open_range.start;
22406 if len > existing_len {
22407 continue;
22408 }
22409 }
22410
22411 result = Some(pair);
22412 }
22413
22414 result
22415 };
22416 let Some(pair) = pair else {
22417 return false;
22418 };
22419 pair.newline_only
22420 && buffer
22421 .chars_for_range(pair.open_range.end..range.start)
22422 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22423 .all(|c| c.is_whitespace() && c != '\n')
22424}
22425
22426fn update_uncommitted_diff_for_buffer(
22427 editor: Entity<Editor>,
22428 project: &Entity<Project>,
22429 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22430 buffer: Entity<MultiBuffer>,
22431 cx: &mut App,
22432) -> Task<()> {
22433 let mut tasks = Vec::new();
22434 project.update(cx, |project, cx| {
22435 for buffer in buffers {
22436 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22437 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22438 }
22439 }
22440 });
22441 cx.spawn(async move |cx| {
22442 let diffs = future::join_all(tasks).await;
22443 if editor
22444 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22445 .unwrap_or(false)
22446 {
22447 return;
22448 }
22449
22450 buffer
22451 .update(cx, |buffer, cx| {
22452 for diff in diffs.into_iter().flatten() {
22453 buffer.add_diff(diff, cx);
22454 }
22455 })
22456 .ok();
22457 })
22458}
22459
22460fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22461 let tab_size = tab_size.get() as usize;
22462 let mut width = offset;
22463
22464 for ch in text.chars() {
22465 width += if ch == '\t' {
22466 tab_size - (width % tab_size)
22467 } else {
22468 1
22469 };
22470 }
22471
22472 width - offset
22473}
22474
22475#[cfg(test)]
22476mod tests {
22477 use super::*;
22478
22479 #[test]
22480 fn test_string_size_with_expanded_tabs() {
22481 let nz = |val| NonZeroU32::new(val).unwrap();
22482 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22483 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22484 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22485 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22486 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22487 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22488 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22489 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22490 }
22491}
22492
22493/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22494struct WordBreakingTokenizer<'a> {
22495 input: &'a str,
22496}
22497
22498impl<'a> WordBreakingTokenizer<'a> {
22499 fn new(input: &'a str) -> Self {
22500 Self { input }
22501 }
22502}
22503
22504fn is_char_ideographic(ch: char) -> bool {
22505 use unicode_script::Script::*;
22506 use unicode_script::UnicodeScript;
22507 matches!(ch.script(), Han | Tangut | Yi)
22508}
22509
22510fn is_grapheme_ideographic(text: &str) -> bool {
22511 text.chars().any(is_char_ideographic)
22512}
22513
22514fn is_grapheme_whitespace(text: &str) -> bool {
22515 text.chars().any(|x| x.is_whitespace())
22516}
22517
22518fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22519 text.chars()
22520 .next()
22521 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22522}
22523
22524#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22525enum WordBreakToken<'a> {
22526 Word { token: &'a str, grapheme_len: usize },
22527 InlineWhitespace { token: &'a str, grapheme_len: usize },
22528 Newline,
22529}
22530
22531impl<'a> Iterator for WordBreakingTokenizer<'a> {
22532 /// Yields a span, the count of graphemes in the token, and whether it was
22533 /// whitespace. Note that it also breaks at word boundaries.
22534 type Item = WordBreakToken<'a>;
22535
22536 fn next(&mut self) -> Option<Self::Item> {
22537 use unicode_segmentation::UnicodeSegmentation;
22538 if self.input.is_empty() {
22539 return None;
22540 }
22541
22542 let mut iter = self.input.graphemes(true).peekable();
22543 let mut offset = 0;
22544 let mut grapheme_len = 0;
22545 if let Some(first_grapheme) = iter.next() {
22546 let is_newline = first_grapheme == "\n";
22547 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22548 offset += first_grapheme.len();
22549 grapheme_len += 1;
22550 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22551 if let Some(grapheme) = iter.peek().copied()
22552 && should_stay_with_preceding_ideograph(grapheme)
22553 {
22554 offset += grapheme.len();
22555 grapheme_len += 1;
22556 }
22557 } else {
22558 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22559 let mut next_word_bound = words.peek().copied();
22560 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22561 next_word_bound = words.next();
22562 }
22563 while let Some(grapheme) = iter.peek().copied() {
22564 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22565 break;
22566 };
22567 if is_grapheme_whitespace(grapheme) != is_whitespace
22568 || (grapheme == "\n") != is_newline
22569 {
22570 break;
22571 };
22572 offset += grapheme.len();
22573 grapheme_len += 1;
22574 iter.next();
22575 }
22576 }
22577 let token = &self.input[..offset];
22578 self.input = &self.input[offset..];
22579 if token == "\n" {
22580 Some(WordBreakToken::Newline)
22581 } else if is_whitespace {
22582 Some(WordBreakToken::InlineWhitespace {
22583 token,
22584 grapheme_len,
22585 })
22586 } else {
22587 Some(WordBreakToken::Word {
22588 token,
22589 grapheme_len,
22590 })
22591 }
22592 } else {
22593 None
22594 }
22595 }
22596}
22597
22598#[test]
22599fn test_word_breaking_tokenizer() {
22600 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22601 ("", &[]),
22602 (" ", &[whitespace(" ", 2)]),
22603 ("Ʒ", &[word("Ʒ", 1)]),
22604 ("Ǽ", &[word("Ǽ", 1)]),
22605 ("⋑", &[word("⋑", 1)]),
22606 ("⋑⋑", &[word("⋑⋑", 2)]),
22607 (
22608 "原理,进而",
22609 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22610 ),
22611 (
22612 "hello world",
22613 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22614 ),
22615 (
22616 "hello, world",
22617 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22618 ),
22619 (
22620 " hello world",
22621 &[
22622 whitespace(" ", 2),
22623 word("hello", 5),
22624 whitespace(" ", 1),
22625 word("world", 5),
22626 ],
22627 ),
22628 (
22629 "这是什么 \n 钢笔",
22630 &[
22631 word("这", 1),
22632 word("是", 1),
22633 word("什", 1),
22634 word("么", 1),
22635 whitespace(" ", 1),
22636 newline(),
22637 whitespace(" ", 1),
22638 word("钢", 1),
22639 word("笔", 1),
22640 ],
22641 ),
22642 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22643 ];
22644
22645 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22646 WordBreakToken::Word {
22647 token,
22648 grapheme_len,
22649 }
22650 }
22651
22652 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22653 WordBreakToken::InlineWhitespace {
22654 token,
22655 grapheme_len,
22656 }
22657 }
22658
22659 fn newline() -> WordBreakToken<'static> {
22660 WordBreakToken::Newline
22661 }
22662
22663 for (input, result) in tests {
22664 assert_eq!(
22665 WordBreakingTokenizer::new(input)
22666 .collect::<Vec<_>>()
22667 .as_slice(),
22668 *result,
22669 );
22670 }
22671}
22672
22673fn wrap_with_prefix(
22674 first_line_prefix: String,
22675 subsequent_lines_prefix: String,
22676 unwrapped_text: String,
22677 wrap_column: usize,
22678 tab_size: NonZeroU32,
22679 preserve_existing_whitespace: bool,
22680) -> String {
22681 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22682 let subsequent_lines_prefix_len =
22683 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22684 let mut wrapped_text = String::new();
22685 let mut current_line = first_line_prefix;
22686 let mut is_first_line = true;
22687
22688 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22689 let mut current_line_len = first_line_prefix_len;
22690 let mut in_whitespace = false;
22691 for token in tokenizer {
22692 let have_preceding_whitespace = in_whitespace;
22693 match token {
22694 WordBreakToken::Word {
22695 token,
22696 grapheme_len,
22697 } => {
22698 in_whitespace = false;
22699 let current_prefix_len = if is_first_line {
22700 first_line_prefix_len
22701 } else {
22702 subsequent_lines_prefix_len
22703 };
22704 if current_line_len + grapheme_len > 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 }
22713 current_line.push_str(token);
22714 current_line_len += grapheme_len;
22715 }
22716 WordBreakToken::InlineWhitespace {
22717 mut token,
22718 mut grapheme_len,
22719 } => {
22720 in_whitespace = true;
22721 if have_preceding_whitespace && !preserve_existing_whitespace {
22722 continue;
22723 }
22724 if !preserve_existing_whitespace {
22725 // Keep a single whitespace grapheme as-is
22726 if let Some(first) =
22727 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22728 {
22729 token = first;
22730 } else {
22731 token = " ";
22732 }
22733 grapheme_len = 1;
22734 }
22735 let current_prefix_len = if is_first_line {
22736 first_line_prefix_len
22737 } else {
22738 subsequent_lines_prefix_len
22739 };
22740 if current_line_len + grapheme_len > wrap_column {
22741 wrapped_text.push_str(current_line.trim_end());
22742 wrapped_text.push('\n');
22743 is_first_line = false;
22744 current_line = subsequent_lines_prefix.clone();
22745 current_line_len = subsequent_lines_prefix_len;
22746 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22747 current_line.push_str(token);
22748 current_line_len += grapheme_len;
22749 }
22750 }
22751 WordBreakToken::Newline => {
22752 in_whitespace = true;
22753 let current_prefix_len = if is_first_line {
22754 first_line_prefix_len
22755 } else {
22756 subsequent_lines_prefix_len
22757 };
22758 if preserve_existing_whitespace {
22759 wrapped_text.push_str(current_line.trim_end());
22760 wrapped_text.push('\n');
22761 is_first_line = false;
22762 current_line = subsequent_lines_prefix.clone();
22763 current_line_len = subsequent_lines_prefix_len;
22764 } else if have_preceding_whitespace {
22765 continue;
22766 } else if current_line_len + 1 > wrap_column
22767 && current_line_len != current_prefix_len
22768 {
22769 wrapped_text.push_str(current_line.trim_end());
22770 wrapped_text.push('\n');
22771 is_first_line = false;
22772 current_line = subsequent_lines_prefix.clone();
22773 current_line_len = subsequent_lines_prefix_len;
22774 } else if current_line_len != current_prefix_len {
22775 current_line.push(' ');
22776 current_line_len += 1;
22777 }
22778 }
22779 }
22780 }
22781
22782 if !current_line.is_empty() {
22783 wrapped_text.push_str(¤t_line);
22784 }
22785 wrapped_text
22786}
22787
22788#[test]
22789fn test_wrap_with_prefix() {
22790 assert_eq!(
22791 wrap_with_prefix(
22792 "# ".to_string(),
22793 "# ".to_string(),
22794 "abcdefg".to_string(),
22795 4,
22796 NonZeroU32::new(4).unwrap(),
22797 false,
22798 ),
22799 "# abcdefg"
22800 );
22801 assert_eq!(
22802 wrap_with_prefix(
22803 "".to_string(),
22804 "".to_string(),
22805 "\thello world".to_string(),
22806 8,
22807 NonZeroU32::new(4).unwrap(),
22808 false,
22809 ),
22810 "hello\nworld"
22811 );
22812 assert_eq!(
22813 wrap_with_prefix(
22814 "// ".to_string(),
22815 "// ".to_string(),
22816 "xx \nyy zz aa bb cc".to_string(),
22817 12,
22818 NonZeroU32::new(4).unwrap(),
22819 false,
22820 ),
22821 "// xx yy zz\n// aa bb cc"
22822 );
22823 assert_eq!(
22824 wrap_with_prefix(
22825 String::new(),
22826 String::new(),
22827 "这是什么 \n 钢笔".to_string(),
22828 3,
22829 NonZeroU32::new(4).unwrap(),
22830 false,
22831 ),
22832 "这是什\n么 钢\n笔"
22833 );
22834 assert_eq!(
22835 wrap_with_prefix(
22836 String::new(),
22837 String::new(),
22838 format!("foo{}bar", '\u{2009}'), // thin space
22839 80,
22840 NonZeroU32::new(4).unwrap(),
22841 false,
22842 ),
22843 format!("foo{}bar", '\u{2009}')
22844 );
22845}
22846
22847pub trait CollaborationHub {
22848 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22849 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22850 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22851}
22852
22853impl CollaborationHub for Entity<Project> {
22854 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22855 self.read(cx).collaborators()
22856 }
22857
22858 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22859 self.read(cx).user_store().read(cx).participant_indices()
22860 }
22861
22862 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22863 let this = self.read(cx);
22864 let user_ids = this.collaborators().values().map(|c| c.user_id);
22865 this.user_store().read(cx).participant_names(user_ids, cx)
22866 }
22867}
22868
22869pub trait SemanticsProvider {
22870 fn hover(
22871 &self,
22872 buffer: &Entity<Buffer>,
22873 position: text::Anchor,
22874 cx: &mut App,
22875 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22876
22877 fn inline_values(
22878 &self,
22879 buffer_handle: Entity<Buffer>,
22880 range: Range<text::Anchor>,
22881 cx: &mut App,
22882 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22883
22884 fn applicable_inlay_chunks(
22885 &self,
22886 buffer: &Entity<Buffer>,
22887 ranges: &[Range<text::Anchor>],
22888 cx: &mut App,
22889 ) -> Vec<Range<BufferRow>>;
22890
22891 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22892
22893 fn inlay_hints(
22894 &self,
22895 invalidate: InvalidationStrategy,
22896 buffer: Entity<Buffer>,
22897 ranges: Vec<Range<text::Anchor>>,
22898 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22899 cx: &mut App,
22900 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22901
22902 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22903
22904 fn document_highlights(
22905 &self,
22906 buffer: &Entity<Buffer>,
22907 position: text::Anchor,
22908 cx: &mut App,
22909 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22910
22911 fn definitions(
22912 &self,
22913 buffer: &Entity<Buffer>,
22914 position: text::Anchor,
22915 kind: GotoDefinitionKind,
22916 cx: &mut App,
22917 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22918
22919 fn range_for_rename(
22920 &self,
22921 buffer: &Entity<Buffer>,
22922 position: text::Anchor,
22923 cx: &mut App,
22924 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22925
22926 fn perform_rename(
22927 &self,
22928 buffer: &Entity<Buffer>,
22929 position: text::Anchor,
22930 new_name: String,
22931 cx: &mut App,
22932 ) -> Option<Task<Result<ProjectTransaction>>>;
22933}
22934
22935pub trait CompletionProvider {
22936 fn completions(
22937 &self,
22938 excerpt_id: ExcerptId,
22939 buffer: &Entity<Buffer>,
22940 buffer_position: text::Anchor,
22941 trigger: CompletionContext,
22942 window: &mut Window,
22943 cx: &mut Context<Editor>,
22944 ) -> Task<Result<Vec<CompletionResponse>>>;
22945
22946 fn resolve_completions(
22947 &self,
22948 _buffer: Entity<Buffer>,
22949 _completion_indices: Vec<usize>,
22950 _completions: Rc<RefCell<Box<[Completion]>>>,
22951 _cx: &mut Context<Editor>,
22952 ) -> Task<Result<bool>> {
22953 Task::ready(Ok(false))
22954 }
22955
22956 fn apply_additional_edits_for_completion(
22957 &self,
22958 _buffer: Entity<Buffer>,
22959 _completions: Rc<RefCell<Box<[Completion]>>>,
22960 _completion_index: usize,
22961 _push_to_history: bool,
22962 _cx: &mut Context<Editor>,
22963 ) -> Task<Result<Option<language::Transaction>>> {
22964 Task::ready(Ok(None))
22965 }
22966
22967 fn is_completion_trigger(
22968 &self,
22969 buffer: &Entity<Buffer>,
22970 position: language::Anchor,
22971 text: &str,
22972 trigger_in_words: bool,
22973 menu_is_open: bool,
22974 cx: &mut Context<Editor>,
22975 ) -> bool;
22976
22977 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22978
22979 fn sort_completions(&self) -> bool {
22980 true
22981 }
22982
22983 fn filter_completions(&self) -> bool {
22984 true
22985 }
22986}
22987
22988pub trait CodeActionProvider {
22989 fn id(&self) -> Arc<str>;
22990
22991 fn code_actions(
22992 &self,
22993 buffer: &Entity<Buffer>,
22994 range: Range<text::Anchor>,
22995 window: &mut Window,
22996 cx: &mut App,
22997 ) -> Task<Result<Vec<CodeAction>>>;
22998
22999 fn apply_code_action(
23000 &self,
23001 buffer_handle: Entity<Buffer>,
23002 action: CodeAction,
23003 excerpt_id: ExcerptId,
23004 push_to_history: bool,
23005 window: &mut Window,
23006 cx: &mut App,
23007 ) -> Task<Result<ProjectTransaction>>;
23008}
23009
23010impl CodeActionProvider for Entity<Project> {
23011 fn id(&self) -> Arc<str> {
23012 "project".into()
23013 }
23014
23015 fn code_actions(
23016 &self,
23017 buffer: &Entity<Buffer>,
23018 range: Range<text::Anchor>,
23019 _window: &mut Window,
23020 cx: &mut App,
23021 ) -> Task<Result<Vec<CodeAction>>> {
23022 self.update(cx, |project, cx| {
23023 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23024 let code_actions = project.code_actions(buffer, range, None, cx);
23025 cx.background_spawn(async move {
23026 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23027 Ok(code_lens_actions
23028 .context("code lens fetch")?
23029 .into_iter()
23030 .flatten()
23031 .chain(
23032 code_actions
23033 .context("code action fetch")?
23034 .into_iter()
23035 .flatten(),
23036 )
23037 .collect())
23038 })
23039 })
23040 }
23041
23042 fn apply_code_action(
23043 &self,
23044 buffer_handle: Entity<Buffer>,
23045 action: CodeAction,
23046 _excerpt_id: ExcerptId,
23047 push_to_history: bool,
23048 _window: &mut Window,
23049 cx: &mut App,
23050 ) -> Task<Result<ProjectTransaction>> {
23051 self.update(cx, |project, cx| {
23052 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23053 })
23054 }
23055}
23056
23057fn snippet_completions(
23058 project: &Project,
23059 buffer: &Entity<Buffer>,
23060 buffer_position: text::Anchor,
23061 cx: &mut App,
23062) -> Task<Result<CompletionResponse>> {
23063 let languages = buffer.read(cx).languages_at(buffer_position);
23064 let snippet_store = project.snippets().read(cx);
23065
23066 let scopes: Vec<_> = languages
23067 .iter()
23068 .filter_map(|language| {
23069 let language_name = language.lsp_id();
23070 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23071
23072 if snippets.is_empty() {
23073 None
23074 } else {
23075 Some((language.default_scope(), snippets))
23076 }
23077 })
23078 .collect();
23079
23080 if scopes.is_empty() {
23081 return Task::ready(Ok(CompletionResponse {
23082 completions: vec![],
23083 display_options: CompletionDisplayOptions::default(),
23084 is_incomplete: false,
23085 }));
23086 }
23087
23088 let snapshot = buffer.read(cx).text_snapshot();
23089 let executor = cx.background_executor().clone();
23090
23091 cx.background_spawn(async move {
23092 let mut is_incomplete = false;
23093 let mut completions: Vec<Completion> = Vec::new();
23094 for (scope, snippets) in scopes.into_iter() {
23095 let classifier =
23096 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23097
23098 const MAX_WORD_PREFIX_LEN: usize = 128;
23099 let last_word: String = snapshot
23100 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23101 .take(MAX_WORD_PREFIX_LEN)
23102 .take_while(|c| classifier.is_word(*c))
23103 .collect::<String>()
23104 .chars()
23105 .rev()
23106 .collect();
23107
23108 if last_word.is_empty() {
23109 return Ok(CompletionResponse {
23110 completions: vec![],
23111 display_options: CompletionDisplayOptions::default(),
23112 is_incomplete: true,
23113 });
23114 }
23115
23116 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23117 let to_lsp = |point: &text::Anchor| {
23118 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23119 point_to_lsp(end)
23120 };
23121 let lsp_end = to_lsp(&buffer_position);
23122
23123 let candidates = snippets
23124 .iter()
23125 .enumerate()
23126 .flat_map(|(ix, snippet)| {
23127 snippet
23128 .prefix
23129 .iter()
23130 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23131 })
23132 .collect::<Vec<StringMatchCandidate>>();
23133
23134 const MAX_RESULTS: usize = 100;
23135 let mut matches = fuzzy::match_strings(
23136 &candidates,
23137 &last_word,
23138 last_word.chars().any(|c| c.is_uppercase()),
23139 true,
23140 MAX_RESULTS,
23141 &Default::default(),
23142 executor.clone(),
23143 )
23144 .await;
23145
23146 if matches.len() >= MAX_RESULTS {
23147 is_incomplete = true;
23148 }
23149
23150 // Remove all candidates where the query's start does not match the start of any word in the candidate
23151 if let Some(query_start) = last_word.chars().next() {
23152 matches.retain(|string_match| {
23153 split_words(&string_match.string).any(|word| {
23154 // Check that the first codepoint of the word as lowercase matches the first
23155 // codepoint of the query as lowercase
23156 word.chars()
23157 .flat_map(|codepoint| codepoint.to_lowercase())
23158 .zip(query_start.to_lowercase())
23159 .all(|(word_cp, query_cp)| word_cp == query_cp)
23160 })
23161 });
23162 }
23163
23164 let matched_strings = matches
23165 .into_iter()
23166 .map(|m| m.string)
23167 .collect::<HashSet<_>>();
23168
23169 completions.extend(snippets.iter().filter_map(|snippet| {
23170 let matching_prefix = snippet
23171 .prefix
23172 .iter()
23173 .find(|prefix| matched_strings.contains(*prefix))?;
23174 let start = as_offset - last_word.len();
23175 let start = snapshot.anchor_before(start);
23176 let range = start..buffer_position;
23177 let lsp_start = to_lsp(&start);
23178 let lsp_range = lsp::Range {
23179 start: lsp_start,
23180 end: lsp_end,
23181 };
23182 Some(Completion {
23183 replace_range: range,
23184 new_text: snippet.body.clone(),
23185 source: CompletionSource::Lsp {
23186 insert_range: None,
23187 server_id: LanguageServerId(usize::MAX),
23188 resolved: true,
23189 lsp_completion: Box::new(lsp::CompletionItem {
23190 label: snippet.prefix.first().unwrap().clone(),
23191 kind: Some(CompletionItemKind::SNIPPET),
23192 label_details: snippet.description.as_ref().map(|description| {
23193 lsp::CompletionItemLabelDetails {
23194 detail: Some(description.clone()),
23195 description: None,
23196 }
23197 }),
23198 insert_text_format: Some(InsertTextFormat::SNIPPET),
23199 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23200 lsp::InsertReplaceEdit {
23201 new_text: snippet.body.clone(),
23202 insert: lsp_range,
23203 replace: lsp_range,
23204 },
23205 )),
23206 filter_text: Some(snippet.body.clone()),
23207 sort_text: Some(char::MAX.to_string()),
23208 ..lsp::CompletionItem::default()
23209 }),
23210 lsp_defaults: None,
23211 },
23212 label: CodeLabel::plain(matching_prefix.clone(), None),
23213 icon_path: None,
23214 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23215 single_line: snippet.name.clone().into(),
23216 plain_text: snippet
23217 .description
23218 .clone()
23219 .map(|description| description.into()),
23220 }),
23221 insert_text_mode: None,
23222 confirm: None,
23223 })
23224 }))
23225 }
23226
23227 Ok(CompletionResponse {
23228 completions,
23229 display_options: CompletionDisplayOptions::default(),
23230 is_incomplete,
23231 })
23232 })
23233}
23234
23235impl CompletionProvider for Entity<Project> {
23236 fn completions(
23237 &self,
23238 _excerpt_id: ExcerptId,
23239 buffer: &Entity<Buffer>,
23240 buffer_position: text::Anchor,
23241 options: CompletionContext,
23242 _window: &mut Window,
23243 cx: &mut Context<Editor>,
23244 ) -> Task<Result<Vec<CompletionResponse>>> {
23245 self.update(cx, |project, cx| {
23246 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23247 let project_completions = project.completions(buffer, buffer_position, options, cx);
23248 cx.background_spawn(async move {
23249 let mut responses = project_completions.await?;
23250 let snippets = snippets.await?;
23251 if !snippets.completions.is_empty() {
23252 responses.push(snippets);
23253 }
23254 Ok(responses)
23255 })
23256 })
23257 }
23258
23259 fn resolve_completions(
23260 &self,
23261 buffer: Entity<Buffer>,
23262 completion_indices: Vec<usize>,
23263 completions: Rc<RefCell<Box<[Completion]>>>,
23264 cx: &mut Context<Editor>,
23265 ) -> Task<Result<bool>> {
23266 self.update(cx, |project, cx| {
23267 project.lsp_store().update(cx, |lsp_store, cx| {
23268 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23269 })
23270 })
23271 }
23272
23273 fn apply_additional_edits_for_completion(
23274 &self,
23275 buffer: Entity<Buffer>,
23276 completions: Rc<RefCell<Box<[Completion]>>>,
23277 completion_index: usize,
23278 push_to_history: bool,
23279 cx: &mut Context<Editor>,
23280 ) -> Task<Result<Option<language::Transaction>>> {
23281 self.update(cx, |project, cx| {
23282 project.lsp_store().update(cx, |lsp_store, cx| {
23283 lsp_store.apply_additional_edits_for_completion(
23284 buffer,
23285 completions,
23286 completion_index,
23287 push_to_history,
23288 cx,
23289 )
23290 })
23291 })
23292 }
23293
23294 fn is_completion_trigger(
23295 &self,
23296 buffer: &Entity<Buffer>,
23297 position: language::Anchor,
23298 text: &str,
23299 trigger_in_words: bool,
23300 menu_is_open: bool,
23301 cx: &mut Context<Editor>,
23302 ) -> bool {
23303 let mut chars = text.chars();
23304 let char = if let Some(char) = chars.next() {
23305 char
23306 } else {
23307 return false;
23308 };
23309 if chars.next().is_some() {
23310 return false;
23311 }
23312
23313 let buffer = buffer.read(cx);
23314 let snapshot = buffer.snapshot();
23315 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23316 return false;
23317 }
23318 let classifier = snapshot
23319 .char_classifier_at(position)
23320 .scope_context(Some(CharScopeContext::Completion));
23321 if trigger_in_words && classifier.is_word(char) {
23322 return true;
23323 }
23324
23325 buffer.completion_triggers().contains(text)
23326 }
23327}
23328
23329impl SemanticsProvider for Entity<Project> {
23330 fn hover(
23331 &self,
23332 buffer: &Entity<Buffer>,
23333 position: text::Anchor,
23334 cx: &mut App,
23335 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23336 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23337 }
23338
23339 fn document_highlights(
23340 &self,
23341 buffer: &Entity<Buffer>,
23342 position: text::Anchor,
23343 cx: &mut App,
23344 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23345 Some(self.update(cx, |project, cx| {
23346 project.document_highlights(buffer, position, cx)
23347 }))
23348 }
23349
23350 fn definitions(
23351 &self,
23352 buffer: &Entity<Buffer>,
23353 position: text::Anchor,
23354 kind: GotoDefinitionKind,
23355 cx: &mut App,
23356 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23357 Some(self.update(cx, |project, cx| match kind {
23358 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23359 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23360 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23361 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23362 }))
23363 }
23364
23365 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23366 self.update(cx, |project, cx| {
23367 if project
23368 .active_debug_session(cx)
23369 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23370 {
23371 return true;
23372 }
23373
23374 buffer.update(cx, |buffer, cx| {
23375 project.any_language_server_supports_inlay_hints(buffer, cx)
23376 })
23377 })
23378 }
23379
23380 fn inline_values(
23381 &self,
23382 buffer_handle: Entity<Buffer>,
23383 range: Range<text::Anchor>,
23384 cx: &mut App,
23385 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23386 self.update(cx, |project, cx| {
23387 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23388
23389 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23390 })
23391 }
23392
23393 fn applicable_inlay_chunks(
23394 &self,
23395 buffer: &Entity<Buffer>,
23396 ranges: &[Range<text::Anchor>],
23397 cx: &mut App,
23398 ) -> Vec<Range<BufferRow>> {
23399 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23400 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23401 })
23402 }
23403
23404 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23405 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23406 lsp_store.invalidate_inlay_hints(for_buffers)
23407 });
23408 }
23409
23410 fn inlay_hints(
23411 &self,
23412 invalidate: InvalidationStrategy,
23413 buffer: Entity<Buffer>,
23414 ranges: Vec<Range<text::Anchor>>,
23415 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23416 cx: &mut App,
23417 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23418 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23419 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23420 }))
23421 }
23422
23423 fn range_for_rename(
23424 &self,
23425 buffer: &Entity<Buffer>,
23426 position: text::Anchor,
23427 cx: &mut App,
23428 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23429 Some(self.update(cx, |project, cx| {
23430 let buffer = buffer.clone();
23431 let task = project.prepare_rename(buffer.clone(), position, cx);
23432 cx.spawn(async move |_, cx| {
23433 Ok(match task.await? {
23434 PrepareRenameResponse::Success(range) => Some(range),
23435 PrepareRenameResponse::InvalidPosition => None,
23436 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23437 // Fallback on using TreeSitter info to determine identifier range
23438 buffer.read_with(cx, |buffer, _| {
23439 let snapshot = buffer.snapshot();
23440 let (range, kind) = snapshot.surrounding_word(position, None);
23441 if kind != Some(CharKind::Word) {
23442 return None;
23443 }
23444 Some(
23445 snapshot.anchor_before(range.start)
23446 ..snapshot.anchor_after(range.end),
23447 )
23448 })?
23449 }
23450 })
23451 })
23452 }))
23453 }
23454
23455 fn perform_rename(
23456 &self,
23457 buffer: &Entity<Buffer>,
23458 position: text::Anchor,
23459 new_name: String,
23460 cx: &mut App,
23461 ) -> Option<Task<Result<ProjectTransaction>>> {
23462 Some(self.update(cx, |project, cx| {
23463 project.perform_rename(buffer.clone(), position, new_name, cx)
23464 }))
23465 }
23466}
23467
23468fn consume_contiguous_rows(
23469 contiguous_row_selections: &mut Vec<Selection<Point>>,
23470 selection: &Selection<Point>,
23471 display_map: &DisplaySnapshot,
23472 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23473) -> (MultiBufferRow, MultiBufferRow) {
23474 contiguous_row_selections.push(selection.clone());
23475 let start_row = starting_row(selection, display_map);
23476 let mut end_row = ending_row(selection, display_map);
23477
23478 while let Some(next_selection) = selections.peek() {
23479 if next_selection.start.row <= end_row.0 {
23480 end_row = ending_row(next_selection, display_map);
23481 contiguous_row_selections.push(selections.next().unwrap().clone());
23482 } else {
23483 break;
23484 }
23485 }
23486 (start_row, end_row)
23487}
23488
23489fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23490 if selection.start.column > 0 {
23491 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23492 } else {
23493 MultiBufferRow(selection.start.row)
23494 }
23495}
23496
23497fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23498 if next_selection.end.column > 0 || next_selection.is_empty() {
23499 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23500 } else {
23501 MultiBufferRow(next_selection.end.row)
23502 }
23503}
23504
23505impl EditorSnapshot {
23506 pub fn remote_selections_in_range<'a>(
23507 &'a self,
23508 range: &'a Range<Anchor>,
23509 collaboration_hub: &dyn CollaborationHub,
23510 cx: &'a App,
23511 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23512 let participant_names = collaboration_hub.user_names(cx);
23513 let participant_indices = collaboration_hub.user_participant_indices(cx);
23514 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23515 let collaborators_by_replica_id = collaborators_by_peer_id
23516 .values()
23517 .map(|collaborator| (collaborator.replica_id, collaborator))
23518 .collect::<HashMap<_, _>>();
23519 self.buffer_snapshot()
23520 .selections_in_range(range, false)
23521 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23522 if replica_id == ReplicaId::AGENT {
23523 Some(RemoteSelection {
23524 replica_id,
23525 selection,
23526 cursor_shape,
23527 line_mode,
23528 collaborator_id: CollaboratorId::Agent,
23529 user_name: Some("Agent".into()),
23530 color: cx.theme().players().agent(),
23531 })
23532 } else {
23533 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23534 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23535 let user_name = participant_names.get(&collaborator.user_id).cloned();
23536 Some(RemoteSelection {
23537 replica_id,
23538 selection,
23539 cursor_shape,
23540 line_mode,
23541 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23542 user_name,
23543 color: if let Some(index) = participant_index {
23544 cx.theme().players().color_for_participant(index.0)
23545 } else {
23546 cx.theme().players().absent()
23547 },
23548 })
23549 }
23550 })
23551 }
23552
23553 pub fn hunks_for_ranges(
23554 &self,
23555 ranges: impl IntoIterator<Item = Range<Point>>,
23556 ) -> Vec<MultiBufferDiffHunk> {
23557 let mut hunks = Vec::new();
23558 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23559 HashMap::default();
23560 for query_range in ranges {
23561 let query_rows =
23562 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23563 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23564 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23565 ) {
23566 // Include deleted hunks that are adjacent to the query range, because
23567 // otherwise they would be missed.
23568 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23569 if hunk.status().is_deleted() {
23570 intersects_range |= hunk.row_range.start == query_rows.end;
23571 intersects_range |= hunk.row_range.end == query_rows.start;
23572 }
23573 if intersects_range {
23574 if !processed_buffer_rows
23575 .entry(hunk.buffer_id)
23576 .or_default()
23577 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23578 {
23579 continue;
23580 }
23581 hunks.push(hunk);
23582 }
23583 }
23584 }
23585
23586 hunks
23587 }
23588
23589 fn display_diff_hunks_for_rows<'a>(
23590 &'a self,
23591 display_rows: Range<DisplayRow>,
23592 folded_buffers: &'a HashSet<BufferId>,
23593 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23594 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23595 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23596
23597 self.buffer_snapshot()
23598 .diff_hunks_in_range(buffer_start..buffer_end)
23599 .filter_map(|hunk| {
23600 if folded_buffers.contains(&hunk.buffer_id) {
23601 return None;
23602 }
23603
23604 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23605 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23606
23607 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23608 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23609
23610 let display_hunk = if hunk_display_start.column() != 0 {
23611 DisplayDiffHunk::Folded {
23612 display_row: hunk_display_start.row(),
23613 }
23614 } else {
23615 let mut end_row = hunk_display_end.row();
23616 if hunk_display_end.column() > 0 {
23617 end_row.0 += 1;
23618 }
23619 let is_created_file = hunk.is_created_file();
23620 DisplayDiffHunk::Unfolded {
23621 status: hunk.status(),
23622 diff_base_byte_range: hunk.diff_base_byte_range,
23623 display_row_range: hunk_display_start.row()..end_row,
23624 multi_buffer_range: Anchor::range_in_buffer(
23625 hunk.excerpt_id,
23626 hunk.buffer_id,
23627 hunk.buffer_range,
23628 ),
23629 is_created_file,
23630 }
23631 };
23632
23633 Some(display_hunk)
23634 })
23635 }
23636
23637 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23638 self.display_snapshot
23639 .buffer_snapshot()
23640 .language_at(position)
23641 }
23642
23643 pub fn is_focused(&self) -> bool {
23644 self.is_focused
23645 }
23646
23647 pub fn placeholder_text(&self) -> Option<String> {
23648 self.placeholder_display_snapshot
23649 .as_ref()
23650 .map(|display_map| display_map.text())
23651 }
23652
23653 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23654 self.scroll_anchor.scroll_position(&self.display_snapshot)
23655 }
23656
23657 fn gutter_dimensions(
23658 &self,
23659 font_id: FontId,
23660 font_size: Pixels,
23661 max_line_number_width: Pixels,
23662 cx: &App,
23663 ) -> Option<GutterDimensions> {
23664 if !self.show_gutter {
23665 return None;
23666 }
23667
23668 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23669 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23670
23671 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23672 matches!(
23673 ProjectSettings::get_global(cx).git.git_gutter,
23674 GitGutterSetting::TrackedFiles
23675 )
23676 });
23677 let gutter_settings = EditorSettings::get_global(cx).gutter;
23678 let show_line_numbers = self
23679 .show_line_numbers
23680 .unwrap_or(gutter_settings.line_numbers);
23681 let line_gutter_width = if show_line_numbers {
23682 // Avoid flicker-like gutter resizes when the line number gains another digit by
23683 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23684 let min_width_for_number_on_gutter =
23685 ch_advance * gutter_settings.min_line_number_digits as f32;
23686 max_line_number_width.max(min_width_for_number_on_gutter)
23687 } else {
23688 0.0.into()
23689 };
23690
23691 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23692 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23693
23694 let git_blame_entries_width =
23695 self.git_blame_gutter_max_author_length
23696 .map(|max_author_length| {
23697 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23698 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23699
23700 /// The number of characters to dedicate to gaps and margins.
23701 const SPACING_WIDTH: usize = 4;
23702
23703 let max_char_count = max_author_length.min(renderer.max_author_length())
23704 + ::git::SHORT_SHA_LENGTH
23705 + MAX_RELATIVE_TIMESTAMP.len()
23706 + SPACING_WIDTH;
23707
23708 ch_advance * max_char_count
23709 });
23710
23711 let is_singleton = self.buffer_snapshot().is_singleton();
23712
23713 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23714 left_padding += if !is_singleton {
23715 ch_width * 4.0
23716 } else if show_runnables || show_breakpoints {
23717 ch_width * 3.0
23718 } else if show_git_gutter && show_line_numbers {
23719 ch_width * 2.0
23720 } else if show_git_gutter || show_line_numbers {
23721 ch_width
23722 } else {
23723 px(0.)
23724 };
23725
23726 let shows_folds = is_singleton && gutter_settings.folds;
23727
23728 let right_padding = if shows_folds && show_line_numbers {
23729 ch_width * 4.0
23730 } else if shows_folds || (!is_singleton && show_line_numbers) {
23731 ch_width * 3.0
23732 } else if show_line_numbers {
23733 ch_width
23734 } else {
23735 px(0.)
23736 };
23737
23738 Some(GutterDimensions {
23739 left_padding,
23740 right_padding,
23741 width: line_gutter_width + left_padding + right_padding,
23742 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23743 git_blame_entries_width,
23744 })
23745 }
23746
23747 pub fn render_crease_toggle(
23748 &self,
23749 buffer_row: MultiBufferRow,
23750 row_contains_cursor: bool,
23751 editor: Entity<Editor>,
23752 window: &mut Window,
23753 cx: &mut App,
23754 ) -> Option<AnyElement> {
23755 let folded = self.is_line_folded(buffer_row);
23756 let mut is_foldable = false;
23757
23758 if let Some(crease) = self
23759 .crease_snapshot
23760 .query_row(buffer_row, self.buffer_snapshot())
23761 {
23762 is_foldable = true;
23763 match crease {
23764 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23765 if let Some(render_toggle) = render_toggle {
23766 let toggle_callback =
23767 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23768 if folded {
23769 editor.update(cx, |editor, cx| {
23770 editor.fold_at(buffer_row, window, cx)
23771 });
23772 } else {
23773 editor.update(cx, |editor, cx| {
23774 editor.unfold_at(buffer_row, window, cx)
23775 });
23776 }
23777 });
23778 return Some((render_toggle)(
23779 buffer_row,
23780 folded,
23781 toggle_callback,
23782 window,
23783 cx,
23784 ));
23785 }
23786 }
23787 }
23788 }
23789
23790 is_foldable |= self.starts_indent(buffer_row);
23791
23792 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23793 Some(
23794 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23795 .toggle_state(folded)
23796 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23797 if folded {
23798 this.unfold_at(buffer_row, window, cx);
23799 } else {
23800 this.fold_at(buffer_row, window, cx);
23801 }
23802 }))
23803 .into_any_element(),
23804 )
23805 } else {
23806 None
23807 }
23808 }
23809
23810 pub fn render_crease_trailer(
23811 &self,
23812 buffer_row: MultiBufferRow,
23813 window: &mut Window,
23814 cx: &mut App,
23815 ) -> Option<AnyElement> {
23816 let folded = self.is_line_folded(buffer_row);
23817 if let Crease::Inline { render_trailer, .. } = self
23818 .crease_snapshot
23819 .query_row(buffer_row, self.buffer_snapshot())?
23820 {
23821 let render_trailer = render_trailer.as_ref()?;
23822 Some(render_trailer(buffer_row, folded, window, cx))
23823 } else {
23824 None
23825 }
23826 }
23827}
23828
23829impl Deref for EditorSnapshot {
23830 type Target = DisplaySnapshot;
23831
23832 fn deref(&self) -> &Self::Target {
23833 &self.display_snapshot
23834 }
23835}
23836
23837#[derive(Clone, Debug, PartialEq, Eq)]
23838pub enum EditorEvent {
23839 InputIgnored {
23840 text: Arc<str>,
23841 },
23842 InputHandled {
23843 utf16_range_to_replace: Option<Range<isize>>,
23844 text: Arc<str>,
23845 },
23846 ExcerptsAdded {
23847 buffer: Entity<Buffer>,
23848 predecessor: ExcerptId,
23849 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23850 },
23851 ExcerptsRemoved {
23852 ids: Vec<ExcerptId>,
23853 removed_buffer_ids: Vec<BufferId>,
23854 },
23855 BufferFoldToggled {
23856 ids: Vec<ExcerptId>,
23857 folded: bool,
23858 },
23859 ExcerptsEdited {
23860 ids: Vec<ExcerptId>,
23861 },
23862 ExcerptsExpanded {
23863 ids: Vec<ExcerptId>,
23864 },
23865 BufferEdited,
23866 Edited {
23867 transaction_id: clock::Lamport,
23868 },
23869 Reparsed(BufferId),
23870 Focused,
23871 FocusedIn,
23872 Blurred,
23873 DirtyChanged,
23874 Saved,
23875 TitleChanged,
23876 SelectionsChanged {
23877 local: bool,
23878 },
23879 ScrollPositionChanged {
23880 local: bool,
23881 autoscroll: bool,
23882 },
23883 TransactionUndone {
23884 transaction_id: clock::Lamport,
23885 },
23886 TransactionBegun {
23887 transaction_id: clock::Lamport,
23888 },
23889 CursorShapeChanged,
23890 BreadcrumbsChanged,
23891 PushedToNavHistory {
23892 anchor: Anchor,
23893 is_deactivate: bool,
23894 },
23895}
23896
23897impl EventEmitter<EditorEvent> for Editor {}
23898
23899impl Focusable for Editor {
23900 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23901 self.focus_handle.clone()
23902 }
23903}
23904
23905impl Render for Editor {
23906 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23907 let settings = ThemeSettings::get_global(cx);
23908
23909 let mut text_style = match self.mode {
23910 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23911 color: cx.theme().colors().editor_foreground,
23912 font_family: settings.ui_font.family.clone(),
23913 font_features: settings.ui_font.features.clone(),
23914 font_fallbacks: settings.ui_font.fallbacks.clone(),
23915 font_size: rems(0.875).into(),
23916 font_weight: settings.ui_font.weight,
23917 line_height: relative(settings.buffer_line_height.value()),
23918 ..Default::default()
23919 },
23920 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23921 color: cx.theme().colors().editor_foreground,
23922 font_family: settings.buffer_font.family.clone(),
23923 font_features: settings.buffer_font.features.clone(),
23924 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23925 font_size: settings.buffer_font_size(cx).into(),
23926 font_weight: settings.buffer_font.weight,
23927 line_height: relative(settings.buffer_line_height.value()),
23928 ..Default::default()
23929 },
23930 };
23931 if let Some(text_style_refinement) = &self.text_style_refinement {
23932 text_style.refine(text_style_refinement)
23933 }
23934
23935 let background = match self.mode {
23936 EditorMode::SingleLine => cx.theme().system().transparent,
23937 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23938 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23939 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23940 };
23941
23942 EditorElement::new(
23943 &cx.entity(),
23944 EditorStyle {
23945 background,
23946 border: cx.theme().colors().border,
23947 local_player: cx.theme().players().local(),
23948 text: text_style,
23949 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23950 syntax: cx.theme().syntax().clone(),
23951 status: cx.theme().status().clone(),
23952 inlay_hints_style: make_inlay_hints_style(cx),
23953 edit_prediction_styles: make_suggestion_styles(cx),
23954 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23955 show_underlines: self.diagnostics_enabled(),
23956 },
23957 )
23958 }
23959}
23960
23961impl EntityInputHandler for Editor {
23962 fn text_for_range(
23963 &mut self,
23964 range_utf16: Range<usize>,
23965 adjusted_range: &mut Option<Range<usize>>,
23966 _: &mut Window,
23967 cx: &mut Context<Self>,
23968 ) -> Option<String> {
23969 let snapshot = self.buffer.read(cx).read(cx);
23970 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23971 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23972 if (start.0..end.0) != range_utf16 {
23973 adjusted_range.replace(start.0..end.0);
23974 }
23975 Some(snapshot.text_for_range(start..end).collect())
23976 }
23977
23978 fn selected_text_range(
23979 &mut self,
23980 ignore_disabled_input: bool,
23981 _: &mut Window,
23982 cx: &mut Context<Self>,
23983 ) -> Option<UTF16Selection> {
23984 // Prevent the IME menu from appearing when holding down an alphabetic key
23985 // while input is disabled.
23986 if !ignore_disabled_input && !self.input_enabled {
23987 return None;
23988 }
23989
23990 let selection = self
23991 .selections
23992 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23993 let range = selection.range();
23994
23995 Some(UTF16Selection {
23996 range: range.start.0..range.end.0,
23997 reversed: selection.reversed,
23998 })
23999 }
24000
24001 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24002 let snapshot = self.buffer.read(cx).read(cx);
24003 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24004 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24005 }
24006
24007 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24008 self.clear_highlights::<InputComposition>(cx);
24009 self.ime_transaction.take();
24010 }
24011
24012 fn replace_text_in_range(
24013 &mut self,
24014 range_utf16: Option<Range<usize>>,
24015 text: &str,
24016 window: &mut Window,
24017 cx: &mut Context<Self>,
24018 ) {
24019 if !self.input_enabled {
24020 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24021 return;
24022 }
24023
24024 self.transact(window, cx, |this, window, cx| {
24025 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24026 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24027 Some(this.selection_replacement_ranges(range_utf16, cx))
24028 } else {
24029 this.marked_text_ranges(cx)
24030 };
24031
24032 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24033 let newest_selection_id = this.selections.newest_anchor().id;
24034 this.selections
24035 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24036 .iter()
24037 .zip(ranges_to_replace.iter())
24038 .find_map(|(selection, range)| {
24039 if selection.id == newest_selection_id {
24040 Some(
24041 (range.start.0 as isize - selection.head().0 as isize)
24042 ..(range.end.0 as isize - selection.head().0 as isize),
24043 )
24044 } else {
24045 None
24046 }
24047 })
24048 });
24049
24050 cx.emit(EditorEvent::InputHandled {
24051 utf16_range_to_replace: range_to_replace,
24052 text: text.into(),
24053 });
24054
24055 if let Some(new_selected_ranges) = new_selected_ranges {
24056 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24057 selections.select_ranges(new_selected_ranges)
24058 });
24059 this.backspace(&Default::default(), window, cx);
24060 }
24061
24062 this.handle_input(text, window, cx);
24063 });
24064
24065 if let Some(transaction) = self.ime_transaction {
24066 self.buffer.update(cx, |buffer, cx| {
24067 buffer.group_until_transaction(transaction, cx);
24068 });
24069 }
24070
24071 self.unmark_text(window, cx);
24072 }
24073
24074 fn replace_and_mark_text_in_range(
24075 &mut self,
24076 range_utf16: Option<Range<usize>>,
24077 text: &str,
24078 new_selected_range_utf16: Option<Range<usize>>,
24079 window: &mut Window,
24080 cx: &mut Context<Self>,
24081 ) {
24082 if !self.input_enabled {
24083 return;
24084 }
24085
24086 let transaction = self.transact(window, cx, |this, window, cx| {
24087 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24088 let snapshot = this.buffer.read(cx).read(cx);
24089 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24090 for marked_range in &mut marked_ranges {
24091 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24092 marked_range.start.0 += relative_range_utf16.start;
24093 marked_range.start =
24094 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24095 marked_range.end =
24096 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24097 }
24098 }
24099 Some(marked_ranges)
24100 } else if let Some(range_utf16) = range_utf16 {
24101 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24102 Some(this.selection_replacement_ranges(range_utf16, cx))
24103 } else {
24104 None
24105 };
24106
24107 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24108 let newest_selection_id = this.selections.newest_anchor().id;
24109 this.selections
24110 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24111 .iter()
24112 .zip(ranges_to_replace.iter())
24113 .find_map(|(selection, range)| {
24114 if selection.id == newest_selection_id {
24115 Some(
24116 (range.start.0 as isize - selection.head().0 as isize)
24117 ..(range.end.0 as isize - selection.head().0 as isize),
24118 )
24119 } else {
24120 None
24121 }
24122 })
24123 });
24124
24125 cx.emit(EditorEvent::InputHandled {
24126 utf16_range_to_replace: range_to_replace,
24127 text: text.into(),
24128 });
24129
24130 if let Some(ranges) = ranges_to_replace {
24131 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24132 s.select_ranges(ranges)
24133 });
24134 }
24135
24136 let marked_ranges = {
24137 let snapshot = this.buffer.read(cx).read(cx);
24138 this.selections
24139 .disjoint_anchors_arc()
24140 .iter()
24141 .map(|selection| {
24142 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24143 })
24144 .collect::<Vec<_>>()
24145 };
24146
24147 if text.is_empty() {
24148 this.unmark_text(window, cx);
24149 } else {
24150 this.highlight_text::<InputComposition>(
24151 marked_ranges.clone(),
24152 HighlightStyle {
24153 underline: Some(UnderlineStyle {
24154 thickness: px(1.),
24155 color: None,
24156 wavy: false,
24157 }),
24158 ..Default::default()
24159 },
24160 cx,
24161 );
24162 }
24163
24164 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24165 let use_autoclose = this.use_autoclose;
24166 let use_auto_surround = this.use_auto_surround;
24167 this.set_use_autoclose(false);
24168 this.set_use_auto_surround(false);
24169 this.handle_input(text, window, cx);
24170 this.set_use_autoclose(use_autoclose);
24171 this.set_use_auto_surround(use_auto_surround);
24172
24173 if let Some(new_selected_range) = new_selected_range_utf16 {
24174 let snapshot = this.buffer.read(cx).read(cx);
24175 let new_selected_ranges = marked_ranges
24176 .into_iter()
24177 .map(|marked_range| {
24178 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24179 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24180 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24181 snapshot.clip_offset_utf16(new_start, Bias::Left)
24182 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24183 })
24184 .collect::<Vec<_>>();
24185
24186 drop(snapshot);
24187 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24188 selections.select_ranges(new_selected_ranges)
24189 });
24190 }
24191 });
24192
24193 self.ime_transaction = self.ime_transaction.or(transaction);
24194 if let Some(transaction) = self.ime_transaction {
24195 self.buffer.update(cx, |buffer, cx| {
24196 buffer.group_until_transaction(transaction, cx);
24197 });
24198 }
24199
24200 if self.text_highlights::<InputComposition>(cx).is_none() {
24201 self.ime_transaction.take();
24202 }
24203 }
24204
24205 fn bounds_for_range(
24206 &mut self,
24207 range_utf16: Range<usize>,
24208 element_bounds: gpui::Bounds<Pixels>,
24209 window: &mut Window,
24210 cx: &mut Context<Self>,
24211 ) -> Option<gpui::Bounds<Pixels>> {
24212 let text_layout_details = self.text_layout_details(window);
24213 let CharacterDimensions {
24214 em_width,
24215 em_advance,
24216 line_height,
24217 } = self.character_dimensions(window);
24218
24219 let snapshot = self.snapshot(window, cx);
24220 let scroll_position = snapshot.scroll_position();
24221 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24222
24223 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24224 let x = Pixels::from(
24225 ScrollOffset::from(
24226 snapshot.x_for_display_point(start, &text_layout_details)
24227 + self.gutter_dimensions.full_width(),
24228 ) - scroll_left,
24229 );
24230 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24231
24232 Some(Bounds {
24233 origin: element_bounds.origin + point(x, y),
24234 size: size(em_width, line_height),
24235 })
24236 }
24237
24238 fn character_index_for_point(
24239 &mut self,
24240 point: gpui::Point<Pixels>,
24241 _window: &mut Window,
24242 _cx: &mut Context<Self>,
24243 ) -> Option<usize> {
24244 let position_map = self.last_position_map.as_ref()?;
24245 if !position_map.text_hitbox.contains(&point) {
24246 return None;
24247 }
24248 let display_point = position_map.point_for_position(point).previous_valid;
24249 let anchor = position_map
24250 .snapshot
24251 .display_point_to_anchor(display_point, Bias::Left);
24252 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24253 Some(utf16_offset.0)
24254 }
24255
24256 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24257 self.input_enabled
24258 }
24259}
24260
24261trait SelectionExt {
24262 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24263 fn spanned_rows(
24264 &self,
24265 include_end_if_at_line_start: bool,
24266 map: &DisplaySnapshot,
24267 ) -> Range<MultiBufferRow>;
24268}
24269
24270impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24271 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24272 let start = self
24273 .start
24274 .to_point(map.buffer_snapshot())
24275 .to_display_point(map);
24276 let end = self
24277 .end
24278 .to_point(map.buffer_snapshot())
24279 .to_display_point(map);
24280 if self.reversed {
24281 end..start
24282 } else {
24283 start..end
24284 }
24285 }
24286
24287 fn spanned_rows(
24288 &self,
24289 include_end_if_at_line_start: bool,
24290 map: &DisplaySnapshot,
24291 ) -> Range<MultiBufferRow> {
24292 let start = self.start.to_point(map.buffer_snapshot());
24293 let mut end = self.end.to_point(map.buffer_snapshot());
24294 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24295 end.row -= 1;
24296 }
24297
24298 let buffer_start = map.prev_line_boundary(start).0;
24299 let buffer_end = map.next_line_boundary(end).0;
24300 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24301 }
24302}
24303
24304impl<T: InvalidationRegion> InvalidationStack<T> {
24305 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24306 where
24307 S: Clone + ToOffset,
24308 {
24309 while let Some(region) = self.last() {
24310 let all_selections_inside_invalidation_ranges =
24311 if selections.len() == region.ranges().len() {
24312 selections
24313 .iter()
24314 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24315 .all(|(selection, invalidation_range)| {
24316 let head = selection.head().to_offset(buffer);
24317 invalidation_range.start <= head && invalidation_range.end >= head
24318 })
24319 } else {
24320 false
24321 };
24322
24323 if all_selections_inside_invalidation_ranges {
24324 break;
24325 } else {
24326 self.pop();
24327 }
24328 }
24329 }
24330}
24331
24332impl<T> Default for InvalidationStack<T> {
24333 fn default() -> Self {
24334 Self(Default::default())
24335 }
24336}
24337
24338impl<T> Deref for InvalidationStack<T> {
24339 type Target = Vec<T>;
24340
24341 fn deref(&self) -> &Self::Target {
24342 &self.0
24343 }
24344}
24345
24346impl<T> DerefMut for InvalidationStack<T> {
24347 fn deref_mut(&mut self) -> &mut Self::Target {
24348 &mut self.0
24349 }
24350}
24351
24352impl InvalidationRegion for SnippetState {
24353 fn ranges(&self) -> &[Range<Anchor>] {
24354 &self.ranges[self.active_index]
24355 }
24356}
24357
24358fn edit_prediction_edit_text(
24359 current_snapshot: &BufferSnapshot,
24360 edits: &[(Range<Anchor>, String)],
24361 edit_preview: &EditPreview,
24362 include_deletions: bool,
24363 cx: &App,
24364) -> HighlightedText {
24365 let edits = edits
24366 .iter()
24367 .map(|(anchor, text)| {
24368 (
24369 anchor.start.text_anchor..anchor.end.text_anchor,
24370 text.clone(),
24371 )
24372 })
24373 .collect::<Vec<_>>();
24374
24375 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24376}
24377
24378fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24379 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24380 // Just show the raw edit text with basic styling
24381 let mut text = String::new();
24382 let mut highlights = Vec::new();
24383
24384 let insertion_highlight_style = HighlightStyle {
24385 color: Some(cx.theme().colors().text),
24386 ..Default::default()
24387 };
24388
24389 for (_, edit_text) in edits {
24390 let start_offset = text.len();
24391 text.push_str(edit_text);
24392 let end_offset = text.len();
24393
24394 if start_offset < end_offset {
24395 highlights.push((start_offset..end_offset, insertion_highlight_style));
24396 }
24397 }
24398
24399 HighlightedText {
24400 text: text.into(),
24401 highlights,
24402 }
24403}
24404
24405pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24406 match severity {
24407 lsp::DiagnosticSeverity::ERROR => colors.error,
24408 lsp::DiagnosticSeverity::WARNING => colors.warning,
24409 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24410 lsp::DiagnosticSeverity::HINT => colors.info,
24411 _ => colors.ignored,
24412 }
24413}
24414
24415pub fn styled_runs_for_code_label<'a>(
24416 label: &'a CodeLabel,
24417 syntax_theme: &'a theme::SyntaxTheme,
24418) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24419 let fade_out = HighlightStyle {
24420 fade_out: Some(0.35),
24421 ..Default::default()
24422 };
24423
24424 let mut prev_end = label.filter_range.end;
24425 label
24426 .runs
24427 .iter()
24428 .enumerate()
24429 .flat_map(move |(ix, (range, highlight_id))| {
24430 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24431 style
24432 } else {
24433 return Default::default();
24434 };
24435 let muted_style = style.highlight(fade_out);
24436
24437 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24438 if range.start >= label.filter_range.end {
24439 if range.start > prev_end {
24440 runs.push((prev_end..range.start, fade_out));
24441 }
24442 runs.push((range.clone(), muted_style));
24443 } else if range.end <= label.filter_range.end {
24444 runs.push((range.clone(), style));
24445 } else {
24446 runs.push((range.start..label.filter_range.end, style));
24447 runs.push((label.filter_range.end..range.end, muted_style));
24448 }
24449 prev_end = cmp::max(prev_end, range.end);
24450
24451 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24452 runs.push((prev_end..label.text.len(), fade_out));
24453 }
24454
24455 runs
24456 })
24457}
24458
24459pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24460 let mut prev_index = 0;
24461 let mut prev_codepoint: Option<char> = None;
24462 text.char_indices()
24463 .chain([(text.len(), '\0')])
24464 .filter_map(move |(index, codepoint)| {
24465 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24466 let is_boundary = index == text.len()
24467 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24468 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24469 if is_boundary {
24470 let chunk = &text[prev_index..index];
24471 prev_index = index;
24472 Some(chunk)
24473 } else {
24474 None
24475 }
24476 })
24477}
24478
24479pub trait RangeToAnchorExt: Sized {
24480 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24481
24482 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24483 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24484 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24485 }
24486}
24487
24488impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24489 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24490 let start_offset = self.start.to_offset(snapshot);
24491 let end_offset = self.end.to_offset(snapshot);
24492 if start_offset == end_offset {
24493 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24494 } else {
24495 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24496 }
24497 }
24498}
24499
24500pub trait RowExt {
24501 fn as_f64(&self) -> f64;
24502
24503 fn next_row(&self) -> Self;
24504
24505 fn previous_row(&self) -> Self;
24506
24507 fn minus(&self, other: Self) -> u32;
24508}
24509
24510impl RowExt for DisplayRow {
24511 fn as_f64(&self) -> f64 {
24512 self.0 as _
24513 }
24514
24515 fn next_row(&self) -> Self {
24516 Self(self.0 + 1)
24517 }
24518
24519 fn previous_row(&self) -> Self {
24520 Self(self.0.saturating_sub(1))
24521 }
24522
24523 fn minus(&self, other: Self) -> u32 {
24524 self.0 - other.0
24525 }
24526}
24527
24528impl RowExt for MultiBufferRow {
24529 fn as_f64(&self) -> f64 {
24530 self.0 as _
24531 }
24532
24533 fn next_row(&self) -> Self {
24534 Self(self.0 + 1)
24535 }
24536
24537 fn previous_row(&self) -> Self {
24538 Self(self.0.saturating_sub(1))
24539 }
24540
24541 fn minus(&self, other: Self) -> u32 {
24542 self.0 - other.0
24543 }
24544}
24545
24546trait RowRangeExt {
24547 type Row;
24548
24549 fn len(&self) -> usize;
24550
24551 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24552}
24553
24554impl RowRangeExt for Range<MultiBufferRow> {
24555 type Row = MultiBufferRow;
24556
24557 fn len(&self) -> usize {
24558 (self.end.0 - self.start.0) as usize
24559 }
24560
24561 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24562 (self.start.0..self.end.0).map(MultiBufferRow)
24563 }
24564}
24565
24566impl RowRangeExt for Range<DisplayRow> {
24567 type Row = DisplayRow;
24568
24569 fn len(&self) -> usize {
24570 (self.end.0 - self.start.0) as usize
24571 }
24572
24573 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24574 (self.start.0..self.end.0).map(DisplayRow)
24575 }
24576}
24577
24578/// If select range has more than one line, we
24579/// just point the cursor to range.start.
24580fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24581 if range.start.row == range.end.row {
24582 range
24583 } else {
24584 range.start..range.start
24585 }
24586}
24587pub struct KillRing(ClipboardItem);
24588impl Global for KillRing {}
24589
24590const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24591
24592enum BreakpointPromptEditAction {
24593 Log,
24594 Condition,
24595 HitCondition,
24596}
24597
24598struct BreakpointPromptEditor {
24599 pub(crate) prompt: Entity<Editor>,
24600 editor: WeakEntity<Editor>,
24601 breakpoint_anchor: Anchor,
24602 breakpoint: Breakpoint,
24603 edit_action: BreakpointPromptEditAction,
24604 block_ids: HashSet<CustomBlockId>,
24605 editor_margins: Arc<Mutex<EditorMargins>>,
24606 _subscriptions: Vec<Subscription>,
24607}
24608
24609impl BreakpointPromptEditor {
24610 const MAX_LINES: u8 = 4;
24611
24612 fn new(
24613 editor: WeakEntity<Editor>,
24614 breakpoint_anchor: Anchor,
24615 breakpoint: Breakpoint,
24616 edit_action: BreakpointPromptEditAction,
24617 window: &mut Window,
24618 cx: &mut Context<Self>,
24619 ) -> Self {
24620 let base_text = match edit_action {
24621 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24622 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24623 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24624 }
24625 .map(|msg| msg.to_string())
24626 .unwrap_or_default();
24627
24628 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24629 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24630
24631 let prompt = cx.new(|cx| {
24632 let mut prompt = Editor::new(
24633 EditorMode::AutoHeight {
24634 min_lines: 1,
24635 max_lines: Some(Self::MAX_LINES as usize),
24636 },
24637 buffer,
24638 None,
24639 window,
24640 cx,
24641 );
24642 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24643 prompt.set_show_cursor_when_unfocused(false, cx);
24644 prompt.set_placeholder_text(
24645 match edit_action {
24646 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24647 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24648 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24649 },
24650 window,
24651 cx,
24652 );
24653
24654 prompt
24655 });
24656
24657 Self {
24658 prompt,
24659 editor,
24660 breakpoint_anchor,
24661 breakpoint,
24662 edit_action,
24663 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24664 block_ids: Default::default(),
24665 _subscriptions: vec![],
24666 }
24667 }
24668
24669 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24670 self.block_ids.extend(block_ids)
24671 }
24672
24673 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24674 if let Some(editor) = self.editor.upgrade() {
24675 let message = self
24676 .prompt
24677 .read(cx)
24678 .buffer
24679 .read(cx)
24680 .as_singleton()
24681 .expect("A multi buffer in breakpoint prompt isn't possible")
24682 .read(cx)
24683 .as_rope()
24684 .to_string();
24685
24686 editor.update(cx, |editor, cx| {
24687 editor.edit_breakpoint_at_anchor(
24688 self.breakpoint_anchor,
24689 self.breakpoint.clone(),
24690 match self.edit_action {
24691 BreakpointPromptEditAction::Log => {
24692 BreakpointEditAction::EditLogMessage(message.into())
24693 }
24694 BreakpointPromptEditAction::Condition => {
24695 BreakpointEditAction::EditCondition(message.into())
24696 }
24697 BreakpointPromptEditAction::HitCondition => {
24698 BreakpointEditAction::EditHitCondition(message.into())
24699 }
24700 },
24701 cx,
24702 );
24703
24704 editor.remove_blocks(self.block_ids.clone(), None, cx);
24705 cx.focus_self(window);
24706 });
24707 }
24708 }
24709
24710 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24711 self.editor
24712 .update(cx, |editor, cx| {
24713 editor.remove_blocks(self.block_ids.clone(), None, cx);
24714 window.focus(&editor.focus_handle);
24715 })
24716 .log_err();
24717 }
24718
24719 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24720 let settings = ThemeSettings::get_global(cx);
24721 let text_style = TextStyle {
24722 color: if self.prompt.read(cx).read_only(cx) {
24723 cx.theme().colors().text_disabled
24724 } else {
24725 cx.theme().colors().text
24726 },
24727 font_family: settings.buffer_font.family.clone(),
24728 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24729 font_size: settings.buffer_font_size(cx).into(),
24730 font_weight: settings.buffer_font.weight,
24731 line_height: relative(settings.buffer_line_height.value()),
24732 ..Default::default()
24733 };
24734 EditorElement::new(
24735 &self.prompt,
24736 EditorStyle {
24737 background: cx.theme().colors().editor_background,
24738 local_player: cx.theme().players().local(),
24739 text: text_style,
24740 ..Default::default()
24741 },
24742 )
24743 }
24744}
24745
24746impl Render for BreakpointPromptEditor {
24747 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24748 let editor_margins = *self.editor_margins.lock();
24749 let gutter_dimensions = editor_margins.gutter;
24750 h_flex()
24751 .key_context("Editor")
24752 .bg(cx.theme().colors().editor_background)
24753 .border_y_1()
24754 .border_color(cx.theme().status().info_border)
24755 .size_full()
24756 .py(window.line_height() / 2.5)
24757 .on_action(cx.listener(Self::confirm))
24758 .on_action(cx.listener(Self::cancel))
24759 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24760 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24761 }
24762}
24763
24764impl Focusable for BreakpointPromptEditor {
24765 fn focus_handle(&self, cx: &App) -> FocusHandle {
24766 self.prompt.focus_handle(cx)
24767 }
24768}
24769
24770fn all_edits_insertions_or_deletions(
24771 edits: &Vec<(Range<Anchor>, String)>,
24772 snapshot: &MultiBufferSnapshot,
24773) -> bool {
24774 let mut all_insertions = true;
24775 let mut all_deletions = true;
24776
24777 for (range, new_text) in edits.iter() {
24778 let range_is_empty = range.to_offset(snapshot).is_empty();
24779 let text_is_empty = new_text.is_empty();
24780
24781 if range_is_empty != text_is_empty {
24782 if range_is_empty {
24783 all_deletions = false;
24784 } else {
24785 all_insertions = false;
24786 }
24787 } else {
24788 return false;
24789 }
24790
24791 if !all_insertions && !all_deletions {
24792 return false;
24793 }
24794 }
24795 all_insertions || all_deletions
24796}
24797
24798struct MissingEditPredictionKeybindingTooltip;
24799
24800impl Render for MissingEditPredictionKeybindingTooltip {
24801 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24802 ui::tooltip_container(cx, |container, cx| {
24803 container
24804 .flex_shrink_0()
24805 .max_w_80()
24806 .min_h(rems_from_px(124.))
24807 .justify_between()
24808 .child(
24809 v_flex()
24810 .flex_1()
24811 .text_ui_sm(cx)
24812 .child(Label::new("Conflict with Accept Keybinding"))
24813 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24814 )
24815 .child(
24816 h_flex()
24817 .pb_1()
24818 .gap_1()
24819 .items_end()
24820 .w_full()
24821 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24822 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24823 }))
24824 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24825 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24826 })),
24827 )
24828 })
24829 }
24830}
24831
24832#[derive(Debug, Clone, Copy, PartialEq)]
24833pub struct LineHighlight {
24834 pub background: Background,
24835 pub border: Option<gpui::Hsla>,
24836 pub include_gutter: bool,
24837 pub type_id: Option<TypeId>,
24838}
24839
24840struct LineManipulationResult {
24841 pub new_text: String,
24842 pub line_count_before: usize,
24843 pub line_count_after: usize,
24844}
24845
24846fn render_diff_hunk_controls(
24847 row: u32,
24848 status: &DiffHunkStatus,
24849 hunk_range: Range<Anchor>,
24850 is_created_file: bool,
24851 line_height: Pixels,
24852 editor: &Entity<Editor>,
24853 _window: &mut Window,
24854 cx: &mut App,
24855) -> AnyElement {
24856 h_flex()
24857 .h(line_height)
24858 .mr_1()
24859 .gap_1()
24860 .px_0p5()
24861 .pb_1()
24862 .border_x_1()
24863 .border_b_1()
24864 .border_color(cx.theme().colors().border_variant)
24865 .rounded_b_lg()
24866 .bg(cx.theme().colors().editor_background)
24867 .gap_1()
24868 .block_mouse_except_scroll()
24869 .shadow_md()
24870 .child(if status.has_secondary_hunk() {
24871 Button::new(("stage", row as u64), "Stage")
24872 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24873 .tooltip({
24874 let focus_handle = editor.focus_handle(cx);
24875 move |_window, cx| {
24876 Tooltip::for_action_in(
24877 "Stage Hunk",
24878 &::git::ToggleStaged,
24879 &focus_handle,
24880 cx,
24881 )
24882 }
24883 })
24884 .on_click({
24885 let editor = editor.clone();
24886 move |_event, _window, cx| {
24887 editor.update(cx, |editor, cx| {
24888 editor.stage_or_unstage_diff_hunks(
24889 true,
24890 vec![hunk_range.start..hunk_range.start],
24891 cx,
24892 );
24893 });
24894 }
24895 })
24896 } else {
24897 Button::new(("unstage", row as u64), "Unstage")
24898 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24899 .tooltip({
24900 let focus_handle = editor.focus_handle(cx);
24901 move |_window, cx| {
24902 Tooltip::for_action_in(
24903 "Unstage Hunk",
24904 &::git::ToggleStaged,
24905 &focus_handle,
24906 cx,
24907 )
24908 }
24909 })
24910 .on_click({
24911 let editor = editor.clone();
24912 move |_event, _window, cx| {
24913 editor.update(cx, |editor, cx| {
24914 editor.stage_or_unstage_diff_hunks(
24915 false,
24916 vec![hunk_range.start..hunk_range.start],
24917 cx,
24918 );
24919 });
24920 }
24921 })
24922 })
24923 .child(
24924 Button::new(("restore", row as u64), "Restore")
24925 .tooltip({
24926 let focus_handle = editor.focus_handle(cx);
24927 move |_window, cx| {
24928 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24929 }
24930 })
24931 .on_click({
24932 let editor = editor.clone();
24933 move |_event, window, cx| {
24934 editor.update(cx, |editor, cx| {
24935 let snapshot = editor.snapshot(window, cx);
24936 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24937 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24938 });
24939 }
24940 })
24941 .disabled(is_created_file),
24942 )
24943 .when(
24944 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24945 |el| {
24946 el.child(
24947 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24948 .shape(IconButtonShape::Square)
24949 .icon_size(IconSize::Small)
24950 // .disabled(!has_multiple_hunks)
24951 .tooltip({
24952 let focus_handle = editor.focus_handle(cx);
24953 move |_window, cx| {
24954 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24955 }
24956 })
24957 .on_click({
24958 let editor = editor.clone();
24959 move |_event, window, cx| {
24960 editor.update(cx, |editor, cx| {
24961 let snapshot = editor.snapshot(window, cx);
24962 let position =
24963 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24964 editor.go_to_hunk_before_or_after_position(
24965 &snapshot,
24966 position,
24967 Direction::Next,
24968 window,
24969 cx,
24970 );
24971 editor.expand_selected_diff_hunks(cx);
24972 });
24973 }
24974 }),
24975 )
24976 .child(
24977 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24978 .shape(IconButtonShape::Square)
24979 .icon_size(IconSize::Small)
24980 // .disabled(!has_multiple_hunks)
24981 .tooltip({
24982 let focus_handle = editor.focus_handle(cx);
24983 move |_window, cx| {
24984 Tooltip::for_action_in(
24985 "Previous Hunk",
24986 &GoToPreviousHunk,
24987 &focus_handle,
24988 cx,
24989 )
24990 }
24991 })
24992 .on_click({
24993 let editor = editor.clone();
24994 move |_event, window, cx| {
24995 editor.update(cx, |editor, cx| {
24996 let snapshot = editor.snapshot(window, cx);
24997 let point =
24998 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24999 editor.go_to_hunk_before_or_after_position(
25000 &snapshot,
25001 point,
25002 Direction::Prev,
25003 window,
25004 cx,
25005 );
25006 editor.expand_selected_diff_hunks(cx);
25007 });
25008 }
25009 }),
25010 )
25011 },
25012 )
25013 .into_any_element()
25014}
25015
25016pub fn multibuffer_context_lines(cx: &App) -> u32 {
25017 EditorSettings::try_get(cx)
25018 .map(|settings| settings.excerpt_context_lines)
25019 .unwrap_or(2)
25020 .min(32)
25021}