1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::{SmallVec, smallvec};
168use snippet::Snippet;
169use std::{
170 any::{Any, TypeId},
171 borrow::Cow,
172 cell::{OnceCell, RefCell},
173 cmp::{self, Ordering, Reverse},
174 iter::{self, Peekable},
175 mem,
176 num::NonZeroU32,
177 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 sync::Arc,
181 time::{Duration, Instant},
182};
183use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
184use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
191 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::{
204 code_context_menus::CompletionsMenuSource,
205 editor_settings::MultiCursorModifier,
206 hover_links::{find_url, find_url_from_range},
207 inlays::{
208 InlineValueCache,
209 inlay_hints::{LspInlayHintData, inlay_hint_settings},
210 },
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 selections_collection::resolve_selections_wrapping_blocks,
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265pub enum ActiveDebugLine {}
266pub enum DebugStackFrameLine {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270pub enum PendingInput {}
271enum SelectedTextHighlight {}
272
273pub enum ConflictsOuter {}
274pub enum ConflictsOurs {}
275pub enum ConflictsTheirs {}
276pub enum ConflictsOursMarker {}
277pub enum ConflictsTheirsMarker {}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq)]
280pub enum Navigated {
281 Yes,
282 No,
283}
284
285impl Navigated {
286 pub fn from_bool(yes: bool) -> Navigated {
287 if yes { Navigated::Yes } else { Navigated::No }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292enum DisplayDiffHunk {
293 Folded {
294 display_row: DisplayRow,
295 },
296 Unfolded {
297 is_created_file: bool,
298 diff_base_byte_range: Range<usize>,
299 display_row_range: Range<DisplayRow>,
300 multi_buffer_range: Range<Anchor>,
301 status: DiffHunkStatus,
302 },
303}
304
305pub enum HideMouseCursorOrigin {
306 TypingAction,
307 MovementAction,
308}
309
310pub fn init_settings(cx: &mut App) {
311 EditorSettings::register(cx);
312}
313
314pub fn init(cx: &mut App) {
315 init_settings(cx);
316
317 cx.set_global(GlobalBlameRenderer(Arc::new(())));
318
319 workspace::register_project_item::<Editor>(cx);
320 workspace::FollowableViewRegistry::register::<Editor>(cx);
321 workspace::register_serializable_item::<Editor>(cx);
322
323 cx.observe_new(
324 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
325 workspace.register_action(Editor::new_file);
326 workspace.register_action(Editor::new_file_split);
327 workspace.register_action(Editor::new_file_vertical);
328 workspace.register_action(Editor::new_file_horizontal);
329 workspace.register_action(Editor::cancel_language_server_work);
330 workspace.register_action(Editor::toggle_focus);
331 },
332 )
333 .detach();
334
335 cx.on_action(move |_: &workspace::NewFile, cx| {
336 let app_state = workspace::AppState::global(cx);
337 if let Some(app_state) = app_state.upgrade() {
338 workspace::open_new(
339 Default::default(),
340 app_state,
341 cx,
342 |workspace, window, cx| {
343 Editor::new_file(workspace, &Default::default(), window, cx)
344 },
345 )
346 .detach();
347 }
348 });
349 cx.on_action(move |_: &workspace::NewWindow, cx| {
350 let app_state = workspace::AppState::global(cx);
351 if let Some(app_state) = app_state.upgrade() {
352 workspace::open_new(
353 Default::default(),
354 app_state,
355 cx,
356 |workspace, window, cx| {
357 cx.activate(true);
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 });
364}
365
366pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
367 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
368}
369
370pub trait DiagnosticRenderer {
371 fn render_group(
372 &self,
373 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
374 buffer_id: BufferId,
375 snapshot: EditorSnapshot,
376 editor: WeakEntity<Editor>,
377 cx: &mut App,
378 ) -> Vec<BlockProperties<Anchor>>;
379
380 fn render_hover(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 range: Range<Point>,
384 buffer_id: BufferId,
385 cx: &mut App,
386 ) -> Option<Entity<markdown::Markdown>>;
387
388 fn open_link(
389 &self,
390 editor: &mut Editor,
391 link: SharedString,
392 window: &mut Window,
393 cx: &mut Context<Editor>,
394 );
395}
396
397pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
398
399impl GlobalDiagnosticRenderer {
400 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
401 cx.try_global::<Self>().map(|g| g.0.clone())
402 }
403}
404
405impl gpui::Global for GlobalDiagnosticRenderer {}
406pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
408}
409
410pub struct SearchWithinRange;
411
412trait InvalidationRegion {
413 fn ranges(&self) -> &[Range<Anchor>];
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum SelectPhase {
418 Begin {
419 position: DisplayPoint,
420 add: bool,
421 click_count: usize,
422 },
423 BeginColumnar {
424 position: DisplayPoint,
425 reset: bool,
426 mode: ColumnarMode,
427 goal_column: u32,
428 },
429 Extend {
430 position: DisplayPoint,
431 click_count: usize,
432 },
433 Update {
434 position: DisplayPoint,
435 goal_column: u32,
436 scroll_delta: gpui::Point<f32>,
437 },
438 End,
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum ColumnarMode {
443 FromMouse,
444 FromSelection,
445}
446
447#[derive(Clone, Debug)]
448pub enum SelectMode {
449 Character,
450 Word(Range<Anchor>),
451 Line(Range<Anchor>),
452 All,
453}
454
455#[derive(Clone, PartialEq, Eq, Debug)]
456pub enum EditorMode {
457 SingleLine,
458 AutoHeight {
459 min_lines: usize,
460 max_lines: Option<usize>,
461 },
462 Full {
463 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
464 scale_ui_elements_with_buffer_font_size: bool,
465 /// When set to `true`, the editor will render a background for the active line.
466 show_active_line_background: bool,
467 /// When set to `true`, the editor's height will be determined by its content.
468 sized_by_content: bool,
469 },
470 Minimap {
471 parent: WeakEntity<Editor>,
472 },
473}
474
475impl EditorMode {
476 pub fn full() -> Self {
477 Self::Full {
478 scale_ui_elements_with_buffer_font_size: true,
479 show_active_line_background: true,
480 sized_by_content: false,
481 }
482 }
483
484 #[inline]
485 pub fn is_full(&self) -> bool {
486 matches!(self, Self::Full { .. })
487 }
488
489 #[inline]
490 pub fn is_single_line(&self) -> bool {
491 matches!(self, Self::SingleLine { .. })
492 }
493
494 #[inline]
495 fn is_minimap(&self) -> bool {
496 matches!(self, Self::Minimap { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub border: Hsla,
521 pub local_player: PlayerColor,
522 pub text: TextStyle,
523 pub scrollbar_width: Pixels,
524 pub syntax: Arc<SyntaxTheme>,
525 pub status: StatusColors,
526 pub inlay_hints_style: HighlightStyle,
527 pub edit_prediction_styles: EditPredictionStyles,
528 pub unnecessary_code_fade: f32,
529 pub show_underlines: bool,
530}
531
532impl Default for EditorStyle {
533 fn default() -> Self {
534 Self {
535 background: Hsla::default(),
536 border: Hsla::default(),
537 local_player: PlayerColor::default(),
538 text: TextStyle::default(),
539 scrollbar_width: Pixels::default(),
540 syntax: Default::default(),
541 // HACK: Status colors don't have a real default.
542 // We should look into removing the status colors from the editor
543 // style and retrieve them directly from the theme.
544 status: StatusColors::dark(),
545 inlay_hints_style: HighlightStyle::default(),
546 edit_prediction_styles: EditPredictionStyles {
547 insertion: HighlightStyle::default(),
548 whitespace: HighlightStyle::default(),
549 },
550 unnecessary_code_fade: Default::default(),
551 show_underlines: true,
552 }
553 }
554}
555
556pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
557 let show_background = language_settings::language_settings(None, None, cx)
558 .inlay_hints
559 .show_background;
560
561 let mut style = cx.theme().syntax().get("hint");
562
563 if style.color.is_none() {
564 style.color = Some(cx.theme().status().hint);
565 }
566
567 if !show_background {
568 style.background_color = None;
569 return style;
570 }
571
572 if style.background_color.is_none() {
573 style.background_color = Some(cx.theme().status().hint_background);
574 }
575
576 style
577}
578
579pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
580 EditPredictionStyles {
581 insertion: HighlightStyle {
582 color: Some(cx.theme().status().predictive),
583 ..HighlightStyle::default()
584 },
585 whitespace: HighlightStyle {
586 background_color: Some(cx.theme().status().created_background),
587 ..HighlightStyle::default()
588 },
589 }
590}
591
592type CompletionId = usize;
593
594pub(crate) enum EditDisplayMode {
595 TabAccept,
596 DiffPopover,
597 Inline,
598}
599
600enum EditPrediction {
601 Edit {
602 edits: Vec<(Range<Anchor>, String)>,
603 edit_preview: Option<EditPreview>,
604 display_mode: EditDisplayMode,
605 snapshot: BufferSnapshot,
606 },
607 /// Move to a specific location in the active editor
608 MoveWithin {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612 /// Move to a specific location in a different editor (not the active one)
613 MoveOutside {
614 target: language::Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct EditPredictionState {
620 inlay_ids: Vec<InlayId>,
621 completion: EditPrediction,
622 completion_id: Option<SharedString>,
623 invalidation_range: Option<Range<Anchor>>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum EditPredictionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuEditPredictionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
708type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
709
710#[derive(Default)]
711struct ScrollbarMarkerState {
712 scrollbar_size: Size<Pixels>,
713 dirty: bool,
714 markers: Arc<[PaintQuad]>,
715 pending_refresh: Option<Task<Result<()>>>,
716}
717
718impl ScrollbarMarkerState {
719 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
720 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
721 }
722}
723
724#[derive(Clone, Copy, PartialEq, Eq)]
725pub enum MinimapVisibility {
726 Disabled,
727 Enabled {
728 /// The configuration currently present in the users settings.
729 setting_configuration: bool,
730 /// Whether to override the currently set visibility from the users setting.
731 toggle_override: bool,
732 },
733}
734
735impl MinimapVisibility {
736 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
737 if mode.is_full() {
738 Self::Enabled {
739 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
740 toggle_override: false,
741 }
742 } else {
743 Self::Disabled
744 }
745 }
746
747 fn hidden(&self) -> Self {
748 match *self {
749 Self::Enabled {
750 setting_configuration,
751 ..
752 } => Self::Enabled {
753 setting_configuration,
754 toggle_override: setting_configuration,
755 },
756 Self::Disabled => Self::Disabled,
757 }
758 }
759
760 fn disabled(&self) -> bool {
761 matches!(*self, Self::Disabled)
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832/// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
846 None
847 }
848
849 fn to_any(&self) -> &dyn std::any::Any;
850
851 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
852 None
853 }
854}
855
856struct ChangeLocation {
857 current: Option<Vec<Anchor>>,
858 original: Vec<Anchor>,
859}
860impl ChangeLocation {
861 fn locations(&self) -> &[Anchor] {
862 self.current.as_ref().unwrap_or(&self.original)
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<ChangeLocation>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|change| change.locations())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if let Some(last) = self.changes.last_mut()
902 && group
903 {
904 last.current = Some(new_positions)
905 } else {
906 self.changes.push(ChangeLocation {
907 original: new_positions,
908 current: None,
909 });
910 }
911 }
912
913 pub fn last(&self) -> Option<&[Anchor]> {
914 self.changes.last().map(|change| change.locations())
915 }
916
917 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
918 self.changes.last().map(|change| change.original.as_slice())
919 }
920
921 pub fn invert_last_group(&mut self) {
922 if let Some(last) = self.changes.last_mut()
923 && let Some(current) = last.current.as_mut()
924 {
925 mem::swap(&mut last.original, current);
926 }
927 }
928}
929
930#[derive(Clone)]
931struct InlineBlamePopoverState {
932 scroll_handle: ScrollHandle,
933 commit_message: Option<ParsedCommitMessage>,
934 markdown: Entity<Markdown>,
935}
936
937struct InlineBlamePopover {
938 position: gpui::Point<Pixels>,
939 hide_task: Option<Task<()>>,
940 popover_bounds: Option<Bounds<Pixels>>,
941 popover_state: InlineBlamePopoverState,
942 keyboard_grace: bool,
943}
944
945enum SelectionDragState {
946 /// State when no drag related activity is detected.
947 None,
948 /// State when the mouse is down on a selection that is about to be dragged.
949 ReadyToDrag {
950 selection: Selection<Anchor>,
951 click_position: gpui::Point<Pixels>,
952 mouse_down_time: Instant,
953 },
954 /// State when the mouse is dragging the selection in the editor.
955 Dragging {
956 selection: Selection<Anchor>,
957 drop_cursor: Selection<Anchor>,
958 hide_drop_cursor: bool,
959 },
960}
961
962enum ColumnarSelectionState {
963 FromMouse {
964 selection_tail: Anchor,
965 display_point: Option<DisplayPoint>,
966 },
967 FromSelection {
968 selection_tail: Anchor,
969 },
970}
971
972/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
973/// a breakpoint on them.
974#[derive(Clone, Copy, Debug, PartialEq, Eq)]
975struct PhantomBreakpointIndicator {
976 display_row: DisplayRow,
977 /// There's a small debounce between hovering over the line and showing the indicator.
978 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
979 is_active: bool,
980 collides_with_existing_breakpoint: bool,
981}
982
983/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
984///
985/// See the [module level documentation](self) for more information.
986pub struct Editor {
987 focus_handle: FocusHandle,
988 last_focused_descendant: Option<WeakFocusHandle>,
989 /// The text buffer being edited
990 buffer: Entity<MultiBuffer>,
991 /// Map of how text in the buffer should be displayed.
992 /// Handles soft wraps, folds, fake inlay text insertions, etc.
993 pub display_map: Entity<DisplayMap>,
994 placeholder_display_map: Option<Entity<DisplayMap>>,
995 pub selections: SelectionsCollection,
996 pub scroll_manager: ScrollManager,
997 /// When inline assist editors are linked, they all render cursors because
998 /// typing enters text into each of them, even the ones that aren't focused.
999 pub(crate) show_cursor_when_unfocused: bool,
1000 columnar_selection_state: Option<ColumnarSelectionState>,
1001 add_selections_state: Option<AddSelectionsState>,
1002 select_next_state: Option<SelectNextState>,
1003 select_prev_state: Option<SelectNextState>,
1004 selection_history: SelectionHistory,
1005 defer_selection_effects: bool,
1006 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1007 autoclose_regions: Vec<AutocloseRegion>,
1008 snippet_stack: InvalidationStack<SnippetState>,
1009 select_syntax_node_history: SelectSyntaxNodeHistory,
1010 ime_transaction: Option<TransactionId>,
1011 pub diagnostics_max_severity: DiagnosticSeverity,
1012 active_diagnostics: ActiveDiagnostic,
1013 show_inline_diagnostics: bool,
1014 inline_diagnostics_update: Task<()>,
1015 inline_diagnostics_enabled: bool,
1016 diagnostics_enabled: bool,
1017 word_completions_enabled: bool,
1018 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1019 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1020 hard_wrap: Option<usize>,
1021 project: Option<Entity<Project>>,
1022 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1023 completion_provider: Option<Rc<dyn CompletionProvider>>,
1024 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1025 blink_manager: Entity<BlinkManager>,
1026 show_cursor_names: bool,
1027 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1028 pub show_local_selections: bool,
1029 mode: EditorMode,
1030 show_breadcrumbs: bool,
1031 show_gutter: bool,
1032 show_scrollbars: ScrollbarAxes,
1033 minimap_visibility: MinimapVisibility,
1034 offset_content: bool,
1035 disable_expand_excerpt_buttons: bool,
1036 show_line_numbers: Option<bool>,
1037 use_relative_line_numbers: Option<bool>,
1038 show_git_diff_gutter: Option<bool>,
1039 show_code_actions: Option<bool>,
1040 show_runnables: Option<bool>,
1041 show_breakpoints: Option<bool>,
1042 show_wrap_guides: Option<bool>,
1043 show_indent_guides: Option<bool>,
1044 highlight_order: usize,
1045 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1046 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1047 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1048 scrollbar_marker_state: ScrollbarMarkerState,
1049 active_indent_guides_state: ActiveIndentGuidesState,
1050 nav_history: Option<ItemNavHistory>,
1051 context_menu: RefCell<Option<CodeContextMenu>>,
1052 context_menu_options: Option<ContextMenuOptions>,
1053 mouse_context_menu: Option<MouseContextMenu>,
1054 completion_tasks: Vec<(CompletionId, Task<()>)>,
1055 inline_blame_popover: Option<InlineBlamePopover>,
1056 inline_blame_popover_show_task: Option<Task<()>>,
1057 signature_help_state: SignatureHelpState,
1058 auto_signature_help: Option<bool>,
1059 find_all_references_task_sources: Vec<Anchor>,
1060 next_completion_id: CompletionId,
1061 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1062 code_actions_task: Option<Task<Result<()>>>,
1063 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1064 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1065 document_highlights_task: Option<Task<()>>,
1066 linked_editing_range_task: Option<Task<Option<()>>>,
1067 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1068 pending_rename: Option<RenameState>,
1069 searchable: bool,
1070 cursor_shape: CursorShape,
1071 current_line_highlight: Option<CurrentLineHighlight>,
1072 collapse_matches: bool,
1073 autoindent_mode: Option<AutoindentMode>,
1074 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1075 input_enabled: bool,
1076 use_modal_editing: bool,
1077 read_only: bool,
1078 leader_id: Option<CollaboratorId>,
1079 remote_id: Option<ViewId>,
1080 pub hover_state: HoverState,
1081 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1082 gutter_hovered: bool,
1083 hovered_link_state: Option<HoveredLinkState>,
1084 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1085 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1086 active_edit_prediction: Option<EditPredictionState>,
1087 /// Used to prevent flickering as the user types while the menu is open
1088 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1089 edit_prediction_settings: EditPredictionSettings,
1090 edit_predictions_hidden_for_vim_mode: bool,
1091 show_edit_predictions_override: Option<bool>,
1092 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1093 edit_prediction_preview: EditPredictionPreview,
1094 edit_prediction_indent_conflict: bool,
1095 edit_prediction_requires_modifier_in_indent_conflict: bool,
1096 next_inlay_id: usize,
1097 next_color_inlay_id: usize,
1098 _subscriptions: Vec<Subscription>,
1099 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1100 gutter_dimensions: GutterDimensions,
1101 style: Option<EditorStyle>,
1102 text_style_refinement: Option<TextStyleRefinement>,
1103 next_editor_action_id: EditorActionId,
1104 editor_actions: Rc<
1105 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1106 >,
1107 use_autoclose: bool,
1108 use_auto_surround: bool,
1109 auto_replace_emoji_shortcode: bool,
1110 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1111 show_git_blame_gutter: bool,
1112 show_git_blame_inline: bool,
1113 show_git_blame_inline_delay_task: Option<Task<()>>,
1114 git_blame_inline_enabled: bool,
1115 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1116 serialize_dirty_buffers: bool,
1117 show_selection_menu: Option<bool>,
1118 blame: Option<Entity<GitBlame>>,
1119 blame_subscription: Option<Subscription>,
1120 custom_context_menu: Option<
1121 Box<
1122 dyn 'static
1123 + Fn(
1124 &mut Self,
1125 DisplayPoint,
1126 &mut Window,
1127 &mut Context<Self>,
1128 ) -> Option<Entity<ui::ContextMenu>>,
1129 >,
1130 >,
1131 last_bounds: Option<Bounds<Pixels>>,
1132 last_position_map: Option<Rc<PositionMap>>,
1133 expect_bounds_change: Option<Bounds<Pixels>>,
1134 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1135 tasks_update_task: Option<Task<()>>,
1136 breakpoint_store: Option<Entity<BreakpointStore>>,
1137 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1138 hovered_diff_hunk_row: Option<DisplayRow>,
1139 pull_diagnostics_task: Task<()>,
1140 in_project_search: bool,
1141 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1142 breadcrumb_header: Option<String>,
1143 focused_block: Option<FocusedBlock>,
1144 next_scroll_position: NextScrollCursorCenterTopBottom,
1145 addons: HashMap<TypeId, Box<dyn Addon>>,
1146 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1147 load_diff_task: Option<Shared<Task<()>>>,
1148 /// Whether we are temporarily displaying a diff other than git's
1149 temporary_diff_override: bool,
1150 selection_mark_mode: bool,
1151 toggle_fold_multiple_buffers: Task<()>,
1152 _scroll_cursor_center_top_bottom_task: Task<()>,
1153 serialize_selections: Task<()>,
1154 serialize_folds: Task<()>,
1155 mouse_cursor_hidden: bool,
1156 minimap: Option<Entity<Self>>,
1157 hide_mouse_mode: HideMouseMode,
1158 pub change_list: ChangeList,
1159 inline_value_cache: InlineValueCache,
1160 selection_drag_state: SelectionDragState,
1161 colors: Option<LspColorData>,
1162 post_scroll_update: Task<()>,
1163 refresh_colors_task: Task<()>,
1164 inlay_hints: Option<LspInlayHintData>,
1165 folding_newlines: Task<()>,
1166 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1167}
1168
1169fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1170 if debounce_ms > 0 {
1171 Some(Duration::from_millis(debounce_ms))
1172 } else {
1173 None
1174 }
1175}
1176
1177#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1178enum NextScrollCursorCenterTopBottom {
1179 #[default]
1180 Center,
1181 Top,
1182 Bottom,
1183}
1184
1185impl NextScrollCursorCenterTopBottom {
1186 fn next(&self) -> Self {
1187 match self {
1188 Self::Center => Self::Top,
1189 Self::Top => Self::Bottom,
1190 Self::Bottom => Self::Center,
1191 }
1192 }
1193}
1194
1195#[derive(Clone)]
1196pub struct EditorSnapshot {
1197 pub mode: EditorMode,
1198 show_gutter: bool,
1199 show_line_numbers: Option<bool>,
1200 show_git_diff_gutter: Option<bool>,
1201 show_code_actions: Option<bool>,
1202 show_runnables: Option<bool>,
1203 show_breakpoints: Option<bool>,
1204 git_blame_gutter_max_author_length: Option<usize>,
1205 pub display_snapshot: DisplaySnapshot,
1206 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1207 is_focused: bool,
1208 scroll_anchor: ScrollAnchor,
1209 ongoing_scroll: OngoingScroll,
1210 current_line_highlight: CurrentLineHighlight,
1211 gutter_hovered: bool,
1212}
1213
1214#[derive(Default, Debug, Clone, Copy)]
1215pub struct GutterDimensions {
1216 pub left_padding: Pixels,
1217 pub right_padding: Pixels,
1218 pub width: Pixels,
1219 pub margin: Pixels,
1220 pub git_blame_entries_width: Option<Pixels>,
1221}
1222
1223impl GutterDimensions {
1224 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1225 Self {
1226 margin: Self::default_gutter_margin(font_id, font_size, cx),
1227 ..Default::default()
1228 }
1229 }
1230
1231 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1232 -cx.text_system().descent(font_id, font_size)
1233 }
1234 /// The full width of the space taken up by the gutter.
1235 pub fn full_width(&self) -> Pixels {
1236 self.margin + self.width
1237 }
1238
1239 /// The width of the space reserved for the fold indicators,
1240 /// use alongside 'justify_end' and `gutter_width` to
1241 /// right align content with the line numbers
1242 pub fn fold_area_width(&self) -> Pixels {
1243 self.margin + self.right_padding
1244 }
1245}
1246
1247struct CharacterDimensions {
1248 em_width: Pixels,
1249 em_advance: Pixels,
1250 line_height: Pixels,
1251}
1252
1253#[derive(Debug)]
1254pub struct RemoteSelection {
1255 pub replica_id: ReplicaId,
1256 pub selection: Selection<Anchor>,
1257 pub cursor_shape: CursorShape,
1258 pub collaborator_id: CollaboratorId,
1259 pub line_mode: bool,
1260 pub user_name: Option<SharedString>,
1261 pub color: PlayerColor,
1262}
1263
1264#[derive(Clone, Debug)]
1265struct SelectionHistoryEntry {
1266 selections: Arc<[Selection<Anchor>]>,
1267 select_next_state: Option<SelectNextState>,
1268 select_prev_state: Option<SelectNextState>,
1269 add_selections_state: Option<AddSelectionsState>,
1270}
1271
1272#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1273enum SelectionHistoryMode {
1274 Normal,
1275 Undoing,
1276 Redoing,
1277 Skipping,
1278}
1279
1280#[derive(Clone, PartialEq, Eq, Hash)]
1281struct HoveredCursor {
1282 replica_id: ReplicaId,
1283 selection_id: usize,
1284}
1285
1286impl Default for SelectionHistoryMode {
1287 fn default() -> Self {
1288 Self::Normal
1289 }
1290}
1291
1292#[derive(Debug)]
1293/// SelectionEffects controls the side-effects of updating the selection.
1294///
1295/// The default behaviour does "what you mostly want":
1296/// - it pushes to the nav history if the cursor moved by >10 lines
1297/// - it re-triggers completion requests
1298/// - it scrolls to fit
1299///
1300/// You might want to modify these behaviours. For example when doing a "jump"
1301/// like go to definition, we always want to add to nav history; but when scrolling
1302/// in vim mode we never do.
1303///
1304/// Similarly, you might want to disable scrolling if you don't want the viewport to
1305/// move.
1306#[derive(Clone)]
1307pub struct SelectionEffects {
1308 nav_history: Option<bool>,
1309 completions: bool,
1310 scroll: Option<Autoscroll>,
1311}
1312
1313impl Default for SelectionEffects {
1314 fn default() -> Self {
1315 Self {
1316 nav_history: None,
1317 completions: true,
1318 scroll: Some(Autoscroll::fit()),
1319 }
1320 }
1321}
1322impl SelectionEffects {
1323 pub fn scroll(scroll: Autoscroll) -> Self {
1324 Self {
1325 scroll: Some(scroll),
1326 ..Default::default()
1327 }
1328 }
1329
1330 pub fn no_scroll() -> Self {
1331 Self {
1332 scroll: None,
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn completions(self, completions: bool) -> Self {
1338 Self {
1339 completions,
1340 ..self
1341 }
1342 }
1343
1344 pub fn nav_history(self, nav_history: bool) -> Self {
1345 Self {
1346 nav_history: Some(nav_history),
1347 ..self
1348 }
1349 }
1350}
1351
1352struct DeferredSelectionEffectsState {
1353 changed: bool,
1354 effects: SelectionEffects,
1355 old_cursor_position: Anchor,
1356 history_entry: SelectionHistoryEntry,
1357}
1358
1359#[derive(Default)]
1360struct SelectionHistory {
1361 #[allow(clippy::type_complexity)]
1362 selections_by_transaction:
1363 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1364 mode: SelectionHistoryMode,
1365 undo_stack: VecDeque<SelectionHistoryEntry>,
1366 redo_stack: VecDeque<SelectionHistoryEntry>,
1367}
1368
1369impl SelectionHistory {
1370 #[track_caller]
1371 fn insert_transaction(
1372 &mut self,
1373 transaction_id: TransactionId,
1374 selections: Arc<[Selection<Anchor>]>,
1375 ) {
1376 if selections.is_empty() {
1377 log::error!(
1378 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1379 std::panic::Location::caller()
1380 );
1381 return;
1382 }
1383 self.selections_by_transaction
1384 .insert(transaction_id, (selections, None));
1385 }
1386
1387 #[allow(clippy::type_complexity)]
1388 fn transaction(
1389 &self,
1390 transaction_id: TransactionId,
1391 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1392 self.selections_by_transaction.get(&transaction_id)
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction_mut(
1397 &mut self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get_mut(&transaction_id)
1401 }
1402
1403 fn push(&mut self, entry: SelectionHistoryEntry) {
1404 if !entry.selections.is_empty() {
1405 match self.mode {
1406 SelectionHistoryMode::Normal => {
1407 self.push_undo(entry);
1408 self.redo_stack.clear();
1409 }
1410 SelectionHistoryMode::Undoing => self.push_redo(entry),
1411 SelectionHistoryMode::Redoing => self.push_undo(entry),
1412 SelectionHistoryMode::Skipping => {}
1413 }
1414 }
1415 }
1416
1417 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1418 if self
1419 .undo_stack
1420 .back()
1421 .is_none_or(|e| e.selections != entry.selections)
1422 {
1423 self.undo_stack.push_back(entry);
1424 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1425 self.undo_stack.pop_front();
1426 }
1427 }
1428 }
1429
1430 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .redo_stack
1433 .back()
1434 .is_none_or(|e| e.selections != entry.selections)
1435 {
1436 self.redo_stack.push_back(entry);
1437 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.redo_stack.pop_front();
1439 }
1440 }
1441 }
1442}
1443
1444#[derive(Clone, Copy)]
1445pub struct RowHighlightOptions {
1446 pub autoscroll: bool,
1447 pub include_gutter: bool,
1448}
1449
1450impl Default for RowHighlightOptions {
1451 fn default() -> Self {
1452 Self {
1453 autoscroll: Default::default(),
1454 include_gutter: true,
1455 }
1456 }
1457}
1458
1459struct RowHighlight {
1460 index: usize,
1461 range: Range<Anchor>,
1462 color: Hsla,
1463 options: RowHighlightOptions,
1464 type_id: TypeId,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsState {
1469 groups: Vec<AddSelectionsGroup>,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsGroup {
1474 above: bool,
1475 stack: Vec<usize>,
1476}
1477
1478#[derive(Clone)]
1479struct SelectNextState {
1480 query: AhoCorasick,
1481 wordwise: bool,
1482 done: bool,
1483}
1484
1485impl std::fmt::Debug for SelectNextState {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 f.debug_struct(std::any::type_name::<Self>())
1488 .field("wordwise", &self.wordwise)
1489 .field("done", &self.done)
1490 .finish()
1491 }
1492}
1493
1494#[derive(Debug)]
1495struct AutocloseRegion {
1496 selection_id: usize,
1497 range: Range<Anchor>,
1498 pair: BracketPair,
1499}
1500
1501#[derive(Debug)]
1502struct SnippetState {
1503 ranges: Vec<Vec<Range<Anchor>>>,
1504 active_index: usize,
1505 choices: Vec<Option<Vec<String>>>,
1506}
1507
1508#[doc(hidden)]
1509pub struct RenameState {
1510 pub range: Range<Anchor>,
1511 pub old_name: Arc<str>,
1512 pub editor: Entity<Editor>,
1513 block_id: CustomBlockId,
1514}
1515
1516struct InvalidationStack<T>(Vec<T>);
1517
1518struct RegisteredEditPredictionProvider {
1519 provider: Arc<dyn EditPredictionProviderHandle>,
1520 _subscription: Subscription,
1521}
1522
1523#[derive(Debug, PartialEq, Eq)]
1524pub struct ActiveDiagnosticGroup {
1525 pub active_range: Range<Anchor>,
1526 pub active_message: String,
1527 pub group_id: usize,
1528 pub blocks: HashSet<CustomBlockId>,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532
1533pub(crate) enum ActiveDiagnostic {
1534 None,
1535 All,
1536 Group(ActiveDiagnosticGroup),
1537}
1538
1539#[derive(Serialize, Deserialize, Clone, Debug)]
1540pub struct ClipboardSelection {
1541 /// The number of bytes in this selection.
1542 pub len: usize,
1543 /// Whether this was a full-line selection.
1544 pub is_entire_line: bool,
1545 /// The indentation of the first line when this content was originally copied.
1546 pub first_line_indent: u32,
1547}
1548
1549// selections, scroll behavior, was newest selection reversed
1550type SelectSyntaxNodeHistoryState = (
1551 Box<[Selection<usize>]>,
1552 SelectSyntaxNodeScrollBehavior,
1553 bool,
1554);
1555
1556#[derive(Default)]
1557struct SelectSyntaxNodeHistory {
1558 stack: Vec<SelectSyntaxNodeHistoryState>,
1559 // disable temporarily to allow changing selections without losing the stack
1560 pub disable_clearing: bool,
1561}
1562
1563impl SelectSyntaxNodeHistory {
1564 pub fn try_clear(&mut self) {
1565 if !self.disable_clearing {
1566 self.stack.clear();
1567 }
1568 }
1569
1570 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1571 self.stack.push(selection);
1572 }
1573
1574 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1575 self.stack.pop()
1576 }
1577}
1578
1579enum SelectSyntaxNodeScrollBehavior {
1580 CursorTop,
1581 FitSelection,
1582 CursorBottom,
1583}
1584
1585#[derive(Debug)]
1586pub(crate) struct NavigationData {
1587 cursor_anchor: Anchor,
1588 cursor_position: Point,
1589 scroll_anchor: ScrollAnchor,
1590 scroll_top_row: u32,
1591}
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1594pub enum GotoDefinitionKind {
1595 Symbol,
1596 Declaration,
1597 Type,
1598 Implementation,
1599}
1600
1601pub enum FormatTarget {
1602 Buffers(HashSet<Entity<Buffer>>),
1603 Ranges(Vec<Range<MultiBufferPoint>>),
1604}
1605
1606pub(crate) struct FocusedBlock {
1607 id: BlockId,
1608 focus_handle: WeakFocusHandle,
1609}
1610
1611#[derive(Clone)]
1612enum JumpData {
1613 MultiBufferRow {
1614 row: MultiBufferRow,
1615 line_offset_from_top: u32,
1616 },
1617 MultiBufferPoint {
1618 excerpt_id: ExcerptId,
1619 position: Point,
1620 anchor: text::Anchor,
1621 line_offset_from_top: u32,
1622 },
1623}
1624
1625pub enum MultibufferSelectionMode {
1626 First,
1627 All,
1628}
1629
1630#[derive(Clone, Copy, Debug, Default)]
1631pub struct RewrapOptions {
1632 pub override_language_settings: bool,
1633 pub preserve_existing_whitespace: bool,
1634}
1635
1636impl Editor {
1637 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1638 let buffer = cx.new(|cx| Buffer::local("", cx));
1639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1640 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1641 }
1642
1643 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1644 let buffer = cx.new(|cx| Buffer::local("", cx));
1645 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1646 Self::new(EditorMode::full(), buffer, None, window, cx)
1647 }
1648
1649 pub fn auto_height(
1650 min_lines: usize,
1651 max_lines: usize,
1652 window: &mut Window,
1653 cx: &mut Context<Self>,
1654 ) -> Self {
1655 let buffer = cx.new(|cx| Buffer::local("", cx));
1656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1657 Self::new(
1658 EditorMode::AutoHeight {
1659 min_lines,
1660 max_lines: Some(max_lines),
1661 },
1662 buffer,
1663 None,
1664 window,
1665 cx,
1666 )
1667 }
1668
1669 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1670 /// The editor grows as tall as needed to fit its content.
1671 pub fn auto_height_unbounded(
1672 min_lines: usize,
1673 window: &mut Window,
1674 cx: &mut Context<Self>,
1675 ) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(
1679 EditorMode::AutoHeight {
1680 min_lines,
1681 max_lines: None,
1682 },
1683 buffer,
1684 None,
1685 window,
1686 cx,
1687 )
1688 }
1689
1690 pub fn for_buffer(
1691 buffer: Entity<Buffer>,
1692 project: Option<Entity<Project>>,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1697 Self::new(EditorMode::full(), buffer, project, window, cx)
1698 }
1699
1700 pub fn for_multibuffer(
1701 buffer: Entity<MultiBuffer>,
1702 project: Option<Entity<Project>>,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 Self::new(EditorMode::full(), buffer, project, window, cx)
1707 }
1708
1709 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1710 let mut clone = Self::new(
1711 self.mode.clone(),
1712 self.buffer.clone(),
1713 self.project.clone(),
1714 window,
1715 cx,
1716 );
1717 self.display_map.update(cx, |display_map, cx| {
1718 let snapshot = display_map.snapshot(cx);
1719 clone.display_map.update(cx, |display_map, cx| {
1720 display_map.set_state(&snapshot, cx);
1721 });
1722 });
1723 clone.folds_did_change(cx);
1724 clone.selections.clone_state(&self.selections);
1725 clone.scroll_manager.clone_state(&self.scroll_manager);
1726 clone.searchable = self.searchable;
1727 clone.read_only = self.read_only;
1728 clone
1729 }
1730
1731 pub fn new(
1732 mode: EditorMode,
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Editor::new_internal(mode, buffer, project, None, window, cx)
1739 }
1740
1741 fn new_internal(
1742 mode: EditorMode,
1743 multi_buffer: Entity<MultiBuffer>,
1744 project: Option<Entity<Project>>,
1745 display_map: Option<Entity<DisplayMap>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 debug_assert!(
1750 display_map.is_none() || mode.is_minimap(),
1751 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1752 );
1753
1754 let full_mode = mode.is_full();
1755 let is_minimap = mode.is_minimap();
1756 let diagnostics_max_severity = if full_mode {
1757 EditorSettings::get_global(cx)
1758 .diagnostics_max_severity
1759 .unwrap_or(DiagnosticSeverity::Hint)
1760 } else {
1761 DiagnosticSeverity::Off
1762 };
1763 let style = window.text_style();
1764 let font_size = style.font_size.to_pixels(window.rem_size());
1765 let editor = cx.entity().downgrade();
1766 let fold_placeholder = FoldPlaceholder {
1767 constrain_width: false,
1768 render: Arc::new(move |fold_id, fold_range, cx| {
1769 let editor = editor.clone();
1770 div()
1771 .id(fold_id)
1772 .bg(cx.theme().colors().ghost_element_background)
1773 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1774 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1775 .rounded_xs()
1776 .size_full()
1777 .cursor_pointer()
1778 .child("⋯")
1779 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1780 .on_click(move |_, _window, cx| {
1781 editor
1782 .update(cx, |editor, cx| {
1783 editor.unfold_ranges(
1784 &[fold_range.start..fold_range.end],
1785 true,
1786 false,
1787 cx,
1788 );
1789 cx.stop_propagation();
1790 })
1791 .ok();
1792 })
1793 .into_any()
1794 }),
1795 merge_adjacent: true,
1796 ..FoldPlaceholder::default()
1797 };
1798 let display_map = display_map.unwrap_or_else(|| {
1799 cx.new(|cx| {
1800 DisplayMap::new(
1801 multi_buffer.clone(),
1802 style.font(),
1803 font_size,
1804 None,
1805 FILE_HEADER_HEIGHT,
1806 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1807 fold_placeholder,
1808 diagnostics_max_severity,
1809 cx,
1810 )
1811 })
1812 });
1813
1814 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1815
1816 let blink_manager = cx.new(|cx| {
1817 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1818 if is_minimap {
1819 blink_manager.disable(cx);
1820 }
1821 blink_manager
1822 });
1823
1824 let soft_wrap_mode_override =
1825 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1826
1827 let mut project_subscriptions = Vec::new();
1828 if full_mode && let Some(project) = project.as_ref() {
1829 project_subscriptions.push(cx.subscribe_in(
1830 project,
1831 window,
1832 |editor, _, event, window, cx| match event {
1833 project::Event::RefreshCodeLens => {
1834 // we always query lens with actions, without storing them, always refreshing them
1835 }
1836 project::Event::RefreshInlayHints {
1837 server_id,
1838 request_id,
1839 } => {
1840 editor.refresh_inlay_hints(
1841 InlayHintRefreshReason::RefreshRequested {
1842 server_id: *server_id,
1843 request_id: *request_id,
1844 },
1845 cx,
1846 );
1847 }
1848 project::Event::LanguageServerRemoved(..) => {
1849 if editor.tasks_update_task.is_none() {
1850 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1851 }
1852 editor.registered_buffers.clear();
1853 editor.register_visible_buffers(cx);
1854 }
1855 project::Event::LanguageServerAdded(..) => {
1856 if editor.tasks_update_task.is_none() {
1857 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1858 }
1859 }
1860 project::Event::SnippetEdit(id, snippet_edits) => {
1861 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1862 let focus_handle = editor.focus_handle(cx);
1863 if focus_handle.is_focused(window) {
1864 let snapshot = buffer.read(cx).snapshot();
1865 for (range, snippet) in snippet_edits {
1866 let editor_range =
1867 language::range_from_lsp(*range).to_offset(&snapshot);
1868 editor
1869 .insert_snippet(
1870 &[editor_range],
1871 snippet.clone(),
1872 window,
1873 cx,
1874 )
1875 .ok();
1876 }
1877 }
1878 }
1879 }
1880 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1881 let buffer_id = *buffer_id;
1882 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1883 editor.register_buffer(buffer_id, cx);
1884 editor.update_lsp_data(Some(buffer_id), window, cx);
1885 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1886 refresh_linked_ranges(editor, window, cx);
1887 editor.refresh_code_actions(window, cx);
1888 editor.refresh_document_highlights(cx);
1889 }
1890 }
1891
1892 project::Event::EntryRenamed(transaction) => {
1893 let Some(workspace) = editor.workspace() else {
1894 return;
1895 };
1896 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1897 else {
1898 return;
1899 };
1900 if active_editor.entity_id() == cx.entity_id() {
1901 let edited_buffers_already_open = {
1902 let other_editors: Vec<Entity<Editor>> = workspace
1903 .read(cx)
1904 .panes()
1905 .iter()
1906 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1907 .filter(|editor| editor.entity_id() != cx.entity_id())
1908 .collect();
1909
1910 transaction.0.keys().all(|buffer| {
1911 other_editors.iter().any(|editor| {
1912 let multi_buffer = editor.read(cx).buffer();
1913 multi_buffer.read(cx).is_singleton()
1914 && multi_buffer.read(cx).as_singleton().map_or(
1915 false,
1916 |singleton| {
1917 singleton.entity_id() == buffer.entity_id()
1918 },
1919 )
1920 })
1921 })
1922 };
1923
1924 if !edited_buffers_already_open {
1925 let workspace = workspace.downgrade();
1926 let transaction = transaction.clone();
1927 cx.defer_in(window, move |_, window, cx| {
1928 cx.spawn_in(window, async move |editor, cx| {
1929 Self::open_project_transaction(
1930 &editor,
1931 workspace,
1932 transaction,
1933 "Rename".to_string(),
1934 cx,
1935 )
1936 .await
1937 .ok()
1938 })
1939 .detach();
1940 });
1941 }
1942 }
1943 }
1944
1945 _ => {}
1946 },
1947 ));
1948 if let Some(task_inventory) = project
1949 .read(cx)
1950 .task_store()
1951 .read(cx)
1952 .task_inventory()
1953 .cloned()
1954 {
1955 project_subscriptions.push(cx.observe_in(
1956 &task_inventory,
1957 window,
1958 |editor, _, window, cx| {
1959 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1960 },
1961 ));
1962 };
1963
1964 project_subscriptions.push(cx.subscribe_in(
1965 &project.read(cx).breakpoint_store(),
1966 window,
1967 |editor, _, event, window, cx| match event {
1968 BreakpointStoreEvent::ClearDebugLines => {
1969 editor.clear_row_highlights::<ActiveDebugLine>();
1970 editor.refresh_inline_values(cx);
1971 }
1972 BreakpointStoreEvent::SetDebugLine => {
1973 if editor.go_to_active_debug_line(window, cx) {
1974 cx.stop_propagation();
1975 }
1976
1977 editor.refresh_inline_values(cx);
1978 }
1979 _ => {}
1980 },
1981 ));
1982 let git_store = project.read(cx).git_store().clone();
1983 let project = project.clone();
1984 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1985 if let GitStoreEvent::RepositoryAdded = event {
1986 this.load_diff_task = Some(
1987 update_uncommitted_diff_for_buffer(
1988 cx.entity(),
1989 &project,
1990 this.buffer.read(cx).all_buffers(),
1991 this.buffer.clone(),
1992 cx,
1993 )
1994 .shared(),
1995 );
1996 }
1997 }));
1998 }
1999
2000 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2001
2002 let inlay_hint_settings =
2003 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2004 let focus_handle = cx.focus_handle();
2005 if !is_minimap {
2006 cx.on_focus(&focus_handle, window, Self::handle_focus)
2007 .detach();
2008 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2009 .detach();
2010 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2011 .detach();
2012 cx.on_blur(&focus_handle, window, Self::handle_blur)
2013 .detach();
2014 cx.observe_pending_input(window, Self::observe_pending_input)
2015 .detach();
2016 }
2017
2018 let show_indent_guides =
2019 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2020 Some(false)
2021 } else {
2022 None
2023 };
2024
2025 let breakpoint_store = match (&mode, project.as_ref()) {
2026 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2027 _ => None,
2028 };
2029
2030 let mut code_action_providers = Vec::new();
2031 let mut load_uncommitted_diff = None;
2032 if let Some(project) = project.clone() {
2033 load_uncommitted_diff = Some(
2034 update_uncommitted_diff_for_buffer(
2035 cx.entity(),
2036 &project,
2037 multi_buffer.read(cx).all_buffers(),
2038 multi_buffer.clone(),
2039 cx,
2040 )
2041 .shared(),
2042 );
2043 code_action_providers.push(Rc::new(project) as Rc<_>);
2044 }
2045
2046 let mut editor = Self {
2047 focus_handle,
2048 show_cursor_when_unfocused: false,
2049 last_focused_descendant: None,
2050 buffer: multi_buffer.clone(),
2051 display_map: display_map.clone(),
2052 placeholder_display_map: None,
2053 selections,
2054 scroll_manager: ScrollManager::new(cx),
2055 columnar_selection_state: None,
2056 add_selections_state: None,
2057 select_next_state: None,
2058 select_prev_state: None,
2059 selection_history: SelectionHistory::default(),
2060 defer_selection_effects: false,
2061 deferred_selection_effects_state: None,
2062 autoclose_regions: Vec::new(),
2063 snippet_stack: InvalidationStack::default(),
2064 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2065 ime_transaction: None,
2066 active_diagnostics: ActiveDiagnostic::None,
2067 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2068 inline_diagnostics_update: Task::ready(()),
2069 inline_diagnostics: Vec::new(),
2070 soft_wrap_mode_override,
2071 diagnostics_max_severity,
2072 hard_wrap: None,
2073 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2074 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2075 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2076 project,
2077 blink_manager: blink_manager.clone(),
2078 show_local_selections: true,
2079 show_scrollbars: ScrollbarAxes {
2080 horizontal: full_mode,
2081 vertical: full_mode,
2082 },
2083 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2084 offset_content: !matches!(mode, EditorMode::SingleLine),
2085 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2086 show_gutter: full_mode,
2087 show_line_numbers: (!full_mode).then_some(false),
2088 use_relative_line_numbers: None,
2089 disable_expand_excerpt_buttons: !full_mode,
2090 show_git_diff_gutter: None,
2091 show_code_actions: None,
2092 show_runnables: None,
2093 show_breakpoints: None,
2094 show_wrap_guides: None,
2095 show_indent_guides,
2096 highlight_order: 0,
2097 highlighted_rows: HashMap::default(),
2098 background_highlights: HashMap::default(),
2099 gutter_highlights: HashMap::default(),
2100 scrollbar_marker_state: ScrollbarMarkerState::default(),
2101 active_indent_guides_state: ActiveIndentGuidesState::default(),
2102 nav_history: None,
2103 context_menu: RefCell::new(None),
2104 context_menu_options: None,
2105 mouse_context_menu: None,
2106 completion_tasks: Vec::new(),
2107 inline_blame_popover: None,
2108 inline_blame_popover_show_task: None,
2109 signature_help_state: SignatureHelpState::default(),
2110 auto_signature_help: None,
2111 find_all_references_task_sources: Vec::new(),
2112 next_completion_id: 0,
2113 next_inlay_id: 0,
2114 code_action_providers,
2115 available_code_actions: None,
2116 code_actions_task: None,
2117 quick_selection_highlight_task: None,
2118 debounced_selection_highlight_task: None,
2119 document_highlights_task: None,
2120 linked_editing_range_task: None,
2121 pending_rename: None,
2122 searchable: !is_minimap,
2123 cursor_shape: EditorSettings::get_global(cx)
2124 .cursor_shape
2125 .unwrap_or_default(),
2126 current_line_highlight: None,
2127 autoindent_mode: Some(AutoindentMode::EachLine),
2128 collapse_matches: false,
2129 workspace: None,
2130 input_enabled: !is_minimap,
2131 use_modal_editing: full_mode,
2132 read_only: is_minimap,
2133 use_autoclose: true,
2134 use_auto_surround: true,
2135 auto_replace_emoji_shortcode: false,
2136 jsx_tag_auto_close_enabled_in_any_buffer: false,
2137 leader_id: None,
2138 remote_id: None,
2139 hover_state: HoverState::default(),
2140 pending_mouse_down: None,
2141 hovered_link_state: None,
2142 edit_prediction_provider: None,
2143 active_edit_prediction: None,
2144 stale_edit_prediction_in_menu: None,
2145 edit_prediction_preview: EditPredictionPreview::Inactive {
2146 released_too_fast: false,
2147 },
2148 inline_diagnostics_enabled: full_mode,
2149 diagnostics_enabled: full_mode,
2150 word_completions_enabled: full_mode,
2151 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2152 gutter_hovered: false,
2153 pixel_position_of_newest_cursor: None,
2154 last_bounds: None,
2155 last_position_map: None,
2156 expect_bounds_change: None,
2157 gutter_dimensions: GutterDimensions::default(),
2158 style: None,
2159 show_cursor_names: false,
2160 hovered_cursors: HashMap::default(),
2161 next_editor_action_id: EditorActionId::default(),
2162 editor_actions: Rc::default(),
2163 edit_predictions_hidden_for_vim_mode: false,
2164 show_edit_predictions_override: None,
2165 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2166 edit_prediction_settings: EditPredictionSettings::Disabled,
2167 edit_prediction_indent_conflict: false,
2168 edit_prediction_requires_modifier_in_indent_conflict: true,
2169 custom_context_menu: None,
2170 show_git_blame_gutter: false,
2171 show_git_blame_inline: false,
2172 show_selection_menu: None,
2173 show_git_blame_inline_delay_task: None,
2174 git_blame_inline_enabled: full_mode
2175 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2176 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2177 serialize_dirty_buffers: !is_minimap
2178 && ProjectSettings::get_global(cx)
2179 .session
2180 .restore_unsaved_buffers,
2181 blame: None,
2182 blame_subscription: None,
2183 tasks: BTreeMap::default(),
2184
2185 breakpoint_store,
2186 gutter_breakpoint_indicator: (None, None),
2187 hovered_diff_hunk_row: None,
2188 _subscriptions: (!is_minimap)
2189 .then(|| {
2190 vec![
2191 cx.observe(&multi_buffer, Self::on_buffer_changed),
2192 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2193 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2194 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2195 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2196 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2197 cx.observe_window_activation(window, |editor, window, cx| {
2198 let active = window.is_window_active();
2199 editor.blink_manager.update(cx, |blink_manager, cx| {
2200 if active {
2201 blink_manager.enable(cx);
2202 } else {
2203 blink_manager.disable(cx);
2204 }
2205 });
2206 if active {
2207 editor.show_mouse_cursor(cx);
2208 }
2209 }),
2210 ]
2211 })
2212 .unwrap_or_default(),
2213 tasks_update_task: None,
2214 pull_diagnostics_task: Task::ready(()),
2215 colors: None,
2216 refresh_colors_task: Task::ready(()),
2217 inlay_hints: None,
2218 next_color_inlay_id: 0,
2219 post_scroll_update: Task::ready(()),
2220 linked_edit_ranges: Default::default(),
2221 in_project_search: false,
2222 previous_search_ranges: None,
2223 breadcrumb_header: None,
2224 focused_block: None,
2225 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2226 addons: HashMap::default(),
2227 registered_buffers: HashMap::default(),
2228 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2229 selection_mark_mode: false,
2230 toggle_fold_multiple_buffers: Task::ready(()),
2231 serialize_selections: Task::ready(()),
2232 serialize_folds: Task::ready(()),
2233 text_style_refinement: None,
2234 load_diff_task: load_uncommitted_diff,
2235 temporary_diff_override: false,
2236 mouse_cursor_hidden: false,
2237 minimap: None,
2238 hide_mouse_mode: EditorSettings::get_global(cx)
2239 .hide_mouse
2240 .unwrap_or_default(),
2241 change_list: ChangeList::new(),
2242 mode,
2243 selection_drag_state: SelectionDragState::None,
2244 folding_newlines: Task::ready(()),
2245 lookup_key: None,
2246 };
2247
2248 if is_minimap {
2249 return editor;
2250 }
2251
2252 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2253 editor
2254 ._subscriptions
2255 .push(cx.observe(breakpoints, |_, _, cx| {
2256 cx.notify();
2257 }));
2258 }
2259 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2260 editor._subscriptions.extend(project_subscriptions);
2261
2262 editor._subscriptions.push(cx.subscribe_in(
2263 &cx.entity(),
2264 window,
2265 |editor, _, e: &EditorEvent, window, cx| match e {
2266 EditorEvent::ScrollPositionChanged { local, .. } => {
2267 if *local {
2268 let new_anchor = editor.scroll_manager.anchor();
2269 let snapshot = editor.snapshot(window, cx);
2270 editor.update_restoration_data(cx, move |data| {
2271 data.scroll_position = (
2272 new_anchor.top_row(snapshot.buffer_snapshot()),
2273 new_anchor.offset,
2274 );
2275 });
2276 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2277 editor.inline_blame_popover.take();
2278 }
2279 }
2280 EditorEvent::Edited { .. } => {
2281 if !vim_enabled(cx) {
2282 let display_map = editor.display_snapshot(cx);
2283 let selections = editor.selections.all_adjusted_display(&display_map);
2284 let pop_state = editor
2285 .change_list
2286 .last()
2287 .map(|previous| {
2288 previous.len() == selections.len()
2289 && previous.iter().enumerate().all(|(ix, p)| {
2290 p.to_display_point(&display_map).row()
2291 == selections[ix].head().row()
2292 })
2293 })
2294 .unwrap_or(false);
2295 let new_positions = selections
2296 .into_iter()
2297 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2298 .collect();
2299 editor
2300 .change_list
2301 .push_to_change_list(pop_state, new_positions);
2302 }
2303 }
2304 _ => (),
2305 },
2306 ));
2307
2308 if let Some(dap_store) = editor
2309 .project
2310 .as_ref()
2311 .map(|project| project.read(cx).dap_store())
2312 {
2313 let weak_editor = cx.weak_entity();
2314
2315 editor
2316 ._subscriptions
2317 .push(
2318 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2319 let session_entity = cx.entity();
2320 weak_editor
2321 .update(cx, |editor, cx| {
2322 editor._subscriptions.push(
2323 cx.subscribe(&session_entity, Self::on_debug_session_event),
2324 );
2325 })
2326 .ok();
2327 }),
2328 );
2329
2330 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2331 editor
2332 ._subscriptions
2333 .push(cx.subscribe(&session, Self::on_debug_session_event));
2334 }
2335 }
2336
2337 // skip adding the initial selection to selection history
2338 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2339 editor.end_selection(window, cx);
2340 editor.selection_history.mode = SelectionHistoryMode::Normal;
2341
2342 editor.scroll_manager.show_scrollbars(window, cx);
2343 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2344
2345 if full_mode {
2346 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2347 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2348
2349 if editor.git_blame_inline_enabled {
2350 editor.start_git_blame_inline(false, window, cx);
2351 }
2352
2353 editor.go_to_active_debug_line(window, cx);
2354
2355 editor.minimap =
2356 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2357 editor.colors = Some(LspColorData::new(cx));
2358 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2359
2360 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2361 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2362 }
2363 editor.update_lsp_data(None, window, cx);
2364 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2365 }
2366
2367 editor
2368 }
2369
2370 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2371 self.selections.display_map(cx)
2372 }
2373
2374 pub fn deploy_mouse_context_menu(
2375 &mut self,
2376 position: gpui::Point<Pixels>,
2377 context_menu: Entity<ContextMenu>,
2378 window: &mut Window,
2379 cx: &mut Context<Self>,
2380 ) {
2381 self.mouse_context_menu = Some(MouseContextMenu::new(
2382 self,
2383 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2384 context_menu,
2385 window,
2386 cx,
2387 ));
2388 }
2389
2390 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2391 self.mouse_context_menu
2392 .as_ref()
2393 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2394 }
2395
2396 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2397 if self
2398 .selections
2399 .pending_anchor()
2400 .is_some_and(|pending_selection| {
2401 let snapshot = self.buffer().read(cx).snapshot(cx);
2402 pending_selection.range().includes(range, &snapshot)
2403 })
2404 {
2405 return true;
2406 }
2407
2408 self.selections
2409 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2410 .into_iter()
2411 .any(|selection| {
2412 // This is needed to cover a corner case, if we just check for an existing
2413 // selection in the fold range, having a cursor at the start of the fold
2414 // marks it as selected. Non-empty selections don't cause this.
2415 let length = selection.end - selection.start;
2416 length > 0
2417 })
2418 }
2419
2420 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2421 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2422 }
2423
2424 fn key_context_internal(
2425 &self,
2426 has_active_edit_prediction: bool,
2427 window: &mut Window,
2428 cx: &mut App,
2429 ) -> KeyContext {
2430 let mut key_context = KeyContext::new_with_defaults();
2431 key_context.add("Editor");
2432 let mode = match self.mode {
2433 EditorMode::SingleLine => "single_line",
2434 EditorMode::AutoHeight { .. } => "auto_height",
2435 EditorMode::Minimap { .. } => "minimap",
2436 EditorMode::Full { .. } => "full",
2437 };
2438
2439 if EditorSettings::jupyter_enabled(cx) {
2440 key_context.add("jupyter");
2441 }
2442
2443 key_context.set("mode", mode);
2444 if self.pending_rename.is_some() {
2445 key_context.add("renaming");
2446 }
2447
2448 match self.context_menu.borrow().as_ref() {
2449 Some(CodeContextMenu::Completions(menu)) => {
2450 if menu.visible() {
2451 key_context.add("menu");
2452 key_context.add("showing_completions");
2453 }
2454 }
2455 Some(CodeContextMenu::CodeActions(menu)) => {
2456 if menu.visible() {
2457 key_context.add("menu");
2458 key_context.add("showing_code_actions")
2459 }
2460 }
2461 None => {}
2462 }
2463
2464 if self.signature_help_state.has_multiple_signatures() {
2465 key_context.add("showing_signature_help");
2466 }
2467
2468 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2469 if !self.focus_handle(cx).contains_focused(window, cx)
2470 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2471 {
2472 for addon in self.addons.values() {
2473 addon.extend_key_context(&mut key_context, cx)
2474 }
2475 }
2476
2477 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2478 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2479 Some(
2480 file.full_path(cx)
2481 .extension()?
2482 .to_string_lossy()
2483 .into_owned(),
2484 )
2485 }) {
2486 key_context.set("extension", extension);
2487 }
2488 } else {
2489 key_context.add("multibuffer");
2490 }
2491
2492 if has_active_edit_prediction {
2493 if self.edit_prediction_in_conflict() {
2494 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2495 } else {
2496 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2497 key_context.add("copilot_suggestion");
2498 }
2499 }
2500
2501 if self.selection_mark_mode {
2502 key_context.add("selection_mode");
2503 }
2504
2505 let disjoint = self.selections.disjoint_anchors();
2506 let snapshot = self.snapshot(window, cx);
2507 let snapshot = snapshot.buffer_snapshot();
2508 if self.mode == EditorMode::SingleLine
2509 && let [selection] = disjoint
2510 && selection.start == selection.end
2511 && selection.end.to_offset(snapshot) == snapshot.len()
2512 {
2513 key_context.add("end_of_input");
2514 }
2515
2516 key_context
2517 }
2518
2519 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2520 self.last_bounds.as_ref()
2521 }
2522
2523 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2524 if self.mouse_cursor_hidden {
2525 self.mouse_cursor_hidden = false;
2526 cx.notify();
2527 }
2528 }
2529
2530 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2531 let hide_mouse_cursor = match origin {
2532 HideMouseCursorOrigin::TypingAction => {
2533 matches!(
2534 self.hide_mouse_mode,
2535 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2536 )
2537 }
2538 HideMouseCursorOrigin::MovementAction => {
2539 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2540 }
2541 };
2542 if self.mouse_cursor_hidden != hide_mouse_cursor {
2543 self.mouse_cursor_hidden = hide_mouse_cursor;
2544 cx.notify();
2545 }
2546 }
2547
2548 pub fn edit_prediction_in_conflict(&self) -> bool {
2549 if !self.show_edit_predictions_in_menu() {
2550 return false;
2551 }
2552
2553 let showing_completions = self
2554 .context_menu
2555 .borrow()
2556 .as_ref()
2557 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2558
2559 showing_completions
2560 || self.edit_prediction_requires_modifier()
2561 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2562 // bindings to insert tab characters.
2563 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2564 }
2565
2566 pub fn accept_edit_prediction_keybind(
2567 &self,
2568 accept_partial: bool,
2569 window: &mut Window,
2570 cx: &mut App,
2571 ) -> AcceptEditPredictionBinding {
2572 let key_context = self.key_context_internal(true, window, cx);
2573 let in_conflict = self.edit_prediction_in_conflict();
2574
2575 let bindings = if accept_partial {
2576 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2577 } else {
2578 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2579 };
2580
2581 // TODO: if the binding contains multiple keystrokes, display all of them, not
2582 // just the first one.
2583 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2584 !in_conflict
2585 || binding
2586 .keystrokes()
2587 .first()
2588 .is_some_and(|keystroke| keystroke.modifiers().modified())
2589 }))
2590 }
2591
2592 pub fn new_file(
2593 workspace: &mut Workspace,
2594 _: &workspace::NewFile,
2595 window: &mut Window,
2596 cx: &mut Context<Workspace>,
2597 ) {
2598 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2599 "Failed to create buffer",
2600 window,
2601 cx,
2602 |e, _, _| match e.error_code() {
2603 ErrorCode::RemoteUpgradeRequired => Some(format!(
2604 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2605 e.error_tag("required").unwrap_or("the latest version")
2606 )),
2607 _ => None,
2608 },
2609 );
2610 }
2611
2612 pub fn new_in_workspace(
2613 workspace: &mut Workspace,
2614 window: &mut Window,
2615 cx: &mut Context<Workspace>,
2616 ) -> Task<Result<Entity<Editor>>> {
2617 let project = workspace.project().clone();
2618 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2619
2620 cx.spawn_in(window, async move |workspace, cx| {
2621 let buffer = create.await?;
2622 workspace.update_in(cx, |workspace, window, cx| {
2623 let editor =
2624 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2625 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2626 editor
2627 })
2628 })
2629 }
2630
2631 fn new_file_vertical(
2632 workspace: &mut Workspace,
2633 _: &workspace::NewFileSplitVertical,
2634 window: &mut Window,
2635 cx: &mut Context<Workspace>,
2636 ) {
2637 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2638 }
2639
2640 fn new_file_horizontal(
2641 workspace: &mut Workspace,
2642 _: &workspace::NewFileSplitHorizontal,
2643 window: &mut Window,
2644 cx: &mut Context<Workspace>,
2645 ) {
2646 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2647 }
2648
2649 fn new_file_split(
2650 workspace: &mut Workspace,
2651 action: &workspace::NewFileSplit,
2652 window: &mut Window,
2653 cx: &mut Context<Workspace>,
2654 ) {
2655 Self::new_file_in_direction(workspace, action.0, window, cx)
2656 }
2657
2658 fn new_file_in_direction(
2659 workspace: &mut Workspace,
2660 direction: SplitDirection,
2661 window: &mut Window,
2662 cx: &mut Context<Workspace>,
2663 ) {
2664 let project = workspace.project().clone();
2665 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2666
2667 cx.spawn_in(window, async move |workspace, cx| {
2668 let buffer = create.await?;
2669 workspace.update_in(cx, move |workspace, window, cx| {
2670 workspace.split_item(
2671 direction,
2672 Box::new(
2673 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2674 ),
2675 window,
2676 cx,
2677 )
2678 })?;
2679 anyhow::Ok(())
2680 })
2681 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2682 match e.error_code() {
2683 ErrorCode::RemoteUpgradeRequired => Some(format!(
2684 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2685 e.error_tag("required").unwrap_or("the latest version")
2686 )),
2687 _ => None,
2688 }
2689 });
2690 }
2691
2692 pub fn leader_id(&self) -> Option<CollaboratorId> {
2693 self.leader_id
2694 }
2695
2696 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2697 &self.buffer
2698 }
2699
2700 pub fn project(&self) -> Option<&Entity<Project>> {
2701 self.project.as_ref()
2702 }
2703
2704 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2705 self.workspace.as_ref()?.0.upgrade()
2706 }
2707
2708 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2709 self.buffer().read(cx).title(cx)
2710 }
2711
2712 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2713 let git_blame_gutter_max_author_length = self
2714 .render_git_blame_gutter(cx)
2715 .then(|| {
2716 if let Some(blame) = self.blame.as_ref() {
2717 let max_author_length =
2718 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2719 Some(max_author_length)
2720 } else {
2721 None
2722 }
2723 })
2724 .flatten();
2725
2726 EditorSnapshot {
2727 mode: self.mode.clone(),
2728 show_gutter: self.show_gutter,
2729 show_line_numbers: self.show_line_numbers,
2730 show_git_diff_gutter: self.show_git_diff_gutter,
2731 show_code_actions: self.show_code_actions,
2732 show_runnables: self.show_runnables,
2733 show_breakpoints: self.show_breakpoints,
2734 git_blame_gutter_max_author_length,
2735 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2736 placeholder_display_snapshot: self
2737 .placeholder_display_map
2738 .as_ref()
2739 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2740 scroll_anchor: self.scroll_manager.anchor(),
2741 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2742 is_focused: self.focus_handle.is_focused(window),
2743 current_line_highlight: self
2744 .current_line_highlight
2745 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2746 gutter_hovered: self.gutter_hovered,
2747 }
2748 }
2749
2750 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2751 self.buffer.read(cx).language_at(point, cx)
2752 }
2753
2754 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2755 self.buffer.read(cx).read(cx).file_at(point).cloned()
2756 }
2757
2758 pub fn active_excerpt(
2759 &self,
2760 cx: &App,
2761 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2762 self.buffer
2763 .read(cx)
2764 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2765 }
2766
2767 pub fn mode(&self) -> &EditorMode {
2768 &self.mode
2769 }
2770
2771 pub fn set_mode(&mut self, mode: EditorMode) {
2772 self.mode = mode;
2773 }
2774
2775 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2776 self.collaboration_hub.as_deref()
2777 }
2778
2779 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2780 self.collaboration_hub = Some(hub);
2781 }
2782
2783 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2784 self.in_project_search = in_project_search;
2785 }
2786
2787 pub fn set_custom_context_menu(
2788 &mut self,
2789 f: impl 'static
2790 + Fn(
2791 &mut Self,
2792 DisplayPoint,
2793 &mut Window,
2794 &mut Context<Self>,
2795 ) -> Option<Entity<ui::ContextMenu>>,
2796 ) {
2797 self.custom_context_menu = Some(Box::new(f))
2798 }
2799
2800 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2801 self.completion_provider = provider;
2802 }
2803
2804 #[cfg(any(test, feature = "test-support"))]
2805 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2806 self.completion_provider.clone()
2807 }
2808
2809 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2810 self.semantics_provider.clone()
2811 }
2812
2813 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2814 self.semantics_provider = provider;
2815 }
2816
2817 pub fn set_edit_prediction_provider<T>(
2818 &mut self,
2819 provider: Option<Entity<T>>,
2820 window: &mut Window,
2821 cx: &mut Context<Self>,
2822 ) where
2823 T: EditPredictionProvider,
2824 {
2825 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2826 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2827 if this.focus_handle.is_focused(window) {
2828 this.update_visible_edit_prediction(window, cx);
2829 }
2830 }),
2831 provider: Arc::new(provider),
2832 });
2833 self.update_edit_prediction_settings(cx);
2834 self.refresh_edit_prediction(false, false, window, cx);
2835 }
2836
2837 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2838 self.placeholder_display_map
2839 .as_ref()
2840 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2841 }
2842
2843 pub fn set_placeholder_text(
2844 &mut self,
2845 placeholder_text: &str,
2846 window: &mut Window,
2847 cx: &mut Context<Self>,
2848 ) {
2849 let multibuffer = cx
2850 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2851
2852 let style = window.text_style();
2853
2854 self.placeholder_display_map = Some(cx.new(|cx| {
2855 DisplayMap::new(
2856 multibuffer,
2857 style.font(),
2858 style.font_size.to_pixels(window.rem_size()),
2859 None,
2860 FILE_HEADER_HEIGHT,
2861 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2862 Default::default(),
2863 DiagnosticSeverity::Off,
2864 cx,
2865 )
2866 }));
2867 cx.notify();
2868 }
2869
2870 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2871 self.cursor_shape = cursor_shape;
2872
2873 // Disrupt blink for immediate user feedback that the cursor shape has changed
2874 self.blink_manager.update(cx, BlinkManager::show_cursor);
2875
2876 cx.notify();
2877 }
2878
2879 pub fn set_current_line_highlight(
2880 &mut self,
2881 current_line_highlight: Option<CurrentLineHighlight>,
2882 ) {
2883 self.current_line_highlight = current_line_highlight;
2884 }
2885
2886 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2887 self.collapse_matches = collapse_matches;
2888 }
2889
2890 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2891 if self.collapse_matches {
2892 return range.start..range.start;
2893 }
2894 range.clone()
2895 }
2896
2897 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2898 if self.display_map.read(cx).clip_at_line_ends != clip {
2899 self.display_map
2900 .update(cx, |map, _| map.clip_at_line_ends = clip);
2901 }
2902 }
2903
2904 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2905 self.input_enabled = input_enabled;
2906 }
2907
2908 pub fn set_edit_predictions_hidden_for_vim_mode(
2909 &mut self,
2910 hidden: bool,
2911 window: &mut Window,
2912 cx: &mut Context<Self>,
2913 ) {
2914 if hidden != self.edit_predictions_hidden_for_vim_mode {
2915 self.edit_predictions_hidden_for_vim_mode = hidden;
2916 if hidden {
2917 self.update_visible_edit_prediction(window, cx);
2918 } else {
2919 self.refresh_edit_prediction(true, false, window, cx);
2920 }
2921 }
2922 }
2923
2924 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2925 self.menu_edit_predictions_policy = value;
2926 }
2927
2928 pub fn set_autoindent(&mut self, autoindent: bool) {
2929 if autoindent {
2930 self.autoindent_mode = Some(AutoindentMode::EachLine);
2931 } else {
2932 self.autoindent_mode = None;
2933 }
2934 }
2935
2936 pub fn read_only(&self, cx: &App) -> bool {
2937 self.read_only || self.buffer.read(cx).read_only()
2938 }
2939
2940 pub fn set_read_only(&mut self, read_only: bool) {
2941 self.read_only = read_only;
2942 }
2943
2944 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2945 self.use_autoclose = autoclose;
2946 }
2947
2948 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2949 self.use_auto_surround = auto_surround;
2950 }
2951
2952 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2953 self.auto_replace_emoji_shortcode = auto_replace;
2954 }
2955
2956 pub fn toggle_edit_predictions(
2957 &mut self,
2958 _: &ToggleEditPrediction,
2959 window: &mut Window,
2960 cx: &mut Context<Self>,
2961 ) {
2962 if self.show_edit_predictions_override.is_some() {
2963 self.set_show_edit_predictions(None, window, cx);
2964 } else {
2965 let show_edit_predictions = !self.edit_predictions_enabled();
2966 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2967 }
2968 }
2969
2970 pub fn set_show_edit_predictions(
2971 &mut self,
2972 show_edit_predictions: Option<bool>,
2973 window: &mut Window,
2974 cx: &mut Context<Self>,
2975 ) {
2976 self.show_edit_predictions_override = show_edit_predictions;
2977 self.update_edit_prediction_settings(cx);
2978
2979 if let Some(false) = show_edit_predictions {
2980 self.discard_edit_prediction(false, cx);
2981 } else {
2982 self.refresh_edit_prediction(false, true, window, cx);
2983 }
2984 }
2985
2986 fn edit_predictions_disabled_in_scope(
2987 &self,
2988 buffer: &Entity<Buffer>,
2989 buffer_position: language::Anchor,
2990 cx: &App,
2991 ) -> bool {
2992 let snapshot = buffer.read(cx).snapshot();
2993 let settings = snapshot.settings_at(buffer_position, cx);
2994
2995 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2996 return false;
2997 };
2998
2999 scope.override_name().is_some_and(|scope_name| {
3000 settings
3001 .edit_predictions_disabled_in
3002 .iter()
3003 .any(|s| s == scope_name)
3004 })
3005 }
3006
3007 pub fn set_use_modal_editing(&mut self, to: bool) {
3008 self.use_modal_editing = to;
3009 }
3010
3011 pub fn use_modal_editing(&self) -> bool {
3012 self.use_modal_editing
3013 }
3014
3015 fn selections_did_change(
3016 &mut self,
3017 local: bool,
3018 old_cursor_position: &Anchor,
3019 effects: SelectionEffects,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 ) {
3023 window.invalidate_character_coordinates();
3024
3025 // Copy selections to primary selection buffer
3026 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3027 if local {
3028 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3029 let buffer_handle = self.buffer.read(cx).read(cx);
3030
3031 let mut text = String::new();
3032 for (index, selection) in selections.iter().enumerate() {
3033 let text_for_selection = buffer_handle
3034 .text_for_range(selection.start..selection.end)
3035 .collect::<String>();
3036
3037 text.push_str(&text_for_selection);
3038 if index != selections.len() - 1 {
3039 text.push('\n');
3040 }
3041 }
3042
3043 if !text.is_empty() {
3044 cx.write_to_primary(ClipboardItem::new_string(text));
3045 }
3046 }
3047
3048 let selection_anchors = self.selections.disjoint_anchors_arc();
3049
3050 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3051 self.buffer.update(cx, |buffer, cx| {
3052 buffer.set_active_selections(
3053 &selection_anchors,
3054 self.selections.line_mode(),
3055 self.cursor_shape,
3056 cx,
3057 )
3058 });
3059 }
3060 let display_map = self
3061 .display_map
3062 .update(cx, |display_map, cx| display_map.snapshot(cx));
3063 let buffer = display_map.buffer_snapshot();
3064 if self.selections.count() == 1 {
3065 self.add_selections_state = None;
3066 }
3067 self.select_next_state = None;
3068 self.select_prev_state = None;
3069 self.select_syntax_node_history.try_clear();
3070 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3071 self.snippet_stack.invalidate(&selection_anchors, buffer);
3072 self.take_rename(false, window, cx);
3073
3074 let newest_selection = self.selections.newest_anchor();
3075 let new_cursor_position = newest_selection.head();
3076 let selection_start = newest_selection.start;
3077
3078 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3079 self.push_to_nav_history(
3080 *old_cursor_position,
3081 Some(new_cursor_position.to_point(buffer)),
3082 false,
3083 effects.nav_history == Some(true),
3084 cx,
3085 );
3086 }
3087
3088 if local {
3089 if let Some(buffer_id) = new_cursor_position.buffer_id {
3090 self.register_buffer(buffer_id, cx);
3091 }
3092
3093 let mut context_menu = self.context_menu.borrow_mut();
3094 let completion_menu = match context_menu.as_ref() {
3095 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3096 Some(CodeContextMenu::CodeActions(_)) => {
3097 *context_menu = None;
3098 None
3099 }
3100 None => None,
3101 };
3102 let completion_position = completion_menu.map(|menu| menu.initial_position);
3103 drop(context_menu);
3104
3105 if effects.completions
3106 && let Some(completion_position) = completion_position
3107 {
3108 let start_offset = selection_start.to_offset(buffer);
3109 let position_matches = start_offset == completion_position.to_offset(buffer);
3110 let continue_showing = if position_matches {
3111 if self.snippet_stack.is_empty() {
3112 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3113 == Some(CharKind::Word)
3114 } else {
3115 // Snippet choices can be shown even when the cursor is in whitespace.
3116 // Dismissing the menu with actions like backspace is handled by
3117 // invalidation regions.
3118 true
3119 }
3120 } else {
3121 false
3122 };
3123
3124 if continue_showing {
3125 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3126 } else {
3127 self.hide_context_menu(window, cx);
3128 }
3129 }
3130
3131 hide_hover(self, cx);
3132
3133 if old_cursor_position.to_display_point(&display_map).row()
3134 != new_cursor_position.to_display_point(&display_map).row()
3135 {
3136 self.available_code_actions.take();
3137 }
3138 self.refresh_code_actions(window, cx);
3139 self.refresh_document_highlights(cx);
3140 refresh_linked_ranges(self, window, cx);
3141
3142 self.refresh_selected_text_highlights(false, window, cx);
3143 self.refresh_matching_bracket_highlights(window, cx);
3144 self.update_visible_edit_prediction(window, cx);
3145 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3146 self.inline_blame_popover.take();
3147 if self.git_blame_inline_enabled {
3148 self.start_inline_blame_timer(window, cx);
3149 }
3150 }
3151
3152 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3153 cx.emit(EditorEvent::SelectionsChanged { local });
3154
3155 let selections = &self.selections.disjoint_anchors_arc();
3156 if selections.len() == 1 {
3157 cx.emit(SearchEvent::ActiveMatchChanged)
3158 }
3159 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3160 let inmemory_selections = selections
3161 .iter()
3162 .map(|s| {
3163 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3164 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3165 })
3166 .collect();
3167 self.update_restoration_data(cx, |data| {
3168 data.selections = inmemory_selections;
3169 });
3170
3171 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3172 && let Some(workspace_id) =
3173 self.workspace.as_ref().and_then(|workspace| workspace.1)
3174 {
3175 let snapshot = self.buffer().read(cx).snapshot(cx);
3176 let selections = selections.clone();
3177 let background_executor = cx.background_executor().clone();
3178 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3179 self.serialize_selections = cx.background_spawn(async move {
3180 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3181 let db_selections = selections
3182 .iter()
3183 .map(|selection| {
3184 (
3185 selection.start.to_offset(&snapshot),
3186 selection.end.to_offset(&snapshot),
3187 )
3188 })
3189 .collect();
3190
3191 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3192 .await
3193 .with_context(|| {
3194 format!(
3195 "persisting editor selections for editor {editor_id}, \
3196 workspace {workspace_id:?}"
3197 )
3198 })
3199 .log_err();
3200 });
3201 }
3202 }
3203
3204 cx.notify();
3205 }
3206
3207 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3208 use text::ToOffset as _;
3209 use text::ToPoint as _;
3210
3211 if self.mode.is_minimap()
3212 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3213 {
3214 return;
3215 }
3216
3217 if !self.buffer().read(cx).is_singleton() {
3218 return;
3219 }
3220
3221 let display_snapshot = self
3222 .display_map
3223 .update(cx, |display_map, cx| display_map.snapshot(cx));
3224 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3225 return;
3226 };
3227 let inmemory_folds = display_snapshot
3228 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3229 .map(|fold| {
3230 fold.range.start.text_anchor.to_point(&snapshot)
3231 ..fold.range.end.text_anchor.to_point(&snapshot)
3232 })
3233 .collect();
3234 self.update_restoration_data(cx, |data| {
3235 data.folds = inmemory_folds;
3236 });
3237
3238 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3239 return;
3240 };
3241 let background_executor = cx.background_executor().clone();
3242 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3243 let db_folds = display_snapshot
3244 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3245 .map(|fold| {
3246 (
3247 fold.range.start.text_anchor.to_offset(&snapshot),
3248 fold.range.end.text_anchor.to_offset(&snapshot),
3249 )
3250 })
3251 .collect();
3252 self.serialize_folds = cx.background_spawn(async move {
3253 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3254 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3255 .await
3256 .with_context(|| {
3257 format!(
3258 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3259 )
3260 })
3261 .log_err();
3262 });
3263 }
3264
3265 pub fn sync_selections(
3266 &mut self,
3267 other: Entity<Editor>,
3268 cx: &mut Context<Self>,
3269 ) -> gpui::Subscription {
3270 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3271 if !other_selections.is_empty() {
3272 self.selections.change_with(cx, |selections| {
3273 selections.select_anchors(other_selections);
3274 });
3275 }
3276
3277 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3278 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3279 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3280 if other_selections.is_empty() {
3281 return;
3282 }
3283 this.selections.change_with(cx, |selections| {
3284 selections.select_anchors(other_selections);
3285 });
3286 }
3287 });
3288
3289 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3290 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3291 let these_selections = this.selections.disjoint_anchors().to_vec();
3292 if these_selections.is_empty() {
3293 return;
3294 }
3295 other.update(cx, |other_editor, cx| {
3296 other_editor.selections.change_with(cx, |selections| {
3297 selections.select_anchors(these_selections);
3298 })
3299 });
3300 }
3301 });
3302
3303 Subscription::join(other_subscription, this_subscription)
3304 }
3305
3306 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3307 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3308 /// effects of selection change occur at the end of the transaction.
3309 pub fn change_selections<R>(
3310 &mut self,
3311 effects: SelectionEffects,
3312 window: &mut Window,
3313 cx: &mut Context<Self>,
3314 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3315 ) -> R {
3316 if let Some(state) = &mut self.deferred_selection_effects_state {
3317 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3318 state.effects.completions = effects.completions;
3319 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3320 let (changed, result) = self.selections.change_with(cx, change);
3321 state.changed |= changed;
3322 return result;
3323 }
3324 let mut state = DeferredSelectionEffectsState {
3325 changed: false,
3326 effects,
3327 old_cursor_position: self.selections.newest_anchor().head(),
3328 history_entry: SelectionHistoryEntry {
3329 selections: self.selections.disjoint_anchors_arc(),
3330 select_next_state: self.select_next_state.clone(),
3331 select_prev_state: self.select_prev_state.clone(),
3332 add_selections_state: self.add_selections_state.clone(),
3333 },
3334 };
3335 let (changed, result) = self.selections.change_with(cx, change);
3336 state.changed = state.changed || changed;
3337 if self.defer_selection_effects {
3338 self.deferred_selection_effects_state = Some(state);
3339 } else {
3340 self.apply_selection_effects(state, window, cx);
3341 }
3342 result
3343 }
3344
3345 /// Defers the effects of selection change, so that the effects of multiple calls to
3346 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3347 /// to selection history and the state of popovers based on selection position aren't
3348 /// erroneously updated.
3349 pub fn with_selection_effects_deferred<R>(
3350 &mut self,
3351 window: &mut Window,
3352 cx: &mut Context<Self>,
3353 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3354 ) -> R {
3355 let already_deferred = self.defer_selection_effects;
3356 self.defer_selection_effects = true;
3357 let result = update(self, window, cx);
3358 if !already_deferred {
3359 self.defer_selection_effects = false;
3360 if let Some(state) = self.deferred_selection_effects_state.take() {
3361 self.apply_selection_effects(state, window, cx);
3362 }
3363 }
3364 result
3365 }
3366
3367 fn apply_selection_effects(
3368 &mut self,
3369 state: DeferredSelectionEffectsState,
3370 window: &mut Window,
3371 cx: &mut Context<Self>,
3372 ) {
3373 if state.changed {
3374 self.selection_history.push(state.history_entry);
3375
3376 if let Some(autoscroll) = state.effects.scroll {
3377 self.request_autoscroll(autoscroll, cx);
3378 }
3379
3380 let old_cursor_position = &state.old_cursor_position;
3381
3382 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3383
3384 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3385 self.show_signature_help(&ShowSignatureHelp, window, cx);
3386 }
3387 }
3388 }
3389
3390 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3391 where
3392 I: IntoIterator<Item = (Range<S>, T)>,
3393 S: ToOffset,
3394 T: Into<Arc<str>>,
3395 {
3396 if self.read_only(cx) {
3397 return;
3398 }
3399
3400 self.buffer
3401 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3402 }
3403
3404 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3405 where
3406 I: IntoIterator<Item = (Range<S>, T)>,
3407 S: ToOffset,
3408 T: Into<Arc<str>>,
3409 {
3410 if self.read_only(cx) {
3411 return;
3412 }
3413
3414 self.buffer.update(cx, |buffer, cx| {
3415 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3416 });
3417 }
3418
3419 pub fn edit_with_block_indent<I, S, T>(
3420 &mut self,
3421 edits: I,
3422 original_indent_columns: Vec<Option<u32>>,
3423 cx: &mut Context<Self>,
3424 ) where
3425 I: IntoIterator<Item = (Range<S>, T)>,
3426 S: ToOffset,
3427 T: Into<Arc<str>>,
3428 {
3429 if self.read_only(cx) {
3430 return;
3431 }
3432
3433 self.buffer.update(cx, |buffer, cx| {
3434 buffer.edit(
3435 edits,
3436 Some(AutoindentMode::Block {
3437 original_indent_columns,
3438 }),
3439 cx,
3440 )
3441 });
3442 }
3443
3444 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3445 self.hide_context_menu(window, cx);
3446
3447 match phase {
3448 SelectPhase::Begin {
3449 position,
3450 add,
3451 click_count,
3452 } => self.begin_selection(position, add, click_count, window, cx),
3453 SelectPhase::BeginColumnar {
3454 position,
3455 goal_column,
3456 reset,
3457 mode,
3458 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3459 SelectPhase::Extend {
3460 position,
3461 click_count,
3462 } => self.extend_selection(position, click_count, window, cx),
3463 SelectPhase::Update {
3464 position,
3465 goal_column,
3466 scroll_delta,
3467 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3468 SelectPhase::End => self.end_selection(window, cx),
3469 }
3470 }
3471
3472 fn extend_selection(
3473 &mut self,
3474 position: DisplayPoint,
3475 click_count: usize,
3476 window: &mut Window,
3477 cx: &mut Context<Self>,
3478 ) {
3479 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3480 let tail = self.selections.newest::<usize>(&display_map).tail();
3481 let click_count = click_count.max(match self.selections.select_mode() {
3482 SelectMode::Character => 1,
3483 SelectMode::Word(_) => 2,
3484 SelectMode::Line(_) => 3,
3485 SelectMode::All => 4,
3486 });
3487 self.begin_selection(position, false, click_count, window, cx);
3488
3489 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3490
3491 let current_selection = match self.selections.select_mode() {
3492 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3493 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3494 };
3495
3496 let mut pending_selection = self
3497 .selections
3498 .pending_anchor()
3499 .cloned()
3500 .expect("extend_selection not called with pending selection");
3501
3502 if pending_selection
3503 .start
3504 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3505 == Ordering::Greater
3506 {
3507 pending_selection.start = current_selection.start;
3508 }
3509 if pending_selection
3510 .end
3511 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3512 == Ordering::Less
3513 {
3514 pending_selection.end = current_selection.end;
3515 pending_selection.reversed = true;
3516 }
3517
3518 let mut pending_mode = self.selections.pending_mode().unwrap();
3519 match &mut pending_mode {
3520 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3521 _ => {}
3522 }
3523
3524 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3525 SelectionEffects::scroll(Autoscroll::fit())
3526 } else {
3527 SelectionEffects::no_scroll()
3528 };
3529
3530 self.change_selections(effects, window, cx, |s| {
3531 s.set_pending(pending_selection.clone(), pending_mode);
3532 s.set_is_extending(true);
3533 });
3534 }
3535
3536 fn begin_selection(
3537 &mut self,
3538 position: DisplayPoint,
3539 add: bool,
3540 click_count: usize,
3541 window: &mut Window,
3542 cx: &mut Context<Self>,
3543 ) {
3544 if !self.focus_handle.is_focused(window) {
3545 self.last_focused_descendant = None;
3546 window.focus(&self.focus_handle);
3547 }
3548
3549 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3550 let buffer = display_map.buffer_snapshot();
3551 let position = display_map.clip_point(position, Bias::Left);
3552
3553 let start;
3554 let end;
3555 let mode;
3556 let mut auto_scroll;
3557 match click_count {
3558 1 => {
3559 start = buffer.anchor_before(position.to_point(&display_map));
3560 end = start;
3561 mode = SelectMode::Character;
3562 auto_scroll = true;
3563 }
3564 2 => {
3565 let position = display_map
3566 .clip_point(position, Bias::Left)
3567 .to_offset(&display_map, Bias::Left);
3568 let (range, _) = buffer.surrounding_word(position, None);
3569 start = buffer.anchor_before(range.start);
3570 end = buffer.anchor_before(range.end);
3571 mode = SelectMode::Word(start..end);
3572 auto_scroll = true;
3573 }
3574 3 => {
3575 let position = display_map
3576 .clip_point(position, Bias::Left)
3577 .to_point(&display_map);
3578 let line_start = display_map.prev_line_boundary(position).0;
3579 let next_line_start = buffer.clip_point(
3580 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3581 Bias::Left,
3582 );
3583 start = buffer.anchor_before(line_start);
3584 end = buffer.anchor_before(next_line_start);
3585 mode = SelectMode::Line(start..end);
3586 auto_scroll = true;
3587 }
3588 _ => {
3589 start = buffer.anchor_before(0);
3590 end = buffer.anchor_before(buffer.len());
3591 mode = SelectMode::All;
3592 auto_scroll = false;
3593 }
3594 }
3595 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3596
3597 let point_to_delete: Option<usize> = {
3598 let selected_points: Vec<Selection<Point>> =
3599 self.selections.disjoint_in_range(start..end, &display_map);
3600
3601 if !add || click_count > 1 {
3602 None
3603 } else if !selected_points.is_empty() {
3604 Some(selected_points[0].id)
3605 } else {
3606 let clicked_point_already_selected =
3607 self.selections.disjoint_anchors().iter().find(|selection| {
3608 selection.start.to_point(buffer) == start.to_point(buffer)
3609 || selection.end.to_point(buffer) == end.to_point(buffer)
3610 });
3611
3612 clicked_point_already_selected.map(|selection| selection.id)
3613 }
3614 };
3615
3616 let selections_count = self.selections.count();
3617 let effects = if auto_scroll {
3618 SelectionEffects::default()
3619 } else {
3620 SelectionEffects::no_scroll()
3621 };
3622
3623 self.change_selections(effects, window, cx, |s| {
3624 if let Some(point_to_delete) = point_to_delete {
3625 s.delete(point_to_delete);
3626
3627 if selections_count == 1 {
3628 s.set_pending_anchor_range(start..end, mode);
3629 }
3630 } else {
3631 if !add {
3632 s.clear_disjoint();
3633 }
3634
3635 s.set_pending_anchor_range(start..end, mode);
3636 }
3637 });
3638 }
3639
3640 fn begin_columnar_selection(
3641 &mut self,
3642 position: DisplayPoint,
3643 goal_column: u32,
3644 reset: bool,
3645 mode: ColumnarMode,
3646 window: &mut Window,
3647 cx: &mut Context<Self>,
3648 ) {
3649 if !self.focus_handle.is_focused(window) {
3650 self.last_focused_descendant = None;
3651 window.focus(&self.focus_handle);
3652 }
3653
3654 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3655
3656 if reset {
3657 let pointer_position = display_map
3658 .buffer_snapshot()
3659 .anchor_before(position.to_point(&display_map));
3660
3661 self.change_selections(
3662 SelectionEffects::scroll(Autoscroll::newest()),
3663 window,
3664 cx,
3665 |s| {
3666 s.clear_disjoint();
3667 s.set_pending_anchor_range(
3668 pointer_position..pointer_position,
3669 SelectMode::Character,
3670 );
3671 },
3672 );
3673 };
3674
3675 let tail = self.selections.newest::<Point>(&display_map).tail();
3676 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3677 self.columnar_selection_state = match mode {
3678 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3679 selection_tail: selection_anchor,
3680 display_point: if reset {
3681 if position.column() != goal_column {
3682 Some(DisplayPoint::new(position.row(), goal_column))
3683 } else {
3684 None
3685 }
3686 } else {
3687 None
3688 },
3689 }),
3690 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3691 selection_tail: selection_anchor,
3692 }),
3693 };
3694
3695 if !reset {
3696 self.select_columns(position, goal_column, &display_map, window, cx);
3697 }
3698 }
3699
3700 fn update_selection(
3701 &mut self,
3702 position: DisplayPoint,
3703 goal_column: u32,
3704 scroll_delta: gpui::Point<f32>,
3705 window: &mut Window,
3706 cx: &mut Context<Self>,
3707 ) {
3708 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3709
3710 if self.columnar_selection_state.is_some() {
3711 self.select_columns(position, goal_column, &display_map, window, cx);
3712 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3713 let buffer = display_map.buffer_snapshot();
3714 let head;
3715 let tail;
3716 let mode = self.selections.pending_mode().unwrap();
3717 match &mode {
3718 SelectMode::Character => {
3719 head = position.to_point(&display_map);
3720 tail = pending.tail().to_point(buffer);
3721 }
3722 SelectMode::Word(original_range) => {
3723 let offset = display_map
3724 .clip_point(position, Bias::Left)
3725 .to_offset(&display_map, Bias::Left);
3726 let original_range = original_range.to_offset(buffer);
3727
3728 let head_offset = if buffer.is_inside_word(offset, None)
3729 || original_range.contains(&offset)
3730 {
3731 let (word_range, _) = buffer.surrounding_word(offset, None);
3732 if word_range.start < original_range.start {
3733 word_range.start
3734 } else {
3735 word_range.end
3736 }
3737 } else {
3738 offset
3739 };
3740
3741 head = head_offset.to_point(buffer);
3742 if head_offset <= original_range.start {
3743 tail = original_range.end.to_point(buffer);
3744 } else {
3745 tail = original_range.start.to_point(buffer);
3746 }
3747 }
3748 SelectMode::Line(original_range) => {
3749 let original_range = original_range.to_point(display_map.buffer_snapshot());
3750
3751 let position = display_map
3752 .clip_point(position, Bias::Left)
3753 .to_point(&display_map);
3754 let line_start = display_map.prev_line_boundary(position).0;
3755 let next_line_start = buffer.clip_point(
3756 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3757 Bias::Left,
3758 );
3759
3760 if line_start < original_range.start {
3761 head = line_start
3762 } else {
3763 head = next_line_start
3764 }
3765
3766 if head <= original_range.start {
3767 tail = original_range.end;
3768 } else {
3769 tail = original_range.start;
3770 }
3771 }
3772 SelectMode::All => {
3773 return;
3774 }
3775 };
3776
3777 if head < tail {
3778 pending.start = buffer.anchor_before(head);
3779 pending.end = buffer.anchor_before(tail);
3780 pending.reversed = true;
3781 } else {
3782 pending.start = buffer.anchor_before(tail);
3783 pending.end = buffer.anchor_before(head);
3784 pending.reversed = false;
3785 }
3786
3787 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3788 s.set_pending(pending.clone(), mode);
3789 });
3790 } else {
3791 log::error!("update_selection dispatched with no pending selection");
3792 return;
3793 }
3794
3795 self.apply_scroll_delta(scroll_delta, window, cx);
3796 cx.notify();
3797 }
3798
3799 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3800 self.columnar_selection_state.take();
3801 if let Some(pending_mode) = self.selections.pending_mode() {
3802 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3803 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3804 s.select(selections);
3805 s.clear_pending();
3806 if s.is_extending() {
3807 s.set_is_extending(false);
3808 } else {
3809 s.set_select_mode(pending_mode);
3810 }
3811 });
3812 }
3813 }
3814
3815 fn select_columns(
3816 &mut self,
3817 head: DisplayPoint,
3818 goal_column: u32,
3819 display_map: &DisplaySnapshot,
3820 window: &mut Window,
3821 cx: &mut Context<Self>,
3822 ) {
3823 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3824 return;
3825 };
3826
3827 let tail = match columnar_state {
3828 ColumnarSelectionState::FromMouse {
3829 selection_tail,
3830 display_point,
3831 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3832 ColumnarSelectionState::FromSelection { selection_tail } => {
3833 selection_tail.to_display_point(display_map)
3834 }
3835 };
3836
3837 let start_row = cmp::min(tail.row(), head.row());
3838 let end_row = cmp::max(tail.row(), head.row());
3839 let start_column = cmp::min(tail.column(), goal_column);
3840 let end_column = cmp::max(tail.column(), goal_column);
3841 let reversed = start_column < tail.column();
3842
3843 let selection_ranges = (start_row.0..=end_row.0)
3844 .map(DisplayRow)
3845 .filter_map(|row| {
3846 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3847 || start_column <= display_map.line_len(row))
3848 && !display_map.is_block_line(row)
3849 {
3850 let start = display_map
3851 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3852 .to_point(display_map);
3853 let end = display_map
3854 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3855 .to_point(display_map);
3856 if reversed {
3857 Some(end..start)
3858 } else {
3859 Some(start..end)
3860 }
3861 } else {
3862 None
3863 }
3864 })
3865 .collect::<Vec<_>>();
3866 if selection_ranges.is_empty() {
3867 return;
3868 }
3869
3870 let ranges = match columnar_state {
3871 ColumnarSelectionState::FromMouse { .. } => {
3872 let mut non_empty_ranges = selection_ranges
3873 .iter()
3874 .filter(|selection_range| selection_range.start != selection_range.end)
3875 .peekable();
3876 if non_empty_ranges.peek().is_some() {
3877 non_empty_ranges.cloned().collect()
3878 } else {
3879 selection_ranges
3880 }
3881 }
3882 _ => selection_ranges,
3883 };
3884
3885 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3886 s.select_ranges(ranges);
3887 });
3888 cx.notify();
3889 }
3890
3891 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3892 self.selections
3893 .all_adjusted(snapshot)
3894 .iter()
3895 .any(|selection| !selection.is_empty())
3896 }
3897
3898 pub fn has_pending_nonempty_selection(&self) -> bool {
3899 let pending_nonempty_selection = match self.selections.pending_anchor() {
3900 Some(Selection { start, end, .. }) => start != end,
3901 None => false,
3902 };
3903
3904 pending_nonempty_selection
3905 || (self.columnar_selection_state.is_some()
3906 && self.selections.disjoint_anchors().len() > 1)
3907 }
3908
3909 pub fn has_pending_selection(&self) -> bool {
3910 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3911 }
3912
3913 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3914 self.selection_mark_mode = false;
3915 self.selection_drag_state = SelectionDragState::None;
3916
3917 if self.clear_expanded_diff_hunks(cx) {
3918 cx.notify();
3919 return;
3920 }
3921 if self.dismiss_menus_and_popups(true, window, cx) {
3922 return;
3923 }
3924
3925 if self.mode.is_full()
3926 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3927 {
3928 return;
3929 }
3930
3931 cx.propagate();
3932 }
3933
3934 pub fn dismiss_menus_and_popups(
3935 &mut self,
3936 is_user_requested: bool,
3937 window: &mut Window,
3938 cx: &mut Context<Self>,
3939 ) -> bool {
3940 if self.take_rename(false, window, cx).is_some() {
3941 return true;
3942 }
3943
3944 if self.hide_blame_popover(true, cx) {
3945 return true;
3946 }
3947
3948 if hide_hover(self, cx) {
3949 return true;
3950 }
3951
3952 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3953 return true;
3954 }
3955
3956 if self.hide_context_menu(window, cx).is_some() {
3957 return true;
3958 }
3959
3960 if self.mouse_context_menu.take().is_some() {
3961 return true;
3962 }
3963
3964 if is_user_requested && self.discard_edit_prediction(true, cx) {
3965 return true;
3966 }
3967
3968 if self.snippet_stack.pop().is_some() {
3969 return true;
3970 }
3971
3972 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3973 self.dismiss_diagnostics(cx);
3974 return true;
3975 }
3976
3977 false
3978 }
3979
3980 fn linked_editing_ranges_for(
3981 &self,
3982 selection: Range<text::Anchor>,
3983 cx: &App,
3984 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3985 if self.linked_edit_ranges.is_empty() {
3986 return None;
3987 }
3988 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3989 selection.end.buffer_id.and_then(|end_buffer_id| {
3990 if selection.start.buffer_id != Some(end_buffer_id) {
3991 return None;
3992 }
3993 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3994 let snapshot = buffer.read(cx).snapshot();
3995 self.linked_edit_ranges
3996 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3997 .map(|ranges| (ranges, snapshot, buffer))
3998 })?;
3999 use text::ToOffset as TO;
4000 // find offset from the start of current range to current cursor position
4001 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4002
4003 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4004 let start_difference = start_offset - start_byte_offset;
4005 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4006 let end_difference = end_offset - start_byte_offset;
4007 // Current range has associated linked ranges.
4008 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4009 for range in linked_ranges.iter() {
4010 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4011 let end_offset = start_offset + end_difference;
4012 let start_offset = start_offset + start_difference;
4013 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4014 continue;
4015 }
4016 if self.selections.disjoint_anchor_ranges().any(|s| {
4017 if s.start.buffer_id != selection.start.buffer_id
4018 || s.end.buffer_id != selection.end.buffer_id
4019 {
4020 return false;
4021 }
4022 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4023 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4024 }) {
4025 continue;
4026 }
4027 let start = buffer_snapshot.anchor_after(start_offset);
4028 let end = buffer_snapshot.anchor_after(end_offset);
4029 linked_edits
4030 .entry(buffer.clone())
4031 .or_default()
4032 .push(start..end);
4033 }
4034 Some(linked_edits)
4035 }
4036
4037 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4038 let text: Arc<str> = text.into();
4039
4040 if self.read_only(cx) {
4041 return;
4042 }
4043
4044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4045
4046 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4047 let mut bracket_inserted = false;
4048 let mut edits = Vec::new();
4049 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4050 let mut new_selections = Vec::with_capacity(selections.len());
4051 let mut new_autoclose_regions = Vec::new();
4052 let snapshot = self.buffer.read(cx).read(cx);
4053 let mut clear_linked_edit_ranges = false;
4054
4055 for (selection, autoclose_region) in
4056 self.selections_with_autoclose_regions(selections, &snapshot)
4057 {
4058 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4059 // Determine if the inserted text matches the opening or closing
4060 // bracket of any of this language's bracket pairs.
4061 let mut bracket_pair = None;
4062 let mut is_bracket_pair_start = false;
4063 let mut is_bracket_pair_end = false;
4064 if !text.is_empty() {
4065 let mut bracket_pair_matching_end = None;
4066 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4067 // and they are removing the character that triggered IME popup.
4068 for (pair, enabled) in scope.brackets() {
4069 if !pair.close && !pair.surround {
4070 continue;
4071 }
4072
4073 if enabled && pair.start.ends_with(text.as_ref()) {
4074 let prefix_len = pair.start.len() - text.len();
4075 let preceding_text_matches_prefix = prefix_len == 0
4076 || (selection.start.column >= (prefix_len as u32)
4077 && snapshot.contains_str_at(
4078 Point::new(
4079 selection.start.row,
4080 selection.start.column - (prefix_len as u32),
4081 ),
4082 &pair.start[..prefix_len],
4083 ));
4084 if preceding_text_matches_prefix {
4085 bracket_pair = Some(pair.clone());
4086 is_bracket_pair_start = true;
4087 break;
4088 }
4089 }
4090 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4091 {
4092 // take first bracket pair matching end, but don't break in case a later bracket
4093 // pair matches start
4094 bracket_pair_matching_end = Some(pair.clone());
4095 }
4096 }
4097 if let Some(end) = bracket_pair_matching_end
4098 && bracket_pair.is_none()
4099 {
4100 bracket_pair = Some(end);
4101 is_bracket_pair_end = true;
4102 }
4103 }
4104
4105 if let Some(bracket_pair) = bracket_pair {
4106 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4107 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4108 let auto_surround =
4109 self.use_auto_surround && snapshot_settings.use_auto_surround;
4110 if selection.is_empty() {
4111 if is_bracket_pair_start {
4112 // If the inserted text is a suffix of an opening bracket and the
4113 // selection is preceded by the rest of the opening bracket, then
4114 // insert the closing bracket.
4115 let following_text_allows_autoclose = snapshot
4116 .chars_at(selection.start)
4117 .next()
4118 .is_none_or(|c| scope.should_autoclose_before(c));
4119
4120 let preceding_text_allows_autoclose = selection.start.column == 0
4121 || snapshot
4122 .reversed_chars_at(selection.start)
4123 .next()
4124 .is_none_or(|c| {
4125 bracket_pair.start != bracket_pair.end
4126 || !snapshot
4127 .char_classifier_at(selection.start)
4128 .is_word(c)
4129 });
4130
4131 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4132 && bracket_pair.start.len() == 1
4133 {
4134 let target = bracket_pair.start.chars().next().unwrap();
4135 let current_line_count = snapshot
4136 .reversed_chars_at(selection.start)
4137 .take_while(|&c| c != '\n')
4138 .filter(|&c| c == target)
4139 .count();
4140 current_line_count % 2 == 1
4141 } else {
4142 false
4143 };
4144
4145 if autoclose
4146 && bracket_pair.close
4147 && following_text_allows_autoclose
4148 && preceding_text_allows_autoclose
4149 && !is_closing_quote
4150 {
4151 let anchor = snapshot.anchor_before(selection.end);
4152 new_selections.push((selection.map(|_| anchor), text.len()));
4153 new_autoclose_regions.push((
4154 anchor,
4155 text.len(),
4156 selection.id,
4157 bracket_pair.clone(),
4158 ));
4159 edits.push((
4160 selection.range(),
4161 format!("{}{}", text, bracket_pair.end).into(),
4162 ));
4163 bracket_inserted = true;
4164 continue;
4165 }
4166 }
4167
4168 if let Some(region) = autoclose_region {
4169 // If the selection is followed by an auto-inserted closing bracket,
4170 // then don't insert that closing bracket again; just move the selection
4171 // past the closing bracket.
4172 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4173 && text.as_ref() == region.pair.end.as_str()
4174 && snapshot.contains_str_at(region.range.end, text.as_ref());
4175 if should_skip {
4176 let anchor = snapshot.anchor_after(selection.end);
4177 new_selections
4178 .push((selection.map(|_| anchor), region.pair.end.len()));
4179 continue;
4180 }
4181 }
4182
4183 let always_treat_brackets_as_autoclosed = snapshot
4184 .language_settings_at(selection.start, cx)
4185 .always_treat_brackets_as_autoclosed;
4186 if always_treat_brackets_as_autoclosed
4187 && is_bracket_pair_end
4188 && snapshot.contains_str_at(selection.end, text.as_ref())
4189 {
4190 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4191 // and the inserted text is a closing bracket and the selection is followed
4192 // by the closing bracket then move the selection past the closing bracket.
4193 let anchor = snapshot.anchor_after(selection.end);
4194 new_selections.push((selection.map(|_| anchor), text.len()));
4195 continue;
4196 }
4197 }
4198 // If an opening bracket is 1 character long and is typed while
4199 // text is selected, then surround that text with the bracket pair.
4200 else if auto_surround
4201 && bracket_pair.surround
4202 && is_bracket_pair_start
4203 && bracket_pair.start.chars().count() == 1
4204 {
4205 edits.push((selection.start..selection.start, text.clone()));
4206 edits.push((
4207 selection.end..selection.end,
4208 bracket_pair.end.as_str().into(),
4209 ));
4210 bracket_inserted = true;
4211 new_selections.push((
4212 Selection {
4213 id: selection.id,
4214 start: snapshot.anchor_after(selection.start),
4215 end: snapshot.anchor_before(selection.end),
4216 reversed: selection.reversed,
4217 goal: selection.goal,
4218 },
4219 0,
4220 ));
4221 continue;
4222 }
4223 }
4224 }
4225
4226 if self.auto_replace_emoji_shortcode
4227 && selection.is_empty()
4228 && text.as_ref().ends_with(':')
4229 && let Some(possible_emoji_short_code) =
4230 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4231 && !possible_emoji_short_code.is_empty()
4232 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4233 {
4234 let emoji_shortcode_start = Point::new(
4235 selection.start.row,
4236 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4237 );
4238
4239 // Remove shortcode from buffer
4240 edits.push((
4241 emoji_shortcode_start..selection.start,
4242 "".to_string().into(),
4243 ));
4244 new_selections.push((
4245 Selection {
4246 id: selection.id,
4247 start: snapshot.anchor_after(emoji_shortcode_start),
4248 end: snapshot.anchor_before(selection.start),
4249 reversed: selection.reversed,
4250 goal: selection.goal,
4251 },
4252 0,
4253 ));
4254
4255 // Insert emoji
4256 let selection_start_anchor = snapshot.anchor_after(selection.start);
4257 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4258 edits.push((selection.start..selection.end, emoji.to_string().into()));
4259
4260 continue;
4261 }
4262
4263 // If not handling any auto-close operation, then just replace the selected
4264 // text with the given input and move the selection to the end of the
4265 // newly inserted text.
4266 let anchor = snapshot.anchor_after(selection.end);
4267 if !self.linked_edit_ranges.is_empty() {
4268 let start_anchor = snapshot.anchor_before(selection.start);
4269
4270 let is_word_char = text.chars().next().is_none_or(|char| {
4271 let classifier = snapshot
4272 .char_classifier_at(start_anchor.to_offset(&snapshot))
4273 .scope_context(Some(CharScopeContext::LinkedEdit));
4274 classifier.is_word(char)
4275 });
4276
4277 if is_word_char {
4278 if let Some(ranges) = self
4279 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4280 {
4281 for (buffer, edits) in ranges {
4282 linked_edits
4283 .entry(buffer.clone())
4284 .or_default()
4285 .extend(edits.into_iter().map(|range| (range, text.clone())));
4286 }
4287 }
4288 } else {
4289 clear_linked_edit_ranges = true;
4290 }
4291 }
4292
4293 new_selections.push((selection.map(|_| anchor), 0));
4294 edits.push((selection.start..selection.end, text.clone()));
4295 }
4296
4297 drop(snapshot);
4298
4299 self.transact(window, cx, |this, window, cx| {
4300 if clear_linked_edit_ranges {
4301 this.linked_edit_ranges.clear();
4302 }
4303 let initial_buffer_versions =
4304 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4305
4306 this.buffer.update(cx, |buffer, cx| {
4307 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4308 });
4309 for (buffer, edits) in linked_edits {
4310 buffer.update(cx, |buffer, cx| {
4311 let snapshot = buffer.snapshot();
4312 let edits = edits
4313 .into_iter()
4314 .map(|(range, text)| {
4315 use text::ToPoint as TP;
4316 let end_point = TP::to_point(&range.end, &snapshot);
4317 let start_point = TP::to_point(&range.start, &snapshot);
4318 (start_point..end_point, text)
4319 })
4320 .sorted_by_key(|(range, _)| range.start);
4321 buffer.edit(edits, None, cx);
4322 })
4323 }
4324 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4325 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4326 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4327 let new_selections =
4328 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4329 .zip(new_selection_deltas)
4330 .map(|(selection, delta)| Selection {
4331 id: selection.id,
4332 start: selection.start + delta,
4333 end: selection.end + delta,
4334 reversed: selection.reversed,
4335 goal: SelectionGoal::None,
4336 })
4337 .collect::<Vec<_>>();
4338
4339 let mut i = 0;
4340 for (position, delta, selection_id, pair) in new_autoclose_regions {
4341 let position = position.to_offset(map.buffer_snapshot()) + delta;
4342 let start = map.buffer_snapshot().anchor_before(position);
4343 let end = map.buffer_snapshot().anchor_after(position);
4344 while let Some(existing_state) = this.autoclose_regions.get(i) {
4345 match existing_state
4346 .range
4347 .start
4348 .cmp(&start, map.buffer_snapshot())
4349 {
4350 Ordering::Less => i += 1,
4351 Ordering::Greater => break,
4352 Ordering::Equal => {
4353 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4354 Ordering::Less => i += 1,
4355 Ordering::Equal => break,
4356 Ordering::Greater => break,
4357 }
4358 }
4359 }
4360 }
4361 this.autoclose_regions.insert(
4362 i,
4363 AutocloseRegion {
4364 selection_id,
4365 range: start..end,
4366 pair,
4367 },
4368 );
4369 }
4370
4371 let had_active_edit_prediction = this.has_active_edit_prediction();
4372 this.change_selections(
4373 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4374 window,
4375 cx,
4376 |s| s.select(new_selections),
4377 );
4378
4379 if !bracket_inserted
4380 && let Some(on_type_format_task) =
4381 this.trigger_on_type_formatting(text.to_string(), window, cx)
4382 {
4383 on_type_format_task.detach_and_log_err(cx);
4384 }
4385
4386 let editor_settings = EditorSettings::get_global(cx);
4387 if bracket_inserted
4388 && (editor_settings.auto_signature_help
4389 || editor_settings.show_signature_help_after_edits)
4390 {
4391 this.show_signature_help(&ShowSignatureHelp, window, cx);
4392 }
4393
4394 let trigger_in_words =
4395 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4396 if this.hard_wrap.is_some() {
4397 let latest: Range<Point> = this.selections.newest(&map).range();
4398 if latest.is_empty()
4399 && this
4400 .buffer()
4401 .read(cx)
4402 .snapshot(cx)
4403 .line_len(MultiBufferRow(latest.start.row))
4404 == latest.start.column
4405 {
4406 this.rewrap_impl(
4407 RewrapOptions {
4408 override_language_settings: true,
4409 preserve_existing_whitespace: true,
4410 },
4411 cx,
4412 )
4413 }
4414 }
4415 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4416 refresh_linked_ranges(this, window, cx);
4417 this.refresh_edit_prediction(true, false, window, cx);
4418 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4419 });
4420 }
4421
4422 fn find_possible_emoji_shortcode_at_position(
4423 snapshot: &MultiBufferSnapshot,
4424 position: Point,
4425 ) -> Option<String> {
4426 let mut chars = Vec::new();
4427 let mut found_colon = false;
4428 for char in snapshot.reversed_chars_at(position).take(100) {
4429 // Found a possible emoji shortcode in the middle of the buffer
4430 if found_colon {
4431 if char.is_whitespace() {
4432 chars.reverse();
4433 return Some(chars.iter().collect());
4434 }
4435 // If the previous character is not a whitespace, we are in the middle of a word
4436 // and we only want to complete the shortcode if the word is made up of other emojis
4437 let mut containing_word = String::new();
4438 for ch in snapshot
4439 .reversed_chars_at(position)
4440 .skip(chars.len() + 1)
4441 .take(100)
4442 {
4443 if ch.is_whitespace() {
4444 break;
4445 }
4446 containing_word.push(ch);
4447 }
4448 let containing_word = containing_word.chars().rev().collect::<String>();
4449 if util::word_consists_of_emojis(containing_word.as_str()) {
4450 chars.reverse();
4451 return Some(chars.iter().collect());
4452 }
4453 }
4454
4455 if char.is_whitespace() || !char.is_ascii() {
4456 return None;
4457 }
4458 if char == ':' {
4459 found_colon = true;
4460 } else {
4461 chars.push(char);
4462 }
4463 }
4464 // Found a possible emoji shortcode at the beginning of the buffer
4465 chars.reverse();
4466 Some(chars.iter().collect())
4467 }
4468
4469 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4470 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4471 self.transact(window, cx, |this, window, cx| {
4472 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4473 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4474 let multi_buffer = this.buffer.read(cx);
4475 let buffer = multi_buffer.snapshot(cx);
4476 selections
4477 .iter()
4478 .map(|selection| {
4479 let start_point = selection.start.to_point(&buffer);
4480 let mut existing_indent =
4481 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4482 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4483 let start = selection.start;
4484 let end = selection.end;
4485 let selection_is_empty = start == end;
4486 let language_scope = buffer.language_scope_at(start);
4487 let (
4488 comment_delimiter,
4489 doc_delimiter,
4490 insert_extra_newline,
4491 indent_on_newline,
4492 indent_on_extra_newline,
4493 ) = if let Some(language) = &language_scope {
4494 let mut insert_extra_newline =
4495 insert_extra_newline_brackets(&buffer, start..end, language)
4496 || insert_extra_newline_tree_sitter(&buffer, start..end);
4497
4498 // Comment extension on newline is allowed only for cursor selections
4499 let comment_delimiter = maybe!({
4500 if !selection_is_empty {
4501 return None;
4502 }
4503
4504 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4505 return None;
4506 }
4507
4508 let delimiters = language.line_comment_prefixes();
4509 let max_len_of_delimiter =
4510 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4511 let (snapshot, range) =
4512 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4513
4514 let num_of_whitespaces = snapshot
4515 .chars_for_range(range.clone())
4516 .take_while(|c| c.is_whitespace())
4517 .count();
4518 let comment_candidate = snapshot
4519 .chars_for_range(range.clone())
4520 .skip(num_of_whitespaces)
4521 .take(max_len_of_delimiter)
4522 .collect::<String>();
4523 let (delimiter, trimmed_len) = delimiters
4524 .iter()
4525 .filter_map(|delimiter| {
4526 let prefix = delimiter.trim_end();
4527 if comment_candidate.starts_with(prefix) {
4528 Some((delimiter, prefix.len()))
4529 } else {
4530 None
4531 }
4532 })
4533 .max_by_key(|(_, len)| *len)?;
4534
4535 if let Some(BlockCommentConfig {
4536 start: block_start, ..
4537 }) = language.block_comment()
4538 {
4539 let block_start_trimmed = block_start.trim_end();
4540 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4541 let line_content = snapshot
4542 .chars_for_range(range)
4543 .skip(num_of_whitespaces)
4544 .take(block_start_trimmed.len())
4545 .collect::<String>();
4546
4547 if line_content.starts_with(block_start_trimmed) {
4548 return None;
4549 }
4550 }
4551 }
4552
4553 let cursor_is_placed_after_comment_marker =
4554 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4555 if cursor_is_placed_after_comment_marker {
4556 Some(delimiter.clone())
4557 } else {
4558 None
4559 }
4560 });
4561
4562 let mut indent_on_newline = IndentSize::spaces(0);
4563 let mut indent_on_extra_newline = IndentSize::spaces(0);
4564
4565 let doc_delimiter = maybe!({
4566 if !selection_is_empty {
4567 return None;
4568 }
4569
4570 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4571 return None;
4572 }
4573
4574 let BlockCommentConfig {
4575 start: start_tag,
4576 end: end_tag,
4577 prefix: delimiter,
4578 tab_size: len,
4579 } = language.documentation_comment()?;
4580 let is_within_block_comment = buffer
4581 .language_scope_at(start_point)
4582 .is_some_and(|scope| scope.override_name() == Some("comment"));
4583 if !is_within_block_comment {
4584 return None;
4585 }
4586
4587 let (snapshot, range) =
4588 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4589
4590 let num_of_whitespaces = snapshot
4591 .chars_for_range(range.clone())
4592 .take_while(|c| c.is_whitespace())
4593 .count();
4594
4595 // 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.
4596 let column = start_point.column;
4597 let cursor_is_after_start_tag = {
4598 let start_tag_len = start_tag.len();
4599 let start_tag_line = snapshot
4600 .chars_for_range(range.clone())
4601 .skip(num_of_whitespaces)
4602 .take(start_tag_len)
4603 .collect::<String>();
4604 if start_tag_line.starts_with(start_tag.as_ref()) {
4605 num_of_whitespaces + start_tag_len <= column as usize
4606 } else {
4607 false
4608 }
4609 };
4610
4611 let cursor_is_after_delimiter = {
4612 let delimiter_trim = delimiter.trim_end();
4613 let delimiter_line = snapshot
4614 .chars_for_range(range.clone())
4615 .skip(num_of_whitespaces)
4616 .take(delimiter_trim.len())
4617 .collect::<String>();
4618 if delimiter_line.starts_with(delimiter_trim) {
4619 num_of_whitespaces + delimiter_trim.len() <= column as usize
4620 } else {
4621 false
4622 }
4623 };
4624
4625 let cursor_is_before_end_tag_if_exists = {
4626 let mut char_position = 0u32;
4627 let mut end_tag_offset = None;
4628
4629 'outer: for chunk in snapshot.text_for_range(range) {
4630 if let Some(byte_pos) = chunk.find(&**end_tag) {
4631 let chars_before_match =
4632 chunk[..byte_pos].chars().count() as u32;
4633 end_tag_offset =
4634 Some(char_position + chars_before_match);
4635 break 'outer;
4636 }
4637 char_position += chunk.chars().count() as u32;
4638 }
4639
4640 if let Some(end_tag_offset) = end_tag_offset {
4641 let cursor_is_before_end_tag = column <= end_tag_offset;
4642 if cursor_is_after_start_tag {
4643 if cursor_is_before_end_tag {
4644 insert_extra_newline = true;
4645 }
4646 let cursor_is_at_start_of_end_tag =
4647 column == end_tag_offset;
4648 if cursor_is_at_start_of_end_tag {
4649 indent_on_extra_newline.len = *len;
4650 }
4651 }
4652 cursor_is_before_end_tag
4653 } else {
4654 true
4655 }
4656 };
4657
4658 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4659 && cursor_is_before_end_tag_if_exists
4660 {
4661 if cursor_is_after_start_tag {
4662 indent_on_newline.len = *len;
4663 }
4664 Some(delimiter.clone())
4665 } else {
4666 None
4667 }
4668 });
4669
4670 (
4671 comment_delimiter,
4672 doc_delimiter,
4673 insert_extra_newline,
4674 indent_on_newline,
4675 indent_on_extra_newline,
4676 )
4677 } else {
4678 (
4679 None,
4680 None,
4681 false,
4682 IndentSize::default(),
4683 IndentSize::default(),
4684 )
4685 };
4686
4687 let prevent_auto_indent = doc_delimiter.is_some();
4688 let delimiter = comment_delimiter.or(doc_delimiter);
4689
4690 let capacity_for_delimiter =
4691 delimiter.as_deref().map(str::len).unwrap_or_default();
4692 let mut new_text = String::with_capacity(
4693 1 + capacity_for_delimiter
4694 + existing_indent.len as usize
4695 + indent_on_newline.len as usize
4696 + indent_on_extra_newline.len as usize,
4697 );
4698 new_text.push('\n');
4699 new_text.extend(existing_indent.chars());
4700 new_text.extend(indent_on_newline.chars());
4701
4702 if let Some(delimiter) = &delimiter {
4703 new_text.push_str(delimiter);
4704 }
4705
4706 if insert_extra_newline {
4707 new_text.push('\n');
4708 new_text.extend(existing_indent.chars());
4709 new_text.extend(indent_on_extra_newline.chars());
4710 }
4711
4712 let anchor = buffer.anchor_after(end);
4713 let new_selection = selection.map(|_| anchor);
4714 (
4715 ((start..end, new_text), prevent_auto_indent),
4716 (insert_extra_newline, new_selection),
4717 )
4718 })
4719 .unzip()
4720 };
4721
4722 let mut auto_indent_edits = Vec::new();
4723 let mut edits = Vec::new();
4724 for (edit, prevent_auto_indent) in edits_with_flags {
4725 if prevent_auto_indent {
4726 edits.push(edit);
4727 } else {
4728 auto_indent_edits.push(edit);
4729 }
4730 }
4731 if !edits.is_empty() {
4732 this.edit(edits, cx);
4733 }
4734 if !auto_indent_edits.is_empty() {
4735 this.edit_with_autoindent(auto_indent_edits, cx);
4736 }
4737
4738 let buffer = this.buffer.read(cx).snapshot(cx);
4739 let new_selections = selection_info
4740 .into_iter()
4741 .map(|(extra_newline_inserted, new_selection)| {
4742 let mut cursor = new_selection.end.to_point(&buffer);
4743 if extra_newline_inserted {
4744 cursor.row -= 1;
4745 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4746 }
4747 new_selection.map(|_| cursor)
4748 })
4749 .collect();
4750
4751 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4752 this.refresh_edit_prediction(true, false, window, cx);
4753 });
4754 }
4755
4756 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4757 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4758
4759 let buffer = self.buffer.read(cx);
4760 let snapshot = buffer.snapshot(cx);
4761
4762 let mut edits = Vec::new();
4763 let mut rows = Vec::new();
4764
4765 for (rows_inserted, selection) in self
4766 .selections
4767 .all_adjusted(&self.display_snapshot(cx))
4768 .into_iter()
4769 .enumerate()
4770 {
4771 let cursor = selection.head();
4772 let row = cursor.row;
4773
4774 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4775
4776 let newline = "\n".to_string();
4777 edits.push((start_of_line..start_of_line, newline));
4778
4779 rows.push(row + rows_inserted as u32);
4780 }
4781
4782 self.transact(window, cx, |editor, window, cx| {
4783 editor.edit(edits, cx);
4784
4785 editor.change_selections(Default::default(), window, cx, |s| {
4786 let mut index = 0;
4787 s.move_cursors_with(|map, _, _| {
4788 let row = rows[index];
4789 index += 1;
4790
4791 let point = Point::new(row, 0);
4792 let boundary = map.next_line_boundary(point).1;
4793 let clipped = map.clip_point(boundary, Bias::Left);
4794
4795 (clipped, SelectionGoal::None)
4796 });
4797 });
4798
4799 let mut indent_edits = Vec::new();
4800 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4801 for row in rows {
4802 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4803 for (row, indent) in indents {
4804 if indent.len == 0 {
4805 continue;
4806 }
4807
4808 let text = match indent.kind {
4809 IndentKind::Space => " ".repeat(indent.len as usize),
4810 IndentKind::Tab => "\t".repeat(indent.len as usize),
4811 };
4812 let point = Point::new(row.0, 0);
4813 indent_edits.push((point..point, text));
4814 }
4815 }
4816 editor.edit(indent_edits, cx);
4817 });
4818 }
4819
4820 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4821 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4822
4823 let buffer = self.buffer.read(cx);
4824 let snapshot = buffer.snapshot(cx);
4825
4826 let mut edits = Vec::new();
4827 let mut rows = Vec::new();
4828 let mut rows_inserted = 0;
4829
4830 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4831 let cursor = selection.head();
4832 let row = cursor.row;
4833
4834 let point = Point::new(row + 1, 0);
4835 let start_of_line = snapshot.clip_point(point, Bias::Left);
4836
4837 let newline = "\n".to_string();
4838 edits.push((start_of_line..start_of_line, newline));
4839
4840 rows_inserted += 1;
4841 rows.push(row + rows_inserted);
4842 }
4843
4844 self.transact(window, cx, |editor, window, cx| {
4845 editor.edit(edits, cx);
4846
4847 editor.change_selections(Default::default(), window, cx, |s| {
4848 let mut index = 0;
4849 s.move_cursors_with(|map, _, _| {
4850 let row = rows[index];
4851 index += 1;
4852
4853 let point = Point::new(row, 0);
4854 let boundary = map.next_line_boundary(point).1;
4855 let clipped = map.clip_point(boundary, Bias::Left);
4856
4857 (clipped, SelectionGoal::None)
4858 });
4859 });
4860
4861 let mut indent_edits = Vec::new();
4862 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4863 for row in rows {
4864 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4865 for (row, indent) in indents {
4866 if indent.len == 0 {
4867 continue;
4868 }
4869
4870 let text = match indent.kind {
4871 IndentKind::Space => " ".repeat(indent.len as usize),
4872 IndentKind::Tab => "\t".repeat(indent.len as usize),
4873 };
4874 let point = Point::new(row.0, 0);
4875 indent_edits.push((point..point, text));
4876 }
4877 }
4878 editor.edit(indent_edits, cx);
4879 });
4880 }
4881
4882 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4883 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4884 original_indent_columns: Vec::new(),
4885 });
4886 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4887 }
4888
4889 fn insert_with_autoindent_mode(
4890 &mut self,
4891 text: &str,
4892 autoindent_mode: Option<AutoindentMode>,
4893 window: &mut Window,
4894 cx: &mut Context<Self>,
4895 ) {
4896 if self.read_only(cx) {
4897 return;
4898 }
4899
4900 let text: Arc<str> = text.into();
4901 self.transact(window, cx, |this, window, cx| {
4902 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4903 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4904 let anchors = {
4905 let snapshot = buffer.read(cx);
4906 old_selections
4907 .iter()
4908 .map(|s| {
4909 let anchor = snapshot.anchor_after(s.head());
4910 s.map(|_| anchor)
4911 })
4912 .collect::<Vec<_>>()
4913 };
4914 buffer.edit(
4915 old_selections
4916 .iter()
4917 .map(|s| (s.start..s.end, text.clone())),
4918 autoindent_mode,
4919 cx,
4920 );
4921 anchors
4922 });
4923
4924 this.change_selections(Default::default(), window, cx, |s| {
4925 s.select_anchors(selection_anchors);
4926 });
4927
4928 cx.notify();
4929 });
4930 }
4931
4932 fn trigger_completion_on_input(
4933 &mut self,
4934 text: &str,
4935 trigger_in_words: bool,
4936 window: &mut Window,
4937 cx: &mut Context<Self>,
4938 ) {
4939 let completions_source = self
4940 .context_menu
4941 .borrow()
4942 .as_ref()
4943 .and_then(|menu| match menu {
4944 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4945 CodeContextMenu::CodeActions(_) => None,
4946 });
4947
4948 match completions_source {
4949 Some(CompletionsMenuSource::Words { .. }) => {
4950 self.open_or_update_completions_menu(
4951 Some(CompletionsMenuSource::Words {
4952 ignore_threshold: false,
4953 }),
4954 None,
4955 window,
4956 cx,
4957 );
4958 }
4959 Some(CompletionsMenuSource::Normal)
4960 | Some(CompletionsMenuSource::SnippetChoices)
4961 | None
4962 if self.is_completion_trigger(
4963 text,
4964 trigger_in_words,
4965 completions_source.is_some(),
4966 cx,
4967 ) =>
4968 {
4969 self.show_completions(
4970 &ShowCompletions {
4971 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4972 },
4973 window,
4974 cx,
4975 )
4976 }
4977 _ => {
4978 self.hide_context_menu(window, cx);
4979 }
4980 }
4981 }
4982
4983 fn is_completion_trigger(
4984 &self,
4985 text: &str,
4986 trigger_in_words: bool,
4987 menu_is_open: bool,
4988 cx: &mut Context<Self>,
4989 ) -> bool {
4990 let position = self.selections.newest_anchor().head();
4991 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4992 return false;
4993 };
4994
4995 if let Some(completion_provider) = &self.completion_provider {
4996 completion_provider.is_completion_trigger(
4997 &buffer,
4998 position.text_anchor,
4999 text,
5000 trigger_in_words,
5001 menu_is_open,
5002 cx,
5003 )
5004 } else {
5005 false
5006 }
5007 }
5008
5009 /// If any empty selections is touching the start of its innermost containing autoclose
5010 /// region, expand it to select the brackets.
5011 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5012 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5013 let buffer = self.buffer.read(cx).read(cx);
5014 let new_selections = self
5015 .selections_with_autoclose_regions(selections, &buffer)
5016 .map(|(mut selection, region)| {
5017 if !selection.is_empty() {
5018 return selection;
5019 }
5020
5021 if let Some(region) = region {
5022 let mut range = region.range.to_offset(&buffer);
5023 if selection.start == range.start && range.start >= region.pair.start.len() {
5024 range.start -= region.pair.start.len();
5025 if buffer.contains_str_at(range.start, ®ion.pair.start)
5026 && buffer.contains_str_at(range.end, ®ion.pair.end)
5027 {
5028 range.end += region.pair.end.len();
5029 selection.start = range.start;
5030 selection.end = range.end;
5031
5032 return selection;
5033 }
5034 }
5035 }
5036
5037 let always_treat_brackets_as_autoclosed = buffer
5038 .language_settings_at(selection.start, cx)
5039 .always_treat_brackets_as_autoclosed;
5040
5041 if !always_treat_brackets_as_autoclosed {
5042 return selection;
5043 }
5044
5045 if let Some(scope) = buffer.language_scope_at(selection.start) {
5046 for (pair, enabled) in scope.brackets() {
5047 if !enabled || !pair.close {
5048 continue;
5049 }
5050
5051 if buffer.contains_str_at(selection.start, &pair.end) {
5052 let pair_start_len = pair.start.len();
5053 if buffer.contains_str_at(
5054 selection.start.saturating_sub(pair_start_len),
5055 &pair.start,
5056 ) {
5057 selection.start -= pair_start_len;
5058 selection.end += pair.end.len();
5059
5060 return selection;
5061 }
5062 }
5063 }
5064 }
5065
5066 selection
5067 })
5068 .collect();
5069
5070 drop(buffer);
5071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5072 selections.select(new_selections)
5073 });
5074 }
5075
5076 /// Iterate the given selections, and for each one, find the smallest surrounding
5077 /// autoclose region. This uses the ordering of the selections and the autoclose
5078 /// regions to avoid repeated comparisons.
5079 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5080 &'a self,
5081 selections: impl IntoIterator<Item = Selection<D>>,
5082 buffer: &'a MultiBufferSnapshot,
5083 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5084 let mut i = 0;
5085 let mut regions = self.autoclose_regions.as_slice();
5086 selections.into_iter().map(move |selection| {
5087 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5088
5089 let mut enclosing = None;
5090 while let Some(pair_state) = regions.get(i) {
5091 if pair_state.range.end.to_offset(buffer) < range.start {
5092 regions = ®ions[i + 1..];
5093 i = 0;
5094 } else if pair_state.range.start.to_offset(buffer) > range.end {
5095 break;
5096 } else {
5097 if pair_state.selection_id == selection.id {
5098 enclosing = Some(pair_state);
5099 }
5100 i += 1;
5101 }
5102 }
5103
5104 (selection, enclosing)
5105 })
5106 }
5107
5108 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5109 fn invalidate_autoclose_regions(
5110 &mut self,
5111 mut selections: &[Selection<Anchor>],
5112 buffer: &MultiBufferSnapshot,
5113 ) {
5114 self.autoclose_regions.retain(|state| {
5115 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5116 return false;
5117 }
5118
5119 let mut i = 0;
5120 while let Some(selection) = selections.get(i) {
5121 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5122 selections = &selections[1..];
5123 continue;
5124 }
5125 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5126 break;
5127 }
5128 if selection.id == state.selection_id {
5129 return true;
5130 } else {
5131 i += 1;
5132 }
5133 }
5134 false
5135 });
5136 }
5137
5138 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5139 let offset = position.to_offset(buffer);
5140 let (word_range, kind) =
5141 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5142 if offset > word_range.start && kind == Some(CharKind::Word) {
5143 Some(
5144 buffer
5145 .text_for_range(word_range.start..offset)
5146 .collect::<String>(),
5147 )
5148 } else {
5149 None
5150 }
5151 }
5152
5153 pub fn visible_excerpts(
5154 &self,
5155 cx: &mut Context<Editor>,
5156 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5157 let Some(project) = self.project() else {
5158 return HashMap::default();
5159 };
5160 let project = project.read(cx);
5161 let multi_buffer = self.buffer().read(cx);
5162 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5163 let multi_buffer_visible_start = self
5164 .scroll_manager
5165 .anchor()
5166 .anchor
5167 .to_point(&multi_buffer_snapshot);
5168 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5169 multi_buffer_visible_start
5170 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5171 Bias::Left,
5172 );
5173 multi_buffer_snapshot
5174 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5175 .into_iter()
5176 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5177 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5178 let buffer_file = project::File::from_dyn(buffer.file())?;
5179 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5180 let worktree_entry = buffer_worktree
5181 .read(cx)
5182 .entry_for_id(buffer_file.project_entry_id()?)?;
5183 if worktree_entry.is_ignored {
5184 None
5185 } else {
5186 Some((
5187 excerpt_id,
5188 (
5189 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5190 buffer.version().clone(),
5191 excerpt_visible_range,
5192 ),
5193 ))
5194 }
5195 })
5196 .collect()
5197 }
5198
5199 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5200 TextLayoutDetails {
5201 text_system: window.text_system().clone(),
5202 editor_style: self.style.clone().unwrap(),
5203 rem_size: window.rem_size(),
5204 scroll_anchor: self.scroll_manager.anchor(),
5205 visible_rows: self.visible_line_count(),
5206 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5207 }
5208 }
5209
5210 fn trigger_on_type_formatting(
5211 &self,
5212 input: String,
5213 window: &mut Window,
5214 cx: &mut Context<Self>,
5215 ) -> Option<Task<Result<()>>> {
5216 if input.len() != 1 {
5217 return None;
5218 }
5219
5220 let project = self.project()?;
5221 let position = self.selections.newest_anchor().head();
5222 let (buffer, buffer_position) = self
5223 .buffer
5224 .read(cx)
5225 .text_anchor_for_position(position, cx)?;
5226
5227 let settings = language_settings::language_settings(
5228 buffer
5229 .read(cx)
5230 .language_at(buffer_position)
5231 .map(|l| l.name()),
5232 buffer.read(cx).file(),
5233 cx,
5234 );
5235 if !settings.use_on_type_format {
5236 return None;
5237 }
5238
5239 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5240 // hence we do LSP request & edit on host side only — add formats to host's history.
5241 let push_to_lsp_host_history = true;
5242 // If this is not the host, append its history with new edits.
5243 let push_to_client_history = project.read(cx).is_via_collab();
5244
5245 let on_type_formatting = project.update(cx, |project, cx| {
5246 project.on_type_format(
5247 buffer.clone(),
5248 buffer_position,
5249 input,
5250 push_to_lsp_host_history,
5251 cx,
5252 )
5253 });
5254 Some(cx.spawn_in(window, async move |editor, cx| {
5255 if let Some(transaction) = on_type_formatting.await? {
5256 if push_to_client_history {
5257 buffer
5258 .update(cx, |buffer, _| {
5259 buffer.push_transaction(transaction, Instant::now());
5260 buffer.finalize_last_transaction();
5261 })
5262 .ok();
5263 }
5264 editor.update(cx, |editor, cx| {
5265 editor.refresh_document_highlights(cx);
5266 })?;
5267 }
5268 Ok(())
5269 }))
5270 }
5271
5272 pub fn show_word_completions(
5273 &mut self,
5274 _: &ShowWordCompletions,
5275 window: &mut Window,
5276 cx: &mut Context<Self>,
5277 ) {
5278 self.open_or_update_completions_menu(
5279 Some(CompletionsMenuSource::Words {
5280 ignore_threshold: true,
5281 }),
5282 None,
5283 window,
5284 cx,
5285 );
5286 }
5287
5288 pub fn show_completions(
5289 &mut self,
5290 options: &ShowCompletions,
5291 window: &mut Window,
5292 cx: &mut Context<Self>,
5293 ) {
5294 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5295 }
5296
5297 fn open_or_update_completions_menu(
5298 &mut self,
5299 requested_source: Option<CompletionsMenuSource>,
5300 trigger: Option<&str>,
5301 window: &mut Window,
5302 cx: &mut Context<Self>,
5303 ) {
5304 if self.pending_rename.is_some() {
5305 return;
5306 }
5307
5308 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5309
5310 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5311 // inserted and selected. To handle that case, the start of the selection is used so that
5312 // the menu starts with all choices.
5313 let position = self
5314 .selections
5315 .newest_anchor()
5316 .start
5317 .bias_right(&multibuffer_snapshot);
5318 if position.diff_base_anchor.is_some() {
5319 return;
5320 }
5321 let buffer_position = multibuffer_snapshot.anchor_before(position);
5322 let Some(buffer) = buffer_position
5323 .buffer_id
5324 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5325 else {
5326 return;
5327 };
5328 let buffer_snapshot = buffer.read(cx).snapshot();
5329
5330 let query: Option<Arc<String>> =
5331 Self::completion_query(&multibuffer_snapshot, buffer_position)
5332 .map(|query| query.into());
5333
5334 drop(multibuffer_snapshot);
5335
5336 // Hide the current completions menu when query is empty. Without this, cached
5337 // completions from before the trigger char may be reused (#32774).
5338 if query.is_none() {
5339 let menu_is_open = matches!(
5340 self.context_menu.borrow().as_ref(),
5341 Some(CodeContextMenu::Completions(_))
5342 );
5343 if menu_is_open {
5344 self.hide_context_menu(window, cx);
5345 }
5346 }
5347
5348 let mut ignore_word_threshold = false;
5349 let provider = match requested_source {
5350 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5351 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5352 ignore_word_threshold = ignore_threshold;
5353 None
5354 }
5355 Some(CompletionsMenuSource::SnippetChoices) => {
5356 log::error!("bug: SnippetChoices requested_source is not handled");
5357 None
5358 }
5359 };
5360
5361 let sort_completions = provider
5362 .as_ref()
5363 .is_some_and(|provider| provider.sort_completions());
5364
5365 let filter_completions = provider
5366 .as_ref()
5367 .is_none_or(|provider| provider.filter_completions());
5368
5369 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5370 if filter_completions {
5371 menu.filter(query.clone(), provider.clone(), window, cx);
5372 }
5373 // When `is_incomplete` is false, no need to re-query completions when the current query
5374 // is a suffix of the initial query.
5375 if !menu.is_incomplete {
5376 // If the new query is a suffix of the old query (typing more characters) and
5377 // the previous result was complete, the existing completions can be filtered.
5378 //
5379 // Note that this is always true for snippet completions.
5380 let query_matches = match (&menu.initial_query, &query) {
5381 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5382 (None, _) => true,
5383 _ => false,
5384 };
5385 if query_matches {
5386 let position_matches = if menu.initial_position == position {
5387 true
5388 } else {
5389 let snapshot = self.buffer.read(cx).read(cx);
5390 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5391 };
5392 if position_matches {
5393 return;
5394 }
5395 }
5396 }
5397 };
5398
5399 let trigger_kind = match trigger {
5400 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5401 CompletionTriggerKind::TRIGGER_CHARACTER
5402 }
5403 _ => CompletionTriggerKind::INVOKED,
5404 };
5405 let completion_context = CompletionContext {
5406 trigger_character: trigger.and_then(|trigger| {
5407 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5408 Some(String::from(trigger))
5409 } else {
5410 None
5411 }
5412 }),
5413 trigger_kind,
5414 };
5415
5416 let Anchor {
5417 excerpt_id: buffer_excerpt_id,
5418 text_anchor: buffer_position,
5419 ..
5420 } = buffer_position;
5421
5422 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5423 buffer_snapshot.surrounding_word(buffer_position, None)
5424 {
5425 let word_to_exclude = buffer_snapshot
5426 .text_for_range(word_range.clone())
5427 .collect::<String>();
5428 (
5429 buffer_snapshot.anchor_before(word_range.start)
5430 ..buffer_snapshot.anchor_after(buffer_position),
5431 Some(word_to_exclude),
5432 )
5433 } else {
5434 (buffer_position..buffer_position, None)
5435 };
5436
5437 let language = buffer_snapshot
5438 .language_at(buffer_position)
5439 .map(|language| language.name());
5440
5441 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5442 .completions
5443 .clone();
5444
5445 let show_completion_documentation = buffer_snapshot
5446 .settings_at(buffer_position, cx)
5447 .show_completion_documentation;
5448
5449 // The document can be large, so stay in reasonable bounds when searching for words,
5450 // otherwise completion pop-up might be slow to appear.
5451 const WORD_LOOKUP_ROWS: u32 = 5_000;
5452 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5453 let min_word_search = buffer_snapshot.clip_point(
5454 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5455 Bias::Left,
5456 );
5457 let max_word_search = buffer_snapshot.clip_point(
5458 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5459 Bias::Right,
5460 );
5461 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5462 ..buffer_snapshot.point_to_offset(max_word_search);
5463
5464 let skip_digits = query
5465 .as_ref()
5466 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5467
5468 let omit_word_completions = !self.word_completions_enabled
5469 || (!ignore_word_threshold
5470 && match &query {
5471 Some(query) => query.chars().count() < completion_settings.words_min_length,
5472 None => completion_settings.words_min_length != 0,
5473 });
5474
5475 let (mut words, provider_responses) = match &provider {
5476 Some(provider) => {
5477 let provider_responses = provider.completions(
5478 buffer_excerpt_id,
5479 &buffer,
5480 buffer_position,
5481 completion_context,
5482 window,
5483 cx,
5484 );
5485
5486 let words = match (omit_word_completions, completion_settings.words) {
5487 (true, _) | (_, WordsCompletionMode::Disabled) => {
5488 Task::ready(BTreeMap::default())
5489 }
5490 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5491 .background_spawn(async move {
5492 buffer_snapshot.words_in_range(WordsQuery {
5493 fuzzy_contents: None,
5494 range: word_search_range,
5495 skip_digits,
5496 })
5497 }),
5498 };
5499
5500 (words, provider_responses)
5501 }
5502 None => {
5503 let words = if omit_word_completions {
5504 Task::ready(BTreeMap::default())
5505 } else {
5506 cx.background_spawn(async move {
5507 buffer_snapshot.words_in_range(WordsQuery {
5508 fuzzy_contents: None,
5509 range: word_search_range,
5510 skip_digits,
5511 })
5512 })
5513 };
5514 (words, Task::ready(Ok(Vec::new())))
5515 }
5516 };
5517
5518 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5519
5520 let id = post_inc(&mut self.next_completion_id);
5521 let task = cx.spawn_in(window, async move |editor, cx| {
5522 let Ok(()) = editor.update(cx, |this, _| {
5523 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5524 }) else {
5525 return;
5526 };
5527
5528 // TODO: Ideally completions from different sources would be selectively re-queried, so
5529 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5530 let mut completions = Vec::new();
5531 let mut is_incomplete = false;
5532 let mut display_options: Option<CompletionDisplayOptions> = None;
5533 if let Some(provider_responses) = provider_responses.await.log_err()
5534 && !provider_responses.is_empty()
5535 {
5536 for response in provider_responses {
5537 completions.extend(response.completions);
5538 is_incomplete = is_incomplete || response.is_incomplete;
5539 match display_options.as_mut() {
5540 None => {
5541 display_options = Some(response.display_options);
5542 }
5543 Some(options) => options.merge(&response.display_options),
5544 }
5545 }
5546 if completion_settings.words == WordsCompletionMode::Fallback {
5547 words = Task::ready(BTreeMap::default());
5548 }
5549 }
5550 let display_options = display_options.unwrap_or_default();
5551
5552 let mut words = words.await;
5553 if let Some(word_to_exclude) = &word_to_exclude {
5554 words.remove(word_to_exclude);
5555 }
5556 for lsp_completion in &completions {
5557 words.remove(&lsp_completion.new_text);
5558 }
5559 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5560 replace_range: word_replace_range.clone(),
5561 new_text: word.clone(),
5562 label: CodeLabel::plain(word, None),
5563 icon_path: None,
5564 documentation: None,
5565 source: CompletionSource::BufferWord {
5566 word_range,
5567 resolved: false,
5568 },
5569 insert_text_mode: Some(InsertTextMode::AS_IS),
5570 confirm: None,
5571 }));
5572
5573 let menu = if completions.is_empty() {
5574 None
5575 } else {
5576 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5577 let languages = editor
5578 .workspace
5579 .as_ref()
5580 .and_then(|(workspace, _)| workspace.upgrade())
5581 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5582 let menu = CompletionsMenu::new(
5583 id,
5584 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5585 sort_completions,
5586 show_completion_documentation,
5587 position,
5588 query.clone(),
5589 is_incomplete,
5590 buffer.clone(),
5591 completions.into(),
5592 display_options,
5593 snippet_sort_order,
5594 languages,
5595 language,
5596 cx,
5597 );
5598
5599 let query = if filter_completions { query } else { None };
5600 let matches_task = if let Some(query) = query {
5601 menu.do_async_filtering(query, cx)
5602 } else {
5603 Task::ready(menu.unfiltered_matches())
5604 };
5605 (menu, matches_task)
5606 }) else {
5607 return;
5608 };
5609
5610 let matches = matches_task.await;
5611
5612 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5613 // Newer menu already set, so exit.
5614 if let Some(CodeContextMenu::Completions(prev_menu)) =
5615 editor.context_menu.borrow().as_ref()
5616 && prev_menu.id > id
5617 {
5618 return;
5619 };
5620
5621 // Only valid to take prev_menu because it the new menu is immediately set
5622 // below, or the menu is hidden.
5623 if let Some(CodeContextMenu::Completions(prev_menu)) =
5624 editor.context_menu.borrow_mut().take()
5625 {
5626 let position_matches =
5627 if prev_menu.initial_position == menu.initial_position {
5628 true
5629 } else {
5630 let snapshot = editor.buffer.read(cx).read(cx);
5631 prev_menu.initial_position.to_offset(&snapshot)
5632 == menu.initial_position.to_offset(&snapshot)
5633 };
5634 if position_matches {
5635 // Preserve markdown cache before `set_filter_results` because it will
5636 // try to populate the documentation cache.
5637 menu.preserve_markdown_cache(prev_menu);
5638 }
5639 };
5640
5641 menu.set_filter_results(matches, provider, window, cx);
5642 }) else {
5643 return;
5644 };
5645
5646 menu.visible().then_some(menu)
5647 };
5648
5649 editor
5650 .update_in(cx, |editor, window, cx| {
5651 if editor.focus_handle.is_focused(window)
5652 && let Some(menu) = menu
5653 {
5654 *editor.context_menu.borrow_mut() =
5655 Some(CodeContextMenu::Completions(menu));
5656
5657 crate::hover_popover::hide_hover(editor, cx);
5658 if editor.show_edit_predictions_in_menu() {
5659 editor.update_visible_edit_prediction(window, cx);
5660 } else {
5661 editor.discard_edit_prediction(false, cx);
5662 }
5663
5664 cx.notify();
5665 return;
5666 }
5667
5668 if editor.completion_tasks.len() <= 1 {
5669 // If there are no more completion tasks and the last menu was empty, we should hide it.
5670 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5671 // If it was already hidden and we don't show edit predictions in the menu,
5672 // we should also show the edit prediction when available.
5673 if was_hidden && editor.show_edit_predictions_in_menu() {
5674 editor.update_visible_edit_prediction(window, cx);
5675 }
5676 }
5677 })
5678 .ok();
5679 });
5680
5681 self.completion_tasks.push((id, task));
5682 }
5683
5684 #[cfg(feature = "test-support")]
5685 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5686 let menu = self.context_menu.borrow();
5687 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5688 let completions = menu.completions.borrow();
5689 Some(completions.to_vec())
5690 } else {
5691 None
5692 }
5693 }
5694
5695 pub fn with_completions_menu_matching_id<R>(
5696 &self,
5697 id: CompletionId,
5698 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5699 ) -> R {
5700 let mut context_menu = self.context_menu.borrow_mut();
5701 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5702 return f(None);
5703 };
5704 if completions_menu.id != id {
5705 return f(None);
5706 }
5707 f(Some(completions_menu))
5708 }
5709
5710 pub fn confirm_completion(
5711 &mut self,
5712 action: &ConfirmCompletion,
5713 window: &mut Window,
5714 cx: &mut Context<Self>,
5715 ) -> Option<Task<Result<()>>> {
5716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5717 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5718 }
5719
5720 pub fn confirm_completion_insert(
5721 &mut self,
5722 _: &ConfirmCompletionInsert,
5723 window: &mut Window,
5724 cx: &mut Context<Self>,
5725 ) -> Option<Task<Result<()>>> {
5726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5727 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5728 }
5729
5730 pub fn confirm_completion_replace(
5731 &mut self,
5732 _: &ConfirmCompletionReplace,
5733 window: &mut Window,
5734 cx: &mut Context<Self>,
5735 ) -> Option<Task<Result<()>>> {
5736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5737 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5738 }
5739
5740 pub fn compose_completion(
5741 &mut self,
5742 action: &ComposeCompletion,
5743 window: &mut Window,
5744 cx: &mut Context<Self>,
5745 ) -> Option<Task<Result<()>>> {
5746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5747 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5748 }
5749
5750 fn do_completion(
5751 &mut self,
5752 item_ix: Option<usize>,
5753 intent: CompletionIntent,
5754 window: &mut Window,
5755 cx: &mut Context<Editor>,
5756 ) -> Option<Task<Result<()>>> {
5757 use language::ToOffset as _;
5758
5759 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5760 else {
5761 return None;
5762 };
5763
5764 let candidate_id = {
5765 let entries = completions_menu.entries.borrow();
5766 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5767 if self.show_edit_predictions_in_menu() {
5768 self.discard_edit_prediction(true, cx);
5769 }
5770 mat.candidate_id
5771 };
5772
5773 let completion = completions_menu
5774 .completions
5775 .borrow()
5776 .get(candidate_id)?
5777 .clone();
5778 cx.stop_propagation();
5779
5780 let buffer_handle = completions_menu.buffer.clone();
5781
5782 let CompletionEdit {
5783 new_text,
5784 snippet,
5785 replace_range,
5786 } = process_completion_for_edit(
5787 &completion,
5788 intent,
5789 &buffer_handle,
5790 &completions_menu.initial_position.text_anchor,
5791 cx,
5792 );
5793
5794 let buffer = buffer_handle.read(cx);
5795 let snapshot = self.buffer.read(cx).snapshot(cx);
5796 let newest_anchor = self.selections.newest_anchor();
5797 let replace_range_multibuffer = {
5798 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5799 excerpt.map_range_from_buffer(replace_range.clone())
5800 };
5801 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5802 return None;
5803 }
5804
5805 let old_text = buffer
5806 .text_for_range(replace_range.clone())
5807 .collect::<String>();
5808 let lookbehind = newest_anchor
5809 .start
5810 .text_anchor
5811 .to_offset(buffer)
5812 .saturating_sub(replace_range.start);
5813 let lookahead = replace_range
5814 .end
5815 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5816 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5817 let suffix = &old_text[lookbehind.min(old_text.len())..];
5818
5819 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5820 let mut ranges = Vec::new();
5821 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5822
5823 for selection in &selections {
5824 let range = if selection.id == newest_anchor.id {
5825 replace_range_multibuffer.clone()
5826 } else {
5827 let mut range = selection.range();
5828
5829 // if prefix is present, don't duplicate it
5830 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5831 range.start = range.start.saturating_sub(lookbehind);
5832
5833 // if suffix is also present, mimic the newest cursor and replace it
5834 if selection.id != newest_anchor.id
5835 && snapshot.contains_str_at(range.end, suffix)
5836 {
5837 range.end += lookahead;
5838 }
5839 }
5840 range
5841 };
5842
5843 ranges.push(range.clone());
5844
5845 if !self.linked_edit_ranges.is_empty() {
5846 let start_anchor = snapshot.anchor_before(range.start);
5847 let end_anchor = snapshot.anchor_after(range.end);
5848 if let Some(ranges) = self
5849 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5850 {
5851 for (buffer, edits) in ranges {
5852 linked_edits
5853 .entry(buffer.clone())
5854 .or_default()
5855 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5856 }
5857 }
5858 }
5859 }
5860
5861 let common_prefix_len = old_text
5862 .chars()
5863 .zip(new_text.chars())
5864 .take_while(|(a, b)| a == b)
5865 .map(|(a, _)| a.len_utf8())
5866 .sum::<usize>();
5867
5868 cx.emit(EditorEvent::InputHandled {
5869 utf16_range_to_replace: None,
5870 text: new_text[common_prefix_len..].into(),
5871 });
5872
5873 self.transact(window, cx, |editor, window, cx| {
5874 if let Some(mut snippet) = snippet {
5875 snippet.text = new_text.to_string();
5876 editor
5877 .insert_snippet(&ranges, snippet, window, cx)
5878 .log_err();
5879 } else {
5880 editor.buffer.update(cx, |multi_buffer, cx| {
5881 let auto_indent = match completion.insert_text_mode {
5882 Some(InsertTextMode::AS_IS) => None,
5883 _ => editor.autoindent_mode.clone(),
5884 };
5885 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5886 multi_buffer.edit(edits, auto_indent, cx);
5887 });
5888 }
5889 for (buffer, edits) in linked_edits {
5890 buffer.update(cx, |buffer, cx| {
5891 let snapshot = buffer.snapshot();
5892 let edits = edits
5893 .into_iter()
5894 .map(|(range, text)| {
5895 use text::ToPoint as TP;
5896 let end_point = TP::to_point(&range.end, &snapshot);
5897 let start_point = TP::to_point(&range.start, &snapshot);
5898 (start_point..end_point, text)
5899 })
5900 .sorted_by_key(|(range, _)| range.start);
5901 buffer.edit(edits, None, cx);
5902 })
5903 }
5904
5905 editor.refresh_edit_prediction(true, false, window, cx);
5906 });
5907 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5908
5909 let show_new_completions_on_confirm = completion
5910 .confirm
5911 .as_ref()
5912 .is_some_and(|confirm| confirm(intent, window, cx));
5913 if show_new_completions_on_confirm {
5914 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5915 }
5916
5917 let provider = self.completion_provider.as_ref()?;
5918 drop(completion);
5919 let apply_edits = provider.apply_additional_edits_for_completion(
5920 buffer_handle,
5921 completions_menu.completions.clone(),
5922 candidate_id,
5923 true,
5924 cx,
5925 );
5926
5927 let editor_settings = EditorSettings::get_global(cx);
5928 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5929 // After the code completion is finished, users often want to know what signatures are needed.
5930 // so we should automatically call signature_help
5931 self.show_signature_help(&ShowSignatureHelp, window, cx);
5932 }
5933
5934 Some(cx.foreground_executor().spawn(async move {
5935 apply_edits.await?;
5936 Ok(())
5937 }))
5938 }
5939
5940 pub fn toggle_code_actions(
5941 &mut self,
5942 action: &ToggleCodeActions,
5943 window: &mut Window,
5944 cx: &mut Context<Self>,
5945 ) {
5946 let quick_launch = action.quick_launch;
5947 let mut context_menu = self.context_menu.borrow_mut();
5948 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5949 if code_actions.deployed_from == action.deployed_from {
5950 // Toggle if we're selecting the same one
5951 *context_menu = None;
5952 cx.notify();
5953 return;
5954 } else {
5955 // Otherwise, clear it and start a new one
5956 *context_menu = None;
5957 cx.notify();
5958 }
5959 }
5960 drop(context_menu);
5961 let snapshot = self.snapshot(window, cx);
5962 let deployed_from = action.deployed_from.clone();
5963 let action = action.clone();
5964 self.completion_tasks.clear();
5965 self.discard_edit_prediction(false, cx);
5966
5967 let multibuffer_point = match &action.deployed_from {
5968 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5969 DisplayPoint::new(*row, 0).to_point(&snapshot)
5970 }
5971 _ => self
5972 .selections
5973 .newest::<Point>(&snapshot.display_snapshot)
5974 .head(),
5975 };
5976 let Some((buffer, buffer_row)) = snapshot
5977 .buffer_snapshot()
5978 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5979 .and_then(|(buffer_snapshot, range)| {
5980 self.buffer()
5981 .read(cx)
5982 .buffer(buffer_snapshot.remote_id())
5983 .map(|buffer| (buffer, range.start.row))
5984 })
5985 else {
5986 return;
5987 };
5988 let buffer_id = buffer.read(cx).remote_id();
5989 let tasks = self
5990 .tasks
5991 .get(&(buffer_id, buffer_row))
5992 .map(|t| Arc::new(t.to_owned()));
5993
5994 if !self.focus_handle.is_focused(window) {
5995 return;
5996 }
5997 let project = self.project.clone();
5998
5999 let code_actions_task = match deployed_from {
6000 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6001 _ => self.code_actions(buffer_row, window, cx),
6002 };
6003
6004 let runnable_task = match deployed_from {
6005 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6006 _ => {
6007 let mut task_context_task = Task::ready(None);
6008 if let Some(tasks) = &tasks
6009 && let Some(project) = project
6010 {
6011 task_context_task =
6012 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6013 }
6014
6015 cx.spawn_in(window, {
6016 let buffer = buffer.clone();
6017 async move |editor, cx| {
6018 let task_context = task_context_task.await;
6019
6020 let resolved_tasks =
6021 tasks
6022 .zip(task_context.clone())
6023 .map(|(tasks, task_context)| ResolvedTasks {
6024 templates: tasks.resolve(&task_context).collect(),
6025 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6026 multibuffer_point.row,
6027 tasks.column,
6028 )),
6029 });
6030 let debug_scenarios = editor
6031 .update(cx, |editor, cx| {
6032 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6033 })?
6034 .await;
6035 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6036 }
6037 })
6038 }
6039 };
6040
6041 cx.spawn_in(window, async move |editor, cx| {
6042 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6043 let code_actions = code_actions_task.await;
6044 let spawn_straight_away = quick_launch
6045 && resolved_tasks
6046 .as_ref()
6047 .is_some_and(|tasks| tasks.templates.len() == 1)
6048 && code_actions
6049 .as_ref()
6050 .is_none_or(|actions| actions.is_empty())
6051 && debug_scenarios.is_empty();
6052
6053 editor.update_in(cx, |editor, window, cx| {
6054 crate::hover_popover::hide_hover(editor, cx);
6055 let actions = CodeActionContents::new(
6056 resolved_tasks,
6057 code_actions,
6058 debug_scenarios,
6059 task_context.unwrap_or_default(),
6060 );
6061
6062 // Don't show the menu if there are no actions available
6063 if actions.is_empty() {
6064 cx.notify();
6065 return Task::ready(Ok(()));
6066 }
6067
6068 *editor.context_menu.borrow_mut() =
6069 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6070 buffer,
6071 actions,
6072 selected_item: Default::default(),
6073 scroll_handle: UniformListScrollHandle::default(),
6074 deployed_from,
6075 }));
6076 cx.notify();
6077 if spawn_straight_away
6078 && let Some(task) = editor.confirm_code_action(
6079 &ConfirmCodeAction { item_ix: Some(0) },
6080 window,
6081 cx,
6082 )
6083 {
6084 return task;
6085 }
6086
6087 Task::ready(Ok(()))
6088 })
6089 })
6090 .detach_and_log_err(cx);
6091 }
6092
6093 fn debug_scenarios(
6094 &mut self,
6095 resolved_tasks: &Option<ResolvedTasks>,
6096 buffer: &Entity<Buffer>,
6097 cx: &mut App,
6098 ) -> Task<Vec<task::DebugScenario>> {
6099 maybe!({
6100 let project = self.project()?;
6101 let dap_store = project.read(cx).dap_store();
6102 let mut scenarios = vec![];
6103 let resolved_tasks = resolved_tasks.as_ref()?;
6104 let buffer = buffer.read(cx);
6105 let language = buffer.language()?;
6106 let file = buffer.file();
6107 let debug_adapter = language_settings(language.name().into(), file, cx)
6108 .debuggers
6109 .first()
6110 .map(SharedString::from)
6111 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6112
6113 dap_store.update(cx, |dap_store, cx| {
6114 for (_, task) in &resolved_tasks.templates {
6115 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6116 task.original_task().clone(),
6117 debug_adapter.clone().into(),
6118 task.display_label().to_owned().into(),
6119 cx,
6120 );
6121 scenarios.push(maybe_scenario);
6122 }
6123 });
6124 Some(cx.background_spawn(async move {
6125 futures::future::join_all(scenarios)
6126 .await
6127 .into_iter()
6128 .flatten()
6129 .collect::<Vec<_>>()
6130 }))
6131 })
6132 .unwrap_or_else(|| Task::ready(vec![]))
6133 }
6134
6135 fn code_actions(
6136 &mut self,
6137 buffer_row: u32,
6138 window: &mut Window,
6139 cx: &mut Context<Self>,
6140 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6141 let mut task = self.code_actions_task.take();
6142 cx.spawn_in(window, async move |editor, cx| {
6143 while let Some(prev_task) = task {
6144 prev_task.await.log_err();
6145 task = editor
6146 .update(cx, |this, _| this.code_actions_task.take())
6147 .ok()?;
6148 }
6149
6150 editor
6151 .update(cx, |editor, cx| {
6152 editor
6153 .available_code_actions
6154 .clone()
6155 .and_then(|(location, code_actions)| {
6156 let snapshot = location.buffer.read(cx).snapshot();
6157 let point_range = location.range.to_point(&snapshot);
6158 let point_range = point_range.start.row..=point_range.end.row;
6159 if point_range.contains(&buffer_row) {
6160 Some(code_actions)
6161 } else {
6162 None
6163 }
6164 })
6165 })
6166 .ok()
6167 .flatten()
6168 })
6169 }
6170
6171 pub fn confirm_code_action(
6172 &mut self,
6173 action: &ConfirmCodeAction,
6174 window: &mut Window,
6175 cx: &mut Context<Self>,
6176 ) -> Option<Task<Result<()>>> {
6177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6178
6179 let actions_menu =
6180 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6181 menu
6182 } else {
6183 return None;
6184 };
6185
6186 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6187 let action = actions_menu.actions.get(action_ix)?;
6188 let title = action.label();
6189 let buffer = actions_menu.buffer;
6190 let workspace = self.workspace()?;
6191
6192 match action {
6193 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6194 workspace.update(cx, |workspace, cx| {
6195 workspace.schedule_resolved_task(
6196 task_source_kind,
6197 resolved_task,
6198 false,
6199 window,
6200 cx,
6201 );
6202
6203 Some(Task::ready(Ok(())))
6204 })
6205 }
6206 CodeActionsItem::CodeAction {
6207 excerpt_id,
6208 action,
6209 provider,
6210 } => {
6211 let apply_code_action =
6212 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6213 let workspace = workspace.downgrade();
6214 Some(cx.spawn_in(window, async move |editor, cx| {
6215 let project_transaction = apply_code_action.await?;
6216 Self::open_project_transaction(
6217 &editor,
6218 workspace,
6219 project_transaction,
6220 title,
6221 cx,
6222 )
6223 .await
6224 }))
6225 }
6226 CodeActionsItem::DebugScenario(scenario) => {
6227 let context = actions_menu.actions.context;
6228
6229 workspace.update(cx, |workspace, cx| {
6230 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6231 workspace.start_debug_session(
6232 scenario,
6233 context,
6234 Some(buffer),
6235 None,
6236 window,
6237 cx,
6238 );
6239 });
6240 Some(Task::ready(Ok(())))
6241 }
6242 }
6243 }
6244
6245 pub async fn open_project_transaction(
6246 editor: &WeakEntity<Editor>,
6247 workspace: WeakEntity<Workspace>,
6248 transaction: ProjectTransaction,
6249 title: String,
6250 cx: &mut AsyncWindowContext,
6251 ) -> Result<()> {
6252 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6253 cx.update(|_, cx| {
6254 entries.sort_unstable_by_key(|(buffer, _)| {
6255 buffer.read(cx).file().map(|f| f.path().clone())
6256 });
6257 })?;
6258 if entries.is_empty() {
6259 return Ok(());
6260 }
6261
6262 // If the project transaction's edits are all contained within this editor, then
6263 // avoid opening a new editor to display them.
6264
6265 if let [(buffer, transaction)] = &*entries {
6266 let excerpt = editor.update(cx, |editor, cx| {
6267 editor
6268 .buffer()
6269 .read(cx)
6270 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6271 })?;
6272 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6273 && excerpted_buffer == *buffer
6274 {
6275 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6276 let excerpt_range = excerpt_range.to_offset(buffer);
6277 buffer
6278 .edited_ranges_for_transaction::<usize>(transaction)
6279 .all(|range| {
6280 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6281 })
6282 })?;
6283
6284 if all_edits_within_excerpt {
6285 return Ok(());
6286 }
6287 }
6288 }
6289
6290 let mut ranges_to_highlight = Vec::new();
6291 let excerpt_buffer = cx.new(|cx| {
6292 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6293 for (buffer_handle, transaction) in &entries {
6294 let edited_ranges = buffer_handle
6295 .read(cx)
6296 .edited_ranges_for_transaction::<Point>(transaction)
6297 .collect::<Vec<_>>();
6298 let (ranges, _) = multibuffer.set_excerpts_for_path(
6299 PathKey::for_buffer(buffer_handle, cx),
6300 buffer_handle.clone(),
6301 edited_ranges,
6302 multibuffer_context_lines(cx),
6303 cx,
6304 );
6305
6306 ranges_to_highlight.extend(ranges);
6307 }
6308 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6309 multibuffer
6310 })?;
6311
6312 workspace.update_in(cx, |workspace, window, cx| {
6313 let project = workspace.project().clone();
6314 let editor =
6315 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6316 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6317 editor.update(cx, |editor, cx| {
6318 editor.highlight_background::<Self>(
6319 &ranges_to_highlight,
6320 |theme| theme.colors().editor_highlighted_line_background,
6321 cx,
6322 );
6323 });
6324 })?;
6325
6326 Ok(())
6327 }
6328
6329 pub fn clear_code_action_providers(&mut self) {
6330 self.code_action_providers.clear();
6331 self.available_code_actions.take();
6332 }
6333
6334 pub fn add_code_action_provider(
6335 &mut self,
6336 provider: Rc<dyn CodeActionProvider>,
6337 window: &mut Window,
6338 cx: &mut Context<Self>,
6339 ) {
6340 if self
6341 .code_action_providers
6342 .iter()
6343 .any(|existing_provider| existing_provider.id() == provider.id())
6344 {
6345 return;
6346 }
6347
6348 self.code_action_providers.push(provider);
6349 self.refresh_code_actions(window, cx);
6350 }
6351
6352 pub fn remove_code_action_provider(
6353 &mut self,
6354 id: Arc<str>,
6355 window: &mut Window,
6356 cx: &mut Context<Self>,
6357 ) {
6358 self.code_action_providers
6359 .retain(|provider| provider.id() != id);
6360 self.refresh_code_actions(window, cx);
6361 }
6362
6363 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6364 !self.code_action_providers.is_empty()
6365 && EditorSettings::get_global(cx).toolbar.code_actions
6366 }
6367
6368 pub fn has_available_code_actions(&self) -> bool {
6369 self.available_code_actions
6370 .as_ref()
6371 .is_some_and(|(_, actions)| !actions.is_empty())
6372 }
6373
6374 fn render_inline_code_actions(
6375 &self,
6376 icon_size: ui::IconSize,
6377 display_row: DisplayRow,
6378 is_active: bool,
6379 cx: &mut Context<Self>,
6380 ) -> AnyElement {
6381 let show_tooltip = !self.context_menu_visible();
6382 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6383 .icon_size(icon_size)
6384 .shape(ui::IconButtonShape::Square)
6385 .icon_color(ui::Color::Hidden)
6386 .toggle_state(is_active)
6387 .when(show_tooltip, |this| {
6388 this.tooltip({
6389 let focus_handle = self.focus_handle.clone();
6390 move |_window, cx| {
6391 Tooltip::for_action_in(
6392 "Toggle Code Actions",
6393 &ToggleCodeActions {
6394 deployed_from: None,
6395 quick_launch: false,
6396 },
6397 &focus_handle,
6398 cx,
6399 )
6400 }
6401 })
6402 })
6403 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6404 window.focus(&editor.focus_handle(cx));
6405 editor.toggle_code_actions(
6406 &crate::actions::ToggleCodeActions {
6407 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6408 display_row,
6409 )),
6410 quick_launch: false,
6411 },
6412 window,
6413 cx,
6414 );
6415 }))
6416 .into_any_element()
6417 }
6418
6419 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6420 &self.context_menu
6421 }
6422
6423 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6424 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6425 cx.background_executor()
6426 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6427 .await;
6428
6429 let (start_buffer, start, _, end, newest_selection) = this
6430 .update(cx, |this, cx| {
6431 let newest_selection = this.selections.newest_anchor().clone();
6432 if newest_selection.head().diff_base_anchor.is_some() {
6433 return None;
6434 }
6435 let display_snapshot = this.display_snapshot(cx);
6436 let newest_selection_adjusted =
6437 this.selections.newest_adjusted(&display_snapshot);
6438 let buffer = this.buffer.read(cx);
6439
6440 let (start_buffer, start) =
6441 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6442 let (end_buffer, end) =
6443 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6444
6445 Some((start_buffer, start, end_buffer, end, newest_selection))
6446 })?
6447 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6448 .context(
6449 "Expected selection to lie in a single buffer when refreshing code actions",
6450 )?;
6451 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6452 let providers = this.code_action_providers.clone();
6453 let tasks = this
6454 .code_action_providers
6455 .iter()
6456 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6457 .collect::<Vec<_>>();
6458 (providers, tasks)
6459 })?;
6460
6461 let mut actions = Vec::new();
6462 for (provider, provider_actions) in
6463 providers.into_iter().zip(future::join_all(tasks).await)
6464 {
6465 if let Some(provider_actions) = provider_actions.log_err() {
6466 actions.extend(provider_actions.into_iter().map(|action| {
6467 AvailableCodeAction {
6468 excerpt_id: newest_selection.start.excerpt_id,
6469 action,
6470 provider: provider.clone(),
6471 }
6472 }));
6473 }
6474 }
6475
6476 this.update(cx, |this, cx| {
6477 this.available_code_actions = if actions.is_empty() {
6478 None
6479 } else {
6480 Some((
6481 Location {
6482 buffer: start_buffer,
6483 range: start..end,
6484 },
6485 actions.into(),
6486 ))
6487 };
6488 cx.notify();
6489 })
6490 }));
6491 }
6492
6493 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6494 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6495 self.show_git_blame_inline = false;
6496
6497 self.show_git_blame_inline_delay_task =
6498 Some(cx.spawn_in(window, async move |this, cx| {
6499 cx.background_executor().timer(delay).await;
6500
6501 this.update(cx, |this, cx| {
6502 this.show_git_blame_inline = true;
6503 cx.notify();
6504 })
6505 .log_err();
6506 }));
6507 }
6508 }
6509
6510 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6511 let snapshot = self.snapshot(window, cx);
6512 let cursor = self
6513 .selections
6514 .newest::<Point>(&snapshot.display_snapshot)
6515 .head();
6516 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6517 else {
6518 return;
6519 };
6520
6521 let Some(blame) = self.blame.as_ref() else {
6522 return;
6523 };
6524
6525 let row_info = RowInfo {
6526 buffer_id: Some(buffer.remote_id()),
6527 buffer_row: Some(point.row),
6528 ..Default::default()
6529 };
6530 let Some((buffer, blame_entry)) = blame
6531 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6532 .flatten()
6533 else {
6534 return;
6535 };
6536
6537 let anchor = self.selections.newest_anchor().head();
6538 let position = self.to_pixel_point(anchor, &snapshot, window);
6539 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6540 self.show_blame_popover(
6541 buffer,
6542 &blame_entry,
6543 position + last_bounds.origin,
6544 true,
6545 cx,
6546 );
6547 };
6548 }
6549
6550 fn show_blame_popover(
6551 &mut self,
6552 buffer: BufferId,
6553 blame_entry: &BlameEntry,
6554 position: gpui::Point<Pixels>,
6555 ignore_timeout: bool,
6556 cx: &mut Context<Self>,
6557 ) {
6558 if let Some(state) = &mut self.inline_blame_popover {
6559 state.hide_task.take();
6560 } else {
6561 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6562 let blame_entry = blame_entry.clone();
6563 let show_task = cx.spawn(async move |editor, cx| {
6564 if !ignore_timeout {
6565 cx.background_executor()
6566 .timer(std::time::Duration::from_millis(blame_popover_delay))
6567 .await;
6568 }
6569 editor
6570 .update(cx, |editor, cx| {
6571 editor.inline_blame_popover_show_task.take();
6572 let Some(blame) = editor.blame.as_ref() else {
6573 return;
6574 };
6575 let blame = blame.read(cx);
6576 let details = blame.details_for_entry(buffer, &blame_entry);
6577 let markdown = cx.new(|cx| {
6578 Markdown::new(
6579 details
6580 .as_ref()
6581 .map(|message| message.message.clone())
6582 .unwrap_or_default(),
6583 None,
6584 None,
6585 cx,
6586 )
6587 });
6588 editor.inline_blame_popover = Some(InlineBlamePopover {
6589 position,
6590 hide_task: None,
6591 popover_bounds: None,
6592 popover_state: InlineBlamePopoverState {
6593 scroll_handle: ScrollHandle::new(),
6594 commit_message: details,
6595 markdown,
6596 },
6597 keyboard_grace: ignore_timeout,
6598 });
6599 cx.notify();
6600 })
6601 .ok();
6602 });
6603 self.inline_blame_popover_show_task = Some(show_task);
6604 }
6605 }
6606
6607 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6608 self.inline_blame_popover_show_task.take();
6609 if let Some(state) = &mut self.inline_blame_popover {
6610 let hide_task = cx.spawn(async move |editor, cx| {
6611 if !ignore_timeout {
6612 cx.background_executor()
6613 .timer(std::time::Duration::from_millis(100))
6614 .await;
6615 }
6616 editor
6617 .update(cx, |editor, cx| {
6618 editor.inline_blame_popover.take();
6619 cx.notify();
6620 })
6621 .ok();
6622 });
6623 state.hide_task = Some(hide_task);
6624 true
6625 } else {
6626 false
6627 }
6628 }
6629
6630 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6631 if self.pending_rename.is_some() {
6632 return None;
6633 }
6634
6635 let provider = self.semantics_provider.clone()?;
6636 let buffer = self.buffer.read(cx);
6637 let newest_selection = self.selections.newest_anchor().clone();
6638 let cursor_position = newest_selection.head();
6639 let (cursor_buffer, cursor_buffer_position) =
6640 buffer.text_anchor_for_position(cursor_position, cx)?;
6641 let (tail_buffer, tail_buffer_position) =
6642 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6643 if cursor_buffer != tail_buffer {
6644 return None;
6645 }
6646
6647 let snapshot = cursor_buffer.read(cx).snapshot();
6648 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6649 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6650 if start_word_range != end_word_range {
6651 self.document_highlights_task.take();
6652 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6653 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6654 return None;
6655 }
6656
6657 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6658 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6659 cx.background_executor()
6660 .timer(Duration::from_millis(debounce))
6661 .await;
6662
6663 let highlights = if let Some(highlights) = cx
6664 .update(|cx| {
6665 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6666 })
6667 .ok()
6668 .flatten()
6669 {
6670 highlights.await.log_err()
6671 } else {
6672 None
6673 };
6674
6675 if let Some(highlights) = highlights {
6676 this.update(cx, |this, cx| {
6677 if this.pending_rename.is_some() {
6678 return;
6679 }
6680
6681 let buffer = this.buffer.read(cx);
6682 if buffer
6683 .text_anchor_for_position(cursor_position, cx)
6684 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6685 {
6686 return;
6687 }
6688
6689 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6690 let mut write_ranges = Vec::new();
6691 let mut read_ranges = Vec::new();
6692 for highlight in highlights {
6693 let buffer_id = cursor_buffer.read(cx).remote_id();
6694 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6695 {
6696 let start = highlight
6697 .range
6698 .start
6699 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6700 let end = highlight
6701 .range
6702 .end
6703 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6704 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6705 continue;
6706 }
6707
6708 let range =
6709 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6710 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6711 write_ranges.push(range);
6712 } else {
6713 read_ranges.push(range);
6714 }
6715 }
6716 }
6717
6718 this.highlight_background::<DocumentHighlightRead>(
6719 &read_ranges,
6720 |theme| theme.colors().editor_document_highlight_read_background,
6721 cx,
6722 );
6723 this.highlight_background::<DocumentHighlightWrite>(
6724 &write_ranges,
6725 |theme| theme.colors().editor_document_highlight_write_background,
6726 cx,
6727 );
6728 cx.notify();
6729 })
6730 .log_err();
6731 }
6732 }));
6733 None
6734 }
6735
6736 fn prepare_highlight_query_from_selection(
6737 &mut self,
6738 window: &Window,
6739 cx: &mut Context<Editor>,
6740 ) -> Option<(String, Range<Anchor>)> {
6741 if matches!(self.mode, EditorMode::SingleLine) {
6742 return None;
6743 }
6744 if !EditorSettings::get_global(cx).selection_highlight {
6745 return None;
6746 }
6747 if self.selections.count() != 1 || self.selections.line_mode() {
6748 return None;
6749 }
6750 let snapshot = self.snapshot(window, cx);
6751 let selection = self.selections.newest::<Point>(&snapshot);
6752 // If the selection spans multiple rows OR it is empty
6753 if selection.start.row != selection.end.row
6754 || selection.start.column == selection.end.column
6755 {
6756 return None;
6757 }
6758 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6759 let query = snapshot
6760 .buffer_snapshot()
6761 .text_for_range(selection_anchor_range.clone())
6762 .collect::<String>();
6763 if query.trim().is_empty() {
6764 return None;
6765 }
6766 Some((query, selection_anchor_range))
6767 }
6768
6769 fn update_selection_occurrence_highlights(
6770 &mut self,
6771 query_text: String,
6772 query_range: Range<Anchor>,
6773 multi_buffer_range_to_query: Range<Point>,
6774 use_debounce: bool,
6775 window: &mut Window,
6776 cx: &mut Context<Editor>,
6777 ) -> Task<()> {
6778 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6779 cx.spawn_in(window, async move |editor, cx| {
6780 if use_debounce {
6781 cx.background_executor()
6782 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6783 .await;
6784 }
6785 let match_task = cx.background_spawn(async move {
6786 let buffer_ranges = multi_buffer_snapshot
6787 .range_to_buffer_ranges(multi_buffer_range_to_query)
6788 .into_iter()
6789 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6790 let mut match_ranges = Vec::new();
6791 let Ok(regex) = project::search::SearchQuery::text(
6792 query_text.clone(),
6793 false,
6794 false,
6795 false,
6796 Default::default(),
6797 Default::default(),
6798 false,
6799 None,
6800 ) else {
6801 return Vec::default();
6802 };
6803 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6804 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6805 match_ranges.extend(
6806 regex
6807 .search(buffer_snapshot, Some(search_range.clone()))
6808 .await
6809 .into_iter()
6810 .filter_map(|match_range| {
6811 let match_start = buffer_snapshot
6812 .anchor_after(search_range.start + match_range.start);
6813 let match_end = buffer_snapshot
6814 .anchor_before(search_range.start + match_range.end);
6815 let match_anchor_range = Anchor::range_in_buffer(
6816 excerpt_id,
6817 buffer_snapshot.remote_id(),
6818 match_start..match_end,
6819 );
6820 (match_anchor_range != query_range).then_some(match_anchor_range)
6821 }),
6822 );
6823 }
6824 match_ranges
6825 });
6826 let match_ranges = match_task.await;
6827 editor
6828 .update_in(cx, |editor, _, cx| {
6829 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6830 if !match_ranges.is_empty() {
6831 editor.highlight_background::<SelectedTextHighlight>(
6832 &match_ranges,
6833 |theme| theme.colors().editor_document_highlight_bracket_background,
6834 cx,
6835 )
6836 }
6837 })
6838 .log_err();
6839 })
6840 }
6841
6842 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6843 struct NewlineFold;
6844 let type_id = std::any::TypeId::of::<NewlineFold>();
6845 if !self.mode.is_single_line() {
6846 return;
6847 }
6848 let snapshot = self.snapshot(window, cx);
6849 if snapshot.buffer_snapshot().max_point().row == 0 {
6850 return;
6851 }
6852 let task = cx.background_spawn(async move {
6853 let new_newlines = snapshot
6854 .buffer_chars_at(0)
6855 .filter_map(|(c, i)| {
6856 if c == '\n' {
6857 Some(
6858 snapshot.buffer_snapshot().anchor_after(i)
6859 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6860 )
6861 } else {
6862 None
6863 }
6864 })
6865 .collect::<Vec<_>>();
6866 let existing_newlines = snapshot
6867 .folds_in_range(0..snapshot.buffer_snapshot().len())
6868 .filter_map(|fold| {
6869 if fold.placeholder.type_tag == Some(type_id) {
6870 Some(fold.range.start..fold.range.end)
6871 } else {
6872 None
6873 }
6874 })
6875 .collect::<Vec<_>>();
6876
6877 (new_newlines, existing_newlines)
6878 });
6879 self.folding_newlines = cx.spawn(async move |this, cx| {
6880 let (new_newlines, existing_newlines) = task.await;
6881 if new_newlines == existing_newlines {
6882 return;
6883 }
6884 let placeholder = FoldPlaceholder {
6885 render: Arc::new(move |_, _, cx| {
6886 div()
6887 .bg(cx.theme().status().hint_background)
6888 .border_b_1()
6889 .size_full()
6890 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6891 .border_color(cx.theme().status().hint)
6892 .child("\\n")
6893 .into_any()
6894 }),
6895 constrain_width: false,
6896 merge_adjacent: false,
6897 type_tag: Some(type_id),
6898 };
6899 let creases = new_newlines
6900 .into_iter()
6901 .map(|range| Crease::simple(range, placeholder.clone()))
6902 .collect();
6903 this.update(cx, |this, cx| {
6904 this.display_map.update(cx, |display_map, cx| {
6905 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6906 display_map.fold(creases, cx);
6907 });
6908 })
6909 .ok();
6910 });
6911 }
6912
6913 fn refresh_selected_text_highlights(
6914 &mut self,
6915 on_buffer_edit: bool,
6916 window: &mut Window,
6917 cx: &mut Context<Editor>,
6918 ) {
6919 let Some((query_text, query_range)) =
6920 self.prepare_highlight_query_from_selection(window, cx)
6921 else {
6922 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6923 self.quick_selection_highlight_task.take();
6924 self.debounced_selection_highlight_task.take();
6925 return;
6926 };
6927 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6928 if on_buffer_edit
6929 || self
6930 .quick_selection_highlight_task
6931 .as_ref()
6932 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6933 {
6934 let multi_buffer_visible_start = self
6935 .scroll_manager
6936 .anchor()
6937 .anchor
6938 .to_point(&multi_buffer_snapshot);
6939 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6940 multi_buffer_visible_start
6941 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6942 Bias::Left,
6943 );
6944 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6945 self.quick_selection_highlight_task = Some((
6946 query_range.clone(),
6947 self.update_selection_occurrence_highlights(
6948 query_text.clone(),
6949 query_range.clone(),
6950 multi_buffer_visible_range,
6951 false,
6952 window,
6953 cx,
6954 ),
6955 ));
6956 }
6957 if on_buffer_edit
6958 || self
6959 .debounced_selection_highlight_task
6960 .as_ref()
6961 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6962 {
6963 let multi_buffer_start = multi_buffer_snapshot
6964 .anchor_before(0)
6965 .to_point(&multi_buffer_snapshot);
6966 let multi_buffer_end = multi_buffer_snapshot
6967 .anchor_after(multi_buffer_snapshot.len())
6968 .to_point(&multi_buffer_snapshot);
6969 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6970 self.debounced_selection_highlight_task = Some((
6971 query_range.clone(),
6972 self.update_selection_occurrence_highlights(
6973 query_text,
6974 query_range,
6975 multi_buffer_full_range,
6976 true,
6977 window,
6978 cx,
6979 ),
6980 ));
6981 }
6982 }
6983
6984 pub fn refresh_edit_prediction(
6985 &mut self,
6986 debounce: bool,
6987 user_requested: bool,
6988 window: &mut Window,
6989 cx: &mut Context<Self>,
6990 ) -> Option<()> {
6991 if DisableAiSettings::get_global(cx).disable_ai {
6992 return None;
6993 }
6994
6995 let provider = self.edit_prediction_provider()?;
6996 let cursor = self.selections.newest_anchor().head();
6997 let (buffer, cursor_buffer_position) =
6998 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6999
7000 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7001 self.discard_edit_prediction(false, cx);
7002 return None;
7003 }
7004
7005 self.update_visible_edit_prediction(window, cx);
7006
7007 if !user_requested
7008 && (!self.should_show_edit_predictions()
7009 || !self.is_focused(window)
7010 || buffer.read(cx).is_empty())
7011 {
7012 self.discard_edit_prediction(false, cx);
7013 return None;
7014 }
7015
7016 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7017 Some(())
7018 }
7019
7020 fn show_edit_predictions_in_menu(&self) -> bool {
7021 match self.edit_prediction_settings {
7022 EditPredictionSettings::Disabled => false,
7023 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7024 }
7025 }
7026
7027 pub fn edit_predictions_enabled(&self) -> bool {
7028 match self.edit_prediction_settings {
7029 EditPredictionSettings::Disabled => false,
7030 EditPredictionSettings::Enabled { .. } => true,
7031 }
7032 }
7033
7034 fn edit_prediction_requires_modifier(&self) -> bool {
7035 match self.edit_prediction_settings {
7036 EditPredictionSettings::Disabled => false,
7037 EditPredictionSettings::Enabled {
7038 preview_requires_modifier,
7039 ..
7040 } => preview_requires_modifier,
7041 }
7042 }
7043
7044 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7045 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7046 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7047 self.discard_edit_prediction(false, cx);
7048 } else {
7049 let selection = self.selections.newest_anchor();
7050 let cursor = selection.head();
7051
7052 if let Some((buffer, cursor_buffer_position)) =
7053 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7054 {
7055 self.edit_prediction_settings =
7056 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7057 }
7058 }
7059 }
7060
7061 fn edit_prediction_settings_at_position(
7062 &self,
7063 buffer: &Entity<Buffer>,
7064 buffer_position: language::Anchor,
7065 cx: &App,
7066 ) -> EditPredictionSettings {
7067 if !self.mode.is_full()
7068 || !self.show_edit_predictions_override.unwrap_or(true)
7069 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7070 {
7071 return EditPredictionSettings::Disabled;
7072 }
7073
7074 let buffer = buffer.read(cx);
7075
7076 let file = buffer.file();
7077
7078 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7079 return EditPredictionSettings::Disabled;
7080 };
7081
7082 let by_provider = matches!(
7083 self.menu_edit_predictions_policy,
7084 MenuEditPredictionsPolicy::ByProvider
7085 );
7086
7087 let show_in_menu = by_provider
7088 && self
7089 .edit_prediction_provider
7090 .as_ref()
7091 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7092
7093 let preview_requires_modifier =
7094 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7095
7096 EditPredictionSettings::Enabled {
7097 show_in_menu,
7098 preview_requires_modifier,
7099 }
7100 }
7101
7102 fn should_show_edit_predictions(&self) -> bool {
7103 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7104 }
7105
7106 pub fn edit_prediction_preview_is_active(&self) -> bool {
7107 matches!(
7108 self.edit_prediction_preview,
7109 EditPredictionPreview::Active { .. }
7110 )
7111 }
7112
7113 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7114 let cursor = self.selections.newest_anchor().head();
7115 if let Some((buffer, cursor_position)) =
7116 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7117 {
7118 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7119 } else {
7120 false
7121 }
7122 }
7123
7124 pub fn supports_minimap(&self, cx: &App) -> bool {
7125 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7126 }
7127
7128 fn edit_predictions_enabled_in_buffer(
7129 &self,
7130 buffer: &Entity<Buffer>,
7131 buffer_position: language::Anchor,
7132 cx: &App,
7133 ) -> bool {
7134 maybe!({
7135 if self.read_only(cx) {
7136 return Some(false);
7137 }
7138 let provider = self.edit_prediction_provider()?;
7139 if !provider.is_enabled(buffer, buffer_position, cx) {
7140 return Some(false);
7141 }
7142 let buffer = buffer.read(cx);
7143 let Some(file) = buffer.file() else {
7144 return Some(true);
7145 };
7146 let settings = all_language_settings(Some(file), cx);
7147 Some(settings.edit_predictions_enabled_for_file(file, cx))
7148 })
7149 .unwrap_or(false)
7150 }
7151
7152 fn cycle_edit_prediction(
7153 &mut self,
7154 direction: Direction,
7155 window: &mut Window,
7156 cx: &mut Context<Self>,
7157 ) -> Option<()> {
7158 let provider = self.edit_prediction_provider()?;
7159 let cursor = self.selections.newest_anchor().head();
7160 let (buffer, cursor_buffer_position) =
7161 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7162 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7163 return None;
7164 }
7165
7166 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7167 self.update_visible_edit_prediction(window, cx);
7168
7169 Some(())
7170 }
7171
7172 pub fn show_edit_prediction(
7173 &mut self,
7174 _: &ShowEditPrediction,
7175 window: &mut Window,
7176 cx: &mut Context<Self>,
7177 ) {
7178 if !self.has_active_edit_prediction() {
7179 self.refresh_edit_prediction(false, true, window, cx);
7180 return;
7181 }
7182
7183 self.update_visible_edit_prediction(window, cx);
7184 }
7185
7186 pub fn display_cursor_names(
7187 &mut self,
7188 _: &DisplayCursorNames,
7189 window: &mut Window,
7190 cx: &mut Context<Self>,
7191 ) {
7192 self.show_cursor_names(window, cx);
7193 }
7194
7195 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7196 self.show_cursor_names = true;
7197 cx.notify();
7198 cx.spawn_in(window, async move |this, cx| {
7199 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7200 this.update(cx, |this, cx| {
7201 this.show_cursor_names = false;
7202 cx.notify()
7203 })
7204 .ok()
7205 })
7206 .detach();
7207 }
7208
7209 pub fn next_edit_prediction(
7210 &mut self,
7211 _: &NextEditPrediction,
7212 window: &mut Window,
7213 cx: &mut Context<Self>,
7214 ) {
7215 if self.has_active_edit_prediction() {
7216 self.cycle_edit_prediction(Direction::Next, window, cx);
7217 } else {
7218 let is_copilot_disabled = self
7219 .refresh_edit_prediction(false, true, window, cx)
7220 .is_none();
7221 if is_copilot_disabled {
7222 cx.propagate();
7223 }
7224 }
7225 }
7226
7227 pub fn previous_edit_prediction(
7228 &mut self,
7229 _: &PreviousEditPrediction,
7230 window: &mut Window,
7231 cx: &mut Context<Self>,
7232 ) {
7233 if self.has_active_edit_prediction() {
7234 self.cycle_edit_prediction(Direction::Prev, window, cx);
7235 } else {
7236 let is_copilot_disabled = self
7237 .refresh_edit_prediction(false, true, window, cx)
7238 .is_none();
7239 if is_copilot_disabled {
7240 cx.propagate();
7241 }
7242 }
7243 }
7244
7245 pub fn accept_edit_prediction(
7246 &mut self,
7247 _: &AcceptEditPrediction,
7248 window: &mut Window,
7249 cx: &mut Context<Self>,
7250 ) {
7251 if self.show_edit_predictions_in_menu() {
7252 self.hide_context_menu(window, cx);
7253 }
7254
7255 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7256 return;
7257 };
7258
7259 match &active_edit_prediction.completion {
7260 EditPrediction::MoveWithin { target, .. } => {
7261 let target = *target;
7262
7263 if let Some(position_map) = &self.last_position_map {
7264 if position_map
7265 .visible_row_range
7266 .contains(&target.to_display_point(&position_map.snapshot).row())
7267 || !self.edit_prediction_requires_modifier()
7268 {
7269 self.unfold_ranges(&[target..target], true, false, cx);
7270 // Note that this is also done in vim's handler of the Tab action.
7271 self.change_selections(
7272 SelectionEffects::scroll(Autoscroll::newest()),
7273 window,
7274 cx,
7275 |selections| {
7276 selections.select_anchor_ranges([target..target]);
7277 },
7278 );
7279 self.clear_row_highlights::<EditPredictionPreview>();
7280
7281 self.edit_prediction_preview
7282 .set_previous_scroll_position(None);
7283 } else {
7284 self.edit_prediction_preview
7285 .set_previous_scroll_position(Some(
7286 position_map.snapshot.scroll_anchor,
7287 ));
7288
7289 self.highlight_rows::<EditPredictionPreview>(
7290 target..target,
7291 cx.theme().colors().editor_highlighted_line_background,
7292 RowHighlightOptions {
7293 autoscroll: true,
7294 ..Default::default()
7295 },
7296 cx,
7297 );
7298 self.request_autoscroll(Autoscroll::fit(), cx);
7299 }
7300 }
7301 }
7302 EditPrediction::MoveOutside { snapshot, target } => {
7303 if let Some(workspace) = self.workspace() {
7304 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7305 .detach_and_log_err(cx);
7306 }
7307 }
7308 EditPrediction::Edit { edits, .. } => {
7309 self.report_edit_prediction_event(
7310 active_edit_prediction.completion_id.clone(),
7311 true,
7312 cx,
7313 );
7314
7315 if let Some(provider) = self.edit_prediction_provider() {
7316 provider.accept(cx);
7317 }
7318
7319 // Store the transaction ID and selections before applying the edit
7320 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7321
7322 let snapshot = self.buffer.read(cx).snapshot(cx);
7323 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7324
7325 self.buffer.update(cx, |buffer, cx| {
7326 buffer.edit(edits.iter().cloned(), None, cx)
7327 });
7328
7329 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7330 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7331 });
7332
7333 let selections = self.selections.disjoint_anchors_arc();
7334 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7335 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7336 if has_new_transaction {
7337 self.selection_history
7338 .insert_transaction(transaction_id_now, selections);
7339 }
7340 }
7341
7342 self.update_visible_edit_prediction(window, cx);
7343 if self.active_edit_prediction.is_none() {
7344 self.refresh_edit_prediction(true, true, window, cx);
7345 }
7346
7347 cx.notify();
7348 }
7349 }
7350
7351 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7352 }
7353
7354 pub fn accept_partial_edit_prediction(
7355 &mut self,
7356 _: &AcceptPartialEditPrediction,
7357 window: &mut Window,
7358 cx: &mut Context<Self>,
7359 ) {
7360 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7361 return;
7362 };
7363 if self.selections.count() != 1 {
7364 return;
7365 }
7366
7367 match &active_edit_prediction.completion {
7368 EditPrediction::MoveWithin { target, .. } => {
7369 let target = *target;
7370 self.change_selections(
7371 SelectionEffects::scroll(Autoscroll::newest()),
7372 window,
7373 cx,
7374 |selections| {
7375 selections.select_anchor_ranges([target..target]);
7376 },
7377 );
7378 }
7379 EditPrediction::MoveOutside { snapshot, target } => {
7380 if let Some(workspace) = self.workspace() {
7381 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7382 .detach_and_log_err(cx);
7383 }
7384 }
7385 EditPrediction::Edit { edits, .. } => {
7386 self.report_edit_prediction_event(
7387 active_edit_prediction.completion_id.clone(),
7388 true,
7389 cx,
7390 );
7391
7392 // Find an insertion that starts at the cursor position.
7393 let snapshot = self.buffer.read(cx).snapshot(cx);
7394 let cursor_offset = self
7395 .selections
7396 .newest::<usize>(&self.display_snapshot(cx))
7397 .head();
7398 let insertion = edits.iter().find_map(|(range, text)| {
7399 let range = range.to_offset(&snapshot);
7400 if range.is_empty() && range.start == cursor_offset {
7401 Some(text)
7402 } else {
7403 None
7404 }
7405 });
7406
7407 if let Some(text) = insertion {
7408 let mut partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_alphabetic())
7412 .collect::<String>();
7413 if partial_completion.is_empty() {
7414 partial_completion = text
7415 .chars()
7416 .by_ref()
7417 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7418 .collect::<String>();
7419 }
7420
7421 cx.emit(EditorEvent::InputHandled {
7422 utf16_range_to_replace: None,
7423 text: partial_completion.clone().into(),
7424 });
7425
7426 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7427
7428 self.refresh_edit_prediction(true, true, window, cx);
7429 cx.notify();
7430 } else {
7431 self.accept_edit_prediction(&Default::default(), window, cx);
7432 }
7433 }
7434 }
7435 }
7436
7437 fn discard_edit_prediction(
7438 &mut self,
7439 should_report_edit_prediction_event: bool,
7440 cx: &mut Context<Self>,
7441 ) -> bool {
7442 if should_report_edit_prediction_event {
7443 let completion_id = self
7444 .active_edit_prediction
7445 .as_ref()
7446 .and_then(|active_completion| active_completion.completion_id.clone());
7447
7448 self.report_edit_prediction_event(completion_id, false, cx);
7449 }
7450
7451 if let Some(provider) = self.edit_prediction_provider() {
7452 provider.discard(cx);
7453 }
7454
7455 self.take_active_edit_prediction(cx)
7456 }
7457
7458 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7459 let Some(provider) = self.edit_prediction_provider() else {
7460 return;
7461 };
7462
7463 let Some((_, buffer, _)) = self
7464 .buffer
7465 .read(cx)
7466 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7467 else {
7468 return;
7469 };
7470
7471 let extension = buffer
7472 .read(cx)
7473 .file()
7474 .and_then(|file| Some(file.path().extension()?.to_string()));
7475
7476 let event_type = match accepted {
7477 true => "Edit Prediction Accepted",
7478 false => "Edit Prediction Discarded",
7479 };
7480 telemetry::event!(
7481 event_type,
7482 provider = provider.name(),
7483 prediction_id = id,
7484 suggestion_accepted = accepted,
7485 file_extension = extension,
7486 );
7487 }
7488
7489 fn open_editor_at_anchor(
7490 snapshot: &language::BufferSnapshot,
7491 target: language::Anchor,
7492 workspace: &Entity<Workspace>,
7493 window: &mut Window,
7494 cx: &mut App,
7495 ) -> Task<Result<()>> {
7496 workspace.update(cx, |workspace, cx| {
7497 let path = snapshot.file().map(|file| file.full_path(cx));
7498 let Some(path) =
7499 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7500 else {
7501 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7502 };
7503 let target = text::ToPoint::to_point(&target, snapshot);
7504 let item = workspace.open_path(path, None, true, window, cx);
7505 window.spawn(cx, async move |cx| {
7506 let Some(editor) = item.await?.downcast::<Editor>() else {
7507 return Ok(());
7508 };
7509 editor
7510 .update_in(cx, |editor, window, cx| {
7511 editor.go_to_singleton_buffer_point(target, window, cx);
7512 })
7513 .ok();
7514 anyhow::Ok(())
7515 })
7516 })
7517 }
7518
7519 pub fn has_active_edit_prediction(&self) -> bool {
7520 self.active_edit_prediction.is_some()
7521 }
7522
7523 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7524 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7525 return false;
7526 };
7527
7528 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7529 self.clear_highlights::<EditPredictionHighlight>(cx);
7530 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7531 true
7532 }
7533
7534 /// Returns true when we're displaying the edit prediction popover below the cursor
7535 /// like we are not previewing and the LSP autocomplete menu is visible
7536 /// or we are in `when_holding_modifier` mode.
7537 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7538 if self.edit_prediction_preview_is_active()
7539 || !self.show_edit_predictions_in_menu()
7540 || !self.edit_predictions_enabled()
7541 {
7542 return false;
7543 }
7544
7545 if self.has_visible_completions_menu() {
7546 return true;
7547 }
7548
7549 has_completion && self.edit_prediction_requires_modifier()
7550 }
7551
7552 fn handle_modifiers_changed(
7553 &mut self,
7554 modifiers: Modifiers,
7555 position_map: &PositionMap,
7556 window: &mut Window,
7557 cx: &mut Context<Self>,
7558 ) {
7559 if self.show_edit_predictions_in_menu() {
7560 self.update_edit_prediction_preview(&modifiers, window, cx);
7561 }
7562
7563 self.update_selection_mode(&modifiers, position_map, window, cx);
7564
7565 let mouse_position = window.mouse_position();
7566 if !position_map.text_hitbox.is_hovered(window) {
7567 return;
7568 }
7569
7570 self.update_hovered_link(
7571 position_map.point_for_position(mouse_position),
7572 &position_map.snapshot,
7573 modifiers,
7574 window,
7575 cx,
7576 )
7577 }
7578
7579 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7580 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7581 if invert {
7582 match multi_cursor_setting {
7583 MultiCursorModifier::Alt => modifiers.alt,
7584 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7585 }
7586 } else {
7587 match multi_cursor_setting {
7588 MultiCursorModifier::Alt => modifiers.secondary(),
7589 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7590 }
7591 }
7592 }
7593
7594 fn columnar_selection_mode(
7595 modifiers: &Modifiers,
7596 cx: &mut Context<Self>,
7597 ) -> Option<ColumnarMode> {
7598 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7599 if Self::multi_cursor_modifier(false, modifiers, cx) {
7600 Some(ColumnarMode::FromMouse)
7601 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7602 Some(ColumnarMode::FromSelection)
7603 } else {
7604 None
7605 }
7606 } else {
7607 None
7608 }
7609 }
7610
7611 fn update_selection_mode(
7612 &mut self,
7613 modifiers: &Modifiers,
7614 position_map: &PositionMap,
7615 window: &mut Window,
7616 cx: &mut Context<Self>,
7617 ) {
7618 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7619 return;
7620 };
7621 if self.selections.pending_anchor().is_none() {
7622 return;
7623 }
7624
7625 let mouse_position = window.mouse_position();
7626 let point_for_position = position_map.point_for_position(mouse_position);
7627 let position = point_for_position.previous_valid;
7628
7629 self.select(
7630 SelectPhase::BeginColumnar {
7631 position,
7632 reset: false,
7633 mode,
7634 goal_column: point_for_position.exact_unclipped.column(),
7635 },
7636 window,
7637 cx,
7638 );
7639 }
7640
7641 fn update_edit_prediction_preview(
7642 &mut self,
7643 modifiers: &Modifiers,
7644 window: &mut Window,
7645 cx: &mut Context<Self>,
7646 ) {
7647 let mut modifiers_held = false;
7648 if let Some(accept_keystroke) = self
7649 .accept_edit_prediction_keybind(false, window, cx)
7650 .keystroke()
7651 {
7652 modifiers_held = modifiers_held
7653 || (accept_keystroke.modifiers() == modifiers
7654 && accept_keystroke.modifiers().modified());
7655 };
7656 if let Some(accept_partial_keystroke) = self
7657 .accept_edit_prediction_keybind(true, window, cx)
7658 .keystroke()
7659 {
7660 modifiers_held = modifiers_held
7661 || (accept_partial_keystroke.modifiers() == modifiers
7662 && accept_partial_keystroke.modifiers().modified());
7663 }
7664
7665 if modifiers_held {
7666 if matches!(
7667 self.edit_prediction_preview,
7668 EditPredictionPreview::Inactive { .. }
7669 ) {
7670 self.edit_prediction_preview = EditPredictionPreview::Active {
7671 previous_scroll_position: None,
7672 since: Instant::now(),
7673 };
7674
7675 self.update_visible_edit_prediction(window, cx);
7676 cx.notify();
7677 }
7678 } else if let EditPredictionPreview::Active {
7679 previous_scroll_position,
7680 since,
7681 } = self.edit_prediction_preview
7682 {
7683 if let (Some(previous_scroll_position), Some(position_map)) =
7684 (previous_scroll_position, self.last_position_map.as_ref())
7685 {
7686 self.set_scroll_position(
7687 previous_scroll_position
7688 .scroll_position(&position_map.snapshot.display_snapshot),
7689 window,
7690 cx,
7691 );
7692 }
7693
7694 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7695 released_too_fast: since.elapsed() < Duration::from_millis(200),
7696 };
7697 self.clear_row_highlights::<EditPredictionPreview>();
7698 self.update_visible_edit_prediction(window, cx);
7699 cx.notify();
7700 }
7701 }
7702
7703 fn update_visible_edit_prediction(
7704 &mut self,
7705 _window: &mut Window,
7706 cx: &mut Context<Self>,
7707 ) -> Option<()> {
7708 if DisableAiSettings::get_global(cx).disable_ai {
7709 return None;
7710 }
7711
7712 if self.ime_transaction.is_some() {
7713 self.discard_edit_prediction(false, cx);
7714 return None;
7715 }
7716
7717 let selection = self.selections.newest_anchor();
7718 let cursor = selection.head();
7719 let multibuffer = self.buffer.read(cx).snapshot(cx);
7720 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7721 let excerpt_id = cursor.excerpt_id;
7722
7723 let show_in_menu = self.show_edit_predictions_in_menu();
7724 let completions_menu_has_precedence = !show_in_menu
7725 && (self.context_menu.borrow().is_some()
7726 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7727
7728 if completions_menu_has_precedence
7729 || !offset_selection.is_empty()
7730 || self
7731 .active_edit_prediction
7732 .as_ref()
7733 .is_some_and(|completion| {
7734 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7735 return false;
7736 };
7737 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7738 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7739 !invalidation_range.contains(&offset_selection.head())
7740 })
7741 {
7742 self.discard_edit_prediction(false, cx);
7743 return None;
7744 }
7745
7746 self.take_active_edit_prediction(cx);
7747 let Some(provider) = self.edit_prediction_provider() else {
7748 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7749 return None;
7750 };
7751
7752 let (buffer, cursor_buffer_position) =
7753 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7754
7755 self.edit_prediction_settings =
7756 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7757
7758 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7759
7760 if self.edit_prediction_indent_conflict {
7761 let cursor_point = cursor.to_point(&multibuffer);
7762
7763 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7764
7765 if let Some((_, indent)) = indents.iter().next()
7766 && indent.len == cursor_point.column
7767 {
7768 self.edit_prediction_indent_conflict = false;
7769 }
7770 }
7771
7772 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7773
7774 let (completion_id, edits, edit_preview) = match edit_prediction {
7775 edit_prediction::EditPrediction::Local {
7776 id,
7777 edits,
7778 edit_preview,
7779 } => (id, edits, edit_preview),
7780 edit_prediction::EditPrediction::Jump {
7781 id,
7782 snapshot,
7783 target,
7784 } => {
7785 self.stale_edit_prediction_in_menu = None;
7786 self.active_edit_prediction = Some(EditPredictionState {
7787 inlay_ids: vec![],
7788 completion: EditPrediction::MoveOutside { snapshot, target },
7789 completion_id: id,
7790 invalidation_range: None,
7791 });
7792 cx.notify();
7793 return Some(());
7794 }
7795 };
7796
7797 let edits = edits
7798 .into_iter()
7799 .flat_map(|(range, new_text)| {
7800 Some((
7801 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7802 new_text,
7803 ))
7804 })
7805 .collect::<Vec<_>>();
7806 if edits.is_empty() {
7807 return None;
7808 }
7809
7810 let first_edit_start = edits.first().unwrap().0.start;
7811 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7812 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7813
7814 let last_edit_end = edits.last().unwrap().0.end;
7815 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7816 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7817
7818 let cursor_row = cursor.to_point(&multibuffer).row;
7819
7820 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7821
7822 let mut inlay_ids = Vec::new();
7823 let invalidation_row_range;
7824 let move_invalidation_row_range = if cursor_row < edit_start_row {
7825 Some(cursor_row..edit_end_row)
7826 } else if cursor_row > edit_end_row {
7827 Some(edit_start_row..cursor_row)
7828 } else {
7829 None
7830 };
7831 let supports_jump = self
7832 .edit_prediction_provider
7833 .as_ref()
7834 .map(|provider| provider.provider.supports_jump_to_edit())
7835 .unwrap_or(true);
7836
7837 let is_move = supports_jump
7838 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7839 let completion = if is_move {
7840 invalidation_row_range =
7841 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7842 let target = first_edit_start;
7843 EditPrediction::MoveWithin { target, snapshot }
7844 } else {
7845 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7846 && !self.edit_predictions_hidden_for_vim_mode;
7847
7848 if show_completions_in_buffer {
7849 if edits
7850 .iter()
7851 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7852 {
7853 let mut inlays = Vec::new();
7854 for (range, new_text) in &edits {
7855 let inlay = Inlay::edit_prediction(
7856 post_inc(&mut self.next_inlay_id),
7857 range.start,
7858 new_text.as_str(),
7859 );
7860 inlay_ids.push(inlay.id);
7861 inlays.push(inlay);
7862 }
7863
7864 self.splice_inlays(&[], inlays, cx);
7865 } else {
7866 let background_color = cx.theme().status().deleted_background;
7867 self.highlight_text::<EditPredictionHighlight>(
7868 edits.iter().map(|(range, _)| range.clone()).collect(),
7869 HighlightStyle {
7870 background_color: Some(background_color),
7871 ..Default::default()
7872 },
7873 cx,
7874 );
7875 }
7876 }
7877
7878 invalidation_row_range = edit_start_row..edit_end_row;
7879
7880 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7881 if provider.show_tab_accept_marker() {
7882 EditDisplayMode::TabAccept
7883 } else {
7884 EditDisplayMode::Inline
7885 }
7886 } else {
7887 EditDisplayMode::DiffPopover
7888 };
7889
7890 EditPrediction::Edit {
7891 edits,
7892 edit_preview,
7893 display_mode,
7894 snapshot,
7895 }
7896 };
7897
7898 let invalidation_range = multibuffer
7899 .anchor_before(Point::new(invalidation_row_range.start, 0))
7900 ..multibuffer.anchor_after(Point::new(
7901 invalidation_row_range.end,
7902 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7903 ));
7904
7905 self.stale_edit_prediction_in_menu = None;
7906 self.active_edit_prediction = Some(EditPredictionState {
7907 inlay_ids,
7908 completion,
7909 completion_id,
7910 invalidation_range: Some(invalidation_range),
7911 });
7912
7913 cx.notify();
7914
7915 Some(())
7916 }
7917
7918 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7919 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7920 }
7921
7922 fn clear_tasks(&mut self) {
7923 self.tasks.clear()
7924 }
7925
7926 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7927 if self.tasks.insert(key, value).is_some() {
7928 // This case should hopefully be rare, but just in case...
7929 log::error!(
7930 "multiple different run targets found on a single line, only the last target will be rendered"
7931 )
7932 }
7933 }
7934
7935 /// Get all display points of breakpoints that will be rendered within editor
7936 ///
7937 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7938 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7939 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7940 fn active_breakpoints(
7941 &self,
7942 range: Range<DisplayRow>,
7943 window: &mut Window,
7944 cx: &mut Context<Self>,
7945 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7946 let mut breakpoint_display_points = HashMap::default();
7947
7948 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7949 return breakpoint_display_points;
7950 };
7951
7952 let snapshot = self.snapshot(window, cx);
7953
7954 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7955 let Some(project) = self.project() else {
7956 return breakpoint_display_points;
7957 };
7958
7959 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7960 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7961
7962 for (buffer_snapshot, range, excerpt_id) in
7963 multi_buffer_snapshot.range_to_buffer_ranges(range)
7964 {
7965 let Some(buffer) = project
7966 .read(cx)
7967 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7968 else {
7969 continue;
7970 };
7971 let breakpoints = breakpoint_store.read(cx).breakpoints(
7972 &buffer,
7973 Some(
7974 buffer_snapshot.anchor_before(range.start)
7975 ..buffer_snapshot.anchor_after(range.end),
7976 ),
7977 buffer_snapshot,
7978 cx,
7979 );
7980 for (breakpoint, state) in breakpoints {
7981 let multi_buffer_anchor =
7982 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7983 let position = multi_buffer_anchor
7984 .to_point(&multi_buffer_snapshot)
7985 .to_display_point(&snapshot);
7986
7987 breakpoint_display_points.insert(
7988 position.row(),
7989 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7990 );
7991 }
7992 }
7993
7994 breakpoint_display_points
7995 }
7996
7997 fn breakpoint_context_menu(
7998 &self,
7999 anchor: Anchor,
8000 window: &mut Window,
8001 cx: &mut Context<Self>,
8002 ) -> Entity<ui::ContextMenu> {
8003 let weak_editor = cx.weak_entity();
8004 let focus_handle = self.focus_handle(cx);
8005
8006 let row = self
8007 .buffer
8008 .read(cx)
8009 .snapshot(cx)
8010 .summary_for_anchor::<Point>(&anchor)
8011 .row;
8012
8013 let breakpoint = self
8014 .breakpoint_at_row(row, window, cx)
8015 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8016
8017 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8018 "Edit Log Breakpoint"
8019 } else {
8020 "Set Log Breakpoint"
8021 };
8022
8023 let condition_breakpoint_msg = if breakpoint
8024 .as_ref()
8025 .is_some_and(|bp| bp.1.condition.is_some())
8026 {
8027 "Edit Condition Breakpoint"
8028 } else {
8029 "Set Condition Breakpoint"
8030 };
8031
8032 let hit_condition_breakpoint_msg = if breakpoint
8033 .as_ref()
8034 .is_some_and(|bp| bp.1.hit_condition.is_some())
8035 {
8036 "Edit Hit Condition Breakpoint"
8037 } else {
8038 "Set Hit Condition Breakpoint"
8039 };
8040
8041 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8042 "Unset Breakpoint"
8043 } else {
8044 "Set Breakpoint"
8045 };
8046
8047 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8048
8049 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8050 BreakpointState::Enabled => Some("Disable"),
8051 BreakpointState::Disabled => Some("Enable"),
8052 });
8053
8054 let (anchor, breakpoint) =
8055 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8056
8057 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8058 menu.on_blur_subscription(Subscription::new(|| {}))
8059 .context(focus_handle)
8060 .when(run_to_cursor, |this| {
8061 let weak_editor = weak_editor.clone();
8062 this.entry("Run to cursor", None, move |window, cx| {
8063 weak_editor
8064 .update(cx, |editor, cx| {
8065 editor.change_selections(
8066 SelectionEffects::no_scroll(),
8067 window,
8068 cx,
8069 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8070 );
8071 })
8072 .ok();
8073
8074 window.dispatch_action(Box::new(RunToCursor), cx);
8075 })
8076 .separator()
8077 })
8078 .when_some(toggle_state_msg, |this, msg| {
8079 this.entry(msg, None, {
8080 let weak_editor = weak_editor.clone();
8081 let breakpoint = breakpoint.clone();
8082 move |_window, cx| {
8083 weak_editor
8084 .update(cx, |this, cx| {
8085 this.edit_breakpoint_at_anchor(
8086 anchor,
8087 breakpoint.as_ref().clone(),
8088 BreakpointEditAction::InvertState,
8089 cx,
8090 );
8091 })
8092 .log_err();
8093 }
8094 })
8095 })
8096 .entry(set_breakpoint_msg, None, {
8097 let weak_editor = weak_editor.clone();
8098 let breakpoint = breakpoint.clone();
8099 move |_window, cx| {
8100 weak_editor
8101 .update(cx, |this, cx| {
8102 this.edit_breakpoint_at_anchor(
8103 anchor,
8104 breakpoint.as_ref().clone(),
8105 BreakpointEditAction::Toggle,
8106 cx,
8107 );
8108 })
8109 .log_err();
8110 }
8111 })
8112 .entry(log_breakpoint_msg, None, {
8113 let breakpoint = breakpoint.clone();
8114 let weak_editor = weak_editor.clone();
8115 move |window, cx| {
8116 weak_editor
8117 .update(cx, |this, cx| {
8118 this.add_edit_breakpoint_block(
8119 anchor,
8120 breakpoint.as_ref(),
8121 BreakpointPromptEditAction::Log,
8122 window,
8123 cx,
8124 );
8125 })
8126 .log_err();
8127 }
8128 })
8129 .entry(condition_breakpoint_msg, None, {
8130 let breakpoint = breakpoint.clone();
8131 let weak_editor = weak_editor.clone();
8132 move |window, cx| {
8133 weak_editor
8134 .update(cx, |this, cx| {
8135 this.add_edit_breakpoint_block(
8136 anchor,
8137 breakpoint.as_ref(),
8138 BreakpointPromptEditAction::Condition,
8139 window,
8140 cx,
8141 );
8142 })
8143 .log_err();
8144 }
8145 })
8146 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8147 weak_editor
8148 .update(cx, |this, cx| {
8149 this.add_edit_breakpoint_block(
8150 anchor,
8151 breakpoint.as_ref(),
8152 BreakpointPromptEditAction::HitCondition,
8153 window,
8154 cx,
8155 );
8156 })
8157 .log_err();
8158 })
8159 })
8160 }
8161
8162 fn render_breakpoint(
8163 &self,
8164 position: Anchor,
8165 row: DisplayRow,
8166 breakpoint: &Breakpoint,
8167 state: Option<BreakpointSessionState>,
8168 cx: &mut Context<Self>,
8169 ) -> IconButton {
8170 let is_rejected = state.is_some_and(|s| !s.verified);
8171 // Is it a breakpoint that shows up when hovering over gutter?
8172 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8173 (false, false),
8174 |PhantomBreakpointIndicator {
8175 is_active,
8176 display_row,
8177 collides_with_existing_breakpoint,
8178 }| {
8179 (
8180 is_active && display_row == row,
8181 collides_with_existing_breakpoint,
8182 )
8183 },
8184 );
8185
8186 let (color, icon) = {
8187 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8188 (false, false) => ui::IconName::DebugBreakpoint,
8189 (true, false) => ui::IconName::DebugLogBreakpoint,
8190 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8191 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8192 };
8193
8194 let color = if is_phantom {
8195 Color::Hint
8196 } else if is_rejected {
8197 Color::Disabled
8198 } else {
8199 Color::Debugger
8200 };
8201
8202 (color, icon)
8203 };
8204
8205 let breakpoint = Arc::from(breakpoint.clone());
8206
8207 let alt_as_text = gpui::Keystroke {
8208 modifiers: Modifiers::secondary_key(),
8209 ..Default::default()
8210 };
8211 let primary_action_text = if breakpoint.is_disabled() {
8212 "Enable breakpoint"
8213 } else if is_phantom && !collides_with_existing {
8214 "Set breakpoint"
8215 } else {
8216 "Unset breakpoint"
8217 };
8218 let focus_handle = self.focus_handle.clone();
8219
8220 let meta = if is_rejected {
8221 SharedString::from("No executable code is associated with this line.")
8222 } else if collides_with_existing && !breakpoint.is_disabled() {
8223 SharedString::from(format!(
8224 "{alt_as_text}-click to disable,\nright-click for more options."
8225 ))
8226 } else {
8227 SharedString::from("Right-click for more options.")
8228 };
8229 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8230 .icon_size(IconSize::XSmall)
8231 .size(ui::ButtonSize::None)
8232 .when(is_rejected, |this| {
8233 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8234 })
8235 .icon_color(color)
8236 .style(ButtonStyle::Transparent)
8237 .on_click(cx.listener({
8238 move |editor, event: &ClickEvent, window, cx| {
8239 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8240 BreakpointEditAction::InvertState
8241 } else {
8242 BreakpointEditAction::Toggle
8243 };
8244
8245 window.focus(&editor.focus_handle(cx));
8246 editor.edit_breakpoint_at_anchor(
8247 position,
8248 breakpoint.as_ref().clone(),
8249 edit_action,
8250 cx,
8251 );
8252 }
8253 }))
8254 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8255 editor.set_breakpoint_context_menu(
8256 row,
8257 Some(position),
8258 event.position(),
8259 window,
8260 cx,
8261 );
8262 }))
8263 .tooltip(move |_window, cx| {
8264 Tooltip::with_meta_in(
8265 primary_action_text,
8266 Some(&ToggleBreakpoint),
8267 meta.clone(),
8268 &focus_handle,
8269 cx,
8270 )
8271 })
8272 }
8273
8274 fn build_tasks_context(
8275 project: &Entity<Project>,
8276 buffer: &Entity<Buffer>,
8277 buffer_row: u32,
8278 tasks: &Arc<RunnableTasks>,
8279 cx: &mut Context<Self>,
8280 ) -> Task<Option<task::TaskContext>> {
8281 let position = Point::new(buffer_row, tasks.column);
8282 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8283 let location = Location {
8284 buffer: buffer.clone(),
8285 range: range_start..range_start,
8286 };
8287 // Fill in the environmental variables from the tree-sitter captures
8288 let mut captured_task_variables = TaskVariables::default();
8289 for (capture_name, value) in tasks.extra_variables.clone() {
8290 captured_task_variables.insert(
8291 task::VariableName::Custom(capture_name.into()),
8292 value.clone(),
8293 );
8294 }
8295 project.update(cx, |project, cx| {
8296 project.task_store().update(cx, |task_store, cx| {
8297 task_store.task_context_for_location(captured_task_variables, location, cx)
8298 })
8299 })
8300 }
8301
8302 pub fn spawn_nearest_task(
8303 &mut self,
8304 action: &SpawnNearestTask,
8305 window: &mut Window,
8306 cx: &mut Context<Self>,
8307 ) {
8308 let Some((workspace, _)) = self.workspace.clone() else {
8309 return;
8310 };
8311 let Some(project) = self.project.clone() else {
8312 return;
8313 };
8314
8315 // Try to find a closest, enclosing node using tree-sitter that has a task
8316 let Some((buffer, buffer_row, tasks)) = self
8317 .find_enclosing_node_task(cx)
8318 // Or find the task that's closest in row-distance.
8319 .or_else(|| self.find_closest_task(cx))
8320 else {
8321 return;
8322 };
8323
8324 let reveal_strategy = action.reveal;
8325 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8326 cx.spawn_in(window, async move |_, cx| {
8327 let context = task_context.await?;
8328 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8329
8330 let resolved = &mut resolved_task.resolved;
8331 resolved.reveal = reveal_strategy;
8332
8333 workspace
8334 .update_in(cx, |workspace, window, cx| {
8335 workspace.schedule_resolved_task(
8336 task_source_kind,
8337 resolved_task,
8338 false,
8339 window,
8340 cx,
8341 );
8342 })
8343 .ok()
8344 })
8345 .detach();
8346 }
8347
8348 fn find_closest_task(
8349 &mut self,
8350 cx: &mut Context<Self>,
8351 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8352 let cursor_row = self
8353 .selections
8354 .newest_adjusted(&self.display_snapshot(cx))
8355 .head()
8356 .row;
8357
8358 let ((buffer_id, row), tasks) = self
8359 .tasks
8360 .iter()
8361 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8362
8363 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8364 let tasks = Arc::new(tasks.to_owned());
8365 Some((buffer, *row, tasks))
8366 }
8367
8368 fn find_enclosing_node_task(
8369 &mut self,
8370 cx: &mut Context<Self>,
8371 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8372 let snapshot = self.buffer.read(cx).snapshot(cx);
8373 let offset = self
8374 .selections
8375 .newest::<usize>(&self.display_snapshot(cx))
8376 .head();
8377 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8378 let buffer_id = excerpt.buffer().remote_id();
8379
8380 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8381 let mut cursor = layer.node().walk();
8382
8383 while cursor.goto_first_child_for_byte(offset).is_some() {
8384 if cursor.node().end_byte() == offset {
8385 cursor.goto_next_sibling();
8386 }
8387 }
8388
8389 // Ascend to the smallest ancestor that contains the range and has a task.
8390 loop {
8391 let node = cursor.node();
8392 let node_range = node.byte_range();
8393 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8394
8395 // Check if this node contains our offset
8396 if node_range.start <= offset && node_range.end >= offset {
8397 // If it contains offset, check for task
8398 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8399 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8400 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8401 }
8402 }
8403
8404 if !cursor.goto_parent() {
8405 break;
8406 }
8407 }
8408 None
8409 }
8410
8411 fn render_run_indicator(
8412 &self,
8413 _style: &EditorStyle,
8414 is_active: bool,
8415 row: DisplayRow,
8416 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8417 cx: &mut Context<Self>,
8418 ) -> IconButton {
8419 let color = Color::Muted;
8420 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8421
8422 IconButton::new(
8423 ("run_indicator", row.0 as usize),
8424 ui::IconName::PlayOutlined,
8425 )
8426 .shape(ui::IconButtonShape::Square)
8427 .icon_size(IconSize::XSmall)
8428 .icon_color(color)
8429 .toggle_state(is_active)
8430 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8431 let quick_launch = match e {
8432 ClickEvent::Keyboard(_) => true,
8433 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8434 };
8435
8436 window.focus(&editor.focus_handle(cx));
8437 editor.toggle_code_actions(
8438 &ToggleCodeActions {
8439 deployed_from: Some(CodeActionSource::RunMenu(row)),
8440 quick_launch,
8441 },
8442 window,
8443 cx,
8444 );
8445 }))
8446 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8447 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8448 }))
8449 }
8450
8451 pub fn context_menu_visible(&self) -> bool {
8452 !self.edit_prediction_preview_is_active()
8453 && self
8454 .context_menu
8455 .borrow()
8456 .as_ref()
8457 .is_some_and(|menu| menu.visible())
8458 }
8459
8460 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8461 self.context_menu
8462 .borrow()
8463 .as_ref()
8464 .map(|menu| menu.origin())
8465 }
8466
8467 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8468 self.context_menu_options = Some(options);
8469 }
8470
8471 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8472 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8473
8474 fn render_edit_prediction_popover(
8475 &mut self,
8476 text_bounds: &Bounds<Pixels>,
8477 content_origin: gpui::Point<Pixels>,
8478 right_margin: Pixels,
8479 editor_snapshot: &EditorSnapshot,
8480 visible_row_range: Range<DisplayRow>,
8481 scroll_top: ScrollOffset,
8482 scroll_bottom: ScrollOffset,
8483 line_layouts: &[LineWithInvisibles],
8484 line_height: Pixels,
8485 scroll_position: gpui::Point<ScrollOffset>,
8486 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8487 newest_selection_head: Option<DisplayPoint>,
8488 editor_width: Pixels,
8489 style: &EditorStyle,
8490 window: &mut Window,
8491 cx: &mut App,
8492 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8493 if self.mode().is_minimap() {
8494 return None;
8495 }
8496 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8497
8498 if self.edit_prediction_visible_in_cursor_popover(true) {
8499 return None;
8500 }
8501
8502 match &active_edit_prediction.completion {
8503 EditPrediction::MoveWithin { target, .. } => {
8504 let target_display_point = target.to_display_point(editor_snapshot);
8505
8506 if self.edit_prediction_requires_modifier() {
8507 if !self.edit_prediction_preview_is_active() {
8508 return None;
8509 }
8510
8511 self.render_edit_prediction_modifier_jump_popover(
8512 text_bounds,
8513 content_origin,
8514 visible_row_range,
8515 line_layouts,
8516 line_height,
8517 scroll_pixel_position,
8518 newest_selection_head,
8519 target_display_point,
8520 window,
8521 cx,
8522 )
8523 } else {
8524 self.render_edit_prediction_eager_jump_popover(
8525 text_bounds,
8526 content_origin,
8527 editor_snapshot,
8528 visible_row_range,
8529 scroll_top,
8530 scroll_bottom,
8531 line_height,
8532 scroll_pixel_position,
8533 target_display_point,
8534 editor_width,
8535 window,
8536 cx,
8537 )
8538 }
8539 }
8540 EditPrediction::Edit {
8541 display_mode: EditDisplayMode::Inline,
8542 ..
8543 } => None,
8544 EditPrediction::Edit {
8545 display_mode: EditDisplayMode::TabAccept,
8546 edits,
8547 ..
8548 } => {
8549 let range = &edits.first()?.0;
8550 let target_display_point = range.end.to_display_point(editor_snapshot);
8551
8552 self.render_edit_prediction_end_of_line_popover(
8553 "Accept",
8554 editor_snapshot,
8555 visible_row_range,
8556 target_display_point,
8557 line_height,
8558 scroll_pixel_position,
8559 content_origin,
8560 editor_width,
8561 window,
8562 cx,
8563 )
8564 }
8565 EditPrediction::Edit {
8566 edits,
8567 edit_preview,
8568 display_mode: EditDisplayMode::DiffPopover,
8569 snapshot,
8570 } => self.render_edit_prediction_diff_popover(
8571 text_bounds,
8572 content_origin,
8573 right_margin,
8574 editor_snapshot,
8575 visible_row_range,
8576 line_layouts,
8577 line_height,
8578 scroll_position,
8579 scroll_pixel_position,
8580 newest_selection_head,
8581 editor_width,
8582 style,
8583 edits,
8584 edit_preview,
8585 snapshot,
8586 window,
8587 cx,
8588 ),
8589 EditPrediction::MoveOutside { snapshot, .. } => {
8590 let file_name = snapshot
8591 .file()
8592 .map(|file| file.file_name(cx))
8593 .unwrap_or("untitled");
8594 let mut element = self
8595 .render_edit_prediction_line_popover(
8596 format!("Jump to {file_name}"),
8597 Some(IconName::ZedPredict),
8598 window,
8599 cx,
8600 )
8601 .into_any();
8602
8603 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8604 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8605 let origin_y = text_bounds.size.height - size.height - px(30.);
8606 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8607 element.prepaint_at(origin, window, cx);
8608
8609 Some((element, origin))
8610 }
8611 }
8612 }
8613
8614 fn render_edit_prediction_modifier_jump_popover(
8615 &mut self,
8616 text_bounds: &Bounds<Pixels>,
8617 content_origin: gpui::Point<Pixels>,
8618 visible_row_range: Range<DisplayRow>,
8619 line_layouts: &[LineWithInvisibles],
8620 line_height: Pixels,
8621 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8622 newest_selection_head: Option<DisplayPoint>,
8623 target_display_point: DisplayPoint,
8624 window: &mut Window,
8625 cx: &mut App,
8626 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8627 let scrolled_content_origin =
8628 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8629
8630 const SCROLL_PADDING_Y: Pixels = px(12.);
8631
8632 if target_display_point.row() < visible_row_range.start {
8633 return self.render_edit_prediction_scroll_popover(
8634 |_| SCROLL_PADDING_Y,
8635 IconName::ArrowUp,
8636 visible_row_range,
8637 line_layouts,
8638 newest_selection_head,
8639 scrolled_content_origin,
8640 window,
8641 cx,
8642 );
8643 } else if target_display_point.row() >= visible_row_range.end {
8644 return self.render_edit_prediction_scroll_popover(
8645 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8646 IconName::ArrowDown,
8647 visible_row_range,
8648 line_layouts,
8649 newest_selection_head,
8650 scrolled_content_origin,
8651 window,
8652 cx,
8653 );
8654 }
8655
8656 const POLE_WIDTH: Pixels = px(2.);
8657
8658 let line_layout =
8659 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8660 let target_column = target_display_point.column() as usize;
8661
8662 let target_x = line_layout.x_for_index(target_column);
8663 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8664 - scroll_pixel_position.y;
8665
8666 let flag_on_right = target_x < text_bounds.size.width / 2.;
8667
8668 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8669 border_color.l += 0.001;
8670
8671 let mut element = v_flex()
8672 .items_end()
8673 .when(flag_on_right, |el| el.items_start())
8674 .child(if flag_on_right {
8675 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8676 .rounded_bl(px(0.))
8677 .rounded_tl(px(0.))
8678 .border_l_2()
8679 .border_color(border_color)
8680 } else {
8681 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8682 .rounded_br(px(0.))
8683 .rounded_tr(px(0.))
8684 .border_r_2()
8685 .border_color(border_color)
8686 })
8687 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8688 .into_any();
8689
8690 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8691
8692 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8693 - point(
8694 if flag_on_right {
8695 POLE_WIDTH
8696 } else {
8697 size.width - POLE_WIDTH
8698 },
8699 size.height - line_height,
8700 );
8701
8702 origin.x = origin.x.max(content_origin.x);
8703
8704 element.prepaint_at(origin, window, cx);
8705
8706 Some((element, origin))
8707 }
8708
8709 fn render_edit_prediction_scroll_popover(
8710 &mut self,
8711 to_y: impl Fn(Size<Pixels>) -> Pixels,
8712 scroll_icon: IconName,
8713 visible_row_range: Range<DisplayRow>,
8714 line_layouts: &[LineWithInvisibles],
8715 newest_selection_head: Option<DisplayPoint>,
8716 scrolled_content_origin: gpui::Point<Pixels>,
8717 window: &mut Window,
8718 cx: &mut App,
8719 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8720 let mut element = self
8721 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8722 .into_any();
8723
8724 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8725
8726 let cursor = newest_selection_head?;
8727 let cursor_row_layout =
8728 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8729 let cursor_column = cursor.column() as usize;
8730
8731 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8732
8733 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8734
8735 element.prepaint_at(origin, window, cx);
8736 Some((element, origin))
8737 }
8738
8739 fn render_edit_prediction_eager_jump_popover(
8740 &mut self,
8741 text_bounds: &Bounds<Pixels>,
8742 content_origin: gpui::Point<Pixels>,
8743 editor_snapshot: &EditorSnapshot,
8744 visible_row_range: Range<DisplayRow>,
8745 scroll_top: ScrollOffset,
8746 scroll_bottom: ScrollOffset,
8747 line_height: Pixels,
8748 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8749 target_display_point: DisplayPoint,
8750 editor_width: Pixels,
8751 window: &mut Window,
8752 cx: &mut App,
8753 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8754 if target_display_point.row().as_f64() < scroll_top {
8755 let mut element = self
8756 .render_edit_prediction_line_popover(
8757 "Jump to Edit",
8758 Some(IconName::ArrowUp),
8759 window,
8760 cx,
8761 )
8762 .into_any();
8763
8764 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8765 let offset = point(
8766 (text_bounds.size.width - size.width) / 2.,
8767 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8768 );
8769
8770 let origin = text_bounds.origin + offset;
8771 element.prepaint_at(origin, window, cx);
8772 Some((element, origin))
8773 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8774 let mut element = self
8775 .render_edit_prediction_line_popover(
8776 "Jump to Edit",
8777 Some(IconName::ArrowDown),
8778 window,
8779 cx,
8780 )
8781 .into_any();
8782
8783 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8784 let offset = point(
8785 (text_bounds.size.width - size.width) / 2.,
8786 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8787 );
8788
8789 let origin = text_bounds.origin + offset;
8790 element.prepaint_at(origin, window, cx);
8791 Some((element, origin))
8792 } else {
8793 self.render_edit_prediction_end_of_line_popover(
8794 "Jump to Edit",
8795 editor_snapshot,
8796 visible_row_range,
8797 target_display_point,
8798 line_height,
8799 scroll_pixel_position,
8800 content_origin,
8801 editor_width,
8802 window,
8803 cx,
8804 )
8805 }
8806 }
8807
8808 fn render_edit_prediction_end_of_line_popover(
8809 self: &mut Editor,
8810 label: &'static str,
8811 editor_snapshot: &EditorSnapshot,
8812 visible_row_range: Range<DisplayRow>,
8813 target_display_point: DisplayPoint,
8814 line_height: Pixels,
8815 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8816 content_origin: gpui::Point<Pixels>,
8817 editor_width: Pixels,
8818 window: &mut Window,
8819 cx: &mut App,
8820 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8821 let target_line_end = DisplayPoint::new(
8822 target_display_point.row(),
8823 editor_snapshot.line_len(target_display_point.row()),
8824 );
8825
8826 let mut element = self
8827 .render_edit_prediction_line_popover(label, None, window, cx)
8828 .into_any();
8829
8830 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8831
8832 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8833
8834 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8835 let mut origin = start_point
8836 + line_origin
8837 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8838 origin.x = origin.x.max(content_origin.x);
8839
8840 let max_x = content_origin.x + editor_width - size.width;
8841
8842 if origin.x > max_x {
8843 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8844
8845 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8846 origin.y += offset;
8847 IconName::ArrowUp
8848 } else {
8849 origin.y -= offset;
8850 IconName::ArrowDown
8851 };
8852
8853 element = self
8854 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8855 .into_any();
8856
8857 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8858
8859 origin.x = content_origin.x + editor_width - size.width - px(2.);
8860 }
8861
8862 element.prepaint_at(origin, window, cx);
8863 Some((element, origin))
8864 }
8865
8866 fn render_edit_prediction_diff_popover(
8867 self: &Editor,
8868 text_bounds: &Bounds<Pixels>,
8869 content_origin: gpui::Point<Pixels>,
8870 right_margin: Pixels,
8871 editor_snapshot: &EditorSnapshot,
8872 visible_row_range: Range<DisplayRow>,
8873 line_layouts: &[LineWithInvisibles],
8874 line_height: Pixels,
8875 scroll_position: gpui::Point<ScrollOffset>,
8876 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8877 newest_selection_head: Option<DisplayPoint>,
8878 editor_width: Pixels,
8879 style: &EditorStyle,
8880 edits: &Vec<(Range<Anchor>, String)>,
8881 edit_preview: &Option<language::EditPreview>,
8882 snapshot: &language::BufferSnapshot,
8883 window: &mut Window,
8884 cx: &mut App,
8885 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8886 let edit_start = edits
8887 .first()
8888 .unwrap()
8889 .0
8890 .start
8891 .to_display_point(editor_snapshot);
8892 let edit_end = edits
8893 .last()
8894 .unwrap()
8895 .0
8896 .end
8897 .to_display_point(editor_snapshot);
8898
8899 let is_visible = visible_row_range.contains(&edit_start.row())
8900 || visible_row_range.contains(&edit_end.row());
8901 if !is_visible {
8902 return None;
8903 }
8904
8905 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8906 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8907 } else {
8908 // Fallback for providers without edit_preview
8909 crate::edit_prediction_fallback_text(edits, cx)
8910 };
8911
8912 let styled_text = highlighted_edits.to_styled_text(&style.text);
8913 let line_count = highlighted_edits.text.lines().count();
8914
8915 const BORDER_WIDTH: Pixels = px(1.);
8916
8917 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8918 let has_keybind = keybind.is_some();
8919
8920 let mut element = h_flex()
8921 .items_start()
8922 .child(
8923 h_flex()
8924 .bg(cx.theme().colors().editor_background)
8925 .border(BORDER_WIDTH)
8926 .shadow_xs()
8927 .border_color(cx.theme().colors().border)
8928 .rounded_l_lg()
8929 .when(line_count > 1, |el| el.rounded_br_lg())
8930 .pr_1()
8931 .child(styled_text),
8932 )
8933 .child(
8934 h_flex()
8935 .h(line_height + BORDER_WIDTH * 2.)
8936 .px_1p5()
8937 .gap_1()
8938 // Workaround: For some reason, there's a gap if we don't do this
8939 .ml(-BORDER_WIDTH)
8940 .shadow(vec![gpui::BoxShadow {
8941 color: gpui::black().opacity(0.05),
8942 offset: point(px(1.), px(1.)),
8943 blur_radius: px(2.),
8944 spread_radius: px(0.),
8945 }])
8946 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8947 .border(BORDER_WIDTH)
8948 .border_color(cx.theme().colors().border)
8949 .rounded_r_lg()
8950 .id("edit_prediction_diff_popover_keybind")
8951 .when(!has_keybind, |el| {
8952 let status_colors = cx.theme().status();
8953
8954 el.bg(status_colors.error_background)
8955 .border_color(status_colors.error.opacity(0.6))
8956 .child(Icon::new(IconName::Info).color(Color::Error))
8957 .cursor_default()
8958 .hoverable_tooltip(move |_window, cx| {
8959 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8960 })
8961 })
8962 .children(keybind),
8963 )
8964 .into_any();
8965
8966 let longest_row =
8967 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8968 let longest_line_width = if visible_row_range.contains(&longest_row) {
8969 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8970 } else {
8971 layout_line(
8972 longest_row,
8973 editor_snapshot,
8974 style,
8975 editor_width,
8976 |_| false,
8977 window,
8978 cx,
8979 )
8980 .width
8981 };
8982
8983 let viewport_bounds =
8984 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8985 right: -right_margin,
8986 ..Default::default()
8987 });
8988
8989 let x_after_longest = Pixels::from(
8990 ScrollPixelOffset::from(
8991 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8992 ) - scroll_pixel_position.x,
8993 );
8994
8995 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8996
8997 // Fully visible if it can be displayed within the window (allow overlapping other
8998 // panes). However, this is only allowed if the popover starts within text_bounds.
8999 let can_position_to_the_right = x_after_longest < text_bounds.right()
9000 && x_after_longest + element_bounds.width < viewport_bounds.right();
9001
9002 let mut origin = if can_position_to_the_right {
9003 point(
9004 x_after_longest,
9005 text_bounds.origin.y
9006 + Pixels::from(
9007 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9008 - scroll_pixel_position.y,
9009 ),
9010 )
9011 } else {
9012 let cursor_row = newest_selection_head.map(|head| head.row());
9013 let above_edit = edit_start
9014 .row()
9015 .0
9016 .checked_sub(line_count as u32)
9017 .map(DisplayRow);
9018 let below_edit = Some(edit_end.row() + 1);
9019 let above_cursor =
9020 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9021 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9022
9023 // Place the edit popover adjacent to the edit if there is a location
9024 // available that is onscreen and does not obscure the cursor. Otherwise,
9025 // place it adjacent to the cursor.
9026 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9027 .into_iter()
9028 .flatten()
9029 .find(|&start_row| {
9030 let end_row = start_row + line_count as u32;
9031 visible_row_range.contains(&start_row)
9032 && visible_row_range.contains(&end_row)
9033 && cursor_row
9034 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9035 })?;
9036
9037 content_origin
9038 + point(
9039 Pixels::from(-scroll_pixel_position.x),
9040 Pixels::from(
9041 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9042 ),
9043 )
9044 };
9045
9046 origin.x -= BORDER_WIDTH;
9047
9048 window.defer_draw(element, origin, 1);
9049
9050 // Do not return an element, since it will already be drawn due to defer_draw.
9051 None
9052 }
9053
9054 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9055 px(30.)
9056 }
9057
9058 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9059 if self.read_only(cx) {
9060 cx.theme().players().read_only()
9061 } else {
9062 self.style.as_ref().unwrap().local_player
9063 }
9064 }
9065
9066 fn render_edit_prediction_accept_keybind(
9067 &self,
9068 window: &mut Window,
9069 cx: &mut App,
9070 ) -> Option<AnyElement> {
9071 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9072 let accept_keystroke = accept_binding.keystroke()?;
9073
9074 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9075
9076 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9077 Color::Accent
9078 } else {
9079 Color::Muted
9080 };
9081
9082 h_flex()
9083 .px_0p5()
9084 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9085 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9086 .text_size(TextSize::XSmall.rems(cx))
9087 .child(h_flex().children(ui::render_modifiers(
9088 accept_keystroke.modifiers(),
9089 PlatformStyle::platform(),
9090 Some(modifiers_color),
9091 Some(IconSize::XSmall.rems().into()),
9092 true,
9093 )))
9094 .when(is_platform_style_mac, |parent| {
9095 parent.child(accept_keystroke.key().to_string())
9096 })
9097 .when(!is_platform_style_mac, |parent| {
9098 parent.child(
9099 Key::new(
9100 util::capitalize(accept_keystroke.key()),
9101 Some(Color::Default),
9102 )
9103 .size(Some(IconSize::XSmall.rems().into())),
9104 )
9105 })
9106 .into_any()
9107 .into()
9108 }
9109
9110 fn render_edit_prediction_line_popover(
9111 &self,
9112 label: impl Into<SharedString>,
9113 icon: Option<IconName>,
9114 window: &mut Window,
9115 cx: &mut App,
9116 ) -> Stateful<Div> {
9117 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9118
9119 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9120 let has_keybind = keybind.is_some();
9121
9122 h_flex()
9123 .id("ep-line-popover")
9124 .py_0p5()
9125 .pl_1()
9126 .pr(padding_right)
9127 .gap_1()
9128 .rounded_md()
9129 .border_1()
9130 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9131 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9132 .shadow_xs()
9133 .when(!has_keybind, |el| {
9134 let status_colors = cx.theme().status();
9135
9136 el.bg(status_colors.error_background)
9137 .border_color(status_colors.error.opacity(0.6))
9138 .pl_2()
9139 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9140 .cursor_default()
9141 .hoverable_tooltip(move |_window, cx| {
9142 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9143 })
9144 })
9145 .children(keybind)
9146 .child(
9147 Label::new(label)
9148 .size(LabelSize::Small)
9149 .when(!has_keybind, |el| {
9150 el.color(cx.theme().status().error.into()).strikethrough()
9151 }),
9152 )
9153 .when(!has_keybind, |el| {
9154 el.child(
9155 h_flex().ml_1().child(
9156 Icon::new(IconName::Info)
9157 .size(IconSize::Small)
9158 .color(cx.theme().status().error.into()),
9159 ),
9160 )
9161 })
9162 .when_some(icon, |element, icon| {
9163 element.child(
9164 div()
9165 .mt(px(1.5))
9166 .child(Icon::new(icon).size(IconSize::Small)),
9167 )
9168 })
9169 }
9170
9171 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9172 let accent_color = cx.theme().colors().text_accent;
9173 let editor_bg_color = cx.theme().colors().editor_background;
9174 editor_bg_color.blend(accent_color.opacity(0.1))
9175 }
9176
9177 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9178 let accent_color = cx.theme().colors().text_accent;
9179 let editor_bg_color = cx.theme().colors().editor_background;
9180 editor_bg_color.blend(accent_color.opacity(0.6))
9181 }
9182 fn get_prediction_provider_icon_name(
9183 provider: &Option<RegisteredEditPredictionProvider>,
9184 ) -> IconName {
9185 match provider {
9186 Some(provider) => match provider.provider.name() {
9187 "copilot" => IconName::Copilot,
9188 "supermaven" => IconName::Supermaven,
9189 _ => IconName::ZedPredict,
9190 },
9191 None => IconName::ZedPredict,
9192 }
9193 }
9194
9195 fn render_edit_prediction_cursor_popover(
9196 &self,
9197 min_width: Pixels,
9198 max_width: Pixels,
9199 cursor_point: Point,
9200 style: &EditorStyle,
9201 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9202 _window: &Window,
9203 cx: &mut Context<Editor>,
9204 ) -> Option<AnyElement> {
9205 let provider = self.edit_prediction_provider.as_ref()?;
9206 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9207
9208 let is_refreshing = provider.provider.is_refreshing(cx);
9209
9210 fn pending_completion_container(icon: IconName) -> Div {
9211 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9212 }
9213
9214 let completion = match &self.active_edit_prediction {
9215 Some(prediction) => {
9216 if !self.has_visible_completions_menu() {
9217 const RADIUS: Pixels = px(6.);
9218 const BORDER_WIDTH: Pixels = px(1.);
9219
9220 return Some(
9221 h_flex()
9222 .elevation_2(cx)
9223 .border(BORDER_WIDTH)
9224 .border_color(cx.theme().colors().border)
9225 .when(accept_keystroke.is_none(), |el| {
9226 el.border_color(cx.theme().status().error)
9227 })
9228 .rounded(RADIUS)
9229 .rounded_tl(px(0.))
9230 .overflow_hidden()
9231 .child(div().px_1p5().child(match &prediction.completion {
9232 EditPrediction::MoveWithin { target, snapshot } => {
9233 use text::ToPoint as _;
9234 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9235 {
9236 Icon::new(IconName::ZedPredictDown)
9237 } else {
9238 Icon::new(IconName::ZedPredictUp)
9239 }
9240 }
9241 EditPrediction::MoveOutside { .. } => {
9242 // TODO [zeta2] custom icon for external jump?
9243 Icon::new(provider_icon)
9244 }
9245 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9246 }))
9247 .child(
9248 h_flex()
9249 .gap_1()
9250 .py_1()
9251 .px_2()
9252 .rounded_r(RADIUS - BORDER_WIDTH)
9253 .border_l_1()
9254 .border_color(cx.theme().colors().border)
9255 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9256 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9257 el.child(
9258 Label::new("Hold")
9259 .size(LabelSize::Small)
9260 .when(accept_keystroke.is_none(), |el| {
9261 el.strikethrough()
9262 })
9263 .line_height_style(LineHeightStyle::UiLabel),
9264 )
9265 })
9266 .id("edit_prediction_cursor_popover_keybind")
9267 .when(accept_keystroke.is_none(), |el| {
9268 let status_colors = cx.theme().status();
9269
9270 el.bg(status_colors.error_background)
9271 .border_color(status_colors.error.opacity(0.6))
9272 .child(Icon::new(IconName::Info).color(Color::Error))
9273 .cursor_default()
9274 .hoverable_tooltip(move |_window, cx| {
9275 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9276 .into()
9277 })
9278 })
9279 .when_some(
9280 accept_keystroke.as_ref(),
9281 |el, accept_keystroke| {
9282 el.child(h_flex().children(ui::render_modifiers(
9283 accept_keystroke.modifiers(),
9284 PlatformStyle::platform(),
9285 Some(Color::Default),
9286 Some(IconSize::XSmall.rems().into()),
9287 false,
9288 )))
9289 },
9290 ),
9291 )
9292 .into_any(),
9293 );
9294 }
9295
9296 self.render_edit_prediction_cursor_popover_preview(
9297 prediction,
9298 cursor_point,
9299 style,
9300 cx,
9301 )?
9302 }
9303
9304 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9305 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9306 stale_completion,
9307 cursor_point,
9308 style,
9309 cx,
9310 )?,
9311
9312 None => pending_completion_container(provider_icon)
9313 .child(Label::new("...").size(LabelSize::Small)),
9314 },
9315
9316 None => pending_completion_container(provider_icon)
9317 .child(Label::new("...").size(LabelSize::Small)),
9318 };
9319
9320 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9321 completion
9322 .with_animation(
9323 "loading-completion",
9324 Animation::new(Duration::from_secs(2))
9325 .repeat()
9326 .with_easing(pulsating_between(0.4, 0.8)),
9327 |label, delta| label.opacity(delta),
9328 )
9329 .into_any_element()
9330 } else {
9331 completion.into_any_element()
9332 };
9333
9334 let has_completion = self.active_edit_prediction.is_some();
9335
9336 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9337 Some(
9338 h_flex()
9339 .min_w(min_width)
9340 .max_w(max_width)
9341 .flex_1()
9342 .elevation_2(cx)
9343 .border_color(cx.theme().colors().border)
9344 .child(
9345 div()
9346 .flex_1()
9347 .py_1()
9348 .px_2()
9349 .overflow_hidden()
9350 .child(completion),
9351 )
9352 .when_some(accept_keystroke, |el, accept_keystroke| {
9353 if !accept_keystroke.modifiers().modified() {
9354 return el;
9355 }
9356
9357 el.child(
9358 h_flex()
9359 .h_full()
9360 .border_l_1()
9361 .rounded_r_lg()
9362 .border_color(cx.theme().colors().border)
9363 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9364 .gap_1()
9365 .py_1()
9366 .px_2()
9367 .child(
9368 h_flex()
9369 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9370 .when(is_platform_style_mac, |parent| parent.gap_1())
9371 .child(h_flex().children(ui::render_modifiers(
9372 accept_keystroke.modifiers(),
9373 PlatformStyle::platform(),
9374 Some(if !has_completion {
9375 Color::Muted
9376 } else {
9377 Color::Default
9378 }),
9379 None,
9380 false,
9381 ))),
9382 )
9383 .child(Label::new("Preview").into_any_element())
9384 .opacity(if has_completion { 1.0 } else { 0.4 }),
9385 )
9386 })
9387 .into_any(),
9388 )
9389 }
9390
9391 fn render_edit_prediction_cursor_popover_preview(
9392 &self,
9393 completion: &EditPredictionState,
9394 cursor_point: Point,
9395 style: &EditorStyle,
9396 cx: &mut Context<Editor>,
9397 ) -> Option<Div> {
9398 use text::ToPoint as _;
9399
9400 fn render_relative_row_jump(
9401 prefix: impl Into<String>,
9402 current_row: u32,
9403 target_row: u32,
9404 ) -> Div {
9405 let (row_diff, arrow) = if target_row < current_row {
9406 (current_row - target_row, IconName::ArrowUp)
9407 } else {
9408 (target_row - current_row, IconName::ArrowDown)
9409 };
9410
9411 h_flex()
9412 .child(
9413 Label::new(format!("{}{}", prefix.into(), row_diff))
9414 .color(Color::Muted)
9415 .size(LabelSize::Small),
9416 )
9417 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9418 }
9419
9420 let supports_jump = self
9421 .edit_prediction_provider
9422 .as_ref()
9423 .map(|provider| provider.provider.supports_jump_to_edit())
9424 .unwrap_or(true);
9425
9426 match &completion.completion {
9427 EditPrediction::MoveWithin {
9428 target, snapshot, ..
9429 } => {
9430 if !supports_jump {
9431 return None;
9432 }
9433
9434 Some(
9435 h_flex()
9436 .px_2()
9437 .gap_2()
9438 .flex_1()
9439 .child(
9440 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9441 Icon::new(IconName::ZedPredictDown)
9442 } else {
9443 Icon::new(IconName::ZedPredictUp)
9444 },
9445 )
9446 .child(Label::new("Jump to Edit")),
9447 )
9448 }
9449 EditPrediction::MoveOutside { snapshot, .. } => {
9450 let file_name = snapshot
9451 .file()
9452 .map(|file| file.file_name(cx))
9453 .unwrap_or("untitled");
9454 Some(
9455 h_flex()
9456 .px_2()
9457 .gap_2()
9458 .flex_1()
9459 .child(Icon::new(IconName::ZedPredict))
9460 .child(Label::new(format!("Jump to {file_name}"))),
9461 )
9462 }
9463 EditPrediction::Edit {
9464 edits,
9465 edit_preview,
9466 snapshot,
9467 display_mode: _,
9468 } => {
9469 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9470
9471 let (highlighted_edits, has_more_lines) =
9472 if let Some(edit_preview) = edit_preview.as_ref() {
9473 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9474 .first_line_preview()
9475 } else {
9476 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9477 };
9478
9479 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9480 .with_default_highlights(&style.text, highlighted_edits.highlights);
9481
9482 let preview = h_flex()
9483 .gap_1()
9484 .min_w_16()
9485 .child(styled_text)
9486 .when(has_more_lines, |parent| parent.child("…"));
9487
9488 let left = if supports_jump && first_edit_row != cursor_point.row {
9489 render_relative_row_jump("", cursor_point.row, first_edit_row)
9490 .into_any_element()
9491 } else {
9492 let icon_name =
9493 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9494 Icon::new(icon_name).into_any_element()
9495 };
9496
9497 Some(
9498 h_flex()
9499 .h_full()
9500 .flex_1()
9501 .gap_2()
9502 .pr_1()
9503 .overflow_x_hidden()
9504 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9505 .child(left)
9506 .child(preview),
9507 )
9508 }
9509 }
9510 }
9511
9512 pub fn render_context_menu(
9513 &self,
9514 style: &EditorStyle,
9515 max_height_in_lines: u32,
9516 window: &mut Window,
9517 cx: &mut Context<Editor>,
9518 ) -> Option<AnyElement> {
9519 let menu = self.context_menu.borrow();
9520 let menu = menu.as_ref()?;
9521 if !menu.visible() {
9522 return None;
9523 };
9524 Some(menu.render(style, max_height_in_lines, window, cx))
9525 }
9526
9527 fn render_context_menu_aside(
9528 &mut self,
9529 max_size: Size<Pixels>,
9530 window: &mut Window,
9531 cx: &mut Context<Editor>,
9532 ) -> Option<AnyElement> {
9533 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9534 if menu.visible() {
9535 menu.render_aside(max_size, window, cx)
9536 } else {
9537 None
9538 }
9539 })
9540 }
9541
9542 fn hide_context_menu(
9543 &mut self,
9544 window: &mut Window,
9545 cx: &mut Context<Self>,
9546 ) -> Option<CodeContextMenu> {
9547 cx.notify();
9548 self.completion_tasks.clear();
9549 let context_menu = self.context_menu.borrow_mut().take();
9550 self.stale_edit_prediction_in_menu.take();
9551 self.update_visible_edit_prediction(window, cx);
9552 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9553 && let Some(completion_provider) = &self.completion_provider
9554 {
9555 completion_provider.selection_changed(None, window, cx);
9556 }
9557 context_menu
9558 }
9559
9560 fn show_snippet_choices(
9561 &mut self,
9562 choices: &Vec<String>,
9563 selection: Range<Anchor>,
9564 cx: &mut Context<Self>,
9565 ) {
9566 let Some((_, buffer, _)) = self
9567 .buffer()
9568 .read(cx)
9569 .excerpt_containing(selection.start, cx)
9570 else {
9571 return;
9572 };
9573 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9574 else {
9575 return;
9576 };
9577 if buffer != end_buffer {
9578 log::error!("expected anchor range to have matching buffer IDs");
9579 return;
9580 }
9581
9582 let id = post_inc(&mut self.next_completion_id);
9583 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9584 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9585 CompletionsMenu::new_snippet_choices(
9586 id,
9587 true,
9588 choices,
9589 selection,
9590 buffer,
9591 snippet_sort_order,
9592 ),
9593 ));
9594 }
9595
9596 pub fn insert_snippet(
9597 &mut self,
9598 insertion_ranges: &[Range<usize>],
9599 snippet: Snippet,
9600 window: &mut Window,
9601 cx: &mut Context<Self>,
9602 ) -> Result<()> {
9603 struct Tabstop<T> {
9604 is_end_tabstop: bool,
9605 ranges: Vec<Range<T>>,
9606 choices: Option<Vec<String>>,
9607 }
9608
9609 let tabstops = self.buffer.update(cx, |buffer, cx| {
9610 let snippet_text: Arc<str> = snippet.text.clone().into();
9611 let edits = insertion_ranges
9612 .iter()
9613 .cloned()
9614 .map(|range| (range, snippet_text.clone()));
9615 let autoindent_mode = AutoindentMode::Block {
9616 original_indent_columns: Vec::new(),
9617 };
9618 buffer.edit(edits, Some(autoindent_mode), cx);
9619
9620 let snapshot = &*buffer.read(cx);
9621 let snippet = &snippet;
9622 snippet
9623 .tabstops
9624 .iter()
9625 .map(|tabstop| {
9626 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9627 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9628 });
9629 let mut tabstop_ranges = tabstop
9630 .ranges
9631 .iter()
9632 .flat_map(|tabstop_range| {
9633 let mut delta = 0_isize;
9634 insertion_ranges.iter().map(move |insertion_range| {
9635 let insertion_start = insertion_range.start as isize + delta;
9636 delta +=
9637 snippet.text.len() as isize - insertion_range.len() as isize;
9638
9639 let start = ((insertion_start + tabstop_range.start) as usize)
9640 .min(snapshot.len());
9641 let end = ((insertion_start + tabstop_range.end) as usize)
9642 .min(snapshot.len());
9643 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9644 })
9645 })
9646 .collect::<Vec<_>>();
9647 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9648
9649 Tabstop {
9650 is_end_tabstop,
9651 ranges: tabstop_ranges,
9652 choices: tabstop.choices.clone(),
9653 }
9654 })
9655 .collect::<Vec<_>>()
9656 });
9657 if let Some(tabstop) = tabstops.first() {
9658 self.change_selections(Default::default(), window, cx, |s| {
9659 // Reverse order so that the first range is the newest created selection.
9660 // Completions will use it and autoscroll will prioritize it.
9661 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9662 });
9663
9664 if let Some(choices) = &tabstop.choices
9665 && let Some(selection) = tabstop.ranges.first()
9666 {
9667 self.show_snippet_choices(choices, selection.clone(), cx)
9668 }
9669
9670 // If we're already at the last tabstop and it's at the end of the snippet,
9671 // we're done, we don't need to keep the state around.
9672 if !tabstop.is_end_tabstop {
9673 let choices = tabstops
9674 .iter()
9675 .map(|tabstop| tabstop.choices.clone())
9676 .collect();
9677
9678 let ranges = tabstops
9679 .into_iter()
9680 .map(|tabstop| tabstop.ranges)
9681 .collect::<Vec<_>>();
9682
9683 self.snippet_stack.push(SnippetState {
9684 active_index: 0,
9685 ranges,
9686 choices,
9687 });
9688 }
9689
9690 // Check whether the just-entered snippet ends with an auto-closable bracket.
9691 if self.autoclose_regions.is_empty() {
9692 let snapshot = self.buffer.read(cx).snapshot(cx);
9693 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9694 let selection_head = selection.head();
9695 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9696 continue;
9697 };
9698
9699 let mut bracket_pair = None;
9700 let max_lookup_length = scope
9701 .brackets()
9702 .map(|(pair, _)| {
9703 pair.start
9704 .as_str()
9705 .chars()
9706 .count()
9707 .max(pair.end.as_str().chars().count())
9708 })
9709 .max();
9710 if let Some(max_lookup_length) = max_lookup_length {
9711 let next_text = snapshot
9712 .chars_at(selection_head)
9713 .take(max_lookup_length)
9714 .collect::<String>();
9715 let prev_text = snapshot
9716 .reversed_chars_at(selection_head)
9717 .take(max_lookup_length)
9718 .collect::<String>();
9719
9720 for (pair, enabled) in scope.brackets() {
9721 if enabled
9722 && pair.close
9723 && prev_text.starts_with(pair.start.as_str())
9724 && next_text.starts_with(pair.end.as_str())
9725 {
9726 bracket_pair = Some(pair.clone());
9727 break;
9728 }
9729 }
9730 }
9731
9732 if let Some(pair) = bracket_pair {
9733 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9734 let autoclose_enabled =
9735 self.use_autoclose && snapshot_settings.use_autoclose;
9736 if autoclose_enabled {
9737 let start = snapshot.anchor_after(selection_head);
9738 let end = snapshot.anchor_after(selection_head);
9739 self.autoclose_regions.push(AutocloseRegion {
9740 selection_id: selection.id,
9741 range: start..end,
9742 pair,
9743 });
9744 }
9745 }
9746 }
9747 }
9748 }
9749 Ok(())
9750 }
9751
9752 pub fn move_to_next_snippet_tabstop(
9753 &mut self,
9754 window: &mut Window,
9755 cx: &mut Context<Self>,
9756 ) -> bool {
9757 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9758 }
9759
9760 pub fn move_to_prev_snippet_tabstop(
9761 &mut self,
9762 window: &mut Window,
9763 cx: &mut Context<Self>,
9764 ) -> bool {
9765 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9766 }
9767
9768 pub fn move_to_snippet_tabstop(
9769 &mut self,
9770 bias: Bias,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) -> bool {
9774 if let Some(mut snippet) = self.snippet_stack.pop() {
9775 match bias {
9776 Bias::Left => {
9777 if snippet.active_index > 0 {
9778 snippet.active_index -= 1;
9779 } else {
9780 self.snippet_stack.push(snippet);
9781 return false;
9782 }
9783 }
9784 Bias::Right => {
9785 if snippet.active_index + 1 < snippet.ranges.len() {
9786 snippet.active_index += 1;
9787 } else {
9788 self.snippet_stack.push(snippet);
9789 return false;
9790 }
9791 }
9792 }
9793 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9794 self.change_selections(Default::default(), window, cx, |s| {
9795 // Reverse order so that the first range is the newest created selection.
9796 // Completions will use it and autoscroll will prioritize it.
9797 s.select_ranges(current_ranges.iter().rev().cloned())
9798 });
9799
9800 if let Some(choices) = &snippet.choices[snippet.active_index]
9801 && let Some(selection) = current_ranges.first()
9802 {
9803 self.show_snippet_choices(choices, selection.clone(), cx);
9804 }
9805
9806 // If snippet state is not at the last tabstop, push it back on the stack
9807 if snippet.active_index + 1 < snippet.ranges.len() {
9808 self.snippet_stack.push(snippet);
9809 }
9810 return true;
9811 }
9812 }
9813
9814 false
9815 }
9816
9817 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9818 self.transact(window, cx, |this, window, cx| {
9819 this.select_all(&SelectAll, window, cx);
9820 this.insert("", window, cx);
9821 });
9822 }
9823
9824 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9825 if self.read_only(cx) {
9826 return;
9827 }
9828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9829 self.transact(window, cx, |this, window, cx| {
9830 this.select_autoclose_pair(window, cx);
9831
9832 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9833
9834 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9835 if !this.linked_edit_ranges.is_empty() {
9836 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9837 let snapshot = this.buffer.read(cx).snapshot(cx);
9838
9839 for selection in selections.iter() {
9840 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9841 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9842 if selection_start.buffer_id != selection_end.buffer_id {
9843 continue;
9844 }
9845 if let Some(ranges) =
9846 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9847 {
9848 for (buffer, entries) in ranges {
9849 linked_ranges.entry(buffer).or_default().extend(entries);
9850 }
9851 }
9852 }
9853 }
9854
9855 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9856 for selection in &mut selections {
9857 if selection.is_empty() {
9858 let old_head = selection.head();
9859 let mut new_head =
9860 movement::left(&display_map, old_head.to_display_point(&display_map))
9861 .to_point(&display_map);
9862 if let Some((buffer, line_buffer_range)) = display_map
9863 .buffer_snapshot()
9864 .buffer_line_for_row(MultiBufferRow(old_head.row))
9865 {
9866 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9867 let indent_len = match indent_size.kind {
9868 IndentKind::Space => {
9869 buffer.settings_at(line_buffer_range.start, cx).tab_size
9870 }
9871 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9872 };
9873 if old_head.column <= indent_size.len && old_head.column > 0 {
9874 let indent_len = indent_len.get();
9875 new_head = cmp::min(
9876 new_head,
9877 MultiBufferPoint::new(
9878 old_head.row,
9879 ((old_head.column - 1) / indent_len) * indent_len,
9880 ),
9881 );
9882 }
9883 }
9884
9885 selection.set_head(new_head, SelectionGoal::None);
9886 }
9887 }
9888
9889 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9890 this.insert("", window, cx);
9891 let empty_str: Arc<str> = Arc::from("");
9892 for (buffer, edits) in linked_ranges {
9893 let snapshot = buffer.read(cx).snapshot();
9894 use text::ToPoint as TP;
9895
9896 let edits = edits
9897 .into_iter()
9898 .map(|range| {
9899 let end_point = TP::to_point(&range.end, &snapshot);
9900 let mut start_point = TP::to_point(&range.start, &snapshot);
9901
9902 if end_point == start_point {
9903 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9904 .saturating_sub(1);
9905 start_point =
9906 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9907 };
9908
9909 (start_point..end_point, empty_str.clone())
9910 })
9911 .sorted_by_key(|(range, _)| range.start)
9912 .collect::<Vec<_>>();
9913 buffer.update(cx, |this, cx| {
9914 this.edit(edits, None, cx);
9915 })
9916 }
9917 this.refresh_edit_prediction(true, false, window, cx);
9918 refresh_linked_ranges(this, window, cx);
9919 });
9920 }
9921
9922 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9923 if self.read_only(cx) {
9924 return;
9925 }
9926 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9927 self.transact(window, cx, |this, window, cx| {
9928 this.change_selections(Default::default(), window, cx, |s| {
9929 s.move_with(|map, selection| {
9930 if selection.is_empty() {
9931 let cursor = movement::right(map, selection.head());
9932 selection.end = cursor;
9933 selection.reversed = true;
9934 selection.goal = SelectionGoal::None;
9935 }
9936 })
9937 });
9938 this.insert("", window, cx);
9939 this.refresh_edit_prediction(true, false, window, cx);
9940 });
9941 }
9942
9943 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9944 if self.mode.is_single_line() {
9945 cx.propagate();
9946 return;
9947 }
9948
9949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9950 if self.move_to_prev_snippet_tabstop(window, cx) {
9951 return;
9952 }
9953 self.outdent(&Outdent, window, cx);
9954 }
9955
9956 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9957 if self.mode.is_single_line() {
9958 cx.propagate();
9959 return;
9960 }
9961
9962 if self.move_to_next_snippet_tabstop(window, cx) {
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 return;
9965 }
9966 if self.read_only(cx) {
9967 return;
9968 }
9969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9970 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9971 let buffer = self.buffer.read(cx);
9972 let snapshot = buffer.snapshot(cx);
9973 let rows_iter = selections.iter().map(|s| s.head().row);
9974 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9975
9976 let has_some_cursor_in_whitespace = selections
9977 .iter()
9978 .filter(|selection| selection.is_empty())
9979 .any(|selection| {
9980 let cursor = selection.head();
9981 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9982 cursor.column < current_indent.len
9983 });
9984
9985 let mut edits = Vec::new();
9986 let mut prev_edited_row = 0;
9987 let mut row_delta = 0;
9988 for selection in &mut selections {
9989 if selection.start.row != prev_edited_row {
9990 row_delta = 0;
9991 }
9992 prev_edited_row = selection.end.row;
9993
9994 // If the selection is non-empty, then increase the indentation of the selected lines.
9995 if !selection.is_empty() {
9996 row_delta =
9997 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9998 continue;
9999 }
10000
10001 let cursor = selection.head();
10002 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10003 if let Some(suggested_indent) =
10004 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10005 {
10006 // Don't do anything if already at suggested indent
10007 // and there is any other cursor which is not
10008 if has_some_cursor_in_whitespace
10009 && cursor.column == current_indent.len
10010 && current_indent.len == suggested_indent.len
10011 {
10012 continue;
10013 }
10014
10015 // Adjust line and move cursor to suggested indent
10016 // if cursor is not at suggested indent
10017 if cursor.column < suggested_indent.len
10018 && cursor.column <= current_indent.len
10019 && current_indent.len <= suggested_indent.len
10020 {
10021 selection.start = Point::new(cursor.row, suggested_indent.len);
10022 selection.end = selection.start;
10023 if row_delta == 0 {
10024 edits.extend(Buffer::edit_for_indent_size_adjustment(
10025 cursor.row,
10026 current_indent,
10027 suggested_indent,
10028 ));
10029 row_delta = suggested_indent.len - current_indent.len;
10030 }
10031 continue;
10032 }
10033
10034 // If current indent is more than suggested indent
10035 // only move cursor to current indent and skip indent
10036 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10037 selection.start = Point::new(cursor.row, current_indent.len);
10038 selection.end = selection.start;
10039 continue;
10040 }
10041 }
10042
10043 // Otherwise, insert a hard or soft tab.
10044 let settings = buffer.language_settings_at(cursor, cx);
10045 let tab_size = if settings.hard_tabs {
10046 IndentSize::tab()
10047 } else {
10048 let tab_size = settings.tab_size.get();
10049 let indent_remainder = snapshot
10050 .text_for_range(Point::new(cursor.row, 0)..cursor)
10051 .flat_map(str::chars)
10052 .fold(row_delta % tab_size, |counter: u32, c| {
10053 if c == '\t' {
10054 0
10055 } else {
10056 (counter + 1) % tab_size
10057 }
10058 });
10059
10060 let chars_to_next_tab_stop = tab_size - indent_remainder;
10061 IndentSize::spaces(chars_to_next_tab_stop)
10062 };
10063 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10064 selection.end = selection.start;
10065 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10066 row_delta += tab_size.len;
10067 }
10068
10069 self.transact(window, cx, |this, window, cx| {
10070 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10071 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10072 this.refresh_edit_prediction(true, false, window, cx);
10073 });
10074 }
10075
10076 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10077 if self.read_only(cx) {
10078 return;
10079 }
10080 if self.mode.is_single_line() {
10081 cx.propagate();
10082 return;
10083 }
10084
10085 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10086 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10087 let mut prev_edited_row = 0;
10088 let mut row_delta = 0;
10089 let mut edits = Vec::new();
10090 let buffer = self.buffer.read(cx);
10091 let snapshot = buffer.snapshot(cx);
10092 for selection in &mut selections {
10093 if selection.start.row != prev_edited_row {
10094 row_delta = 0;
10095 }
10096 prev_edited_row = selection.end.row;
10097
10098 row_delta =
10099 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10100 }
10101
10102 self.transact(window, cx, |this, window, cx| {
10103 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10104 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10105 });
10106 }
10107
10108 fn indent_selection(
10109 buffer: &MultiBuffer,
10110 snapshot: &MultiBufferSnapshot,
10111 selection: &mut Selection<Point>,
10112 edits: &mut Vec<(Range<Point>, String)>,
10113 delta_for_start_row: u32,
10114 cx: &App,
10115 ) -> u32 {
10116 let settings = buffer.language_settings_at(selection.start, cx);
10117 let tab_size = settings.tab_size.get();
10118 let indent_kind = if settings.hard_tabs {
10119 IndentKind::Tab
10120 } else {
10121 IndentKind::Space
10122 };
10123 let mut start_row = selection.start.row;
10124 let mut end_row = selection.end.row + 1;
10125
10126 // If a selection ends at the beginning of a line, don't indent
10127 // that last line.
10128 if selection.end.column == 0 && selection.end.row > selection.start.row {
10129 end_row -= 1;
10130 }
10131
10132 // Avoid re-indenting a row that has already been indented by a
10133 // previous selection, but still update this selection's column
10134 // to reflect that indentation.
10135 if delta_for_start_row > 0 {
10136 start_row += 1;
10137 selection.start.column += delta_for_start_row;
10138 if selection.end.row == selection.start.row {
10139 selection.end.column += delta_for_start_row;
10140 }
10141 }
10142
10143 let mut delta_for_end_row = 0;
10144 let has_multiple_rows = start_row + 1 != end_row;
10145 for row in start_row..end_row {
10146 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10147 let indent_delta = match (current_indent.kind, indent_kind) {
10148 (IndentKind::Space, IndentKind::Space) => {
10149 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10150 IndentSize::spaces(columns_to_next_tab_stop)
10151 }
10152 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10153 (_, IndentKind::Tab) => IndentSize::tab(),
10154 };
10155
10156 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10157 0
10158 } else {
10159 selection.start.column
10160 };
10161 let row_start = Point::new(row, start);
10162 edits.push((
10163 row_start..row_start,
10164 indent_delta.chars().collect::<String>(),
10165 ));
10166
10167 // Update this selection's endpoints to reflect the indentation.
10168 if row == selection.start.row {
10169 selection.start.column += indent_delta.len;
10170 }
10171 if row == selection.end.row {
10172 selection.end.column += indent_delta.len;
10173 delta_for_end_row = indent_delta.len;
10174 }
10175 }
10176
10177 if selection.start.row == selection.end.row {
10178 delta_for_start_row + delta_for_end_row
10179 } else {
10180 delta_for_end_row
10181 }
10182 }
10183
10184 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10185 if self.read_only(cx) {
10186 return;
10187 }
10188 if self.mode.is_single_line() {
10189 cx.propagate();
10190 return;
10191 }
10192
10193 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10194 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10195 let selections = self.selections.all::<Point>(&display_map);
10196 let mut deletion_ranges = Vec::new();
10197 let mut last_outdent = None;
10198 {
10199 let buffer = self.buffer.read(cx);
10200 let snapshot = buffer.snapshot(cx);
10201 for selection in &selections {
10202 let settings = buffer.language_settings_at(selection.start, cx);
10203 let tab_size = settings.tab_size.get();
10204 let mut rows = selection.spanned_rows(false, &display_map);
10205
10206 // Avoid re-outdenting a row that has already been outdented by a
10207 // previous selection.
10208 if let Some(last_row) = last_outdent
10209 && last_row == rows.start
10210 {
10211 rows.start = rows.start.next_row();
10212 }
10213 let has_multiple_rows = rows.len() > 1;
10214 for row in rows.iter_rows() {
10215 let indent_size = snapshot.indent_size_for_line(row);
10216 if indent_size.len > 0 {
10217 let deletion_len = match indent_size.kind {
10218 IndentKind::Space => {
10219 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10220 if columns_to_prev_tab_stop == 0 {
10221 tab_size
10222 } else {
10223 columns_to_prev_tab_stop
10224 }
10225 }
10226 IndentKind::Tab => 1,
10227 };
10228 let start = if has_multiple_rows
10229 || deletion_len > selection.start.column
10230 || indent_size.len < selection.start.column
10231 {
10232 0
10233 } else {
10234 selection.start.column - deletion_len
10235 };
10236 deletion_ranges.push(
10237 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10238 );
10239 last_outdent = Some(row);
10240 }
10241 }
10242 }
10243 }
10244
10245 self.transact(window, cx, |this, window, cx| {
10246 this.buffer.update(cx, |buffer, cx| {
10247 let empty_str: Arc<str> = Arc::default();
10248 buffer.edit(
10249 deletion_ranges
10250 .into_iter()
10251 .map(|range| (range, empty_str.clone())),
10252 None,
10253 cx,
10254 );
10255 });
10256 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10257 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10258 });
10259 }
10260
10261 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10262 if self.read_only(cx) {
10263 return;
10264 }
10265 if self.mode.is_single_line() {
10266 cx.propagate();
10267 return;
10268 }
10269
10270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10271 let selections = self
10272 .selections
10273 .all::<usize>(&self.display_snapshot(cx))
10274 .into_iter()
10275 .map(|s| s.range());
10276
10277 self.transact(window, cx, |this, window, cx| {
10278 this.buffer.update(cx, |buffer, cx| {
10279 buffer.autoindent_ranges(selections, cx);
10280 });
10281 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10282 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10283 });
10284 }
10285
10286 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10289 let selections = self.selections.all::<Point>(&display_map);
10290
10291 let mut new_cursors = Vec::new();
10292 let mut edit_ranges = Vec::new();
10293 let mut selections = selections.iter().peekable();
10294 while let Some(selection) = selections.next() {
10295 let mut rows = selection.spanned_rows(false, &display_map);
10296
10297 // Accumulate contiguous regions of rows that we want to delete.
10298 while let Some(next_selection) = selections.peek() {
10299 let next_rows = next_selection.spanned_rows(false, &display_map);
10300 if next_rows.start <= rows.end {
10301 rows.end = next_rows.end;
10302 selections.next().unwrap();
10303 } else {
10304 break;
10305 }
10306 }
10307
10308 let buffer = display_map.buffer_snapshot();
10309 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10310 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10311 // If there's a line after the range, delete the \n from the end of the row range
10312 (
10313 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10314 rows.end,
10315 )
10316 } else {
10317 // If there isn't a line after the range, delete the \n from the line before the
10318 // start of the row range
10319 edit_start = edit_start.saturating_sub(1);
10320 (buffer.len(), rows.start.previous_row())
10321 };
10322
10323 let text_layout_details = self.text_layout_details(window);
10324 let x = display_map.x_for_display_point(
10325 selection.head().to_display_point(&display_map),
10326 &text_layout_details,
10327 );
10328 let row = Point::new(target_row.0, 0)
10329 .to_display_point(&display_map)
10330 .row();
10331 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10332
10333 new_cursors.push((
10334 selection.id,
10335 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10336 SelectionGoal::None,
10337 ));
10338 edit_ranges.push(edit_start..edit_end);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 let buffer = this.buffer.update(cx, |buffer, cx| {
10343 let empty_str: Arc<str> = Arc::default();
10344 buffer.edit(
10345 edit_ranges
10346 .into_iter()
10347 .map(|range| (range, empty_str.clone())),
10348 None,
10349 cx,
10350 );
10351 buffer.snapshot(cx)
10352 });
10353 let new_selections = new_cursors
10354 .into_iter()
10355 .map(|(id, cursor, goal)| {
10356 let cursor = cursor.to_point(&buffer);
10357 Selection {
10358 id,
10359 start: cursor,
10360 end: cursor,
10361 reversed: false,
10362 goal,
10363 }
10364 })
10365 .collect();
10366
10367 this.change_selections(Default::default(), window, cx, |s| {
10368 s.select(new_selections);
10369 });
10370 });
10371 }
10372
10373 pub fn join_lines_impl(
10374 &mut self,
10375 insert_whitespace: bool,
10376 window: &mut Window,
10377 cx: &mut Context<Self>,
10378 ) {
10379 if self.read_only(cx) {
10380 return;
10381 }
10382 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10383 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10384 let start = MultiBufferRow(selection.start.row);
10385 // Treat single line selections as if they include the next line. Otherwise this action
10386 // would do nothing for single line selections individual cursors.
10387 let end = if selection.start.row == selection.end.row {
10388 MultiBufferRow(selection.start.row + 1)
10389 } else {
10390 MultiBufferRow(selection.end.row)
10391 };
10392
10393 if let Some(last_row_range) = row_ranges.last_mut()
10394 && start <= last_row_range.end
10395 {
10396 last_row_range.end = end;
10397 continue;
10398 }
10399 row_ranges.push(start..end);
10400 }
10401
10402 let snapshot = self.buffer.read(cx).snapshot(cx);
10403 let mut cursor_positions = Vec::new();
10404 for row_range in &row_ranges {
10405 let anchor = snapshot.anchor_before(Point::new(
10406 row_range.end.previous_row().0,
10407 snapshot.line_len(row_range.end.previous_row()),
10408 ));
10409 cursor_positions.push(anchor..anchor);
10410 }
10411
10412 self.transact(window, cx, |this, window, cx| {
10413 for row_range in row_ranges.into_iter().rev() {
10414 for row in row_range.iter_rows().rev() {
10415 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10416 let next_line_row = row.next_row();
10417 let indent = snapshot.indent_size_for_line(next_line_row);
10418 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10419
10420 let replace =
10421 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10422 " "
10423 } else {
10424 ""
10425 };
10426
10427 this.buffer.update(cx, |buffer, cx| {
10428 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10429 });
10430 }
10431 }
10432
10433 this.change_selections(Default::default(), window, cx, |s| {
10434 s.select_anchor_ranges(cursor_positions)
10435 });
10436 });
10437 }
10438
10439 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10440 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10441 self.join_lines_impl(true, window, cx);
10442 }
10443
10444 pub fn sort_lines_case_sensitive(
10445 &mut self,
10446 _: &SortLinesCaseSensitive,
10447 window: &mut Window,
10448 cx: &mut Context<Self>,
10449 ) {
10450 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10451 }
10452
10453 pub fn sort_lines_by_length(
10454 &mut self,
10455 _: &SortLinesByLength,
10456 window: &mut Window,
10457 cx: &mut Context<Self>,
10458 ) {
10459 self.manipulate_immutable_lines(window, cx, |lines| {
10460 lines.sort_by_key(|&line| line.chars().count())
10461 })
10462 }
10463
10464 pub fn sort_lines_case_insensitive(
10465 &mut self,
10466 _: &SortLinesCaseInsensitive,
10467 window: &mut Window,
10468 cx: &mut Context<Self>,
10469 ) {
10470 self.manipulate_immutable_lines(window, cx, |lines| {
10471 lines.sort_by_key(|line| line.to_lowercase())
10472 })
10473 }
10474
10475 pub fn unique_lines_case_insensitive(
10476 &mut self,
10477 _: &UniqueLinesCaseInsensitive,
10478 window: &mut Window,
10479 cx: &mut Context<Self>,
10480 ) {
10481 self.manipulate_immutable_lines(window, cx, |lines| {
10482 let mut seen = HashSet::default();
10483 lines.retain(|line| seen.insert(line.to_lowercase()));
10484 })
10485 }
10486
10487 pub fn unique_lines_case_sensitive(
10488 &mut self,
10489 _: &UniqueLinesCaseSensitive,
10490 window: &mut Window,
10491 cx: &mut Context<Self>,
10492 ) {
10493 self.manipulate_immutable_lines(window, cx, |lines| {
10494 let mut seen = HashSet::default();
10495 lines.retain(|line| seen.insert(*line));
10496 })
10497 }
10498
10499 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10500 let snapshot = self.buffer.read(cx).snapshot(cx);
10501 for selection in self.selections.disjoint_anchors_arc().iter() {
10502 if snapshot
10503 .language_at(selection.start)
10504 .and_then(|lang| lang.config().wrap_characters.as_ref())
10505 .is_some()
10506 {
10507 return true;
10508 }
10509 }
10510 false
10511 }
10512
10513 fn wrap_selections_in_tag(
10514 &mut self,
10515 _: &WrapSelectionsInTag,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10520
10521 let snapshot = self.buffer.read(cx).snapshot(cx);
10522
10523 let mut edits = Vec::new();
10524 let mut boundaries = Vec::new();
10525
10526 for selection in self
10527 .selections
10528 .all_adjusted(&self.display_snapshot(cx))
10529 .iter()
10530 {
10531 let Some(wrap_config) = snapshot
10532 .language_at(selection.start)
10533 .and_then(|lang| lang.config().wrap_characters.clone())
10534 else {
10535 continue;
10536 };
10537
10538 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10539 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10540
10541 let start_before = snapshot.anchor_before(selection.start);
10542 let end_after = snapshot.anchor_after(selection.end);
10543
10544 edits.push((start_before..start_before, open_tag));
10545 edits.push((end_after..end_after, close_tag));
10546
10547 boundaries.push((
10548 start_before,
10549 end_after,
10550 wrap_config.start_prefix.len(),
10551 wrap_config.end_suffix.len(),
10552 ));
10553 }
10554
10555 if edits.is_empty() {
10556 return;
10557 }
10558
10559 self.transact(window, cx, |this, window, cx| {
10560 let buffer = this.buffer.update(cx, |buffer, cx| {
10561 buffer.edit(edits, None, cx);
10562 buffer.snapshot(cx)
10563 });
10564
10565 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10566 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10567 boundaries.into_iter()
10568 {
10569 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10570 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10571 new_selections.push(open_offset..open_offset);
10572 new_selections.push(close_offset..close_offset);
10573 }
10574
10575 this.change_selections(Default::default(), window, cx, |s| {
10576 s.select_ranges(new_selections);
10577 });
10578
10579 this.request_autoscroll(Autoscroll::fit(), cx);
10580 });
10581 }
10582
10583 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10584 let Some(project) = self.project.clone() else {
10585 return;
10586 };
10587 self.reload(project, window, cx)
10588 .detach_and_notify_err(window, cx);
10589 }
10590
10591 pub fn restore_file(
10592 &mut self,
10593 _: &::git::RestoreFile,
10594 window: &mut Window,
10595 cx: &mut Context<Self>,
10596 ) {
10597 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10598 let mut buffer_ids = HashSet::default();
10599 let snapshot = self.buffer().read(cx).snapshot(cx);
10600 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10601 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10602 }
10603
10604 let buffer = self.buffer().read(cx);
10605 let ranges = buffer_ids
10606 .into_iter()
10607 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10608 .collect::<Vec<_>>();
10609
10610 self.restore_hunks_in_ranges(ranges, window, cx);
10611 }
10612
10613 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10614 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10615 let selections = self
10616 .selections
10617 .all(&self.display_snapshot(cx))
10618 .into_iter()
10619 .map(|s| s.range())
10620 .collect();
10621 self.restore_hunks_in_ranges(selections, window, cx);
10622 }
10623
10624 pub fn restore_hunks_in_ranges(
10625 &mut self,
10626 ranges: Vec<Range<Point>>,
10627 window: &mut Window,
10628 cx: &mut Context<Editor>,
10629 ) {
10630 let mut revert_changes = HashMap::default();
10631 let chunk_by = self
10632 .snapshot(window, cx)
10633 .hunks_for_ranges(ranges)
10634 .into_iter()
10635 .chunk_by(|hunk| hunk.buffer_id);
10636 for (buffer_id, hunks) in &chunk_by {
10637 let hunks = hunks.collect::<Vec<_>>();
10638 for hunk in &hunks {
10639 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10640 }
10641 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10642 }
10643 drop(chunk_by);
10644 if !revert_changes.is_empty() {
10645 self.transact(window, cx, |editor, window, cx| {
10646 editor.restore(revert_changes, window, cx);
10647 });
10648 }
10649 }
10650
10651 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10652 if let Some(status) = self
10653 .addons
10654 .iter()
10655 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10656 {
10657 return Some(status);
10658 }
10659 self.project
10660 .as_ref()?
10661 .read(cx)
10662 .status_for_buffer_id(buffer_id, cx)
10663 }
10664
10665 pub fn open_active_item_in_terminal(
10666 &mut self,
10667 _: &OpenInTerminal,
10668 window: &mut Window,
10669 cx: &mut Context<Self>,
10670 ) {
10671 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10672 let project_path = buffer.read(cx).project_path(cx)?;
10673 let project = self.project()?.read(cx);
10674 let entry = project.entry_for_path(&project_path, cx)?;
10675 let parent = match &entry.canonical_path {
10676 Some(canonical_path) => canonical_path.to_path_buf(),
10677 None => project.absolute_path(&project_path, cx)?,
10678 }
10679 .parent()?
10680 .to_path_buf();
10681 Some(parent)
10682 }) {
10683 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10684 }
10685 }
10686
10687 fn set_breakpoint_context_menu(
10688 &mut self,
10689 display_row: DisplayRow,
10690 position: Option<Anchor>,
10691 clicked_point: gpui::Point<Pixels>,
10692 window: &mut Window,
10693 cx: &mut Context<Self>,
10694 ) {
10695 let source = self
10696 .buffer
10697 .read(cx)
10698 .snapshot(cx)
10699 .anchor_before(Point::new(display_row.0, 0u32));
10700
10701 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10702
10703 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10704 self,
10705 source,
10706 clicked_point,
10707 context_menu,
10708 window,
10709 cx,
10710 );
10711 }
10712
10713 fn add_edit_breakpoint_block(
10714 &mut self,
10715 anchor: Anchor,
10716 breakpoint: &Breakpoint,
10717 edit_action: BreakpointPromptEditAction,
10718 window: &mut Window,
10719 cx: &mut Context<Self>,
10720 ) {
10721 let weak_editor = cx.weak_entity();
10722 let bp_prompt = cx.new(|cx| {
10723 BreakpointPromptEditor::new(
10724 weak_editor,
10725 anchor,
10726 breakpoint.clone(),
10727 edit_action,
10728 window,
10729 cx,
10730 )
10731 });
10732
10733 let height = bp_prompt.update(cx, |this, cx| {
10734 this.prompt
10735 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10736 });
10737 let cloned_prompt = bp_prompt.clone();
10738 let blocks = vec![BlockProperties {
10739 style: BlockStyle::Sticky,
10740 placement: BlockPlacement::Above(anchor),
10741 height: Some(height),
10742 render: Arc::new(move |cx| {
10743 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10744 cloned_prompt.clone().into_any_element()
10745 }),
10746 priority: 0,
10747 }];
10748
10749 let focus_handle = bp_prompt.focus_handle(cx);
10750 window.focus(&focus_handle);
10751
10752 let block_ids = self.insert_blocks(blocks, None, cx);
10753 bp_prompt.update(cx, |prompt, _| {
10754 prompt.add_block_ids(block_ids);
10755 });
10756 }
10757
10758 pub(crate) fn breakpoint_at_row(
10759 &self,
10760 row: u32,
10761 window: &mut Window,
10762 cx: &mut Context<Self>,
10763 ) -> Option<(Anchor, Breakpoint)> {
10764 let snapshot = self.snapshot(window, cx);
10765 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10766
10767 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10768 }
10769
10770 pub(crate) fn breakpoint_at_anchor(
10771 &self,
10772 breakpoint_position: Anchor,
10773 snapshot: &EditorSnapshot,
10774 cx: &mut Context<Self>,
10775 ) -> Option<(Anchor, Breakpoint)> {
10776 let buffer = self
10777 .buffer
10778 .read(cx)
10779 .buffer_for_anchor(breakpoint_position, cx)?;
10780
10781 let enclosing_excerpt = breakpoint_position.excerpt_id;
10782 let buffer_snapshot = buffer.read(cx).snapshot();
10783
10784 let row = buffer_snapshot
10785 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10786 .row;
10787
10788 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10789 let anchor_end = snapshot
10790 .buffer_snapshot()
10791 .anchor_after(Point::new(row, line_len));
10792
10793 self.breakpoint_store
10794 .as_ref()?
10795 .read_with(cx, |breakpoint_store, cx| {
10796 breakpoint_store
10797 .breakpoints(
10798 &buffer,
10799 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10800 &buffer_snapshot,
10801 cx,
10802 )
10803 .next()
10804 .and_then(|(bp, _)| {
10805 let breakpoint_row = buffer_snapshot
10806 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10807 .row;
10808
10809 if breakpoint_row == row {
10810 snapshot
10811 .buffer_snapshot()
10812 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10813 .map(|position| (position, bp.bp.clone()))
10814 } else {
10815 None
10816 }
10817 })
10818 })
10819 }
10820
10821 pub fn edit_log_breakpoint(
10822 &mut self,
10823 _: &EditLogBreakpoint,
10824 window: &mut Window,
10825 cx: &mut Context<Self>,
10826 ) {
10827 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10828 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10829 message: None,
10830 state: BreakpointState::Enabled,
10831 condition: None,
10832 hit_condition: None,
10833 });
10834
10835 self.add_edit_breakpoint_block(
10836 anchor,
10837 &breakpoint,
10838 BreakpointPromptEditAction::Log,
10839 window,
10840 cx,
10841 );
10842 }
10843 }
10844
10845 fn breakpoints_at_cursors(
10846 &self,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10850 let snapshot = self.snapshot(window, cx);
10851 let cursors = self
10852 .selections
10853 .disjoint_anchors_arc()
10854 .iter()
10855 .map(|selection| {
10856 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10857
10858 let breakpoint_position = self
10859 .breakpoint_at_row(cursor_position.row, window, cx)
10860 .map(|bp| bp.0)
10861 .unwrap_or_else(|| {
10862 snapshot
10863 .display_snapshot
10864 .buffer_snapshot()
10865 .anchor_after(Point::new(cursor_position.row, 0))
10866 });
10867
10868 let breakpoint = self
10869 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10870 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10871
10872 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10873 })
10874 // 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.
10875 .collect::<HashMap<Anchor, _>>();
10876
10877 cursors.into_iter().collect()
10878 }
10879
10880 pub fn enable_breakpoint(
10881 &mut self,
10882 _: &crate::actions::EnableBreakpoint,
10883 window: &mut Window,
10884 cx: &mut Context<Self>,
10885 ) {
10886 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10887 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10888 continue;
10889 };
10890 self.edit_breakpoint_at_anchor(
10891 anchor,
10892 breakpoint,
10893 BreakpointEditAction::InvertState,
10894 cx,
10895 );
10896 }
10897 }
10898
10899 pub fn disable_breakpoint(
10900 &mut self,
10901 _: &crate::actions::DisableBreakpoint,
10902 window: &mut Window,
10903 cx: &mut Context<Self>,
10904 ) {
10905 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10906 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10907 continue;
10908 };
10909 self.edit_breakpoint_at_anchor(
10910 anchor,
10911 breakpoint,
10912 BreakpointEditAction::InvertState,
10913 cx,
10914 );
10915 }
10916 }
10917
10918 pub fn toggle_breakpoint(
10919 &mut self,
10920 _: &crate::actions::ToggleBreakpoint,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10925 if let Some(breakpoint) = breakpoint {
10926 self.edit_breakpoint_at_anchor(
10927 anchor,
10928 breakpoint,
10929 BreakpointEditAction::Toggle,
10930 cx,
10931 );
10932 } else {
10933 self.edit_breakpoint_at_anchor(
10934 anchor,
10935 Breakpoint::new_standard(),
10936 BreakpointEditAction::Toggle,
10937 cx,
10938 );
10939 }
10940 }
10941 }
10942
10943 pub fn edit_breakpoint_at_anchor(
10944 &mut self,
10945 breakpoint_position: Anchor,
10946 breakpoint: Breakpoint,
10947 edit_action: BreakpointEditAction,
10948 cx: &mut Context<Self>,
10949 ) {
10950 let Some(breakpoint_store) = &self.breakpoint_store else {
10951 return;
10952 };
10953
10954 let Some(buffer) = self
10955 .buffer
10956 .read(cx)
10957 .buffer_for_anchor(breakpoint_position, cx)
10958 else {
10959 return;
10960 };
10961
10962 breakpoint_store.update(cx, |breakpoint_store, cx| {
10963 breakpoint_store.toggle_breakpoint(
10964 buffer,
10965 BreakpointWithPosition {
10966 position: breakpoint_position.text_anchor,
10967 bp: breakpoint,
10968 },
10969 edit_action,
10970 cx,
10971 );
10972 });
10973
10974 cx.notify();
10975 }
10976
10977 #[cfg(any(test, feature = "test-support"))]
10978 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10979 self.breakpoint_store.clone()
10980 }
10981
10982 pub fn prepare_restore_change(
10983 &self,
10984 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10985 hunk: &MultiBufferDiffHunk,
10986 cx: &mut App,
10987 ) -> Option<()> {
10988 if hunk.is_created_file() {
10989 return None;
10990 }
10991 let buffer = self.buffer.read(cx);
10992 let diff = buffer.diff_for(hunk.buffer_id)?;
10993 let buffer = buffer.buffer(hunk.buffer_id)?;
10994 let buffer = buffer.read(cx);
10995 let original_text = diff
10996 .read(cx)
10997 .base_text()
10998 .as_rope()
10999 .slice(hunk.diff_base_byte_range.clone());
11000 let buffer_snapshot = buffer.snapshot();
11001 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11002 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11003 probe
11004 .0
11005 .start
11006 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11007 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11008 }) {
11009 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11010 Some(())
11011 } else {
11012 None
11013 }
11014 }
11015
11016 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11017 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11018 }
11019
11020 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11021 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11022 }
11023
11024 fn manipulate_lines<M>(
11025 &mut self,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 mut manipulate: M,
11029 ) where
11030 M: FnMut(&str) -> LineManipulationResult,
11031 {
11032 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11033
11034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11035 let buffer = self.buffer.read(cx).snapshot(cx);
11036
11037 let mut edits = Vec::new();
11038
11039 let selections = self.selections.all::<Point>(&display_map);
11040 let mut selections = selections.iter().peekable();
11041 let mut contiguous_row_selections = Vec::new();
11042 let mut new_selections = Vec::new();
11043 let mut added_lines = 0;
11044 let mut removed_lines = 0;
11045
11046 while let Some(selection) = selections.next() {
11047 let (start_row, end_row) = consume_contiguous_rows(
11048 &mut contiguous_row_selections,
11049 selection,
11050 &display_map,
11051 &mut selections,
11052 );
11053
11054 let start_point = Point::new(start_row.0, 0);
11055 let end_point = Point::new(
11056 end_row.previous_row().0,
11057 buffer.line_len(end_row.previous_row()),
11058 );
11059 let text = buffer
11060 .text_for_range(start_point..end_point)
11061 .collect::<String>();
11062
11063 let LineManipulationResult {
11064 new_text,
11065 line_count_before,
11066 line_count_after,
11067 } = manipulate(&text);
11068
11069 edits.push((start_point..end_point, new_text));
11070
11071 // Selections must change based on added and removed line count
11072 let start_row =
11073 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11074 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11075 new_selections.push(Selection {
11076 id: selection.id,
11077 start: start_row,
11078 end: end_row,
11079 goal: SelectionGoal::None,
11080 reversed: selection.reversed,
11081 });
11082
11083 if line_count_after > line_count_before {
11084 added_lines += line_count_after - line_count_before;
11085 } else if line_count_before > line_count_after {
11086 removed_lines += line_count_before - line_count_after;
11087 }
11088 }
11089
11090 self.transact(window, cx, |this, window, cx| {
11091 let buffer = this.buffer.update(cx, |buffer, cx| {
11092 buffer.edit(edits, None, cx);
11093 buffer.snapshot(cx)
11094 });
11095
11096 // Recalculate offsets on newly edited buffer
11097 let new_selections = new_selections
11098 .iter()
11099 .map(|s| {
11100 let start_point = Point::new(s.start.0, 0);
11101 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11102 Selection {
11103 id: s.id,
11104 start: buffer.point_to_offset(start_point),
11105 end: buffer.point_to_offset(end_point),
11106 goal: s.goal,
11107 reversed: s.reversed,
11108 }
11109 })
11110 .collect();
11111
11112 this.change_selections(Default::default(), window, cx, |s| {
11113 s.select(new_selections);
11114 });
11115
11116 this.request_autoscroll(Autoscroll::fit(), cx);
11117 });
11118 }
11119
11120 fn manipulate_immutable_lines<Fn>(
11121 &mut self,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 mut callback: Fn,
11125 ) where
11126 Fn: FnMut(&mut Vec<&str>),
11127 {
11128 self.manipulate_lines(window, cx, |text| {
11129 let mut lines: Vec<&str> = text.split('\n').collect();
11130 let line_count_before = lines.len();
11131
11132 callback(&mut lines);
11133
11134 LineManipulationResult {
11135 new_text: lines.join("\n"),
11136 line_count_before,
11137 line_count_after: lines.len(),
11138 }
11139 });
11140 }
11141
11142 fn manipulate_mutable_lines<Fn>(
11143 &mut self,
11144 window: &mut Window,
11145 cx: &mut Context<Self>,
11146 mut callback: Fn,
11147 ) where
11148 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11149 {
11150 self.manipulate_lines(window, cx, |text| {
11151 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11152 let line_count_before = lines.len();
11153
11154 callback(&mut lines);
11155
11156 LineManipulationResult {
11157 new_text: lines.join("\n"),
11158 line_count_before,
11159 line_count_after: lines.len(),
11160 }
11161 });
11162 }
11163
11164 pub fn convert_indentation_to_spaces(
11165 &mut self,
11166 _: &ConvertIndentationToSpaces,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 let settings = self.buffer.read(cx).language_settings(cx);
11171 let tab_size = settings.tab_size.get() as usize;
11172
11173 self.manipulate_mutable_lines(window, cx, |lines| {
11174 // Allocates a reasonably sized scratch buffer once for the whole loop
11175 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11176 // Avoids recomputing spaces that could be inserted many times
11177 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11178 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11179 .collect();
11180
11181 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11182 let mut chars = line.as_ref().chars();
11183 let mut col = 0;
11184 let mut changed = false;
11185
11186 for ch in chars.by_ref() {
11187 match ch {
11188 ' ' => {
11189 reindented_line.push(' ');
11190 col += 1;
11191 }
11192 '\t' => {
11193 // \t are converted to spaces depending on the current column
11194 let spaces_len = tab_size - (col % tab_size);
11195 reindented_line.extend(&space_cache[spaces_len - 1]);
11196 col += spaces_len;
11197 changed = true;
11198 }
11199 _ => {
11200 // If we dont append before break, the character is consumed
11201 reindented_line.push(ch);
11202 break;
11203 }
11204 }
11205 }
11206
11207 if !changed {
11208 reindented_line.clear();
11209 continue;
11210 }
11211 // Append the rest of the line and replace old reference with new one
11212 reindented_line.extend(chars);
11213 *line = Cow::Owned(reindented_line.clone());
11214 reindented_line.clear();
11215 }
11216 });
11217 }
11218
11219 pub fn convert_indentation_to_tabs(
11220 &mut self,
11221 _: &ConvertIndentationToTabs,
11222 window: &mut Window,
11223 cx: &mut Context<Self>,
11224 ) {
11225 let settings = self.buffer.read(cx).language_settings(cx);
11226 let tab_size = settings.tab_size.get() as usize;
11227
11228 self.manipulate_mutable_lines(window, cx, |lines| {
11229 // Allocates a reasonably sized buffer once for the whole loop
11230 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11231 // Avoids recomputing spaces that could be inserted many times
11232 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11233 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11234 .collect();
11235
11236 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11237 let mut chars = line.chars();
11238 let mut spaces_count = 0;
11239 let mut first_non_indent_char = None;
11240 let mut changed = false;
11241
11242 for ch in chars.by_ref() {
11243 match ch {
11244 ' ' => {
11245 // Keep track of spaces. Append \t when we reach tab_size
11246 spaces_count += 1;
11247 changed = true;
11248 if spaces_count == tab_size {
11249 reindented_line.push('\t');
11250 spaces_count = 0;
11251 }
11252 }
11253 '\t' => {
11254 reindented_line.push('\t');
11255 spaces_count = 0;
11256 }
11257 _ => {
11258 // Dont append it yet, we might have remaining spaces
11259 first_non_indent_char = Some(ch);
11260 break;
11261 }
11262 }
11263 }
11264
11265 if !changed {
11266 reindented_line.clear();
11267 continue;
11268 }
11269 // Remaining spaces that didn't make a full tab stop
11270 if spaces_count > 0 {
11271 reindented_line.extend(&space_cache[spaces_count - 1]);
11272 }
11273 // If we consume an extra character that was not indentation, add it back
11274 if let Some(extra_char) = first_non_indent_char {
11275 reindented_line.push(extra_char);
11276 }
11277 // Append the rest of the line and replace old reference with new one
11278 reindented_line.extend(chars);
11279 *line = Cow::Owned(reindented_line.clone());
11280 reindented_line.clear();
11281 }
11282 });
11283 }
11284
11285 pub fn convert_to_upper_case(
11286 &mut self,
11287 _: &ConvertToUpperCase,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 self.manipulate_text(window, cx, |text| text.to_uppercase())
11292 }
11293
11294 pub fn convert_to_lower_case(
11295 &mut self,
11296 _: &ConvertToLowerCase,
11297 window: &mut Window,
11298 cx: &mut Context<Self>,
11299 ) {
11300 self.manipulate_text(window, cx, |text| text.to_lowercase())
11301 }
11302
11303 pub fn convert_to_title_case(
11304 &mut self,
11305 _: &ConvertToTitleCase,
11306 window: &mut Window,
11307 cx: &mut Context<Self>,
11308 ) {
11309 self.manipulate_text(window, cx, |text| {
11310 text.split('\n')
11311 .map(|line| line.to_case(Case::Title))
11312 .join("\n")
11313 })
11314 }
11315
11316 pub fn convert_to_snake_case(
11317 &mut self,
11318 _: &ConvertToSnakeCase,
11319 window: &mut Window,
11320 cx: &mut Context<Self>,
11321 ) {
11322 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11323 }
11324
11325 pub fn convert_to_kebab_case(
11326 &mut self,
11327 _: &ConvertToKebabCase,
11328 window: &mut Window,
11329 cx: &mut Context<Self>,
11330 ) {
11331 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11332 }
11333
11334 pub fn convert_to_upper_camel_case(
11335 &mut self,
11336 _: &ConvertToUpperCamelCase,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 self.manipulate_text(window, cx, |text| {
11341 text.split('\n')
11342 .map(|line| line.to_case(Case::UpperCamel))
11343 .join("\n")
11344 })
11345 }
11346
11347 pub fn convert_to_lower_camel_case(
11348 &mut self,
11349 _: &ConvertToLowerCamelCase,
11350 window: &mut Window,
11351 cx: &mut Context<Self>,
11352 ) {
11353 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11354 }
11355
11356 pub fn convert_to_opposite_case(
11357 &mut self,
11358 _: &ConvertToOppositeCase,
11359 window: &mut Window,
11360 cx: &mut Context<Self>,
11361 ) {
11362 self.manipulate_text(window, cx, |text| {
11363 text.chars()
11364 .fold(String::with_capacity(text.len()), |mut t, c| {
11365 if c.is_uppercase() {
11366 t.extend(c.to_lowercase());
11367 } else {
11368 t.extend(c.to_uppercase());
11369 }
11370 t
11371 })
11372 })
11373 }
11374
11375 pub fn convert_to_sentence_case(
11376 &mut self,
11377 _: &ConvertToSentenceCase,
11378 window: &mut Window,
11379 cx: &mut Context<Self>,
11380 ) {
11381 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11382 }
11383
11384 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11385 self.manipulate_text(window, cx, |text| {
11386 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11387 if has_upper_case_characters {
11388 text.to_lowercase()
11389 } else {
11390 text.to_uppercase()
11391 }
11392 })
11393 }
11394
11395 pub fn convert_to_rot13(
11396 &mut self,
11397 _: &ConvertToRot13,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 self.manipulate_text(window, cx, |text| {
11402 text.chars()
11403 .map(|c| match c {
11404 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11405 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11406 _ => c,
11407 })
11408 .collect()
11409 })
11410 }
11411
11412 pub fn convert_to_rot47(
11413 &mut self,
11414 _: &ConvertToRot47,
11415 window: &mut Window,
11416 cx: &mut Context<Self>,
11417 ) {
11418 self.manipulate_text(window, cx, |text| {
11419 text.chars()
11420 .map(|c| {
11421 let code_point = c as u32;
11422 if code_point >= 33 && code_point <= 126 {
11423 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11424 }
11425 c
11426 })
11427 .collect()
11428 })
11429 }
11430
11431 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11432 where
11433 Fn: FnMut(&str) -> String,
11434 {
11435 let buffer = self.buffer.read(cx).snapshot(cx);
11436
11437 let mut new_selections = Vec::new();
11438 let mut edits = Vec::new();
11439 let mut selection_adjustment = 0i32;
11440
11441 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11442 let selection_is_empty = selection.is_empty();
11443
11444 let (start, end) = if selection_is_empty {
11445 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11446 (word_range.start, word_range.end)
11447 } else {
11448 (
11449 buffer.point_to_offset(selection.start),
11450 buffer.point_to_offset(selection.end),
11451 )
11452 };
11453
11454 let text = buffer.text_for_range(start..end).collect::<String>();
11455 let old_length = text.len() as i32;
11456 let text = callback(&text);
11457
11458 new_selections.push(Selection {
11459 start: (start as i32 - selection_adjustment) as usize,
11460 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11461 goal: SelectionGoal::None,
11462 id: selection.id,
11463 reversed: selection.reversed,
11464 });
11465
11466 selection_adjustment += old_length - text.len() as i32;
11467
11468 edits.push((start..end, text));
11469 }
11470
11471 self.transact(window, cx, |this, window, cx| {
11472 this.buffer.update(cx, |buffer, cx| {
11473 buffer.edit(edits, None, cx);
11474 });
11475
11476 this.change_selections(Default::default(), window, cx, |s| {
11477 s.select(new_selections);
11478 });
11479
11480 this.request_autoscroll(Autoscroll::fit(), cx);
11481 });
11482 }
11483
11484 pub fn move_selection_on_drop(
11485 &mut self,
11486 selection: &Selection<Anchor>,
11487 target: DisplayPoint,
11488 is_cut: bool,
11489 window: &mut Window,
11490 cx: &mut Context<Self>,
11491 ) {
11492 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11493 let buffer = display_map.buffer_snapshot();
11494 let mut edits = Vec::new();
11495 let insert_point = display_map
11496 .clip_point(target, Bias::Left)
11497 .to_point(&display_map);
11498 let text = buffer
11499 .text_for_range(selection.start..selection.end)
11500 .collect::<String>();
11501 if is_cut {
11502 edits.push(((selection.start..selection.end), String::new()));
11503 }
11504 let insert_anchor = buffer.anchor_before(insert_point);
11505 edits.push(((insert_anchor..insert_anchor), text));
11506 let last_edit_start = insert_anchor.bias_left(buffer);
11507 let last_edit_end = insert_anchor.bias_right(buffer);
11508 self.transact(window, cx, |this, window, cx| {
11509 this.buffer.update(cx, |buffer, cx| {
11510 buffer.edit(edits, None, cx);
11511 });
11512 this.change_selections(Default::default(), window, cx, |s| {
11513 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11514 });
11515 });
11516 }
11517
11518 pub fn clear_selection_drag_state(&mut self) {
11519 self.selection_drag_state = SelectionDragState::None;
11520 }
11521
11522 pub fn duplicate(
11523 &mut self,
11524 upwards: bool,
11525 whole_lines: bool,
11526 window: &mut Window,
11527 cx: &mut Context<Self>,
11528 ) {
11529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11530
11531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11532 let buffer = display_map.buffer_snapshot();
11533 let selections = self.selections.all::<Point>(&display_map);
11534
11535 let mut edits = Vec::new();
11536 let mut selections_iter = selections.iter().peekable();
11537 while let Some(selection) = selections_iter.next() {
11538 let mut rows = selection.spanned_rows(false, &display_map);
11539 // duplicate line-wise
11540 if whole_lines || selection.start == selection.end {
11541 // Avoid duplicating the same lines twice.
11542 while let Some(next_selection) = selections_iter.peek() {
11543 let next_rows = next_selection.spanned_rows(false, &display_map);
11544 if next_rows.start < rows.end {
11545 rows.end = next_rows.end;
11546 selections_iter.next().unwrap();
11547 } else {
11548 break;
11549 }
11550 }
11551
11552 // Copy the text from the selected row region and splice it either at the start
11553 // or end of the region.
11554 let start = Point::new(rows.start.0, 0);
11555 let end = Point::new(
11556 rows.end.previous_row().0,
11557 buffer.line_len(rows.end.previous_row()),
11558 );
11559
11560 let mut text = buffer.text_for_range(start..end).collect::<String>();
11561
11562 let insert_location = if upwards {
11563 // When duplicating upward, we need to insert before the current line.
11564 // If we're on the last line and it doesn't end with a newline,
11565 // we need to add a newline before the duplicated content.
11566 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11567 && buffer.max_point().column > 0
11568 && !text.ends_with('\n');
11569
11570 if needs_leading_newline {
11571 text.insert(0, '\n');
11572 end
11573 } else {
11574 text.push('\n');
11575 Point::new(rows.start.0, 0)
11576 }
11577 } else {
11578 text.push('\n');
11579 start
11580 };
11581 edits.push((insert_location..insert_location, text));
11582 } else {
11583 // duplicate character-wise
11584 let start = selection.start;
11585 let end = selection.end;
11586 let text = buffer.text_for_range(start..end).collect::<String>();
11587 edits.push((selection.end..selection.end, text));
11588 }
11589 }
11590
11591 self.transact(window, cx, |this, window, cx| {
11592 this.buffer.update(cx, |buffer, cx| {
11593 buffer.edit(edits, None, cx);
11594 });
11595
11596 // When duplicating upward with whole lines, move the cursor to the duplicated line
11597 if upwards && whole_lines {
11598 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11599
11600 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11601 let mut new_ranges = Vec::new();
11602 let selections = s.all::<Point>(&display_map);
11603 let mut selections_iter = selections.iter().peekable();
11604
11605 while let Some(first_selection) = selections_iter.next() {
11606 // Group contiguous selections together to find the total row span
11607 let mut group_selections = vec![first_selection];
11608 let mut rows = first_selection.spanned_rows(false, &display_map);
11609
11610 while let Some(next_selection) = selections_iter.peek() {
11611 let next_rows = next_selection.spanned_rows(false, &display_map);
11612 if next_rows.start < rows.end {
11613 rows.end = next_rows.end;
11614 group_selections.push(selections_iter.next().unwrap());
11615 } else {
11616 break;
11617 }
11618 }
11619
11620 let row_count = rows.end.0 - rows.start.0;
11621
11622 // Move all selections in this group up by the total number of duplicated rows
11623 for selection in group_selections {
11624 let new_start = Point::new(
11625 selection.start.row.saturating_sub(row_count),
11626 selection.start.column,
11627 );
11628
11629 let new_end = Point::new(
11630 selection.end.row.saturating_sub(row_count),
11631 selection.end.column,
11632 );
11633
11634 new_ranges.push(new_start..new_end);
11635 }
11636 }
11637
11638 s.select_ranges(new_ranges);
11639 });
11640 }
11641
11642 this.request_autoscroll(Autoscroll::fit(), cx);
11643 });
11644 }
11645
11646 pub fn duplicate_line_up(
11647 &mut self,
11648 _: &DuplicateLineUp,
11649 window: &mut Window,
11650 cx: &mut Context<Self>,
11651 ) {
11652 self.duplicate(true, true, window, cx);
11653 }
11654
11655 pub fn duplicate_line_down(
11656 &mut self,
11657 _: &DuplicateLineDown,
11658 window: &mut Window,
11659 cx: &mut Context<Self>,
11660 ) {
11661 self.duplicate(false, true, window, cx);
11662 }
11663
11664 pub fn duplicate_selection(
11665 &mut self,
11666 _: &DuplicateSelection,
11667 window: &mut Window,
11668 cx: &mut Context<Self>,
11669 ) {
11670 self.duplicate(false, false, window, cx);
11671 }
11672
11673 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11674 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11675 if self.mode.is_single_line() {
11676 cx.propagate();
11677 return;
11678 }
11679
11680 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11681 let buffer = self.buffer.read(cx).snapshot(cx);
11682
11683 let mut edits = Vec::new();
11684 let mut unfold_ranges = Vec::new();
11685 let mut refold_creases = Vec::new();
11686
11687 let selections = self.selections.all::<Point>(&display_map);
11688 let mut selections = selections.iter().peekable();
11689 let mut contiguous_row_selections = Vec::new();
11690 let mut new_selections = Vec::new();
11691
11692 while let Some(selection) = selections.next() {
11693 // Find all the selections that span a contiguous row range
11694 let (start_row, end_row) = consume_contiguous_rows(
11695 &mut contiguous_row_selections,
11696 selection,
11697 &display_map,
11698 &mut selections,
11699 );
11700
11701 // Move the text spanned by the row range to be before the line preceding the row range
11702 if start_row.0 > 0 {
11703 let range_to_move = Point::new(
11704 start_row.previous_row().0,
11705 buffer.line_len(start_row.previous_row()),
11706 )
11707 ..Point::new(
11708 end_row.previous_row().0,
11709 buffer.line_len(end_row.previous_row()),
11710 );
11711 let insertion_point = display_map
11712 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11713 .0;
11714
11715 // Don't move lines across excerpts
11716 if buffer
11717 .excerpt_containing(insertion_point..range_to_move.end)
11718 .is_some()
11719 {
11720 let text = buffer
11721 .text_for_range(range_to_move.clone())
11722 .flat_map(|s| s.chars())
11723 .skip(1)
11724 .chain(['\n'])
11725 .collect::<String>();
11726
11727 edits.push((
11728 buffer.anchor_after(range_to_move.start)
11729 ..buffer.anchor_before(range_to_move.end),
11730 String::new(),
11731 ));
11732 let insertion_anchor = buffer.anchor_after(insertion_point);
11733 edits.push((insertion_anchor..insertion_anchor, text));
11734
11735 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11736
11737 // Move selections up
11738 new_selections.extend(contiguous_row_selections.drain(..).map(
11739 |mut selection| {
11740 selection.start.row -= row_delta;
11741 selection.end.row -= row_delta;
11742 selection
11743 },
11744 ));
11745
11746 // Move folds up
11747 unfold_ranges.push(range_to_move.clone());
11748 for fold in display_map.folds_in_range(
11749 buffer.anchor_before(range_to_move.start)
11750 ..buffer.anchor_after(range_to_move.end),
11751 ) {
11752 let mut start = fold.range.start.to_point(&buffer);
11753 let mut end = fold.range.end.to_point(&buffer);
11754 start.row -= row_delta;
11755 end.row -= row_delta;
11756 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11757 }
11758 }
11759 }
11760
11761 // If we didn't move line(s), preserve the existing selections
11762 new_selections.append(&mut contiguous_row_selections);
11763 }
11764
11765 self.transact(window, cx, |this, window, cx| {
11766 this.unfold_ranges(&unfold_ranges, true, true, cx);
11767 this.buffer.update(cx, |buffer, cx| {
11768 for (range, text) in edits {
11769 buffer.edit([(range, text)], None, cx);
11770 }
11771 });
11772 this.fold_creases(refold_creases, true, window, cx);
11773 this.change_selections(Default::default(), window, cx, |s| {
11774 s.select(new_selections);
11775 })
11776 });
11777 }
11778
11779 pub fn move_line_down(
11780 &mut self,
11781 _: &MoveLineDown,
11782 window: &mut Window,
11783 cx: &mut Context<Self>,
11784 ) {
11785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11786 if self.mode.is_single_line() {
11787 cx.propagate();
11788 return;
11789 }
11790
11791 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11792 let buffer = self.buffer.read(cx).snapshot(cx);
11793
11794 let mut edits = Vec::new();
11795 let mut unfold_ranges = Vec::new();
11796 let mut refold_creases = Vec::new();
11797
11798 let selections = self.selections.all::<Point>(&display_map);
11799 let mut selections = selections.iter().peekable();
11800 let mut contiguous_row_selections = Vec::new();
11801 let mut new_selections = Vec::new();
11802
11803 while let Some(selection) = selections.next() {
11804 // Find all the selections that span a contiguous row range
11805 let (start_row, end_row) = consume_contiguous_rows(
11806 &mut contiguous_row_selections,
11807 selection,
11808 &display_map,
11809 &mut selections,
11810 );
11811
11812 // Move the text spanned by the row range to be after the last line of the row range
11813 if end_row.0 <= buffer.max_point().row {
11814 let range_to_move =
11815 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11816 let insertion_point = display_map
11817 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11818 .0;
11819
11820 // Don't move lines across excerpt boundaries
11821 if buffer
11822 .excerpt_containing(range_to_move.start..insertion_point)
11823 .is_some()
11824 {
11825 let mut text = String::from("\n");
11826 text.extend(buffer.text_for_range(range_to_move.clone()));
11827 text.pop(); // Drop trailing newline
11828 edits.push((
11829 buffer.anchor_after(range_to_move.start)
11830 ..buffer.anchor_before(range_to_move.end),
11831 String::new(),
11832 ));
11833 let insertion_anchor = buffer.anchor_after(insertion_point);
11834 edits.push((insertion_anchor..insertion_anchor, text));
11835
11836 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11837
11838 // Move selections down
11839 new_selections.extend(contiguous_row_selections.drain(..).map(
11840 |mut selection| {
11841 selection.start.row += row_delta;
11842 selection.end.row += row_delta;
11843 selection
11844 },
11845 ));
11846
11847 // Move folds down
11848 unfold_ranges.push(range_to_move.clone());
11849 for fold in display_map.folds_in_range(
11850 buffer.anchor_before(range_to_move.start)
11851 ..buffer.anchor_after(range_to_move.end),
11852 ) {
11853 let mut start = fold.range.start.to_point(&buffer);
11854 let mut end = fold.range.end.to_point(&buffer);
11855 start.row += row_delta;
11856 end.row += row_delta;
11857 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11858 }
11859 }
11860 }
11861
11862 // If we didn't move line(s), preserve the existing selections
11863 new_selections.append(&mut contiguous_row_selections);
11864 }
11865
11866 self.transact(window, cx, |this, window, cx| {
11867 this.unfold_ranges(&unfold_ranges, true, true, cx);
11868 this.buffer.update(cx, |buffer, cx| {
11869 for (range, text) in edits {
11870 buffer.edit([(range, text)], None, cx);
11871 }
11872 });
11873 this.fold_creases(refold_creases, true, window, cx);
11874 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11875 });
11876 }
11877
11878 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11879 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11880 let text_layout_details = &self.text_layout_details(window);
11881 self.transact(window, cx, |this, window, cx| {
11882 let edits = this.change_selections(Default::default(), window, cx, |s| {
11883 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11884 s.move_with(|display_map, selection| {
11885 if !selection.is_empty() {
11886 return;
11887 }
11888
11889 let mut head = selection.head();
11890 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11891 if head.column() == display_map.line_len(head.row()) {
11892 transpose_offset = display_map
11893 .buffer_snapshot()
11894 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11895 }
11896
11897 if transpose_offset == 0 {
11898 return;
11899 }
11900
11901 *head.column_mut() += 1;
11902 head = display_map.clip_point(head, Bias::Right);
11903 let goal = SelectionGoal::HorizontalPosition(
11904 display_map
11905 .x_for_display_point(head, text_layout_details)
11906 .into(),
11907 );
11908 selection.collapse_to(head, goal);
11909
11910 let transpose_start = display_map
11911 .buffer_snapshot()
11912 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11913 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11914 let transpose_end = display_map
11915 .buffer_snapshot()
11916 .clip_offset(transpose_offset + 1, Bias::Right);
11917 if let Some(ch) = display_map
11918 .buffer_snapshot()
11919 .chars_at(transpose_start)
11920 .next()
11921 {
11922 edits.push((transpose_start..transpose_offset, String::new()));
11923 edits.push((transpose_end..transpose_end, ch.to_string()));
11924 }
11925 }
11926 });
11927 edits
11928 });
11929 this.buffer
11930 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11931 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11932 this.change_selections(Default::default(), window, cx, |s| {
11933 s.select(selections);
11934 });
11935 });
11936 }
11937
11938 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11940 if self.mode.is_single_line() {
11941 cx.propagate();
11942 return;
11943 }
11944
11945 self.rewrap_impl(RewrapOptions::default(), cx)
11946 }
11947
11948 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11949 let buffer = self.buffer.read(cx).snapshot(cx);
11950 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11951
11952 #[derive(Clone, Debug, PartialEq)]
11953 enum CommentFormat {
11954 /// single line comment, with prefix for line
11955 Line(String),
11956 /// single line within a block comment, with prefix for line
11957 BlockLine(String),
11958 /// a single line of a block comment that includes the initial delimiter
11959 BlockCommentWithStart(BlockCommentConfig),
11960 /// a single line of a block comment that includes the ending delimiter
11961 BlockCommentWithEnd(BlockCommentConfig),
11962 }
11963
11964 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11965 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11966 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11967 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11968 .peekable();
11969
11970 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11971 row
11972 } else {
11973 return Vec::new();
11974 };
11975
11976 let language_settings = buffer.language_settings_at(selection.head(), cx);
11977 let language_scope = buffer.language_scope_at(selection.head());
11978
11979 let indent_and_prefix_for_row =
11980 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11981 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11982 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11983 &language_scope
11984 {
11985 let indent_end = Point::new(row, indent.len);
11986 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11987 let line_text_after_indent = buffer
11988 .text_for_range(indent_end..line_end)
11989 .collect::<String>();
11990
11991 let is_within_comment_override = buffer
11992 .language_scope_at(indent_end)
11993 .is_some_and(|scope| scope.override_name() == Some("comment"));
11994 let comment_delimiters = if is_within_comment_override {
11995 // we are within a comment syntax node, but we don't
11996 // yet know what kind of comment: block, doc or line
11997 match (
11998 language_scope.documentation_comment(),
11999 language_scope.block_comment(),
12000 ) {
12001 (Some(config), _) | (_, Some(config))
12002 if buffer.contains_str_at(indent_end, &config.start) =>
12003 {
12004 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12005 }
12006 (Some(config), _) | (_, Some(config))
12007 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12008 {
12009 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12010 }
12011 (Some(config), _) | (_, Some(config))
12012 if buffer.contains_str_at(indent_end, &config.prefix) =>
12013 {
12014 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12015 }
12016 (_, _) => language_scope
12017 .line_comment_prefixes()
12018 .iter()
12019 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12020 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12021 }
12022 } else {
12023 // we not in an overridden comment node, but we may
12024 // be within a non-overridden line comment node
12025 language_scope
12026 .line_comment_prefixes()
12027 .iter()
12028 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12029 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12030 };
12031
12032 let rewrap_prefix = language_scope
12033 .rewrap_prefixes()
12034 .iter()
12035 .find_map(|prefix_regex| {
12036 prefix_regex.find(&line_text_after_indent).map(|mat| {
12037 if mat.start() == 0 {
12038 Some(mat.as_str().to_string())
12039 } else {
12040 None
12041 }
12042 })
12043 })
12044 .flatten();
12045 (comment_delimiters, rewrap_prefix)
12046 } else {
12047 (None, None)
12048 };
12049 (indent, comment_prefix, rewrap_prefix)
12050 };
12051
12052 let mut ranges = Vec::new();
12053 let from_empty_selection = selection.is_empty();
12054
12055 let mut current_range_start = first_row;
12056 let mut prev_row = first_row;
12057 let (
12058 mut current_range_indent,
12059 mut current_range_comment_delimiters,
12060 mut current_range_rewrap_prefix,
12061 ) = indent_and_prefix_for_row(first_row);
12062
12063 for row in non_blank_rows_iter.skip(1) {
12064 let has_paragraph_break = row > prev_row + 1;
12065
12066 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12067 indent_and_prefix_for_row(row);
12068
12069 let has_indent_change = row_indent != current_range_indent;
12070 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12071
12072 let has_boundary_change = has_comment_change
12073 || row_rewrap_prefix.is_some()
12074 || (has_indent_change && current_range_comment_delimiters.is_some());
12075
12076 if has_paragraph_break || has_boundary_change {
12077 ranges.push((
12078 language_settings.clone(),
12079 Point::new(current_range_start, 0)
12080 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12081 current_range_indent,
12082 current_range_comment_delimiters.clone(),
12083 current_range_rewrap_prefix.clone(),
12084 from_empty_selection,
12085 ));
12086 current_range_start = row;
12087 current_range_indent = row_indent;
12088 current_range_comment_delimiters = row_comment_delimiters;
12089 current_range_rewrap_prefix = row_rewrap_prefix;
12090 }
12091 prev_row = row;
12092 }
12093
12094 ranges.push((
12095 language_settings.clone(),
12096 Point::new(current_range_start, 0)
12097 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12098 current_range_indent,
12099 current_range_comment_delimiters,
12100 current_range_rewrap_prefix,
12101 from_empty_selection,
12102 ));
12103
12104 ranges
12105 });
12106
12107 let mut edits = Vec::new();
12108 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12109
12110 for (
12111 language_settings,
12112 wrap_range,
12113 mut indent_size,
12114 comment_prefix,
12115 rewrap_prefix,
12116 from_empty_selection,
12117 ) in wrap_ranges
12118 {
12119 let mut start_row = wrap_range.start.row;
12120 let mut end_row = wrap_range.end.row;
12121
12122 // Skip selections that overlap with a range that has already been rewrapped.
12123 let selection_range = start_row..end_row;
12124 if rewrapped_row_ranges
12125 .iter()
12126 .any(|range| range.overlaps(&selection_range))
12127 {
12128 continue;
12129 }
12130
12131 let tab_size = language_settings.tab_size;
12132
12133 let (line_prefix, inside_comment) = match &comment_prefix {
12134 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12135 (Some(prefix.as_str()), true)
12136 }
12137 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12138 (Some(prefix.as_ref()), true)
12139 }
12140 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12141 start: _,
12142 end: _,
12143 prefix,
12144 tab_size,
12145 })) => {
12146 indent_size.len += tab_size;
12147 (Some(prefix.as_ref()), true)
12148 }
12149 None => (None, false),
12150 };
12151 let indent_prefix = indent_size.chars().collect::<String>();
12152 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12153
12154 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12155 RewrapBehavior::InComments => inside_comment,
12156 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12157 RewrapBehavior::Anywhere => true,
12158 };
12159
12160 let should_rewrap = options.override_language_settings
12161 || allow_rewrap_based_on_language
12162 || self.hard_wrap.is_some();
12163 if !should_rewrap {
12164 continue;
12165 }
12166
12167 if from_empty_selection {
12168 'expand_upwards: while start_row > 0 {
12169 let prev_row = start_row - 1;
12170 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12171 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12172 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12173 {
12174 start_row = prev_row;
12175 } else {
12176 break 'expand_upwards;
12177 }
12178 }
12179
12180 'expand_downwards: while end_row < buffer.max_point().row {
12181 let next_row = end_row + 1;
12182 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12183 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12184 && !buffer.is_line_blank(MultiBufferRow(next_row))
12185 {
12186 end_row = next_row;
12187 } else {
12188 break 'expand_downwards;
12189 }
12190 }
12191 }
12192
12193 let start = Point::new(start_row, 0);
12194 let start_offset = ToOffset::to_offset(&start, &buffer);
12195 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12196 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12197 let mut first_line_delimiter = None;
12198 let mut last_line_delimiter = None;
12199 let Some(lines_without_prefixes) = selection_text
12200 .lines()
12201 .enumerate()
12202 .map(|(ix, line)| {
12203 let line_trimmed = line.trim_start();
12204 if rewrap_prefix.is_some() && ix > 0 {
12205 Ok(line_trimmed)
12206 } else if let Some(
12207 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12208 start,
12209 prefix,
12210 end,
12211 tab_size,
12212 })
12213 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12214 start,
12215 prefix,
12216 end,
12217 tab_size,
12218 }),
12219 ) = &comment_prefix
12220 {
12221 let line_trimmed = line_trimmed
12222 .strip_prefix(start.as_ref())
12223 .map(|s| {
12224 let mut indent_size = indent_size;
12225 indent_size.len -= tab_size;
12226 let indent_prefix: String = indent_size.chars().collect();
12227 first_line_delimiter = Some((indent_prefix, start));
12228 s.trim_start()
12229 })
12230 .unwrap_or(line_trimmed);
12231 let line_trimmed = line_trimmed
12232 .strip_suffix(end.as_ref())
12233 .map(|s| {
12234 last_line_delimiter = Some(end);
12235 s.trim_end()
12236 })
12237 .unwrap_or(line_trimmed);
12238 let line_trimmed = line_trimmed
12239 .strip_prefix(prefix.as_ref())
12240 .unwrap_or(line_trimmed);
12241 Ok(line_trimmed)
12242 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12243 line_trimmed.strip_prefix(prefix).with_context(|| {
12244 format!("line did not start with prefix {prefix:?}: {line:?}")
12245 })
12246 } else {
12247 line_trimmed
12248 .strip_prefix(&line_prefix.trim_start())
12249 .with_context(|| {
12250 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12251 })
12252 }
12253 })
12254 .collect::<Result<Vec<_>, _>>()
12255 .log_err()
12256 else {
12257 continue;
12258 };
12259
12260 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12261 buffer
12262 .language_settings_at(Point::new(start_row, 0), cx)
12263 .preferred_line_length as usize
12264 });
12265
12266 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12267 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12268 } else {
12269 line_prefix.clone()
12270 };
12271
12272 let wrapped_text = {
12273 let mut wrapped_text = wrap_with_prefix(
12274 line_prefix,
12275 subsequent_lines_prefix,
12276 lines_without_prefixes.join("\n"),
12277 wrap_column,
12278 tab_size,
12279 options.preserve_existing_whitespace,
12280 );
12281
12282 if let Some((indent, delimiter)) = first_line_delimiter {
12283 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12284 }
12285 if let Some(last_line) = last_line_delimiter {
12286 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12287 }
12288
12289 wrapped_text
12290 };
12291
12292 // TODO: should always use char-based diff while still supporting cursor behavior that
12293 // matches vim.
12294 let mut diff_options = DiffOptions::default();
12295 if options.override_language_settings {
12296 diff_options.max_word_diff_len = 0;
12297 diff_options.max_word_diff_line_count = 0;
12298 } else {
12299 diff_options.max_word_diff_len = usize::MAX;
12300 diff_options.max_word_diff_line_count = usize::MAX;
12301 }
12302
12303 for (old_range, new_text) in
12304 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12305 {
12306 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12307 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12308 edits.push((edit_start..edit_end, new_text));
12309 }
12310
12311 rewrapped_row_ranges.push(start_row..=end_row);
12312 }
12313
12314 self.buffer
12315 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12316 }
12317
12318 pub fn cut_common(
12319 &mut self,
12320 cut_no_selection_line: bool,
12321 window: &mut Window,
12322 cx: &mut Context<Self>,
12323 ) -> ClipboardItem {
12324 let mut text = String::new();
12325 let buffer = self.buffer.read(cx).snapshot(cx);
12326 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12327 let mut clipboard_selections = Vec::with_capacity(selections.len());
12328 {
12329 let max_point = buffer.max_point();
12330 let mut is_first = true;
12331 for selection in &mut selections {
12332 let is_entire_line =
12333 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12334 if is_entire_line {
12335 selection.start = Point::new(selection.start.row, 0);
12336 if !selection.is_empty() && selection.end.column == 0 {
12337 selection.end = cmp::min(max_point, selection.end);
12338 } else {
12339 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12340 }
12341 selection.goal = SelectionGoal::None;
12342 }
12343 if is_first {
12344 is_first = false;
12345 } else {
12346 text += "\n";
12347 }
12348 let mut len = 0;
12349 for chunk in buffer.text_for_range(selection.start..selection.end) {
12350 text.push_str(chunk);
12351 len += chunk.len();
12352 }
12353 clipboard_selections.push(ClipboardSelection {
12354 len,
12355 is_entire_line,
12356 first_line_indent: buffer
12357 .indent_size_for_line(MultiBufferRow(selection.start.row))
12358 .len,
12359 });
12360 }
12361 }
12362
12363 self.transact(window, cx, |this, window, cx| {
12364 this.change_selections(Default::default(), window, cx, |s| {
12365 s.select(selections);
12366 });
12367 this.insert("", window, cx);
12368 });
12369 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12370 }
12371
12372 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12374 let item = self.cut_common(true, window, cx);
12375 cx.write_to_clipboard(item);
12376 }
12377
12378 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12380 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12381 s.move_with(|snapshot, sel| {
12382 if sel.is_empty() {
12383 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12384 }
12385 if sel.is_empty() {
12386 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12387 }
12388 });
12389 });
12390 let item = self.cut_common(false, window, cx);
12391 cx.set_global(KillRing(item))
12392 }
12393
12394 pub fn kill_ring_yank(
12395 &mut self,
12396 _: &KillRingYank,
12397 window: &mut Window,
12398 cx: &mut Context<Self>,
12399 ) {
12400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12401 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12402 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12403 (kill_ring.text().to_string(), kill_ring.metadata_json())
12404 } else {
12405 return;
12406 }
12407 } else {
12408 return;
12409 };
12410 self.do_paste(&text, metadata, false, window, cx);
12411 }
12412
12413 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12414 self.do_copy(true, cx);
12415 }
12416
12417 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12418 self.do_copy(false, cx);
12419 }
12420
12421 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12422 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12423 let buffer = self.buffer.read(cx).read(cx);
12424 let mut text = String::new();
12425
12426 let mut clipboard_selections = Vec::with_capacity(selections.len());
12427 {
12428 let max_point = buffer.max_point();
12429 let mut is_first = true;
12430 for selection in &selections {
12431 let mut start = selection.start;
12432 let mut end = selection.end;
12433 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12434 let mut add_trailing_newline = false;
12435 if is_entire_line {
12436 start = Point::new(start.row, 0);
12437 let next_line_start = Point::new(end.row + 1, 0);
12438 if next_line_start <= max_point {
12439 end = next_line_start;
12440 } else {
12441 // We're on the last line without a trailing newline.
12442 // Copy to the end of the line and add a newline afterwards.
12443 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12444 add_trailing_newline = true;
12445 }
12446 }
12447
12448 let mut trimmed_selections = Vec::new();
12449 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12450 let row = MultiBufferRow(start.row);
12451 let first_indent = buffer.indent_size_for_line(row);
12452 if first_indent.len == 0 || start.column > first_indent.len {
12453 trimmed_selections.push(start..end);
12454 } else {
12455 trimmed_selections.push(
12456 Point::new(row.0, first_indent.len)
12457 ..Point::new(row.0, buffer.line_len(row)),
12458 );
12459 for row in start.row + 1..=end.row {
12460 let mut line_len = buffer.line_len(MultiBufferRow(row));
12461 if row == end.row {
12462 line_len = end.column;
12463 }
12464 if line_len == 0 {
12465 trimmed_selections
12466 .push(Point::new(row, 0)..Point::new(row, line_len));
12467 continue;
12468 }
12469 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12470 if row_indent_size.len >= first_indent.len {
12471 trimmed_selections.push(
12472 Point::new(row, first_indent.len)..Point::new(row, line_len),
12473 );
12474 } else {
12475 trimmed_selections.clear();
12476 trimmed_selections.push(start..end);
12477 break;
12478 }
12479 }
12480 }
12481 } else {
12482 trimmed_selections.push(start..end);
12483 }
12484
12485 for trimmed_range in trimmed_selections {
12486 if is_first {
12487 is_first = false;
12488 } else {
12489 text += "\n";
12490 }
12491 let mut len = 0;
12492 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12493 text.push_str(chunk);
12494 len += chunk.len();
12495 }
12496 if add_trailing_newline {
12497 text.push('\n');
12498 len += 1;
12499 }
12500 clipboard_selections.push(ClipboardSelection {
12501 len,
12502 is_entire_line,
12503 first_line_indent: buffer
12504 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12505 .len,
12506 });
12507 }
12508 }
12509 }
12510
12511 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12512 text,
12513 clipboard_selections,
12514 ));
12515 }
12516
12517 pub fn do_paste(
12518 &mut self,
12519 text: &String,
12520 clipboard_selections: Option<Vec<ClipboardSelection>>,
12521 handle_entire_lines: bool,
12522 window: &mut Window,
12523 cx: &mut Context<Self>,
12524 ) {
12525 if self.read_only(cx) {
12526 return;
12527 }
12528
12529 let clipboard_text = Cow::Borrowed(text.as_str());
12530
12531 self.transact(window, cx, |this, window, cx| {
12532 let had_active_edit_prediction = this.has_active_edit_prediction();
12533 let display_map = this.display_snapshot(cx);
12534 let old_selections = this.selections.all::<usize>(&display_map);
12535 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12536
12537 if let Some(mut clipboard_selections) = clipboard_selections {
12538 let all_selections_were_entire_line =
12539 clipboard_selections.iter().all(|s| s.is_entire_line);
12540 let first_selection_indent_column =
12541 clipboard_selections.first().map(|s| s.first_line_indent);
12542 if clipboard_selections.len() != old_selections.len() {
12543 clipboard_selections.drain(..);
12544 }
12545 let mut auto_indent_on_paste = true;
12546
12547 this.buffer.update(cx, |buffer, cx| {
12548 let snapshot = buffer.read(cx);
12549 auto_indent_on_paste = snapshot
12550 .language_settings_at(cursor_offset, cx)
12551 .auto_indent_on_paste;
12552
12553 let mut start_offset = 0;
12554 let mut edits = Vec::new();
12555 let mut original_indent_columns = Vec::new();
12556 for (ix, selection) in old_selections.iter().enumerate() {
12557 let to_insert;
12558 let entire_line;
12559 let original_indent_column;
12560 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12561 let end_offset = start_offset + clipboard_selection.len;
12562 to_insert = &clipboard_text[start_offset..end_offset];
12563 entire_line = clipboard_selection.is_entire_line;
12564 start_offset = end_offset + 1;
12565 original_indent_column = Some(clipboard_selection.first_line_indent);
12566 } else {
12567 to_insert = &*clipboard_text;
12568 entire_line = all_selections_were_entire_line;
12569 original_indent_column = first_selection_indent_column
12570 }
12571
12572 let (range, to_insert) =
12573 if selection.is_empty() && handle_entire_lines && entire_line {
12574 // If the corresponding selection was empty when this slice of the
12575 // clipboard text was written, then the entire line containing the
12576 // selection was copied. If this selection is also currently empty,
12577 // then paste the line before the current line of the buffer.
12578 let column = selection.start.to_point(&snapshot).column as usize;
12579 let line_start = selection.start - column;
12580 (line_start..line_start, Cow::Borrowed(to_insert))
12581 } else {
12582 let language = snapshot.language_at(selection.head());
12583 let range = selection.range();
12584 if let Some(language) = language
12585 && language.name() == "Markdown".into()
12586 {
12587 edit_for_markdown_paste(
12588 &snapshot,
12589 range,
12590 to_insert,
12591 url::Url::parse(to_insert).ok(),
12592 )
12593 } else {
12594 (range, Cow::Borrowed(to_insert))
12595 }
12596 };
12597
12598 edits.push((range, to_insert));
12599 original_indent_columns.push(original_indent_column);
12600 }
12601 drop(snapshot);
12602
12603 buffer.edit(
12604 edits,
12605 if auto_indent_on_paste {
12606 Some(AutoindentMode::Block {
12607 original_indent_columns,
12608 })
12609 } else {
12610 None
12611 },
12612 cx,
12613 );
12614 });
12615
12616 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12617 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12618 } else {
12619 let url = url::Url::parse(&clipboard_text).ok();
12620
12621 let auto_indent_mode = if !clipboard_text.is_empty() {
12622 Some(AutoindentMode::Block {
12623 original_indent_columns: Vec::new(),
12624 })
12625 } else {
12626 None
12627 };
12628
12629 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12630 let snapshot = buffer.snapshot(cx);
12631
12632 let anchors = old_selections
12633 .iter()
12634 .map(|s| {
12635 let anchor = snapshot.anchor_after(s.head());
12636 s.map(|_| anchor)
12637 })
12638 .collect::<Vec<_>>();
12639
12640 let mut edits = Vec::new();
12641
12642 for selection in old_selections.iter() {
12643 let language = snapshot.language_at(selection.head());
12644 let range = selection.range();
12645
12646 let (edit_range, edit_text) = if let Some(language) = language
12647 && language.name() == "Markdown".into()
12648 {
12649 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12650 } else {
12651 (range, clipboard_text.clone())
12652 };
12653
12654 edits.push((edit_range, edit_text));
12655 }
12656
12657 drop(snapshot);
12658 buffer.edit(edits, auto_indent_mode, cx);
12659
12660 anchors
12661 });
12662
12663 this.change_selections(Default::default(), window, cx, |s| {
12664 s.select_anchors(selection_anchors);
12665 });
12666 }
12667
12668 let trigger_in_words =
12669 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12670
12671 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12672 });
12673 }
12674
12675 pub fn diff_clipboard_with_selection(
12676 &mut self,
12677 _: &DiffClipboardWithSelection,
12678 window: &mut Window,
12679 cx: &mut Context<Self>,
12680 ) {
12681 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12682
12683 if selections.is_empty() {
12684 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12685 return;
12686 };
12687
12688 let clipboard_text = match cx.read_from_clipboard() {
12689 Some(item) => match item.entries().first() {
12690 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12691 _ => None,
12692 },
12693 None => None,
12694 };
12695
12696 let Some(clipboard_text) = clipboard_text else {
12697 log::warn!("Clipboard doesn't contain text.");
12698 return;
12699 };
12700
12701 window.dispatch_action(
12702 Box::new(DiffClipboardWithSelectionData {
12703 clipboard_text,
12704 editor: cx.entity(),
12705 }),
12706 cx,
12707 );
12708 }
12709
12710 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12711 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12712 if let Some(item) = cx.read_from_clipboard() {
12713 let entries = item.entries();
12714
12715 match entries.first() {
12716 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12717 // of all the pasted entries.
12718 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12719 .do_paste(
12720 clipboard_string.text(),
12721 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12722 true,
12723 window,
12724 cx,
12725 ),
12726 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12727 }
12728 }
12729 }
12730
12731 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12732 if self.read_only(cx) {
12733 return;
12734 }
12735
12736 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12737
12738 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12739 if let Some((selections, _)) =
12740 self.selection_history.transaction(transaction_id).cloned()
12741 {
12742 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12743 s.select_anchors(selections.to_vec());
12744 });
12745 } else {
12746 log::error!(
12747 "No entry in selection_history found for undo. \
12748 This may correspond to a bug where undo does not update the selection. \
12749 If this is occurring, please add details to \
12750 https://github.com/zed-industries/zed/issues/22692"
12751 );
12752 }
12753 self.request_autoscroll(Autoscroll::fit(), cx);
12754 self.unmark_text(window, cx);
12755 self.refresh_edit_prediction(true, false, window, cx);
12756 cx.emit(EditorEvent::Edited { transaction_id });
12757 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12758 }
12759 }
12760
12761 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12762 if self.read_only(cx) {
12763 return;
12764 }
12765
12766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12767
12768 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12769 if let Some((_, Some(selections))) =
12770 self.selection_history.transaction(transaction_id).cloned()
12771 {
12772 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12773 s.select_anchors(selections.to_vec());
12774 });
12775 } else {
12776 log::error!(
12777 "No entry in selection_history found for redo. \
12778 This may correspond to a bug where undo does not update the selection. \
12779 If this is occurring, please add details to \
12780 https://github.com/zed-industries/zed/issues/22692"
12781 );
12782 }
12783 self.request_autoscroll(Autoscroll::fit(), cx);
12784 self.unmark_text(window, cx);
12785 self.refresh_edit_prediction(true, false, window, cx);
12786 cx.emit(EditorEvent::Edited { transaction_id });
12787 }
12788 }
12789
12790 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12791 self.buffer
12792 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12793 }
12794
12795 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12796 self.buffer
12797 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12798 }
12799
12800 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12802 self.change_selections(Default::default(), window, cx, |s| {
12803 s.move_with(|map, selection| {
12804 let cursor = if selection.is_empty() {
12805 movement::left(map, selection.start)
12806 } else {
12807 selection.start
12808 };
12809 selection.collapse_to(cursor, SelectionGoal::None);
12810 });
12811 })
12812 }
12813
12814 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12816 self.change_selections(Default::default(), window, cx, |s| {
12817 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12818 })
12819 }
12820
12821 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12823 self.change_selections(Default::default(), window, cx, |s| {
12824 s.move_with(|map, selection| {
12825 let cursor = if selection.is_empty() {
12826 movement::right(map, selection.end)
12827 } else {
12828 selection.end
12829 };
12830 selection.collapse_to(cursor, SelectionGoal::None)
12831 });
12832 })
12833 }
12834
12835 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12836 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12837 self.change_selections(Default::default(), window, cx, |s| {
12838 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12839 });
12840 }
12841
12842 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12843 if self.take_rename(true, window, cx).is_some() {
12844 return;
12845 }
12846
12847 if self.mode.is_single_line() {
12848 cx.propagate();
12849 return;
12850 }
12851
12852 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12853
12854 let text_layout_details = &self.text_layout_details(window);
12855 let selection_count = self.selections.count();
12856 let first_selection = self.selections.first_anchor();
12857
12858 self.change_selections(Default::default(), window, cx, |s| {
12859 s.move_with(|map, selection| {
12860 if !selection.is_empty() {
12861 selection.goal = SelectionGoal::None;
12862 }
12863 let (cursor, goal) = movement::up(
12864 map,
12865 selection.start,
12866 selection.goal,
12867 false,
12868 text_layout_details,
12869 );
12870 selection.collapse_to(cursor, goal);
12871 });
12872 });
12873
12874 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12875 {
12876 cx.propagate();
12877 }
12878 }
12879
12880 pub fn move_up_by_lines(
12881 &mut self,
12882 action: &MoveUpByLines,
12883 window: &mut Window,
12884 cx: &mut Context<Self>,
12885 ) {
12886 if self.take_rename(true, window, cx).is_some() {
12887 return;
12888 }
12889
12890 if self.mode.is_single_line() {
12891 cx.propagate();
12892 return;
12893 }
12894
12895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12896
12897 let text_layout_details = &self.text_layout_details(window);
12898
12899 self.change_selections(Default::default(), window, cx, |s| {
12900 s.move_with(|map, selection| {
12901 if !selection.is_empty() {
12902 selection.goal = SelectionGoal::None;
12903 }
12904 let (cursor, goal) = movement::up_by_rows(
12905 map,
12906 selection.start,
12907 action.lines,
12908 selection.goal,
12909 false,
12910 text_layout_details,
12911 );
12912 selection.collapse_to(cursor, goal);
12913 });
12914 })
12915 }
12916
12917 pub fn move_down_by_lines(
12918 &mut self,
12919 action: &MoveDownByLines,
12920 window: &mut Window,
12921 cx: &mut Context<Self>,
12922 ) {
12923 if self.take_rename(true, window, cx).is_some() {
12924 return;
12925 }
12926
12927 if self.mode.is_single_line() {
12928 cx.propagate();
12929 return;
12930 }
12931
12932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12933
12934 let text_layout_details = &self.text_layout_details(window);
12935
12936 self.change_selections(Default::default(), window, cx, |s| {
12937 s.move_with(|map, selection| {
12938 if !selection.is_empty() {
12939 selection.goal = SelectionGoal::None;
12940 }
12941 let (cursor, goal) = movement::down_by_rows(
12942 map,
12943 selection.start,
12944 action.lines,
12945 selection.goal,
12946 false,
12947 text_layout_details,
12948 );
12949 selection.collapse_to(cursor, goal);
12950 });
12951 })
12952 }
12953
12954 pub fn select_down_by_lines(
12955 &mut self,
12956 action: &SelectDownByLines,
12957 window: &mut Window,
12958 cx: &mut Context<Self>,
12959 ) {
12960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12961 let text_layout_details = &self.text_layout_details(window);
12962 self.change_selections(Default::default(), window, cx, |s| {
12963 s.move_heads_with(|map, head, goal| {
12964 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12965 })
12966 })
12967 }
12968
12969 pub fn select_up_by_lines(
12970 &mut self,
12971 action: &SelectUpByLines,
12972 window: &mut Window,
12973 cx: &mut Context<Self>,
12974 ) {
12975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12976 let text_layout_details = &self.text_layout_details(window);
12977 self.change_selections(Default::default(), window, cx, |s| {
12978 s.move_heads_with(|map, head, goal| {
12979 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12980 })
12981 })
12982 }
12983
12984 pub fn select_page_up(
12985 &mut self,
12986 _: &SelectPageUp,
12987 window: &mut Window,
12988 cx: &mut Context<Self>,
12989 ) {
12990 let Some(row_count) = self.visible_row_count() else {
12991 return;
12992 };
12993
12994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12995
12996 let text_layout_details = &self.text_layout_details(window);
12997
12998 self.change_selections(Default::default(), window, cx, |s| {
12999 s.move_heads_with(|map, head, goal| {
13000 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13001 })
13002 })
13003 }
13004
13005 pub fn move_page_up(
13006 &mut self,
13007 action: &MovePageUp,
13008 window: &mut Window,
13009 cx: &mut Context<Self>,
13010 ) {
13011 if self.take_rename(true, window, cx).is_some() {
13012 return;
13013 }
13014
13015 if self
13016 .context_menu
13017 .borrow_mut()
13018 .as_mut()
13019 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13020 .unwrap_or(false)
13021 {
13022 return;
13023 }
13024
13025 if matches!(self.mode, EditorMode::SingleLine) {
13026 cx.propagate();
13027 return;
13028 }
13029
13030 let Some(row_count) = self.visible_row_count() else {
13031 return;
13032 };
13033
13034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13035
13036 let effects = if action.center_cursor {
13037 SelectionEffects::scroll(Autoscroll::center())
13038 } else {
13039 SelectionEffects::default()
13040 };
13041
13042 let text_layout_details = &self.text_layout_details(window);
13043
13044 self.change_selections(effects, window, cx, |s| {
13045 s.move_with(|map, selection| {
13046 if !selection.is_empty() {
13047 selection.goal = SelectionGoal::None;
13048 }
13049 let (cursor, goal) = movement::up_by_rows(
13050 map,
13051 selection.end,
13052 row_count,
13053 selection.goal,
13054 false,
13055 text_layout_details,
13056 );
13057 selection.collapse_to(cursor, goal);
13058 });
13059 });
13060 }
13061
13062 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13063 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13064 let text_layout_details = &self.text_layout_details(window);
13065 self.change_selections(Default::default(), window, cx, |s| {
13066 s.move_heads_with(|map, head, goal| {
13067 movement::up(map, head, goal, false, text_layout_details)
13068 })
13069 })
13070 }
13071
13072 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13073 self.take_rename(true, window, cx);
13074
13075 if self.mode.is_single_line() {
13076 cx.propagate();
13077 return;
13078 }
13079
13080 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13081
13082 let text_layout_details = &self.text_layout_details(window);
13083 let selection_count = self.selections.count();
13084 let first_selection = self.selections.first_anchor();
13085
13086 self.change_selections(Default::default(), window, cx, |s| {
13087 s.move_with(|map, selection| {
13088 if !selection.is_empty() {
13089 selection.goal = SelectionGoal::None;
13090 }
13091 let (cursor, goal) = movement::down(
13092 map,
13093 selection.end,
13094 selection.goal,
13095 false,
13096 text_layout_details,
13097 );
13098 selection.collapse_to(cursor, goal);
13099 });
13100 });
13101
13102 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13103 {
13104 cx.propagate();
13105 }
13106 }
13107
13108 pub fn select_page_down(
13109 &mut self,
13110 _: &SelectPageDown,
13111 window: &mut Window,
13112 cx: &mut Context<Self>,
13113 ) {
13114 let Some(row_count) = self.visible_row_count() else {
13115 return;
13116 };
13117
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13119
13120 let text_layout_details = &self.text_layout_details(window);
13121
13122 self.change_selections(Default::default(), window, cx, |s| {
13123 s.move_heads_with(|map, head, goal| {
13124 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13125 })
13126 })
13127 }
13128
13129 pub fn move_page_down(
13130 &mut self,
13131 action: &MovePageDown,
13132 window: &mut Window,
13133 cx: &mut Context<Self>,
13134 ) {
13135 if self.take_rename(true, window, cx).is_some() {
13136 return;
13137 }
13138
13139 if self
13140 .context_menu
13141 .borrow_mut()
13142 .as_mut()
13143 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13144 .unwrap_or(false)
13145 {
13146 return;
13147 }
13148
13149 if matches!(self.mode, EditorMode::SingleLine) {
13150 cx.propagate();
13151 return;
13152 }
13153
13154 let Some(row_count) = self.visible_row_count() else {
13155 return;
13156 };
13157
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159
13160 let effects = if action.center_cursor {
13161 SelectionEffects::scroll(Autoscroll::center())
13162 } else {
13163 SelectionEffects::default()
13164 };
13165
13166 let text_layout_details = &self.text_layout_details(window);
13167 self.change_selections(effects, window, cx, |s| {
13168 s.move_with(|map, selection| {
13169 if !selection.is_empty() {
13170 selection.goal = SelectionGoal::None;
13171 }
13172 let (cursor, goal) = movement::down_by_rows(
13173 map,
13174 selection.end,
13175 row_count,
13176 selection.goal,
13177 false,
13178 text_layout_details,
13179 );
13180 selection.collapse_to(cursor, goal);
13181 });
13182 });
13183 }
13184
13185 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13187 let text_layout_details = &self.text_layout_details(window);
13188 self.change_selections(Default::default(), window, cx, |s| {
13189 s.move_heads_with(|map, head, goal| {
13190 movement::down(map, head, goal, false, text_layout_details)
13191 })
13192 });
13193 }
13194
13195 pub fn context_menu_first(
13196 &mut self,
13197 _: &ContextMenuFirst,
13198 window: &mut Window,
13199 cx: &mut Context<Self>,
13200 ) {
13201 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13202 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13203 }
13204 }
13205
13206 pub fn context_menu_prev(
13207 &mut self,
13208 _: &ContextMenuPrevious,
13209 window: &mut Window,
13210 cx: &mut Context<Self>,
13211 ) {
13212 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13213 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13214 }
13215 }
13216
13217 pub fn context_menu_next(
13218 &mut self,
13219 _: &ContextMenuNext,
13220 window: &mut Window,
13221 cx: &mut Context<Self>,
13222 ) {
13223 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13224 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13225 }
13226 }
13227
13228 pub fn context_menu_last(
13229 &mut self,
13230 _: &ContextMenuLast,
13231 window: &mut Window,
13232 cx: &mut Context<Self>,
13233 ) {
13234 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13235 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13236 }
13237 }
13238
13239 pub fn signature_help_prev(
13240 &mut self,
13241 _: &SignatureHelpPrevious,
13242 _: &mut Window,
13243 cx: &mut Context<Self>,
13244 ) {
13245 if let Some(popover) = self.signature_help_state.popover_mut() {
13246 if popover.current_signature == 0 {
13247 popover.current_signature = popover.signatures.len() - 1;
13248 } else {
13249 popover.current_signature -= 1;
13250 }
13251 cx.notify();
13252 }
13253 }
13254
13255 pub fn signature_help_next(
13256 &mut self,
13257 _: &SignatureHelpNext,
13258 _: &mut Window,
13259 cx: &mut Context<Self>,
13260 ) {
13261 if let Some(popover) = self.signature_help_state.popover_mut() {
13262 if popover.current_signature + 1 == popover.signatures.len() {
13263 popover.current_signature = 0;
13264 } else {
13265 popover.current_signature += 1;
13266 }
13267 cx.notify();
13268 }
13269 }
13270
13271 pub fn move_to_previous_word_start(
13272 &mut self,
13273 _: &MoveToPreviousWordStart,
13274 window: &mut Window,
13275 cx: &mut Context<Self>,
13276 ) {
13277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13278 self.change_selections(Default::default(), window, cx, |s| {
13279 s.move_cursors_with(|map, head, _| {
13280 (
13281 movement::previous_word_start(map, head),
13282 SelectionGoal::None,
13283 )
13284 });
13285 })
13286 }
13287
13288 pub fn move_to_previous_subword_start(
13289 &mut self,
13290 _: &MoveToPreviousSubwordStart,
13291 window: &mut Window,
13292 cx: &mut Context<Self>,
13293 ) {
13294 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13295 self.change_selections(Default::default(), window, cx, |s| {
13296 s.move_cursors_with(|map, head, _| {
13297 (
13298 movement::previous_subword_start(map, head),
13299 SelectionGoal::None,
13300 )
13301 });
13302 })
13303 }
13304
13305 pub fn select_to_previous_word_start(
13306 &mut self,
13307 _: &SelectToPreviousWordStart,
13308 window: &mut Window,
13309 cx: &mut Context<Self>,
13310 ) {
13311 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13312 self.change_selections(Default::default(), window, cx, |s| {
13313 s.move_heads_with(|map, head, _| {
13314 (
13315 movement::previous_word_start(map, head),
13316 SelectionGoal::None,
13317 )
13318 });
13319 })
13320 }
13321
13322 pub fn select_to_previous_subword_start(
13323 &mut self,
13324 _: &SelectToPreviousSubwordStart,
13325 window: &mut Window,
13326 cx: &mut Context<Self>,
13327 ) {
13328 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13329 self.change_selections(Default::default(), window, cx, |s| {
13330 s.move_heads_with(|map, head, _| {
13331 (
13332 movement::previous_subword_start(map, head),
13333 SelectionGoal::None,
13334 )
13335 });
13336 })
13337 }
13338
13339 pub fn delete_to_previous_word_start(
13340 &mut self,
13341 action: &DeleteToPreviousWordStart,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13346 self.transact(window, cx, |this, window, cx| {
13347 this.select_autoclose_pair(window, cx);
13348 this.change_selections(Default::default(), window, cx, |s| {
13349 s.move_with(|map, selection| {
13350 if selection.is_empty() {
13351 let mut cursor = if action.ignore_newlines {
13352 movement::previous_word_start(map, selection.head())
13353 } else {
13354 movement::previous_word_start_or_newline(map, selection.head())
13355 };
13356 cursor = movement::adjust_greedy_deletion(
13357 map,
13358 selection.head(),
13359 cursor,
13360 action.ignore_brackets,
13361 );
13362 selection.set_head(cursor, SelectionGoal::None);
13363 }
13364 });
13365 });
13366 this.insert("", window, cx);
13367 });
13368 }
13369
13370 pub fn delete_to_previous_subword_start(
13371 &mut self,
13372 _: &DeleteToPreviousSubwordStart,
13373 window: &mut Window,
13374 cx: &mut Context<Self>,
13375 ) {
13376 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13377 self.transact(window, cx, |this, window, cx| {
13378 this.select_autoclose_pair(window, cx);
13379 this.change_selections(Default::default(), window, cx, |s| {
13380 s.move_with(|map, selection| {
13381 if selection.is_empty() {
13382 let mut cursor = movement::previous_subword_start(map, selection.head());
13383 cursor =
13384 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13385 selection.set_head(cursor, SelectionGoal::None);
13386 }
13387 });
13388 });
13389 this.insert("", window, cx);
13390 });
13391 }
13392
13393 pub fn move_to_next_word_end(
13394 &mut self,
13395 _: &MoveToNextWordEnd,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13400 self.change_selections(Default::default(), window, cx, |s| {
13401 s.move_cursors_with(|map, head, _| {
13402 (movement::next_word_end(map, head), SelectionGoal::None)
13403 });
13404 })
13405 }
13406
13407 pub fn move_to_next_subword_end(
13408 &mut self,
13409 _: &MoveToNextSubwordEnd,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13414 self.change_selections(Default::default(), window, cx, |s| {
13415 s.move_cursors_with(|map, head, _| {
13416 (movement::next_subword_end(map, head), SelectionGoal::None)
13417 });
13418 })
13419 }
13420
13421 pub fn select_to_next_word_end(
13422 &mut self,
13423 _: &SelectToNextWordEnd,
13424 window: &mut Window,
13425 cx: &mut Context<Self>,
13426 ) {
13427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13428 self.change_selections(Default::default(), window, cx, |s| {
13429 s.move_heads_with(|map, head, _| {
13430 (movement::next_word_end(map, head), SelectionGoal::None)
13431 });
13432 })
13433 }
13434
13435 pub fn select_to_next_subword_end(
13436 &mut self,
13437 _: &SelectToNextSubwordEnd,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13442 self.change_selections(Default::default(), window, cx, |s| {
13443 s.move_heads_with(|map, head, _| {
13444 (movement::next_subword_end(map, head), SelectionGoal::None)
13445 });
13446 })
13447 }
13448
13449 pub fn delete_to_next_word_end(
13450 &mut self,
13451 action: &DeleteToNextWordEnd,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13456 self.transact(window, cx, |this, 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::next_word_end(map, selection.head())
13462 } else {
13463 movement::next_word_end_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_next_subword_end(
13480 &mut self,
13481 _: &DeleteToNextSubwordEnd,
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.change_selections(Default::default(), window, cx, |s| {
13488 s.move_with(|map, selection| {
13489 if selection.is_empty() {
13490 let mut cursor = movement::next_subword_end(map, selection.head());
13491 cursor =
13492 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13493 selection.set_head(cursor, SelectionGoal::None);
13494 }
13495 });
13496 });
13497 this.insert("", window, cx);
13498 });
13499 }
13500
13501 pub fn move_to_beginning_of_line(
13502 &mut self,
13503 action: &MoveToBeginningOfLine,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_cursors_with(|map, head, _| {
13510 (
13511 movement::indented_line_beginning(
13512 map,
13513 head,
13514 action.stop_at_soft_wraps,
13515 action.stop_at_indent,
13516 ),
13517 SelectionGoal::None,
13518 )
13519 });
13520 })
13521 }
13522
13523 pub fn select_to_beginning_of_line(
13524 &mut self,
13525 action: &SelectToBeginningOfLine,
13526 window: &mut Window,
13527 cx: &mut Context<Self>,
13528 ) {
13529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13530 self.change_selections(Default::default(), window, cx, |s| {
13531 s.move_heads_with(|map, head, _| {
13532 (
13533 movement::indented_line_beginning(
13534 map,
13535 head,
13536 action.stop_at_soft_wraps,
13537 action.stop_at_indent,
13538 ),
13539 SelectionGoal::None,
13540 )
13541 });
13542 });
13543 }
13544
13545 pub fn delete_to_beginning_of_line(
13546 &mut self,
13547 action: &DeleteToBeginningOfLine,
13548 window: &mut Window,
13549 cx: &mut Context<Self>,
13550 ) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13552 self.transact(window, cx, |this, window, cx| {
13553 this.change_selections(Default::default(), window, cx, |s| {
13554 s.move_with(|_, selection| {
13555 selection.reversed = true;
13556 });
13557 });
13558
13559 this.select_to_beginning_of_line(
13560 &SelectToBeginningOfLine {
13561 stop_at_soft_wraps: false,
13562 stop_at_indent: action.stop_at_indent,
13563 },
13564 window,
13565 cx,
13566 );
13567 this.backspace(&Backspace, window, cx);
13568 });
13569 }
13570
13571 pub fn move_to_end_of_line(
13572 &mut self,
13573 action: &MoveToEndOfLine,
13574 window: &mut Window,
13575 cx: &mut Context<Self>,
13576 ) {
13577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13578 self.change_selections(Default::default(), window, cx, |s| {
13579 s.move_cursors_with(|map, head, _| {
13580 (
13581 movement::line_end(map, head, action.stop_at_soft_wraps),
13582 SelectionGoal::None,
13583 )
13584 });
13585 })
13586 }
13587
13588 pub fn select_to_end_of_line(
13589 &mut self,
13590 action: &SelectToEndOfLine,
13591 window: &mut Window,
13592 cx: &mut Context<Self>,
13593 ) {
13594 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13595 self.change_selections(Default::default(), window, cx, |s| {
13596 s.move_heads_with(|map, head, _| {
13597 (
13598 movement::line_end(map, head, action.stop_at_soft_wraps),
13599 SelectionGoal::None,
13600 )
13601 });
13602 })
13603 }
13604
13605 pub fn delete_to_end_of_line(
13606 &mut self,
13607 _: &DeleteToEndOfLine,
13608 window: &mut Window,
13609 cx: &mut Context<Self>,
13610 ) {
13611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13612 self.transact(window, cx, |this, window, cx| {
13613 this.select_to_end_of_line(
13614 &SelectToEndOfLine {
13615 stop_at_soft_wraps: false,
13616 },
13617 window,
13618 cx,
13619 );
13620 this.delete(&Delete, window, cx);
13621 });
13622 }
13623
13624 pub fn cut_to_end_of_line(
13625 &mut self,
13626 action: &CutToEndOfLine,
13627 window: &mut Window,
13628 cx: &mut Context<Self>,
13629 ) {
13630 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13631 self.transact(window, cx, |this, window, cx| {
13632 this.select_to_end_of_line(
13633 &SelectToEndOfLine {
13634 stop_at_soft_wraps: false,
13635 },
13636 window,
13637 cx,
13638 );
13639 if !action.stop_at_newlines {
13640 this.change_selections(Default::default(), window, cx, |s| {
13641 s.move_with(|_, sel| {
13642 if sel.is_empty() {
13643 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13644 }
13645 });
13646 });
13647 }
13648 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13649 let item = this.cut_common(false, window, cx);
13650 cx.write_to_clipboard(item);
13651 });
13652 }
13653
13654 pub fn move_to_start_of_paragraph(
13655 &mut self,
13656 _: &MoveToStartOfParagraph,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 if matches!(self.mode, EditorMode::SingleLine) {
13661 cx.propagate();
13662 return;
13663 }
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.move_with(|map, selection| {
13667 selection.collapse_to(
13668 movement::start_of_paragraph(map, selection.head(), 1),
13669 SelectionGoal::None,
13670 )
13671 });
13672 })
13673 }
13674
13675 pub fn move_to_end_of_paragraph(
13676 &mut self,
13677 _: &MoveToEndOfParagraph,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 if matches!(self.mode, EditorMode::SingleLine) {
13682 cx.propagate();
13683 return;
13684 }
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686 self.change_selections(Default::default(), window, cx, |s| {
13687 s.move_with(|map, selection| {
13688 selection.collapse_to(
13689 movement::end_of_paragraph(map, selection.head(), 1),
13690 SelectionGoal::None,
13691 )
13692 });
13693 })
13694 }
13695
13696 pub fn select_to_start_of_paragraph(
13697 &mut self,
13698 _: &SelectToStartOfParagraph,
13699 window: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 if matches!(self.mode, EditorMode::SingleLine) {
13703 cx.propagate();
13704 return;
13705 }
13706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13707 self.change_selections(Default::default(), window, cx, |s| {
13708 s.move_heads_with(|map, head, _| {
13709 (
13710 movement::start_of_paragraph(map, head, 1),
13711 SelectionGoal::None,
13712 )
13713 });
13714 })
13715 }
13716
13717 pub fn select_to_end_of_paragraph(
13718 &mut self,
13719 _: &SelectToEndOfParagraph,
13720 window: &mut Window,
13721 cx: &mut Context<Self>,
13722 ) {
13723 if matches!(self.mode, EditorMode::SingleLine) {
13724 cx.propagate();
13725 return;
13726 }
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13728 self.change_selections(Default::default(), window, cx, |s| {
13729 s.move_heads_with(|map, head, _| {
13730 (
13731 movement::end_of_paragraph(map, head, 1),
13732 SelectionGoal::None,
13733 )
13734 });
13735 })
13736 }
13737
13738 pub fn move_to_start_of_excerpt(
13739 &mut self,
13740 _: &MoveToStartOfExcerpt,
13741 window: &mut Window,
13742 cx: &mut Context<Self>,
13743 ) {
13744 if matches!(self.mode, EditorMode::SingleLine) {
13745 cx.propagate();
13746 return;
13747 }
13748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13749 self.change_selections(Default::default(), window, cx, |s| {
13750 s.move_with(|map, selection| {
13751 selection.collapse_to(
13752 movement::start_of_excerpt(
13753 map,
13754 selection.head(),
13755 workspace::searchable::Direction::Prev,
13756 ),
13757 SelectionGoal::None,
13758 )
13759 });
13760 })
13761 }
13762
13763 pub fn move_to_start_of_next_excerpt(
13764 &mut self,
13765 _: &MoveToStartOfNextExcerpt,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 if matches!(self.mode, EditorMode::SingleLine) {
13770 cx.propagate();
13771 return;
13772 }
13773
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_with(|map, selection| {
13776 selection.collapse_to(
13777 movement::start_of_excerpt(
13778 map,
13779 selection.head(),
13780 workspace::searchable::Direction::Next,
13781 ),
13782 SelectionGoal::None,
13783 )
13784 });
13785 })
13786 }
13787
13788 pub fn move_to_end_of_excerpt(
13789 &mut self,
13790 _: &MoveToEndOfExcerpt,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 if matches!(self.mode, EditorMode::SingleLine) {
13795 cx.propagate();
13796 return;
13797 }
13798 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13799 self.change_selections(Default::default(), window, cx, |s| {
13800 s.move_with(|map, selection| {
13801 selection.collapse_to(
13802 movement::end_of_excerpt(
13803 map,
13804 selection.head(),
13805 workspace::searchable::Direction::Next,
13806 ),
13807 SelectionGoal::None,
13808 )
13809 });
13810 })
13811 }
13812
13813 pub fn move_to_end_of_previous_excerpt(
13814 &mut self,
13815 _: &MoveToEndOfPreviousExcerpt,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 if matches!(self.mode, EditorMode::SingleLine) {
13820 cx.propagate();
13821 return;
13822 }
13823 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13824 self.change_selections(Default::default(), window, cx, |s| {
13825 s.move_with(|map, selection| {
13826 selection.collapse_to(
13827 movement::end_of_excerpt(
13828 map,
13829 selection.head(),
13830 workspace::searchable::Direction::Prev,
13831 ),
13832 SelectionGoal::None,
13833 )
13834 });
13835 })
13836 }
13837
13838 pub fn select_to_start_of_excerpt(
13839 &mut self,
13840 _: &SelectToStartOfExcerpt,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) {
13844 if matches!(self.mode, EditorMode::SingleLine) {
13845 cx.propagate();
13846 return;
13847 }
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849 self.change_selections(Default::default(), window, cx, |s| {
13850 s.move_heads_with(|map, head, _| {
13851 (
13852 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13853 SelectionGoal::None,
13854 )
13855 });
13856 })
13857 }
13858
13859 pub fn select_to_start_of_next_excerpt(
13860 &mut self,
13861 _: &SelectToStartOfNextExcerpt,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) {
13865 if matches!(self.mode, EditorMode::SingleLine) {
13866 cx.propagate();
13867 return;
13868 }
13869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13870 self.change_selections(Default::default(), window, cx, |s| {
13871 s.move_heads_with(|map, head, _| {
13872 (
13873 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13874 SelectionGoal::None,
13875 )
13876 });
13877 })
13878 }
13879
13880 pub fn select_to_end_of_excerpt(
13881 &mut self,
13882 _: &SelectToEndOfExcerpt,
13883 window: &mut Window,
13884 cx: &mut Context<Self>,
13885 ) {
13886 if matches!(self.mode, EditorMode::SingleLine) {
13887 cx.propagate();
13888 return;
13889 }
13890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13891 self.change_selections(Default::default(), window, cx, |s| {
13892 s.move_heads_with(|map, head, _| {
13893 (
13894 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13895 SelectionGoal::None,
13896 )
13897 });
13898 })
13899 }
13900
13901 pub fn select_to_end_of_previous_excerpt(
13902 &mut self,
13903 _: &SelectToEndOfPreviousExcerpt,
13904 window: &mut Window,
13905 cx: &mut Context<Self>,
13906 ) {
13907 if matches!(self.mode, EditorMode::SingleLine) {
13908 cx.propagate();
13909 return;
13910 }
13911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13912 self.change_selections(Default::default(), window, cx, |s| {
13913 s.move_heads_with(|map, head, _| {
13914 (
13915 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13916 SelectionGoal::None,
13917 )
13918 });
13919 })
13920 }
13921
13922 pub fn move_to_beginning(
13923 &mut self,
13924 _: &MoveToBeginning,
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.select_ranges(vec![0..0]);
13935 });
13936 }
13937
13938 pub fn select_to_beginning(
13939 &mut self,
13940 _: &SelectToBeginning,
13941 window: &mut Window,
13942 cx: &mut Context<Self>,
13943 ) {
13944 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13945 selection.set_head(Point::zero(), SelectionGoal::None);
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.select(vec![selection]);
13949 });
13950 }
13951
13952 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13953 if matches!(self.mode, EditorMode::SingleLine) {
13954 cx.propagate();
13955 return;
13956 }
13957 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13958 let cursor = self.buffer.read(cx).read(cx).len();
13959 self.change_selections(Default::default(), window, cx, |s| {
13960 s.select_ranges(vec![cursor..cursor])
13961 });
13962 }
13963
13964 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13965 self.nav_history = nav_history;
13966 }
13967
13968 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13969 self.nav_history.as_ref()
13970 }
13971
13972 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13973 self.push_to_nav_history(
13974 self.selections.newest_anchor().head(),
13975 None,
13976 false,
13977 true,
13978 cx,
13979 );
13980 }
13981
13982 fn push_to_nav_history(
13983 &mut self,
13984 cursor_anchor: Anchor,
13985 new_position: Option<Point>,
13986 is_deactivate: bool,
13987 always: bool,
13988 cx: &mut Context<Self>,
13989 ) {
13990 if let Some(nav_history) = self.nav_history.as_mut() {
13991 let buffer = self.buffer.read(cx).read(cx);
13992 let cursor_position = cursor_anchor.to_point(&buffer);
13993 let scroll_state = self.scroll_manager.anchor();
13994 let scroll_top_row = scroll_state.top_row(&buffer);
13995 drop(buffer);
13996
13997 if let Some(new_position) = new_position {
13998 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13999 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14000 return;
14001 }
14002 }
14003
14004 nav_history.push(
14005 Some(NavigationData {
14006 cursor_anchor,
14007 cursor_position,
14008 scroll_anchor: scroll_state,
14009 scroll_top_row,
14010 }),
14011 cx,
14012 );
14013 cx.emit(EditorEvent::PushedToNavHistory {
14014 anchor: cursor_anchor,
14015 is_deactivate,
14016 })
14017 }
14018 }
14019
14020 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14022 let buffer = self.buffer.read(cx).snapshot(cx);
14023 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14024 selection.set_head(buffer.len(), SelectionGoal::None);
14025 self.change_selections(Default::default(), window, cx, |s| {
14026 s.select(vec![selection]);
14027 });
14028 }
14029
14030 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14032 let end = self.buffer.read(cx).read(cx).len();
14033 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14034 s.select_ranges(vec![0..end]);
14035 });
14036 }
14037
14038 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14039 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14040 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14041 let mut selections = self.selections.all::<Point>(&display_map);
14042 let max_point = display_map.buffer_snapshot().max_point();
14043 for selection in &mut selections {
14044 let rows = selection.spanned_rows(true, &display_map);
14045 selection.start = Point::new(rows.start.0, 0);
14046 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14047 selection.reversed = false;
14048 }
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.select(selections);
14051 });
14052 }
14053
14054 pub fn split_selection_into_lines(
14055 &mut self,
14056 action: &SplitSelectionIntoLines,
14057 window: &mut Window,
14058 cx: &mut Context<Self>,
14059 ) {
14060 let selections = self
14061 .selections
14062 .all::<Point>(&self.display_snapshot(cx))
14063 .into_iter()
14064 .map(|selection| selection.start..selection.end)
14065 .collect::<Vec<_>>();
14066 self.unfold_ranges(&selections, true, true, cx);
14067
14068 let mut new_selection_ranges = Vec::new();
14069 {
14070 let buffer = self.buffer.read(cx).read(cx);
14071 for selection in selections {
14072 for row in selection.start.row..selection.end.row {
14073 let line_start = Point::new(row, 0);
14074 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14075
14076 if action.keep_selections {
14077 // Keep the selection range for each line
14078 let selection_start = if row == selection.start.row {
14079 selection.start
14080 } else {
14081 line_start
14082 };
14083 new_selection_ranges.push(selection_start..line_end);
14084 } else {
14085 // Collapse to cursor at end of line
14086 new_selection_ranges.push(line_end..line_end);
14087 }
14088 }
14089
14090 let is_multiline_selection = selection.start.row != selection.end.row;
14091 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14092 // so this action feels more ergonomic when paired with other selection operations
14093 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14094 if !should_skip_last {
14095 if action.keep_selections {
14096 if is_multiline_selection {
14097 let line_start = Point::new(selection.end.row, 0);
14098 new_selection_ranges.push(line_start..selection.end);
14099 } else {
14100 new_selection_ranges.push(selection.start..selection.end);
14101 }
14102 } else {
14103 new_selection_ranges.push(selection.end..selection.end);
14104 }
14105 }
14106 }
14107 }
14108 self.change_selections(Default::default(), window, cx, |s| {
14109 s.select_ranges(new_selection_ranges);
14110 });
14111 }
14112
14113 pub fn add_selection_above(
14114 &mut self,
14115 action: &AddSelectionAbove,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) {
14119 self.add_selection(true, action.skip_soft_wrap, window, cx);
14120 }
14121
14122 pub fn add_selection_below(
14123 &mut self,
14124 action: &AddSelectionBelow,
14125 window: &mut Window,
14126 cx: &mut Context<Self>,
14127 ) {
14128 self.add_selection(false, action.skip_soft_wrap, window, cx);
14129 }
14130
14131 fn add_selection(
14132 &mut self,
14133 above: bool,
14134 skip_soft_wrap: bool,
14135 window: &mut Window,
14136 cx: &mut Context<Self>,
14137 ) {
14138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14139
14140 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14141 let all_selections = self.selections.all::<Point>(&display_map);
14142 let text_layout_details = self.text_layout_details(window);
14143
14144 let (mut columnar_selections, new_selections_to_columnarize) = {
14145 if let Some(state) = self.add_selections_state.as_ref() {
14146 let columnar_selection_ids: HashSet<_> = state
14147 .groups
14148 .iter()
14149 .flat_map(|group| group.stack.iter())
14150 .copied()
14151 .collect();
14152
14153 all_selections
14154 .into_iter()
14155 .partition(|s| columnar_selection_ids.contains(&s.id))
14156 } else {
14157 (Vec::new(), all_selections)
14158 }
14159 };
14160
14161 let mut state = self
14162 .add_selections_state
14163 .take()
14164 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14165
14166 for selection in new_selections_to_columnarize {
14167 let range = selection.display_range(&display_map).sorted();
14168 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14169 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14170 let positions = start_x.min(end_x)..start_x.max(end_x);
14171 let mut stack = Vec::new();
14172 for row in range.start.row().0..=range.end.row().0 {
14173 if let Some(selection) = self.selections.build_columnar_selection(
14174 &display_map,
14175 DisplayRow(row),
14176 &positions,
14177 selection.reversed,
14178 &text_layout_details,
14179 ) {
14180 stack.push(selection.id);
14181 columnar_selections.push(selection);
14182 }
14183 }
14184 if !stack.is_empty() {
14185 if above {
14186 stack.reverse();
14187 }
14188 state.groups.push(AddSelectionsGroup { above, stack });
14189 }
14190 }
14191
14192 let mut final_selections = Vec::new();
14193 let end_row = if above {
14194 DisplayRow(0)
14195 } else {
14196 display_map.max_point().row()
14197 };
14198
14199 let mut last_added_item_per_group = HashMap::default();
14200 for group in state.groups.iter_mut() {
14201 if let Some(last_id) = group.stack.last() {
14202 last_added_item_per_group.insert(*last_id, group);
14203 }
14204 }
14205
14206 for selection in columnar_selections {
14207 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14208 if above == group.above {
14209 let range = selection.display_range(&display_map).sorted();
14210 debug_assert_eq!(range.start.row(), range.end.row());
14211 let mut row = range.start.row();
14212 let positions =
14213 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14214 Pixels::from(start)..Pixels::from(end)
14215 } else {
14216 let start_x =
14217 display_map.x_for_display_point(range.start, &text_layout_details);
14218 let end_x =
14219 display_map.x_for_display_point(range.end, &text_layout_details);
14220 start_x.min(end_x)..start_x.max(end_x)
14221 };
14222
14223 let mut maybe_new_selection = None;
14224 let direction = if above { -1 } else { 1 };
14225
14226 while row != end_row {
14227 if skip_soft_wrap {
14228 row = display_map
14229 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14230 .row();
14231 } else if above {
14232 row.0 -= 1;
14233 } else {
14234 row.0 += 1;
14235 }
14236
14237 if let Some(new_selection) = self.selections.build_columnar_selection(
14238 &display_map,
14239 row,
14240 &positions,
14241 selection.reversed,
14242 &text_layout_details,
14243 ) {
14244 maybe_new_selection = Some(new_selection);
14245 break;
14246 }
14247 }
14248
14249 if let Some(new_selection) = maybe_new_selection {
14250 group.stack.push(new_selection.id);
14251 if above {
14252 final_selections.push(new_selection);
14253 final_selections.push(selection);
14254 } else {
14255 final_selections.push(selection);
14256 final_selections.push(new_selection);
14257 }
14258 } else {
14259 final_selections.push(selection);
14260 }
14261 } else {
14262 group.stack.pop();
14263 }
14264 } else {
14265 final_selections.push(selection);
14266 }
14267 }
14268
14269 self.change_selections(Default::default(), window, cx, |s| {
14270 s.select(final_selections);
14271 });
14272
14273 let final_selection_ids: HashSet<_> = self
14274 .selections
14275 .all::<Point>(&display_map)
14276 .iter()
14277 .map(|s| s.id)
14278 .collect();
14279 state.groups.retain_mut(|group| {
14280 // selections might get merged above so we remove invalid items from stacks
14281 group.stack.retain(|id| final_selection_ids.contains(id));
14282
14283 // single selection in stack can be treated as initial state
14284 group.stack.len() > 1
14285 });
14286
14287 if !state.groups.is_empty() {
14288 self.add_selections_state = Some(state);
14289 }
14290 }
14291
14292 fn select_match_ranges(
14293 &mut self,
14294 range: Range<usize>,
14295 reversed: bool,
14296 replace_newest: bool,
14297 auto_scroll: Option<Autoscroll>,
14298 window: &mut Window,
14299 cx: &mut Context<Editor>,
14300 ) {
14301 self.unfold_ranges(
14302 std::slice::from_ref(&range),
14303 false,
14304 auto_scroll.is_some(),
14305 cx,
14306 );
14307 let effects = if let Some(scroll) = auto_scroll {
14308 SelectionEffects::scroll(scroll)
14309 } else {
14310 SelectionEffects::no_scroll()
14311 };
14312 self.change_selections(effects, window, cx, |s| {
14313 if replace_newest {
14314 s.delete(s.newest_anchor().id);
14315 }
14316 if reversed {
14317 s.insert_range(range.end..range.start);
14318 } else {
14319 s.insert_range(range);
14320 }
14321 });
14322 }
14323
14324 pub fn select_next_match_internal(
14325 &mut self,
14326 display_map: &DisplaySnapshot,
14327 replace_newest: bool,
14328 autoscroll: Option<Autoscroll>,
14329 window: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) -> Result<()> {
14332 let buffer = display_map.buffer_snapshot();
14333 let mut selections = self.selections.all::<usize>(&display_map);
14334 if let Some(mut select_next_state) = self.select_next_state.take() {
14335 let query = &select_next_state.query;
14336 if !select_next_state.done {
14337 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14338 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14339 let mut next_selected_range = None;
14340
14341 let bytes_after_last_selection =
14342 buffer.bytes_in_range(last_selection.end..buffer.len());
14343 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14344 let query_matches = query
14345 .stream_find_iter(bytes_after_last_selection)
14346 .map(|result| (last_selection.end, result))
14347 .chain(
14348 query
14349 .stream_find_iter(bytes_before_first_selection)
14350 .map(|result| (0, result)),
14351 );
14352
14353 for (start_offset, query_match) in query_matches {
14354 let query_match = query_match.unwrap(); // can only fail due to I/O
14355 let offset_range =
14356 start_offset + query_match.start()..start_offset + query_match.end();
14357
14358 if !select_next_state.wordwise
14359 || (!buffer.is_inside_word(offset_range.start, None)
14360 && !buffer.is_inside_word(offset_range.end, None))
14361 {
14362 let idx = selections
14363 .partition_point(|selection| selection.end <= offset_range.start);
14364 let overlaps = selections
14365 .get(idx)
14366 .map_or(false, |selection| selection.start < offset_range.end);
14367
14368 if !overlaps {
14369 next_selected_range = Some(offset_range);
14370 break;
14371 }
14372 }
14373 }
14374
14375 if let Some(next_selected_range) = next_selected_range {
14376 self.select_match_ranges(
14377 next_selected_range,
14378 last_selection.reversed,
14379 replace_newest,
14380 autoscroll,
14381 window,
14382 cx,
14383 );
14384 } else {
14385 select_next_state.done = true;
14386 }
14387 }
14388
14389 self.select_next_state = Some(select_next_state);
14390 } else {
14391 let mut only_carets = true;
14392 let mut same_text_selected = true;
14393 let mut selected_text = None;
14394
14395 let mut selections_iter = selections.iter().peekable();
14396 while let Some(selection) = selections_iter.next() {
14397 if selection.start != selection.end {
14398 only_carets = false;
14399 }
14400
14401 if same_text_selected {
14402 if selected_text.is_none() {
14403 selected_text =
14404 Some(buffer.text_for_range(selection.range()).collect::<String>());
14405 }
14406
14407 if let Some(next_selection) = selections_iter.peek() {
14408 if next_selection.range().len() == selection.range().len() {
14409 let next_selected_text = buffer
14410 .text_for_range(next_selection.range())
14411 .collect::<String>();
14412 if Some(next_selected_text) != selected_text {
14413 same_text_selected = false;
14414 selected_text = None;
14415 }
14416 } else {
14417 same_text_selected = false;
14418 selected_text = None;
14419 }
14420 }
14421 }
14422 }
14423
14424 if only_carets {
14425 for selection in &mut selections {
14426 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14427 selection.start = word_range.start;
14428 selection.end = word_range.end;
14429 selection.goal = SelectionGoal::None;
14430 selection.reversed = false;
14431 self.select_match_ranges(
14432 selection.start..selection.end,
14433 selection.reversed,
14434 replace_newest,
14435 autoscroll,
14436 window,
14437 cx,
14438 );
14439 }
14440
14441 if selections.len() == 1 {
14442 let selection = selections
14443 .last()
14444 .expect("ensured that there's only one selection");
14445 let query = buffer
14446 .text_for_range(selection.start..selection.end)
14447 .collect::<String>();
14448 let is_empty = query.is_empty();
14449 let select_state = SelectNextState {
14450 query: AhoCorasick::new(&[query])?,
14451 wordwise: true,
14452 done: is_empty,
14453 };
14454 self.select_next_state = Some(select_state);
14455 } else {
14456 self.select_next_state = None;
14457 }
14458 } else if let Some(selected_text) = selected_text {
14459 self.select_next_state = Some(SelectNextState {
14460 query: AhoCorasick::new(&[selected_text])?,
14461 wordwise: false,
14462 done: false,
14463 });
14464 self.select_next_match_internal(
14465 display_map,
14466 replace_newest,
14467 autoscroll,
14468 window,
14469 cx,
14470 )?;
14471 }
14472 }
14473 Ok(())
14474 }
14475
14476 pub fn select_all_matches(
14477 &mut self,
14478 _action: &SelectAllMatches,
14479 window: &mut Window,
14480 cx: &mut Context<Self>,
14481 ) -> Result<()> {
14482 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14483
14484 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14485
14486 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14487 let Some(select_next_state) = self.select_next_state.as_mut() else {
14488 return Ok(());
14489 };
14490 if select_next_state.done {
14491 return Ok(());
14492 }
14493
14494 let mut new_selections = Vec::new();
14495
14496 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14497 let buffer = display_map.buffer_snapshot();
14498 let query_matches = select_next_state
14499 .query
14500 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14501
14502 for query_match in query_matches.into_iter() {
14503 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14504 let offset_range = if reversed {
14505 query_match.end()..query_match.start()
14506 } else {
14507 query_match.start()..query_match.end()
14508 };
14509
14510 if !select_next_state.wordwise
14511 || (!buffer.is_inside_word(offset_range.start, None)
14512 && !buffer.is_inside_word(offset_range.end, None))
14513 {
14514 new_selections.push(offset_range.start..offset_range.end);
14515 }
14516 }
14517
14518 select_next_state.done = true;
14519
14520 if new_selections.is_empty() {
14521 log::error!("bug: new_selections is empty in select_all_matches");
14522 return Ok(());
14523 }
14524
14525 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14526 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14527 selections.select_ranges(new_selections)
14528 });
14529
14530 Ok(())
14531 }
14532
14533 pub fn select_next(
14534 &mut self,
14535 action: &SelectNext,
14536 window: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) -> Result<()> {
14539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14541 self.select_next_match_internal(
14542 &display_map,
14543 action.replace_newest,
14544 Some(Autoscroll::newest()),
14545 window,
14546 cx,
14547 )?;
14548 Ok(())
14549 }
14550
14551 pub fn select_previous(
14552 &mut self,
14553 action: &SelectPrevious,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) -> Result<()> {
14557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14559 let buffer = display_map.buffer_snapshot();
14560 let mut selections = self.selections.all::<usize>(&display_map);
14561 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14562 let query = &select_prev_state.query;
14563 if !select_prev_state.done {
14564 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14565 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14566 let mut next_selected_range = None;
14567 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14568 let bytes_before_last_selection =
14569 buffer.reversed_bytes_in_range(0..last_selection.start);
14570 let bytes_after_first_selection =
14571 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14572 let query_matches = query
14573 .stream_find_iter(bytes_before_last_selection)
14574 .map(|result| (last_selection.start, result))
14575 .chain(
14576 query
14577 .stream_find_iter(bytes_after_first_selection)
14578 .map(|result| (buffer.len(), result)),
14579 );
14580 for (end_offset, query_match) in query_matches {
14581 let query_match = query_match.unwrap(); // can only fail due to I/O
14582 let offset_range =
14583 end_offset - query_match.end()..end_offset - query_match.start();
14584
14585 if !select_prev_state.wordwise
14586 || (!buffer.is_inside_word(offset_range.start, None)
14587 && !buffer.is_inside_word(offset_range.end, None))
14588 {
14589 next_selected_range = Some(offset_range);
14590 break;
14591 }
14592 }
14593
14594 if let Some(next_selected_range) = next_selected_range {
14595 self.select_match_ranges(
14596 next_selected_range,
14597 last_selection.reversed,
14598 action.replace_newest,
14599 Some(Autoscroll::newest()),
14600 window,
14601 cx,
14602 );
14603 } else {
14604 select_prev_state.done = true;
14605 }
14606 }
14607
14608 self.select_prev_state = Some(select_prev_state);
14609 } else {
14610 let mut only_carets = true;
14611 let mut same_text_selected = true;
14612 let mut selected_text = None;
14613
14614 let mut selections_iter = selections.iter().peekable();
14615 while let Some(selection) = selections_iter.next() {
14616 if selection.start != selection.end {
14617 only_carets = false;
14618 }
14619
14620 if same_text_selected {
14621 if selected_text.is_none() {
14622 selected_text =
14623 Some(buffer.text_for_range(selection.range()).collect::<String>());
14624 }
14625
14626 if let Some(next_selection) = selections_iter.peek() {
14627 if next_selection.range().len() == selection.range().len() {
14628 let next_selected_text = buffer
14629 .text_for_range(next_selection.range())
14630 .collect::<String>();
14631 if Some(next_selected_text) != selected_text {
14632 same_text_selected = false;
14633 selected_text = None;
14634 }
14635 } else {
14636 same_text_selected = false;
14637 selected_text = None;
14638 }
14639 }
14640 }
14641 }
14642
14643 if only_carets {
14644 for selection in &mut selections {
14645 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14646 selection.start = word_range.start;
14647 selection.end = word_range.end;
14648 selection.goal = SelectionGoal::None;
14649 selection.reversed = false;
14650 self.select_match_ranges(
14651 selection.start..selection.end,
14652 selection.reversed,
14653 action.replace_newest,
14654 Some(Autoscroll::newest()),
14655 window,
14656 cx,
14657 );
14658 }
14659 if selections.len() == 1 {
14660 let selection = selections
14661 .last()
14662 .expect("ensured that there's only one selection");
14663 let query = buffer
14664 .text_for_range(selection.start..selection.end)
14665 .collect::<String>();
14666 let is_empty = query.is_empty();
14667 let select_state = SelectNextState {
14668 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14669 wordwise: true,
14670 done: is_empty,
14671 };
14672 self.select_prev_state = Some(select_state);
14673 } else {
14674 self.select_prev_state = None;
14675 }
14676 } else if let Some(selected_text) = selected_text {
14677 self.select_prev_state = Some(SelectNextState {
14678 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14679 wordwise: false,
14680 done: false,
14681 });
14682 self.select_previous(action, window, cx)?;
14683 }
14684 }
14685 Ok(())
14686 }
14687
14688 pub fn find_next_match(
14689 &mut self,
14690 _: &FindNextMatch,
14691 window: &mut Window,
14692 cx: &mut Context<Self>,
14693 ) -> Result<()> {
14694 let selections = self.selections.disjoint_anchors_arc();
14695 match selections.first() {
14696 Some(first) if selections.len() >= 2 => {
14697 self.change_selections(Default::default(), window, cx, |s| {
14698 s.select_ranges([first.range()]);
14699 });
14700 }
14701 _ => self.select_next(
14702 &SelectNext {
14703 replace_newest: true,
14704 },
14705 window,
14706 cx,
14707 )?,
14708 }
14709 Ok(())
14710 }
14711
14712 pub fn find_previous_match(
14713 &mut self,
14714 _: &FindPreviousMatch,
14715 window: &mut Window,
14716 cx: &mut Context<Self>,
14717 ) -> Result<()> {
14718 let selections = self.selections.disjoint_anchors_arc();
14719 match selections.last() {
14720 Some(last) if selections.len() >= 2 => {
14721 self.change_selections(Default::default(), window, cx, |s| {
14722 s.select_ranges([last.range()]);
14723 });
14724 }
14725 _ => self.select_previous(
14726 &SelectPrevious {
14727 replace_newest: true,
14728 },
14729 window,
14730 cx,
14731 )?,
14732 }
14733 Ok(())
14734 }
14735
14736 pub fn toggle_comments(
14737 &mut self,
14738 action: &ToggleComments,
14739 window: &mut Window,
14740 cx: &mut Context<Self>,
14741 ) {
14742 if self.read_only(cx) {
14743 return;
14744 }
14745 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14746 let text_layout_details = &self.text_layout_details(window);
14747 self.transact(window, cx, |this, window, cx| {
14748 let mut selections = this
14749 .selections
14750 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14751 let mut edits = Vec::new();
14752 let mut selection_edit_ranges = Vec::new();
14753 let mut last_toggled_row = None;
14754 let snapshot = this.buffer.read(cx).read(cx);
14755 let empty_str: Arc<str> = Arc::default();
14756 let mut suffixes_inserted = Vec::new();
14757 let ignore_indent = action.ignore_indent;
14758
14759 fn comment_prefix_range(
14760 snapshot: &MultiBufferSnapshot,
14761 row: MultiBufferRow,
14762 comment_prefix: &str,
14763 comment_prefix_whitespace: &str,
14764 ignore_indent: bool,
14765 ) -> Range<Point> {
14766 let indent_size = if ignore_indent {
14767 0
14768 } else {
14769 snapshot.indent_size_for_line(row).len
14770 };
14771
14772 let start = Point::new(row.0, indent_size);
14773
14774 let mut line_bytes = snapshot
14775 .bytes_in_range(start..snapshot.max_point())
14776 .flatten()
14777 .copied();
14778
14779 // If this line currently begins with the line comment prefix, then record
14780 // the range containing the prefix.
14781 if line_bytes
14782 .by_ref()
14783 .take(comment_prefix.len())
14784 .eq(comment_prefix.bytes())
14785 {
14786 // Include any whitespace that matches the comment prefix.
14787 let matching_whitespace_len = line_bytes
14788 .zip(comment_prefix_whitespace.bytes())
14789 .take_while(|(a, b)| a == b)
14790 .count() as u32;
14791 let end = Point::new(
14792 start.row,
14793 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14794 );
14795 start..end
14796 } else {
14797 start..start
14798 }
14799 }
14800
14801 fn comment_suffix_range(
14802 snapshot: &MultiBufferSnapshot,
14803 row: MultiBufferRow,
14804 comment_suffix: &str,
14805 comment_suffix_has_leading_space: bool,
14806 ) -> Range<Point> {
14807 let end = Point::new(row.0, snapshot.line_len(row));
14808 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14809
14810 let mut line_end_bytes = snapshot
14811 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14812 .flatten()
14813 .copied();
14814
14815 let leading_space_len = if suffix_start_column > 0
14816 && line_end_bytes.next() == Some(b' ')
14817 && comment_suffix_has_leading_space
14818 {
14819 1
14820 } else {
14821 0
14822 };
14823
14824 // If this line currently begins with the line comment prefix, then record
14825 // the range containing the prefix.
14826 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14827 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14828 start..end
14829 } else {
14830 end..end
14831 }
14832 }
14833
14834 // TODO: Handle selections that cross excerpts
14835 for selection in &mut selections {
14836 let start_column = snapshot
14837 .indent_size_for_line(MultiBufferRow(selection.start.row))
14838 .len;
14839 let language = if let Some(language) =
14840 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14841 {
14842 language
14843 } else {
14844 continue;
14845 };
14846
14847 selection_edit_ranges.clear();
14848
14849 // If multiple selections contain a given row, avoid processing that
14850 // row more than once.
14851 let mut start_row = MultiBufferRow(selection.start.row);
14852 if last_toggled_row == Some(start_row) {
14853 start_row = start_row.next_row();
14854 }
14855 let end_row =
14856 if selection.end.row > selection.start.row && selection.end.column == 0 {
14857 MultiBufferRow(selection.end.row - 1)
14858 } else {
14859 MultiBufferRow(selection.end.row)
14860 };
14861 last_toggled_row = Some(end_row);
14862
14863 if start_row > end_row {
14864 continue;
14865 }
14866
14867 // If the language has line comments, toggle those.
14868 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14869
14870 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14871 if ignore_indent {
14872 full_comment_prefixes = full_comment_prefixes
14873 .into_iter()
14874 .map(|s| Arc::from(s.trim_end()))
14875 .collect();
14876 }
14877
14878 if !full_comment_prefixes.is_empty() {
14879 let first_prefix = full_comment_prefixes
14880 .first()
14881 .expect("prefixes is non-empty");
14882 let prefix_trimmed_lengths = full_comment_prefixes
14883 .iter()
14884 .map(|p| p.trim_end_matches(' ').len())
14885 .collect::<SmallVec<[usize; 4]>>();
14886
14887 let mut all_selection_lines_are_comments = true;
14888
14889 for row in start_row.0..=end_row.0 {
14890 let row = MultiBufferRow(row);
14891 if start_row < end_row && snapshot.is_line_blank(row) {
14892 continue;
14893 }
14894
14895 let prefix_range = full_comment_prefixes
14896 .iter()
14897 .zip(prefix_trimmed_lengths.iter().copied())
14898 .map(|(prefix, trimmed_prefix_len)| {
14899 comment_prefix_range(
14900 snapshot.deref(),
14901 row,
14902 &prefix[..trimmed_prefix_len],
14903 &prefix[trimmed_prefix_len..],
14904 ignore_indent,
14905 )
14906 })
14907 .max_by_key(|range| range.end.column - range.start.column)
14908 .expect("prefixes is non-empty");
14909
14910 if prefix_range.is_empty() {
14911 all_selection_lines_are_comments = false;
14912 }
14913
14914 selection_edit_ranges.push(prefix_range);
14915 }
14916
14917 if all_selection_lines_are_comments {
14918 edits.extend(
14919 selection_edit_ranges
14920 .iter()
14921 .cloned()
14922 .map(|range| (range, empty_str.clone())),
14923 );
14924 } else {
14925 let min_column = selection_edit_ranges
14926 .iter()
14927 .map(|range| range.start.column)
14928 .min()
14929 .unwrap_or(0);
14930 edits.extend(selection_edit_ranges.iter().map(|range| {
14931 let position = Point::new(range.start.row, min_column);
14932 (position..position, first_prefix.clone())
14933 }));
14934 }
14935 } else if let Some(BlockCommentConfig {
14936 start: full_comment_prefix,
14937 end: comment_suffix,
14938 ..
14939 }) = language.block_comment()
14940 {
14941 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14942 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14943 let prefix_range = comment_prefix_range(
14944 snapshot.deref(),
14945 start_row,
14946 comment_prefix,
14947 comment_prefix_whitespace,
14948 ignore_indent,
14949 );
14950 let suffix_range = comment_suffix_range(
14951 snapshot.deref(),
14952 end_row,
14953 comment_suffix.trim_start_matches(' '),
14954 comment_suffix.starts_with(' '),
14955 );
14956
14957 if prefix_range.is_empty() || suffix_range.is_empty() {
14958 edits.push((
14959 prefix_range.start..prefix_range.start,
14960 full_comment_prefix.clone(),
14961 ));
14962 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14963 suffixes_inserted.push((end_row, comment_suffix.len()));
14964 } else {
14965 edits.push((prefix_range, empty_str.clone()));
14966 edits.push((suffix_range, empty_str.clone()));
14967 }
14968 } else {
14969 continue;
14970 }
14971 }
14972
14973 drop(snapshot);
14974 this.buffer.update(cx, |buffer, cx| {
14975 buffer.edit(edits, None, cx);
14976 });
14977
14978 // Adjust selections so that they end before any comment suffixes that
14979 // were inserted.
14980 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14981 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14982 let snapshot = this.buffer.read(cx).read(cx);
14983 for selection in &mut selections {
14984 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14985 match row.cmp(&MultiBufferRow(selection.end.row)) {
14986 Ordering::Less => {
14987 suffixes_inserted.next();
14988 continue;
14989 }
14990 Ordering::Greater => break,
14991 Ordering::Equal => {
14992 if selection.end.column == snapshot.line_len(row) {
14993 if selection.is_empty() {
14994 selection.start.column -= suffix_len as u32;
14995 }
14996 selection.end.column -= suffix_len as u32;
14997 }
14998 break;
14999 }
15000 }
15001 }
15002 }
15003
15004 drop(snapshot);
15005 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15006
15007 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15008 let selections_on_single_row = selections.windows(2).all(|selections| {
15009 selections[0].start.row == selections[1].start.row
15010 && selections[0].end.row == selections[1].end.row
15011 && selections[0].start.row == selections[0].end.row
15012 });
15013 let selections_selecting = selections
15014 .iter()
15015 .any(|selection| selection.start != selection.end);
15016 let advance_downwards = action.advance_downwards
15017 && selections_on_single_row
15018 && !selections_selecting
15019 && !matches!(this.mode, EditorMode::SingleLine);
15020
15021 if advance_downwards {
15022 let snapshot = this.buffer.read(cx).snapshot(cx);
15023
15024 this.change_selections(Default::default(), window, cx, |s| {
15025 s.move_cursors_with(|display_snapshot, display_point, _| {
15026 let mut point = display_point.to_point(display_snapshot);
15027 point.row += 1;
15028 point = snapshot.clip_point(point, Bias::Left);
15029 let display_point = point.to_display_point(display_snapshot);
15030 let goal = SelectionGoal::HorizontalPosition(
15031 display_snapshot
15032 .x_for_display_point(display_point, text_layout_details)
15033 .into(),
15034 );
15035 (display_point, goal)
15036 })
15037 });
15038 }
15039 });
15040 }
15041
15042 pub fn select_enclosing_symbol(
15043 &mut self,
15044 _: &SelectEnclosingSymbol,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) {
15048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15049
15050 let buffer = self.buffer.read(cx).snapshot(cx);
15051 let old_selections = self
15052 .selections
15053 .all::<usize>(&self.display_snapshot(cx))
15054 .into_boxed_slice();
15055
15056 fn update_selection(
15057 selection: &Selection<usize>,
15058 buffer_snap: &MultiBufferSnapshot,
15059 ) -> Option<Selection<usize>> {
15060 let cursor = selection.head();
15061 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15062 for symbol in symbols.iter().rev() {
15063 let start = symbol.range.start.to_offset(buffer_snap);
15064 let end = symbol.range.end.to_offset(buffer_snap);
15065 let new_range = start..end;
15066 if start < selection.start || end > selection.end {
15067 return Some(Selection {
15068 id: selection.id,
15069 start: new_range.start,
15070 end: new_range.end,
15071 goal: SelectionGoal::None,
15072 reversed: selection.reversed,
15073 });
15074 }
15075 }
15076 None
15077 }
15078
15079 let mut selected_larger_symbol = false;
15080 let new_selections = old_selections
15081 .iter()
15082 .map(|selection| match update_selection(selection, &buffer) {
15083 Some(new_selection) => {
15084 if new_selection.range() != selection.range() {
15085 selected_larger_symbol = true;
15086 }
15087 new_selection
15088 }
15089 None => selection.clone(),
15090 })
15091 .collect::<Vec<_>>();
15092
15093 if selected_larger_symbol {
15094 self.change_selections(Default::default(), window, cx, |s| {
15095 s.select(new_selections);
15096 });
15097 }
15098 }
15099
15100 pub fn select_larger_syntax_node(
15101 &mut self,
15102 _: &SelectLargerSyntaxNode,
15103 window: &mut Window,
15104 cx: &mut Context<Self>,
15105 ) {
15106 let Some(visible_row_count) = self.visible_row_count() else {
15107 return;
15108 };
15109 let old_selections: Box<[_]> = self
15110 .selections
15111 .all::<usize>(&self.display_snapshot(cx))
15112 .into();
15113 if old_selections.is_empty() {
15114 return;
15115 }
15116
15117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15118
15119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15120 let buffer = self.buffer.read(cx).snapshot(cx);
15121
15122 let mut selected_larger_node = false;
15123 let mut new_selections = old_selections
15124 .iter()
15125 .map(|selection| {
15126 let old_range = selection.start..selection.end;
15127
15128 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15129 // manually select word at selection
15130 if ["string_content", "inline"].contains(&node.kind()) {
15131 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15132 // ignore if word is already selected
15133 if !word_range.is_empty() && old_range != word_range {
15134 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15135 // only select word if start and end point belongs to same word
15136 if word_range == last_word_range {
15137 selected_larger_node = true;
15138 return Selection {
15139 id: selection.id,
15140 start: word_range.start,
15141 end: word_range.end,
15142 goal: SelectionGoal::None,
15143 reversed: selection.reversed,
15144 };
15145 }
15146 }
15147 }
15148 }
15149
15150 let mut new_range = old_range.clone();
15151 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15152 new_range = range;
15153 if !node.is_named() {
15154 continue;
15155 }
15156 if !display_map.intersects_fold(new_range.start)
15157 && !display_map.intersects_fold(new_range.end)
15158 {
15159 break;
15160 }
15161 }
15162
15163 selected_larger_node |= new_range != old_range;
15164 Selection {
15165 id: selection.id,
15166 start: new_range.start,
15167 end: new_range.end,
15168 goal: SelectionGoal::None,
15169 reversed: selection.reversed,
15170 }
15171 })
15172 .collect::<Vec<_>>();
15173
15174 if !selected_larger_node {
15175 return; // don't put this call in the history
15176 }
15177
15178 // scroll based on transformation done to the last selection created by the user
15179 let (last_old, last_new) = old_selections
15180 .last()
15181 .zip(new_selections.last().cloned())
15182 .expect("old_selections isn't empty");
15183
15184 // revert selection
15185 let is_selection_reversed = {
15186 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15187 new_selections.last_mut().expect("checked above").reversed =
15188 should_newest_selection_be_reversed;
15189 should_newest_selection_be_reversed
15190 };
15191
15192 if selected_larger_node {
15193 self.select_syntax_node_history.disable_clearing = true;
15194 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15195 s.select(new_selections.clone());
15196 });
15197 self.select_syntax_node_history.disable_clearing = false;
15198 }
15199
15200 let start_row = last_new.start.to_display_point(&display_map).row().0;
15201 let end_row = last_new.end.to_display_point(&display_map).row().0;
15202 let selection_height = end_row - start_row + 1;
15203 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15204
15205 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15206 let scroll_behavior = if fits_on_the_screen {
15207 self.request_autoscroll(Autoscroll::fit(), cx);
15208 SelectSyntaxNodeScrollBehavior::FitSelection
15209 } else if is_selection_reversed {
15210 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15211 SelectSyntaxNodeScrollBehavior::CursorTop
15212 } else {
15213 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15214 SelectSyntaxNodeScrollBehavior::CursorBottom
15215 };
15216
15217 self.select_syntax_node_history.push((
15218 old_selections,
15219 scroll_behavior,
15220 is_selection_reversed,
15221 ));
15222 }
15223
15224 pub fn select_smaller_syntax_node(
15225 &mut self,
15226 _: &SelectSmallerSyntaxNode,
15227 window: &mut Window,
15228 cx: &mut Context<Self>,
15229 ) {
15230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15231
15232 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15233 self.select_syntax_node_history.pop()
15234 {
15235 if let Some(selection) = selections.last_mut() {
15236 selection.reversed = is_selection_reversed;
15237 }
15238
15239 self.select_syntax_node_history.disable_clearing = true;
15240 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15241 s.select(selections.to_vec());
15242 });
15243 self.select_syntax_node_history.disable_clearing = false;
15244
15245 match scroll_behavior {
15246 SelectSyntaxNodeScrollBehavior::CursorTop => {
15247 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15248 }
15249 SelectSyntaxNodeScrollBehavior::FitSelection => {
15250 self.request_autoscroll(Autoscroll::fit(), cx);
15251 }
15252 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15253 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15254 }
15255 }
15256 }
15257 }
15258
15259 pub fn unwrap_syntax_node(
15260 &mut self,
15261 _: &UnwrapSyntaxNode,
15262 window: &mut Window,
15263 cx: &mut Context<Self>,
15264 ) {
15265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15266
15267 let buffer = self.buffer.read(cx).snapshot(cx);
15268 let selections = self
15269 .selections
15270 .all::<usize>(&self.display_snapshot(cx))
15271 .into_iter()
15272 // subtracting the offset requires sorting
15273 .sorted_by_key(|i| i.start);
15274
15275 let full_edits = selections
15276 .into_iter()
15277 .filter_map(|selection| {
15278 let child = if selection.is_empty()
15279 && let Some((_, ancestor_range)) =
15280 buffer.syntax_ancestor(selection.start..selection.end)
15281 {
15282 ancestor_range
15283 } else {
15284 selection.range()
15285 };
15286
15287 let mut parent = child.clone();
15288 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15289 parent = ancestor_range;
15290 if parent.start < child.start || parent.end > child.end {
15291 break;
15292 }
15293 }
15294
15295 if parent == child {
15296 return None;
15297 }
15298 let text = buffer.text_for_range(child).collect::<String>();
15299 Some((selection.id, parent, text))
15300 })
15301 .collect::<Vec<_>>();
15302 if full_edits.is_empty() {
15303 return;
15304 }
15305
15306 self.transact(window, cx, |this, window, cx| {
15307 this.buffer.update(cx, |buffer, cx| {
15308 buffer.edit(
15309 full_edits
15310 .iter()
15311 .map(|(_, p, t)| (p.clone(), t.clone()))
15312 .collect::<Vec<_>>(),
15313 None,
15314 cx,
15315 );
15316 });
15317 this.change_selections(Default::default(), window, cx, |s| {
15318 let mut offset = 0;
15319 let mut selections = vec![];
15320 for (id, parent, text) in full_edits {
15321 let start = parent.start - offset;
15322 offset += parent.len() - text.len();
15323 selections.push(Selection {
15324 id,
15325 start,
15326 end: start + text.len(),
15327 reversed: false,
15328 goal: Default::default(),
15329 });
15330 }
15331 s.select(selections);
15332 });
15333 });
15334 }
15335
15336 pub fn select_next_syntax_node(
15337 &mut self,
15338 _: &SelectNextSyntaxNode,
15339 window: &mut Window,
15340 cx: &mut Context<Self>,
15341 ) {
15342 let old_selections: Box<[_]> = self
15343 .selections
15344 .all::<usize>(&self.display_snapshot(cx))
15345 .into();
15346 if old_selections.is_empty() {
15347 return;
15348 }
15349
15350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15351
15352 let buffer = self.buffer.read(cx).snapshot(cx);
15353 let mut selected_sibling = false;
15354
15355 let new_selections = old_selections
15356 .iter()
15357 .map(|selection| {
15358 let old_range = selection.start..selection.end;
15359
15360 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15361 let new_range = node.byte_range();
15362 selected_sibling = true;
15363 Selection {
15364 id: selection.id,
15365 start: new_range.start,
15366 end: new_range.end,
15367 goal: SelectionGoal::None,
15368 reversed: selection.reversed,
15369 }
15370 } else {
15371 selection.clone()
15372 }
15373 })
15374 .collect::<Vec<_>>();
15375
15376 if selected_sibling {
15377 self.change_selections(
15378 SelectionEffects::scroll(Autoscroll::fit()),
15379 window,
15380 cx,
15381 |s| {
15382 s.select(new_selections);
15383 },
15384 );
15385 }
15386 }
15387
15388 pub fn select_prev_syntax_node(
15389 &mut self,
15390 _: &SelectPreviousSyntaxNode,
15391 window: &mut Window,
15392 cx: &mut Context<Self>,
15393 ) {
15394 let old_selections: Box<[_]> = self
15395 .selections
15396 .all::<usize>(&self.display_snapshot(cx))
15397 .into();
15398 if old_selections.is_empty() {
15399 return;
15400 }
15401
15402 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15403
15404 let buffer = self.buffer.read(cx).snapshot(cx);
15405 let mut selected_sibling = false;
15406
15407 let new_selections = old_selections
15408 .iter()
15409 .map(|selection| {
15410 let old_range = selection.start..selection.end;
15411
15412 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15413 let new_range = node.byte_range();
15414 selected_sibling = true;
15415 Selection {
15416 id: selection.id,
15417 start: new_range.start,
15418 end: new_range.end,
15419 goal: SelectionGoal::None,
15420 reversed: selection.reversed,
15421 }
15422 } else {
15423 selection.clone()
15424 }
15425 })
15426 .collect::<Vec<_>>();
15427
15428 if selected_sibling {
15429 self.change_selections(
15430 SelectionEffects::scroll(Autoscroll::fit()),
15431 window,
15432 cx,
15433 |s| {
15434 s.select(new_selections);
15435 },
15436 );
15437 }
15438 }
15439
15440 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15441 if !EditorSettings::get_global(cx).gutter.runnables {
15442 self.clear_tasks();
15443 return Task::ready(());
15444 }
15445 let project = self.project().map(Entity::downgrade);
15446 let task_sources = self.lsp_task_sources(cx);
15447 let multi_buffer = self.buffer.downgrade();
15448 cx.spawn_in(window, async move |editor, cx| {
15449 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15450 let Some(project) = project.and_then(|p| p.upgrade()) else {
15451 return;
15452 };
15453 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15454 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15455 }) else {
15456 return;
15457 };
15458
15459 let hide_runnables = project
15460 .update(cx, |project, _| project.is_via_collab())
15461 .unwrap_or(true);
15462 if hide_runnables {
15463 return;
15464 }
15465 let new_rows =
15466 cx.background_spawn({
15467 let snapshot = display_snapshot.clone();
15468 async move {
15469 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15470 }
15471 })
15472 .await;
15473 let Ok(lsp_tasks) =
15474 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15475 else {
15476 return;
15477 };
15478 let lsp_tasks = lsp_tasks.await;
15479
15480 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15481 lsp_tasks
15482 .into_iter()
15483 .flat_map(|(kind, tasks)| {
15484 tasks.into_iter().filter_map(move |(location, task)| {
15485 Some((kind.clone(), location?, task))
15486 })
15487 })
15488 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15489 let buffer = location.target.buffer;
15490 let buffer_snapshot = buffer.read(cx).snapshot();
15491 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15492 |(excerpt_id, snapshot, _)| {
15493 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15494 display_snapshot
15495 .buffer_snapshot()
15496 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15497 } else {
15498 None
15499 }
15500 },
15501 );
15502 if let Some(offset) = offset {
15503 let task_buffer_range =
15504 location.target.range.to_point(&buffer_snapshot);
15505 let context_buffer_range =
15506 task_buffer_range.to_offset(&buffer_snapshot);
15507 let context_range = BufferOffset(context_buffer_range.start)
15508 ..BufferOffset(context_buffer_range.end);
15509
15510 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15511 .or_insert_with(|| RunnableTasks {
15512 templates: Vec::new(),
15513 offset,
15514 column: task_buffer_range.start.column,
15515 extra_variables: HashMap::default(),
15516 context_range,
15517 })
15518 .templates
15519 .push((kind, task.original_task().clone()));
15520 }
15521
15522 acc
15523 })
15524 }) else {
15525 return;
15526 };
15527
15528 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15529 buffer.language_settings(cx).tasks.prefer_lsp
15530 }) else {
15531 return;
15532 };
15533
15534 let rows = Self::runnable_rows(
15535 project,
15536 display_snapshot,
15537 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15538 new_rows,
15539 cx.clone(),
15540 )
15541 .await;
15542 editor
15543 .update(cx, |editor, _| {
15544 editor.clear_tasks();
15545 for (key, mut value) in rows {
15546 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15547 value.templates.extend(lsp_tasks.templates);
15548 }
15549
15550 editor.insert_tasks(key, value);
15551 }
15552 for (key, value) in lsp_tasks_by_rows {
15553 editor.insert_tasks(key, value);
15554 }
15555 })
15556 .ok();
15557 })
15558 }
15559 fn fetch_runnable_ranges(
15560 snapshot: &DisplaySnapshot,
15561 range: Range<Anchor>,
15562 ) -> Vec<language::RunnableRange> {
15563 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15564 }
15565
15566 fn runnable_rows(
15567 project: Entity<Project>,
15568 snapshot: DisplaySnapshot,
15569 prefer_lsp: bool,
15570 runnable_ranges: Vec<RunnableRange>,
15571 cx: AsyncWindowContext,
15572 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15573 cx.spawn(async move |cx| {
15574 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15575 for mut runnable in runnable_ranges {
15576 let Some(tasks) = cx
15577 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15578 .ok()
15579 else {
15580 continue;
15581 };
15582 let mut tasks = tasks.await;
15583
15584 if prefer_lsp {
15585 tasks.retain(|(task_kind, _)| {
15586 !matches!(task_kind, TaskSourceKind::Language { .. })
15587 });
15588 }
15589 if tasks.is_empty() {
15590 continue;
15591 }
15592
15593 let point = runnable
15594 .run_range
15595 .start
15596 .to_point(&snapshot.buffer_snapshot());
15597 let Some(row) = snapshot
15598 .buffer_snapshot()
15599 .buffer_line_for_row(MultiBufferRow(point.row))
15600 .map(|(_, range)| range.start.row)
15601 else {
15602 continue;
15603 };
15604
15605 let context_range =
15606 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15607 runnable_rows.push((
15608 (runnable.buffer_id, row),
15609 RunnableTasks {
15610 templates: tasks,
15611 offset: snapshot
15612 .buffer_snapshot()
15613 .anchor_before(runnable.run_range.start),
15614 context_range,
15615 column: point.column,
15616 extra_variables: runnable.extra_captures,
15617 },
15618 ));
15619 }
15620 runnable_rows
15621 })
15622 }
15623
15624 fn templates_with_tags(
15625 project: &Entity<Project>,
15626 runnable: &mut Runnable,
15627 cx: &mut App,
15628 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15629 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15630 let (worktree_id, file) = project
15631 .buffer_for_id(runnable.buffer, cx)
15632 .and_then(|buffer| buffer.read(cx).file())
15633 .map(|file| (file.worktree_id(cx), file.clone()))
15634 .unzip();
15635
15636 (
15637 project.task_store().read(cx).task_inventory().cloned(),
15638 worktree_id,
15639 file,
15640 )
15641 });
15642
15643 let tags = mem::take(&mut runnable.tags);
15644 let language = runnable.language.clone();
15645 cx.spawn(async move |cx| {
15646 let mut templates_with_tags = Vec::new();
15647 if let Some(inventory) = inventory {
15648 for RunnableTag(tag) in tags {
15649 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15650 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15651 }) else {
15652 return templates_with_tags;
15653 };
15654 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15655 move |(_, template)| {
15656 template.tags.iter().any(|source_tag| source_tag == &tag)
15657 },
15658 ));
15659 }
15660 }
15661 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15662
15663 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15664 // Strongest source wins; if we have worktree tag binding, prefer that to
15665 // global and language bindings;
15666 // if we have a global binding, prefer that to language binding.
15667 let first_mismatch = templates_with_tags
15668 .iter()
15669 .position(|(tag_source, _)| tag_source != leading_tag_source);
15670 if let Some(index) = first_mismatch {
15671 templates_with_tags.truncate(index);
15672 }
15673 }
15674
15675 templates_with_tags
15676 })
15677 }
15678
15679 pub fn move_to_enclosing_bracket(
15680 &mut self,
15681 _: &MoveToEnclosingBracket,
15682 window: &mut Window,
15683 cx: &mut Context<Self>,
15684 ) {
15685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15686 self.change_selections(Default::default(), window, cx, |s| {
15687 s.move_offsets_with(|snapshot, selection| {
15688 let Some(enclosing_bracket_ranges) =
15689 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15690 else {
15691 return;
15692 };
15693
15694 let mut best_length = usize::MAX;
15695 let mut best_inside = false;
15696 let mut best_in_bracket_range = false;
15697 let mut best_destination = None;
15698 for (open, close) in enclosing_bracket_ranges {
15699 let close = close.to_inclusive();
15700 let length = close.end() - open.start;
15701 let inside = selection.start >= open.end && selection.end <= *close.start();
15702 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15703 || close.contains(&selection.head());
15704
15705 // If best is next to a bracket and current isn't, skip
15706 if !in_bracket_range && best_in_bracket_range {
15707 continue;
15708 }
15709
15710 // Prefer smaller lengths unless best is inside and current isn't
15711 if length > best_length && (best_inside || !inside) {
15712 continue;
15713 }
15714
15715 best_length = length;
15716 best_inside = inside;
15717 best_in_bracket_range = in_bracket_range;
15718 best_destination = Some(
15719 if close.contains(&selection.start) && close.contains(&selection.end) {
15720 if inside { open.end } else { open.start }
15721 } else if inside {
15722 *close.start()
15723 } else {
15724 *close.end()
15725 },
15726 );
15727 }
15728
15729 if let Some(destination) = best_destination {
15730 selection.collapse_to(destination, SelectionGoal::None);
15731 }
15732 })
15733 });
15734 }
15735
15736 pub fn undo_selection(
15737 &mut self,
15738 _: &UndoSelection,
15739 window: &mut Window,
15740 cx: &mut Context<Self>,
15741 ) {
15742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15743 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15744 self.selection_history.mode = SelectionHistoryMode::Undoing;
15745 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15746 this.end_selection(window, cx);
15747 this.change_selections(
15748 SelectionEffects::scroll(Autoscroll::newest()),
15749 window,
15750 cx,
15751 |s| s.select_anchors(entry.selections.to_vec()),
15752 );
15753 });
15754 self.selection_history.mode = SelectionHistoryMode::Normal;
15755
15756 self.select_next_state = entry.select_next_state;
15757 self.select_prev_state = entry.select_prev_state;
15758 self.add_selections_state = entry.add_selections_state;
15759 }
15760 }
15761
15762 pub fn redo_selection(
15763 &mut self,
15764 _: &RedoSelection,
15765 window: &mut Window,
15766 cx: &mut Context<Self>,
15767 ) {
15768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15769 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15770 self.selection_history.mode = SelectionHistoryMode::Redoing;
15771 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15772 this.end_selection(window, cx);
15773 this.change_selections(
15774 SelectionEffects::scroll(Autoscroll::newest()),
15775 window,
15776 cx,
15777 |s| s.select_anchors(entry.selections.to_vec()),
15778 );
15779 });
15780 self.selection_history.mode = SelectionHistoryMode::Normal;
15781
15782 self.select_next_state = entry.select_next_state;
15783 self.select_prev_state = entry.select_prev_state;
15784 self.add_selections_state = entry.add_selections_state;
15785 }
15786 }
15787
15788 pub fn expand_excerpts(
15789 &mut self,
15790 action: &ExpandExcerpts,
15791 _: &mut Window,
15792 cx: &mut Context<Self>,
15793 ) {
15794 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15795 }
15796
15797 pub fn expand_excerpts_down(
15798 &mut self,
15799 action: &ExpandExcerptsDown,
15800 _: &mut Window,
15801 cx: &mut Context<Self>,
15802 ) {
15803 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15804 }
15805
15806 pub fn expand_excerpts_up(
15807 &mut self,
15808 action: &ExpandExcerptsUp,
15809 _: &mut Window,
15810 cx: &mut Context<Self>,
15811 ) {
15812 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15813 }
15814
15815 pub fn expand_excerpts_for_direction(
15816 &mut self,
15817 lines: u32,
15818 direction: ExpandExcerptDirection,
15819
15820 cx: &mut Context<Self>,
15821 ) {
15822 let selections = self.selections.disjoint_anchors_arc();
15823
15824 let lines = if lines == 0 {
15825 EditorSettings::get_global(cx).expand_excerpt_lines
15826 } else {
15827 lines
15828 };
15829
15830 self.buffer.update(cx, |buffer, cx| {
15831 let snapshot = buffer.snapshot(cx);
15832 let mut excerpt_ids = selections
15833 .iter()
15834 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15835 .collect::<Vec<_>>();
15836 excerpt_ids.sort();
15837 excerpt_ids.dedup();
15838 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15839 })
15840 }
15841
15842 pub fn expand_excerpt(
15843 &mut self,
15844 excerpt: ExcerptId,
15845 direction: ExpandExcerptDirection,
15846 window: &mut Window,
15847 cx: &mut Context<Self>,
15848 ) {
15849 let current_scroll_position = self.scroll_position(cx);
15850 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15851 let mut should_scroll_up = false;
15852
15853 if direction == ExpandExcerptDirection::Down {
15854 let multi_buffer = self.buffer.read(cx);
15855 let snapshot = multi_buffer.snapshot(cx);
15856 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15857 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15858 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15859 {
15860 let buffer_snapshot = buffer.read(cx).snapshot();
15861 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15862 let last_row = buffer_snapshot.max_point().row;
15863 let lines_below = last_row.saturating_sub(excerpt_end_row);
15864 should_scroll_up = lines_below >= lines_to_expand;
15865 }
15866 }
15867
15868 self.buffer.update(cx, |buffer, cx| {
15869 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15870 });
15871
15872 if should_scroll_up {
15873 let new_scroll_position =
15874 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15875 self.set_scroll_position(new_scroll_position, window, cx);
15876 }
15877 }
15878
15879 pub fn go_to_singleton_buffer_point(
15880 &mut self,
15881 point: Point,
15882 window: &mut Window,
15883 cx: &mut Context<Self>,
15884 ) {
15885 self.go_to_singleton_buffer_range(point..point, window, cx);
15886 }
15887
15888 pub fn go_to_singleton_buffer_range(
15889 &mut self,
15890 range: Range<Point>,
15891 window: &mut Window,
15892 cx: &mut Context<Self>,
15893 ) {
15894 let multibuffer = self.buffer().read(cx);
15895 let Some(buffer) = multibuffer.as_singleton() else {
15896 return;
15897 };
15898 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15899 return;
15900 };
15901 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15902 return;
15903 };
15904 self.change_selections(
15905 SelectionEffects::default().nav_history(true),
15906 window,
15907 cx,
15908 |s| s.select_anchor_ranges([start..end]),
15909 );
15910 }
15911
15912 pub fn go_to_diagnostic(
15913 &mut self,
15914 action: &GoToDiagnostic,
15915 window: &mut Window,
15916 cx: &mut Context<Self>,
15917 ) {
15918 if !self.diagnostics_enabled() {
15919 return;
15920 }
15921 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15922 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15923 }
15924
15925 pub fn go_to_prev_diagnostic(
15926 &mut self,
15927 action: &GoToPreviousDiagnostic,
15928 window: &mut Window,
15929 cx: &mut Context<Self>,
15930 ) {
15931 if !self.diagnostics_enabled() {
15932 return;
15933 }
15934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15935 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15936 }
15937
15938 pub fn go_to_diagnostic_impl(
15939 &mut self,
15940 direction: Direction,
15941 severity: GoToDiagnosticSeverityFilter,
15942 window: &mut Window,
15943 cx: &mut Context<Self>,
15944 ) {
15945 let buffer = self.buffer.read(cx).snapshot(cx);
15946 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15947
15948 let mut active_group_id = None;
15949 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15950 && active_group.active_range.start.to_offset(&buffer) == selection.start
15951 {
15952 active_group_id = Some(active_group.group_id);
15953 }
15954
15955 fn filtered<'a>(
15956 snapshot: EditorSnapshot,
15957 severity: GoToDiagnosticSeverityFilter,
15958 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15959 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15960 diagnostics
15961 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15962 .filter(|entry| entry.range.start != entry.range.end)
15963 .filter(|entry| !entry.diagnostic.is_unnecessary)
15964 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15965 }
15966
15967 let snapshot = self.snapshot(window, cx);
15968 let before = filtered(
15969 snapshot.clone(),
15970 severity,
15971 buffer
15972 .diagnostics_in_range(0..selection.start)
15973 .filter(|entry| entry.range.start <= selection.start),
15974 );
15975 let after = filtered(
15976 snapshot,
15977 severity,
15978 buffer
15979 .diagnostics_in_range(selection.start..buffer.len())
15980 .filter(|entry| entry.range.start >= selection.start),
15981 );
15982
15983 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15984 if direction == Direction::Prev {
15985 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15986 {
15987 for diagnostic in prev_diagnostics.into_iter().rev() {
15988 if diagnostic.range.start != selection.start
15989 || active_group_id
15990 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15991 {
15992 found = Some(diagnostic);
15993 break 'outer;
15994 }
15995 }
15996 }
15997 } else {
15998 for diagnostic in after.chain(before) {
15999 if diagnostic.range.start != selection.start
16000 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16001 {
16002 found = Some(diagnostic);
16003 break;
16004 }
16005 }
16006 }
16007 let Some(next_diagnostic) = found else {
16008 return;
16009 };
16010
16011 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16012 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16013 return;
16014 };
16015 self.change_selections(Default::default(), window, cx, |s| {
16016 s.select_ranges(vec![
16017 next_diagnostic.range.start..next_diagnostic.range.start,
16018 ])
16019 });
16020 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16021 self.refresh_edit_prediction(false, true, window, cx);
16022 }
16023
16024 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16026 let snapshot = self.snapshot(window, cx);
16027 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16028 self.go_to_hunk_before_or_after_position(
16029 &snapshot,
16030 selection.head(),
16031 Direction::Next,
16032 window,
16033 cx,
16034 );
16035 }
16036
16037 pub fn go_to_hunk_before_or_after_position(
16038 &mut self,
16039 snapshot: &EditorSnapshot,
16040 position: Point,
16041 direction: Direction,
16042 window: &mut Window,
16043 cx: &mut Context<Editor>,
16044 ) {
16045 let row = if direction == Direction::Next {
16046 self.hunk_after_position(snapshot, position)
16047 .map(|hunk| hunk.row_range.start)
16048 } else {
16049 self.hunk_before_position(snapshot, position)
16050 };
16051
16052 if let Some(row) = row {
16053 let destination = Point::new(row.0, 0);
16054 let autoscroll = Autoscroll::center();
16055
16056 self.unfold_ranges(&[destination..destination], false, false, cx);
16057 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16058 s.select_ranges([destination..destination]);
16059 });
16060 }
16061 }
16062
16063 fn hunk_after_position(
16064 &mut self,
16065 snapshot: &EditorSnapshot,
16066 position: Point,
16067 ) -> Option<MultiBufferDiffHunk> {
16068 snapshot
16069 .buffer_snapshot()
16070 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16071 .find(|hunk| hunk.row_range.start.0 > position.row)
16072 .or_else(|| {
16073 snapshot
16074 .buffer_snapshot()
16075 .diff_hunks_in_range(Point::zero()..position)
16076 .find(|hunk| hunk.row_range.end.0 < position.row)
16077 })
16078 }
16079
16080 fn go_to_prev_hunk(
16081 &mut self,
16082 _: &GoToPreviousHunk,
16083 window: &mut Window,
16084 cx: &mut Context<Self>,
16085 ) {
16086 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16087 let snapshot = self.snapshot(window, cx);
16088 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16089 self.go_to_hunk_before_or_after_position(
16090 &snapshot,
16091 selection.head(),
16092 Direction::Prev,
16093 window,
16094 cx,
16095 );
16096 }
16097
16098 fn hunk_before_position(
16099 &mut self,
16100 snapshot: &EditorSnapshot,
16101 position: Point,
16102 ) -> Option<MultiBufferRow> {
16103 snapshot
16104 .buffer_snapshot()
16105 .diff_hunk_before(position)
16106 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16107 }
16108
16109 fn go_to_next_change(
16110 &mut self,
16111 _: &GoToNextChange,
16112 window: &mut Window,
16113 cx: &mut Context<Self>,
16114 ) {
16115 if let Some(selections) = self
16116 .change_list
16117 .next_change(1, Direction::Next)
16118 .map(|s| s.to_vec())
16119 {
16120 self.change_selections(Default::default(), window, cx, |s| {
16121 let map = s.display_map();
16122 s.select_display_ranges(selections.iter().map(|a| {
16123 let point = a.to_display_point(&map);
16124 point..point
16125 }))
16126 })
16127 }
16128 }
16129
16130 fn go_to_previous_change(
16131 &mut self,
16132 _: &GoToPreviousChange,
16133 window: &mut Window,
16134 cx: &mut Context<Self>,
16135 ) {
16136 if let Some(selections) = self
16137 .change_list
16138 .next_change(1, Direction::Prev)
16139 .map(|s| s.to_vec())
16140 {
16141 self.change_selections(Default::default(), window, cx, |s| {
16142 let map = s.display_map();
16143 s.select_display_ranges(selections.iter().map(|a| {
16144 let point = a.to_display_point(&map);
16145 point..point
16146 }))
16147 })
16148 }
16149 }
16150
16151 pub fn go_to_next_document_highlight(
16152 &mut self,
16153 _: &GoToNextDocumentHighlight,
16154 window: &mut Window,
16155 cx: &mut Context<Self>,
16156 ) {
16157 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16158 }
16159
16160 pub fn go_to_prev_document_highlight(
16161 &mut self,
16162 _: &GoToPreviousDocumentHighlight,
16163 window: &mut Window,
16164 cx: &mut Context<Self>,
16165 ) {
16166 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16167 }
16168
16169 pub fn go_to_document_highlight_before_or_after_position(
16170 &mut self,
16171 direction: Direction,
16172 window: &mut Window,
16173 cx: &mut Context<Editor>,
16174 ) {
16175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16176 let snapshot = self.snapshot(window, cx);
16177 let buffer = &snapshot.buffer_snapshot();
16178 let position = self
16179 .selections
16180 .newest::<Point>(&snapshot.display_snapshot)
16181 .head();
16182 let anchor_position = buffer.anchor_after(position);
16183
16184 // Get all document highlights (both read and write)
16185 let mut all_highlights = Vec::new();
16186
16187 if let Some((_, read_highlights)) = self
16188 .background_highlights
16189 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16190 {
16191 all_highlights.extend(read_highlights.iter());
16192 }
16193
16194 if let Some((_, write_highlights)) = self
16195 .background_highlights
16196 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16197 {
16198 all_highlights.extend(write_highlights.iter());
16199 }
16200
16201 if all_highlights.is_empty() {
16202 return;
16203 }
16204
16205 // Sort highlights by position
16206 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16207
16208 let target_highlight = match direction {
16209 Direction::Next => {
16210 // Find the first highlight after the current position
16211 all_highlights
16212 .iter()
16213 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16214 }
16215 Direction::Prev => {
16216 // Find the last highlight before the current position
16217 all_highlights
16218 .iter()
16219 .rev()
16220 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16221 }
16222 };
16223
16224 if let Some(highlight) = target_highlight {
16225 let destination = highlight.start.to_point(buffer);
16226 let autoscroll = Autoscroll::center();
16227
16228 self.unfold_ranges(&[destination..destination], false, false, cx);
16229 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16230 s.select_ranges([destination..destination]);
16231 });
16232 }
16233 }
16234
16235 fn go_to_line<T: 'static>(
16236 &mut self,
16237 position: Anchor,
16238 highlight_color: Option<Hsla>,
16239 window: &mut Window,
16240 cx: &mut Context<Self>,
16241 ) {
16242 let snapshot = self.snapshot(window, cx).display_snapshot;
16243 let position = position.to_point(&snapshot.buffer_snapshot());
16244 let start = snapshot
16245 .buffer_snapshot()
16246 .clip_point(Point::new(position.row, 0), Bias::Left);
16247 let end = start + Point::new(1, 0);
16248 let start = snapshot.buffer_snapshot().anchor_before(start);
16249 let end = snapshot.buffer_snapshot().anchor_before(end);
16250
16251 self.highlight_rows::<T>(
16252 start..end,
16253 highlight_color
16254 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16255 Default::default(),
16256 cx,
16257 );
16258
16259 if self.buffer.read(cx).is_singleton() {
16260 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16261 }
16262 }
16263
16264 pub fn go_to_definition(
16265 &mut self,
16266 _: &GoToDefinition,
16267 window: &mut Window,
16268 cx: &mut Context<Self>,
16269 ) -> Task<Result<Navigated>> {
16270 let definition =
16271 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16272 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16273 cx.spawn_in(window, async move |editor, cx| {
16274 if definition.await? == Navigated::Yes {
16275 return Ok(Navigated::Yes);
16276 }
16277 match fallback_strategy {
16278 GoToDefinitionFallback::None => Ok(Navigated::No),
16279 GoToDefinitionFallback::FindAllReferences => {
16280 match editor.update_in(cx, |editor, window, cx| {
16281 editor.find_all_references(&FindAllReferences, window, cx)
16282 })? {
16283 Some(references) => references.await,
16284 None => Ok(Navigated::No),
16285 }
16286 }
16287 }
16288 })
16289 }
16290
16291 pub fn go_to_declaration(
16292 &mut self,
16293 _: &GoToDeclaration,
16294 window: &mut Window,
16295 cx: &mut Context<Self>,
16296 ) -> Task<Result<Navigated>> {
16297 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16298 }
16299
16300 pub fn go_to_declaration_split(
16301 &mut self,
16302 _: &GoToDeclaration,
16303 window: &mut Window,
16304 cx: &mut Context<Self>,
16305 ) -> Task<Result<Navigated>> {
16306 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16307 }
16308
16309 pub fn go_to_implementation(
16310 &mut self,
16311 _: &GoToImplementation,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) -> Task<Result<Navigated>> {
16315 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16316 }
16317
16318 pub fn go_to_implementation_split(
16319 &mut self,
16320 _: &GoToImplementationSplit,
16321 window: &mut Window,
16322 cx: &mut Context<Self>,
16323 ) -> Task<Result<Navigated>> {
16324 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16325 }
16326
16327 pub fn go_to_type_definition(
16328 &mut self,
16329 _: &GoToTypeDefinition,
16330 window: &mut Window,
16331 cx: &mut Context<Self>,
16332 ) -> Task<Result<Navigated>> {
16333 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16334 }
16335
16336 pub fn go_to_definition_split(
16337 &mut self,
16338 _: &GoToDefinitionSplit,
16339 window: &mut Window,
16340 cx: &mut Context<Self>,
16341 ) -> Task<Result<Navigated>> {
16342 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16343 }
16344
16345 pub fn go_to_type_definition_split(
16346 &mut self,
16347 _: &GoToTypeDefinitionSplit,
16348 window: &mut Window,
16349 cx: &mut Context<Self>,
16350 ) -> Task<Result<Navigated>> {
16351 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16352 }
16353
16354 fn go_to_definition_of_kind(
16355 &mut self,
16356 kind: GotoDefinitionKind,
16357 split: bool,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) -> Task<Result<Navigated>> {
16361 let Some(provider) = self.semantics_provider.clone() else {
16362 return Task::ready(Ok(Navigated::No));
16363 };
16364 let head = self
16365 .selections
16366 .newest::<usize>(&self.display_snapshot(cx))
16367 .head();
16368 let buffer = self.buffer.read(cx);
16369 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16370 return Task::ready(Ok(Navigated::No));
16371 };
16372 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16373 return Task::ready(Ok(Navigated::No));
16374 };
16375
16376 cx.spawn_in(window, async move |editor, cx| {
16377 let Some(definitions) = definitions.await? else {
16378 return Ok(Navigated::No);
16379 };
16380 let navigated = editor
16381 .update_in(cx, |editor, window, cx| {
16382 editor.navigate_to_hover_links(
16383 Some(kind),
16384 definitions
16385 .into_iter()
16386 .filter(|location| {
16387 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16388 })
16389 .map(HoverLink::Text)
16390 .collect::<Vec<_>>(),
16391 split,
16392 window,
16393 cx,
16394 )
16395 })?
16396 .await?;
16397 anyhow::Ok(navigated)
16398 })
16399 }
16400
16401 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16402 let selection = self.selections.newest_anchor();
16403 let head = selection.head();
16404 let tail = selection.tail();
16405
16406 let Some((buffer, start_position)) =
16407 self.buffer.read(cx).text_anchor_for_position(head, cx)
16408 else {
16409 return;
16410 };
16411
16412 let end_position = if head != tail {
16413 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16414 return;
16415 };
16416 Some(pos)
16417 } else {
16418 None
16419 };
16420
16421 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16422 let url = if let Some(end_pos) = end_position {
16423 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16424 } else {
16425 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16426 };
16427
16428 if let Some(url) = url {
16429 cx.update(|window, cx| {
16430 if parse_zed_link(&url, cx).is_some() {
16431 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16432 } else {
16433 cx.open_url(&url);
16434 }
16435 })?;
16436 }
16437
16438 anyhow::Ok(())
16439 });
16440
16441 url_finder.detach();
16442 }
16443
16444 pub fn open_selected_filename(
16445 &mut self,
16446 _: &OpenSelectedFilename,
16447 window: &mut Window,
16448 cx: &mut Context<Self>,
16449 ) {
16450 let Some(workspace) = self.workspace() else {
16451 return;
16452 };
16453
16454 let position = self.selections.newest_anchor().head();
16455
16456 let Some((buffer, buffer_position)) =
16457 self.buffer.read(cx).text_anchor_for_position(position, cx)
16458 else {
16459 return;
16460 };
16461
16462 let project = self.project.clone();
16463
16464 cx.spawn_in(window, async move |_, cx| {
16465 let result = find_file(&buffer, project, buffer_position, cx).await;
16466
16467 if let Some((_, path)) = result {
16468 workspace
16469 .update_in(cx, |workspace, window, cx| {
16470 workspace.open_resolved_path(path, window, cx)
16471 })?
16472 .await?;
16473 }
16474 anyhow::Ok(())
16475 })
16476 .detach();
16477 }
16478
16479 pub(crate) fn navigate_to_hover_links(
16480 &mut self,
16481 kind: Option<GotoDefinitionKind>,
16482 definitions: Vec<HoverLink>,
16483 split: bool,
16484 window: &mut Window,
16485 cx: &mut Context<Editor>,
16486 ) -> Task<Result<Navigated>> {
16487 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16488 let mut first_url_or_file = None;
16489 let definitions: Vec<_> = definitions
16490 .into_iter()
16491 .filter_map(|def| match def {
16492 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16493 HoverLink::InlayHint(lsp_location, server_id) => {
16494 let computation =
16495 self.compute_target_location(lsp_location, server_id, window, cx);
16496 Some(cx.background_spawn(computation))
16497 }
16498 HoverLink::Url(url) => {
16499 first_url_or_file = Some(Either::Left(url));
16500 None
16501 }
16502 HoverLink::File(path) => {
16503 first_url_or_file = Some(Either::Right(path));
16504 None
16505 }
16506 })
16507 .collect();
16508
16509 let workspace = self.workspace();
16510
16511 cx.spawn_in(window, async move |editor, cx| {
16512 let locations: Vec<Location> = future::join_all(definitions)
16513 .await
16514 .into_iter()
16515 .filter_map(|location| location.transpose())
16516 .collect::<Result<_>>()
16517 .context("location tasks")?;
16518 let mut locations = cx.update(|_, cx| {
16519 locations
16520 .into_iter()
16521 .map(|location| {
16522 let buffer = location.buffer.read(cx);
16523 (location.buffer, location.range.to_point(buffer))
16524 })
16525 .into_group_map()
16526 })?;
16527 let mut num_locations = 0;
16528 for ranges in locations.values_mut() {
16529 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16530 ranges.dedup();
16531 num_locations += ranges.len();
16532 }
16533
16534 if num_locations > 1 {
16535 let Some(workspace) = workspace else {
16536 return Ok(Navigated::No);
16537 };
16538
16539 let tab_kind = match kind {
16540 Some(GotoDefinitionKind::Implementation) => "Implementations",
16541 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16542 Some(GotoDefinitionKind::Declaration) => "Declarations",
16543 Some(GotoDefinitionKind::Type) => "Types",
16544 };
16545 let title = editor
16546 .update_in(cx, |_, _, cx| {
16547 let target = locations
16548 .iter()
16549 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16550 .map(|(buffer, location)| {
16551 buffer
16552 .read(cx)
16553 .text_for_range(location.clone())
16554 .collect::<String>()
16555 })
16556 .filter(|text| !text.contains('\n'))
16557 .unique()
16558 .take(3)
16559 .join(", ");
16560 if target.is_empty() {
16561 tab_kind.to_owned()
16562 } else {
16563 format!("{tab_kind} for {target}")
16564 }
16565 })
16566 .context("buffer title")?;
16567
16568 let opened = workspace
16569 .update_in(cx, |workspace, window, cx| {
16570 Self::open_locations_in_multibuffer(
16571 workspace,
16572 locations,
16573 title,
16574 split,
16575 MultibufferSelectionMode::First,
16576 window,
16577 cx,
16578 )
16579 })
16580 .is_ok();
16581
16582 anyhow::Ok(Navigated::from_bool(opened))
16583 } else if num_locations == 0 {
16584 // If there is one url or file, open it directly
16585 match first_url_or_file {
16586 Some(Either::Left(url)) => {
16587 cx.update(|_, cx| cx.open_url(&url))?;
16588 Ok(Navigated::Yes)
16589 }
16590 Some(Either::Right(path)) => {
16591 let Some(workspace) = workspace else {
16592 return Ok(Navigated::No);
16593 };
16594
16595 workspace
16596 .update_in(cx, |workspace, window, cx| {
16597 workspace.open_resolved_path(path, window, cx)
16598 })?
16599 .await?;
16600 Ok(Navigated::Yes)
16601 }
16602 None => Ok(Navigated::No),
16603 }
16604 } else {
16605 let Some(workspace) = workspace else {
16606 return Ok(Navigated::No);
16607 };
16608
16609 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16610 let target_range = target_ranges.first().unwrap().clone();
16611
16612 editor.update_in(cx, |editor, window, cx| {
16613 let range = target_range.to_point(target_buffer.read(cx));
16614 let range = editor.range_for_match(&range);
16615 let range = collapse_multiline_range(range);
16616
16617 if !split
16618 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16619 {
16620 editor.go_to_singleton_buffer_range(range, window, cx);
16621 } else {
16622 let pane = workspace.read(cx).active_pane().clone();
16623 window.defer(cx, move |window, cx| {
16624 let target_editor: Entity<Self> =
16625 workspace.update(cx, |workspace, cx| {
16626 let pane = if split {
16627 workspace.adjacent_pane(window, cx)
16628 } else {
16629 workspace.active_pane().clone()
16630 };
16631
16632 workspace.open_project_item(
16633 pane,
16634 target_buffer.clone(),
16635 true,
16636 true,
16637 window,
16638 cx,
16639 )
16640 });
16641 target_editor.update(cx, |target_editor, cx| {
16642 // When selecting a definition in a different buffer, disable the nav history
16643 // to avoid creating a history entry at the previous cursor location.
16644 pane.update(cx, |pane, _| pane.disable_history());
16645 target_editor.go_to_singleton_buffer_range(range, window, cx);
16646 pane.update(cx, |pane, _| pane.enable_history());
16647 });
16648 });
16649 }
16650 Navigated::Yes
16651 })
16652 }
16653 })
16654 }
16655
16656 fn compute_target_location(
16657 &self,
16658 lsp_location: lsp::Location,
16659 server_id: LanguageServerId,
16660 window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) -> Task<anyhow::Result<Option<Location>>> {
16663 let Some(project) = self.project.clone() else {
16664 return Task::ready(Ok(None));
16665 };
16666
16667 cx.spawn_in(window, async move |editor, cx| {
16668 let location_task = editor.update(cx, |_, cx| {
16669 project.update(cx, |project, cx| {
16670 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16671 })
16672 })?;
16673 let location = Some({
16674 let target_buffer_handle = location_task.await.context("open local buffer")?;
16675 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16676 let target_start = target_buffer
16677 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16678 let target_end = target_buffer
16679 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16680 target_buffer.anchor_after(target_start)
16681 ..target_buffer.anchor_before(target_end)
16682 })?;
16683 Location {
16684 buffer: target_buffer_handle,
16685 range,
16686 }
16687 });
16688 Ok(location)
16689 })
16690 }
16691
16692 fn go_to_next_reference(
16693 &mut self,
16694 _: &GoToNextReference,
16695 window: &mut Window,
16696 cx: &mut Context<Self>,
16697 ) {
16698 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16699 if let Some(task) = task {
16700 task.detach();
16701 };
16702 }
16703
16704 fn go_to_prev_reference(
16705 &mut self,
16706 _: &GoToPreviousReference,
16707 window: &mut Window,
16708 cx: &mut Context<Self>,
16709 ) {
16710 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16711 if let Some(task) = task {
16712 task.detach();
16713 };
16714 }
16715
16716 pub fn go_to_reference_before_or_after_position(
16717 &mut self,
16718 direction: Direction,
16719 count: usize,
16720 window: &mut Window,
16721 cx: &mut Context<Self>,
16722 ) -> Option<Task<Result<()>>> {
16723 let selection = self.selections.newest_anchor();
16724 let head = selection.head();
16725
16726 let multi_buffer = self.buffer.read(cx);
16727
16728 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16729 let workspace = self.workspace()?;
16730 let project = workspace.read(cx).project().clone();
16731 let references =
16732 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16733 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16734 let Some(locations) = references.await? else {
16735 return Ok(());
16736 };
16737
16738 if locations.is_empty() {
16739 // totally normal - the cursor may be on something which is not
16740 // a symbol (e.g. a keyword)
16741 log::info!("no references found under cursor");
16742 return Ok(());
16743 }
16744
16745 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16746
16747 let multi_buffer_snapshot =
16748 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16749
16750 let (locations, current_location_index) =
16751 multi_buffer.update(cx, |multi_buffer, cx| {
16752 let mut locations = locations
16753 .into_iter()
16754 .filter_map(|loc| {
16755 let start = multi_buffer.buffer_anchor_to_anchor(
16756 &loc.buffer,
16757 loc.range.start,
16758 cx,
16759 )?;
16760 let end = multi_buffer.buffer_anchor_to_anchor(
16761 &loc.buffer,
16762 loc.range.end,
16763 cx,
16764 )?;
16765 Some(start..end)
16766 })
16767 .collect::<Vec<_>>();
16768
16769 // There is an O(n) implementation, but given this list will be
16770 // small (usually <100 items), the extra O(log(n)) factor isn't
16771 // worth the (surprisingly large amount of) extra complexity.
16772 locations
16773 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16774
16775 let head_offset = head.to_offset(&multi_buffer_snapshot);
16776
16777 let current_location_index = locations.iter().position(|loc| {
16778 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16779 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16780 });
16781
16782 (locations, current_location_index)
16783 })?;
16784
16785 let Some(current_location_index) = current_location_index else {
16786 // This indicates something has gone wrong, because we already
16787 // handle the "no references" case above
16788 log::error!(
16789 "failed to find current reference under cursor. Total references: {}",
16790 locations.len()
16791 );
16792 return Ok(());
16793 };
16794
16795 let destination_location_index = match direction {
16796 Direction::Next => (current_location_index + count) % locations.len(),
16797 Direction::Prev => {
16798 (current_location_index + locations.len() - count % locations.len())
16799 % locations.len()
16800 }
16801 };
16802
16803 // TODO(cameron): is this needed?
16804 // the thinking is to avoid "jumping to the current location" (avoid
16805 // polluting "jumplist" in vim terms)
16806 if current_location_index == destination_location_index {
16807 return Ok(());
16808 }
16809
16810 let Range { start, end } = locations[destination_location_index];
16811
16812 editor.update_in(cx, |editor, window, cx| {
16813 let effects = SelectionEffects::default();
16814
16815 editor.unfold_ranges(&[start..end], false, false, cx);
16816 editor.change_selections(effects, window, cx, |s| {
16817 s.select_ranges([start..start]);
16818 });
16819 })?;
16820
16821 Ok(())
16822 }))
16823 }
16824
16825 pub fn find_all_references(
16826 &mut self,
16827 _: &FindAllReferences,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) -> Option<Task<Result<Navigated>>> {
16831 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16832 let multi_buffer = self.buffer.read(cx);
16833 let head = selection.head();
16834
16835 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16836 let head_anchor = multi_buffer_snapshot.anchor_at(
16837 head,
16838 if head < selection.tail() {
16839 Bias::Right
16840 } else {
16841 Bias::Left
16842 },
16843 );
16844
16845 match self
16846 .find_all_references_task_sources
16847 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16848 {
16849 Ok(_) => {
16850 log::info!(
16851 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16852 );
16853 return None;
16854 }
16855 Err(i) => {
16856 self.find_all_references_task_sources.insert(i, head_anchor);
16857 }
16858 }
16859
16860 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16861 let workspace = self.workspace()?;
16862 let project = workspace.read(cx).project().clone();
16863 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16864 Some(cx.spawn_in(window, async move |editor, cx| {
16865 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16866 if let Ok(i) = editor
16867 .find_all_references_task_sources
16868 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16869 {
16870 editor.find_all_references_task_sources.remove(i);
16871 }
16872 });
16873
16874 let Some(locations) = references.await? else {
16875 return anyhow::Ok(Navigated::No);
16876 };
16877 let mut locations = cx.update(|_, cx| {
16878 locations
16879 .into_iter()
16880 .map(|location| {
16881 let buffer = location.buffer.read(cx);
16882 (location.buffer, location.range.to_point(buffer))
16883 })
16884 .into_group_map()
16885 })?;
16886 if locations.is_empty() {
16887 return anyhow::Ok(Navigated::No);
16888 }
16889 for ranges in locations.values_mut() {
16890 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16891 ranges.dedup();
16892 }
16893
16894 workspace.update_in(cx, |workspace, window, cx| {
16895 let target = locations
16896 .iter()
16897 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16898 .map(|(buffer, location)| {
16899 buffer
16900 .read(cx)
16901 .text_for_range(location.clone())
16902 .collect::<String>()
16903 })
16904 .filter(|text| !text.contains('\n'))
16905 .unique()
16906 .take(3)
16907 .join(", ");
16908 let title = if target.is_empty() {
16909 "References".to_owned()
16910 } else {
16911 format!("References to {target}")
16912 };
16913 Self::open_locations_in_multibuffer(
16914 workspace,
16915 locations,
16916 title,
16917 false,
16918 MultibufferSelectionMode::First,
16919 window,
16920 cx,
16921 );
16922 Navigated::Yes
16923 })
16924 }))
16925 }
16926
16927 /// Opens a multibuffer with the given project locations in it
16928 pub fn open_locations_in_multibuffer(
16929 workspace: &mut Workspace,
16930 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16931 title: String,
16932 split: bool,
16933 multibuffer_selection_mode: MultibufferSelectionMode,
16934 window: &mut Window,
16935 cx: &mut Context<Workspace>,
16936 ) {
16937 if locations.is_empty() {
16938 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16939 return;
16940 }
16941
16942 let capability = workspace.project().read(cx).capability();
16943 let mut ranges = <Vec<Range<Anchor>>>::new();
16944
16945 // a key to find existing multibuffer editors with the same set of locations
16946 // to prevent us from opening more and more multibuffer tabs for searches and the like
16947 let mut key = (title.clone(), vec![]);
16948 let excerpt_buffer = cx.new(|cx| {
16949 let key = &mut key.1;
16950 let mut multibuffer = MultiBuffer::new(capability);
16951 for (buffer, mut ranges_for_buffer) in locations {
16952 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16953 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16954 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16955 PathKey::for_buffer(&buffer, cx),
16956 buffer.clone(),
16957 ranges_for_buffer,
16958 multibuffer_context_lines(cx),
16959 cx,
16960 );
16961 ranges.extend(new_ranges)
16962 }
16963
16964 multibuffer.with_title(title)
16965 });
16966 let existing = workspace.active_pane().update(cx, |pane, cx| {
16967 pane.items()
16968 .filter_map(|item| item.downcast::<Editor>())
16969 .find(|editor| {
16970 editor
16971 .read(cx)
16972 .lookup_key
16973 .as_ref()
16974 .and_then(|it| {
16975 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16976 })
16977 .is_some_and(|it| *it == key)
16978 })
16979 });
16980 let editor = existing.unwrap_or_else(|| {
16981 cx.new(|cx| {
16982 let mut editor = Editor::for_multibuffer(
16983 excerpt_buffer,
16984 Some(workspace.project().clone()),
16985 window,
16986 cx,
16987 );
16988 editor.lookup_key = Some(Box::new(key));
16989 editor
16990 })
16991 });
16992 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16993 MultibufferSelectionMode::First => {
16994 if let Some(first_range) = ranges.first() {
16995 editor.change_selections(
16996 SelectionEffects::no_scroll(),
16997 window,
16998 cx,
16999 |selections| {
17000 selections.clear_disjoint();
17001 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17002 },
17003 );
17004 }
17005 editor.highlight_background::<Self>(
17006 &ranges,
17007 |theme| theme.colors().editor_highlighted_line_background,
17008 cx,
17009 );
17010 }
17011 MultibufferSelectionMode::All => {
17012 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17013 selections.clear_disjoint();
17014 selections.select_anchor_ranges(ranges);
17015 });
17016 }
17017 });
17018
17019 let item = Box::new(editor);
17020 let item_id = item.item_id();
17021
17022 if split {
17023 let pane = workspace.adjacent_pane(window, cx);
17024 workspace.add_item(pane, item, None, true, true, window, cx);
17025 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17026 let (preview_item_id, preview_item_idx) =
17027 workspace.active_pane().read_with(cx, |pane, _| {
17028 (pane.preview_item_id(), pane.preview_item_idx())
17029 });
17030
17031 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17032
17033 if let Some(preview_item_id) = preview_item_id {
17034 workspace.active_pane().update(cx, |pane, cx| {
17035 pane.remove_item(preview_item_id, false, false, window, cx);
17036 });
17037 }
17038 } else {
17039 workspace.add_item_to_active_pane(item, None, true, window, cx);
17040 }
17041 workspace.active_pane().update(cx, |pane, cx| {
17042 pane.set_preview_item_id(Some(item_id), cx);
17043 });
17044 }
17045
17046 pub fn rename(
17047 &mut self,
17048 _: &Rename,
17049 window: &mut Window,
17050 cx: &mut Context<Self>,
17051 ) -> Option<Task<Result<()>>> {
17052 use language::ToOffset as _;
17053
17054 let provider = self.semantics_provider.clone()?;
17055 let selection = self.selections.newest_anchor().clone();
17056 let (cursor_buffer, cursor_buffer_position) = self
17057 .buffer
17058 .read(cx)
17059 .text_anchor_for_position(selection.head(), cx)?;
17060 let (tail_buffer, cursor_buffer_position_end) = self
17061 .buffer
17062 .read(cx)
17063 .text_anchor_for_position(selection.tail(), cx)?;
17064 if tail_buffer != cursor_buffer {
17065 return None;
17066 }
17067
17068 let snapshot = cursor_buffer.read(cx).snapshot();
17069 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17070 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17071 let prepare_rename = provider
17072 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17073 .unwrap_or_else(|| Task::ready(Ok(None)));
17074 drop(snapshot);
17075
17076 Some(cx.spawn_in(window, async move |this, cx| {
17077 let rename_range = if let Some(range) = prepare_rename.await? {
17078 Some(range)
17079 } else {
17080 this.update(cx, |this, cx| {
17081 let buffer = this.buffer.read(cx).snapshot(cx);
17082 let mut buffer_highlights = this
17083 .document_highlights_for_position(selection.head(), &buffer)
17084 .filter(|highlight| {
17085 highlight.start.excerpt_id == selection.head().excerpt_id
17086 && highlight.end.excerpt_id == selection.head().excerpt_id
17087 });
17088 buffer_highlights
17089 .next()
17090 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17091 })?
17092 };
17093 if let Some(rename_range) = rename_range {
17094 this.update_in(cx, |this, window, cx| {
17095 let snapshot = cursor_buffer.read(cx).snapshot();
17096 let rename_buffer_range = rename_range.to_offset(&snapshot);
17097 let cursor_offset_in_rename_range =
17098 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17099 let cursor_offset_in_rename_range_end =
17100 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17101
17102 this.take_rename(false, window, cx);
17103 let buffer = this.buffer.read(cx).read(cx);
17104 let cursor_offset = selection.head().to_offset(&buffer);
17105 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17106 let rename_end = rename_start + rename_buffer_range.len();
17107 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17108 let mut old_highlight_id = None;
17109 let old_name: Arc<str> = buffer
17110 .chunks(rename_start..rename_end, true)
17111 .map(|chunk| {
17112 if old_highlight_id.is_none() {
17113 old_highlight_id = chunk.syntax_highlight_id;
17114 }
17115 chunk.text
17116 })
17117 .collect::<String>()
17118 .into();
17119
17120 drop(buffer);
17121
17122 // Position the selection in the rename editor so that it matches the current selection.
17123 this.show_local_selections = false;
17124 let rename_editor = cx.new(|cx| {
17125 let mut editor = Editor::single_line(window, cx);
17126 editor.buffer.update(cx, |buffer, cx| {
17127 buffer.edit([(0..0, old_name.clone())], None, cx)
17128 });
17129 let rename_selection_range = match cursor_offset_in_rename_range
17130 .cmp(&cursor_offset_in_rename_range_end)
17131 {
17132 Ordering::Equal => {
17133 editor.select_all(&SelectAll, window, cx);
17134 return editor;
17135 }
17136 Ordering::Less => {
17137 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17138 }
17139 Ordering::Greater => {
17140 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17141 }
17142 };
17143 if rename_selection_range.end > old_name.len() {
17144 editor.select_all(&SelectAll, window, cx);
17145 } else {
17146 editor.change_selections(Default::default(), window, cx, |s| {
17147 s.select_ranges([rename_selection_range]);
17148 });
17149 }
17150 editor
17151 });
17152 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17153 if e == &EditorEvent::Focused {
17154 cx.emit(EditorEvent::FocusedIn)
17155 }
17156 })
17157 .detach();
17158
17159 let write_highlights =
17160 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17161 let read_highlights =
17162 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17163 let ranges = write_highlights
17164 .iter()
17165 .flat_map(|(_, ranges)| ranges.iter())
17166 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17167 .cloned()
17168 .collect();
17169
17170 this.highlight_text::<Rename>(
17171 ranges,
17172 HighlightStyle {
17173 fade_out: Some(0.6),
17174 ..Default::default()
17175 },
17176 cx,
17177 );
17178 let rename_focus_handle = rename_editor.focus_handle(cx);
17179 window.focus(&rename_focus_handle);
17180 let block_id = this.insert_blocks(
17181 [BlockProperties {
17182 style: BlockStyle::Flex,
17183 placement: BlockPlacement::Below(range.start),
17184 height: Some(1),
17185 render: Arc::new({
17186 let rename_editor = rename_editor.clone();
17187 move |cx: &mut BlockContext| {
17188 let mut text_style = cx.editor_style.text.clone();
17189 if let Some(highlight_style) = old_highlight_id
17190 .and_then(|h| h.style(&cx.editor_style.syntax))
17191 {
17192 text_style = text_style.highlight(highlight_style);
17193 }
17194 div()
17195 .block_mouse_except_scroll()
17196 .pl(cx.anchor_x)
17197 .child(EditorElement::new(
17198 &rename_editor,
17199 EditorStyle {
17200 background: cx.theme().system().transparent,
17201 local_player: cx.editor_style.local_player,
17202 text: text_style,
17203 scrollbar_width: cx.editor_style.scrollbar_width,
17204 syntax: cx.editor_style.syntax.clone(),
17205 status: cx.editor_style.status.clone(),
17206 inlay_hints_style: HighlightStyle {
17207 font_weight: Some(FontWeight::BOLD),
17208 ..make_inlay_hints_style(cx.app)
17209 },
17210 edit_prediction_styles: make_suggestion_styles(
17211 cx.app,
17212 ),
17213 ..EditorStyle::default()
17214 },
17215 ))
17216 .into_any_element()
17217 }
17218 }),
17219 priority: 0,
17220 }],
17221 Some(Autoscroll::fit()),
17222 cx,
17223 )[0];
17224 this.pending_rename = Some(RenameState {
17225 range,
17226 old_name,
17227 editor: rename_editor,
17228 block_id,
17229 });
17230 })?;
17231 }
17232
17233 Ok(())
17234 }))
17235 }
17236
17237 pub fn confirm_rename(
17238 &mut self,
17239 _: &ConfirmRename,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) -> Option<Task<Result<()>>> {
17243 let rename = self.take_rename(false, window, cx)?;
17244 let workspace = self.workspace()?.downgrade();
17245 let (buffer, start) = self
17246 .buffer
17247 .read(cx)
17248 .text_anchor_for_position(rename.range.start, cx)?;
17249 let (end_buffer, _) = self
17250 .buffer
17251 .read(cx)
17252 .text_anchor_for_position(rename.range.end, cx)?;
17253 if buffer != end_buffer {
17254 return None;
17255 }
17256
17257 let old_name = rename.old_name;
17258 let new_name = rename.editor.read(cx).text(cx);
17259
17260 let rename = self.semantics_provider.as_ref()?.perform_rename(
17261 &buffer,
17262 start,
17263 new_name.clone(),
17264 cx,
17265 )?;
17266
17267 Some(cx.spawn_in(window, async move |editor, cx| {
17268 let project_transaction = rename.await?;
17269 Self::open_project_transaction(
17270 &editor,
17271 workspace,
17272 project_transaction,
17273 format!("Rename: {} → {}", old_name, new_name),
17274 cx,
17275 )
17276 .await?;
17277
17278 editor.update(cx, |editor, cx| {
17279 editor.refresh_document_highlights(cx);
17280 })?;
17281 Ok(())
17282 }))
17283 }
17284
17285 fn take_rename(
17286 &mut self,
17287 moving_cursor: bool,
17288 window: &mut Window,
17289 cx: &mut Context<Self>,
17290 ) -> Option<RenameState> {
17291 let rename = self.pending_rename.take()?;
17292 if rename.editor.focus_handle(cx).is_focused(window) {
17293 window.focus(&self.focus_handle);
17294 }
17295
17296 self.remove_blocks(
17297 [rename.block_id].into_iter().collect(),
17298 Some(Autoscroll::fit()),
17299 cx,
17300 );
17301 self.clear_highlights::<Rename>(cx);
17302 self.show_local_selections = true;
17303
17304 if moving_cursor {
17305 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17306 editor
17307 .selections
17308 .newest::<usize>(&editor.display_snapshot(cx))
17309 .head()
17310 });
17311
17312 // Update the selection to match the position of the selection inside
17313 // the rename editor.
17314 let snapshot = self.buffer.read(cx).read(cx);
17315 let rename_range = rename.range.to_offset(&snapshot);
17316 let cursor_in_editor = snapshot
17317 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17318 .min(rename_range.end);
17319 drop(snapshot);
17320
17321 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17322 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17323 });
17324 } else {
17325 self.refresh_document_highlights(cx);
17326 }
17327
17328 Some(rename)
17329 }
17330
17331 pub fn pending_rename(&self) -> Option<&RenameState> {
17332 self.pending_rename.as_ref()
17333 }
17334
17335 fn format(
17336 &mut self,
17337 _: &Format,
17338 window: &mut Window,
17339 cx: &mut Context<Self>,
17340 ) -> Option<Task<Result<()>>> {
17341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17342
17343 let project = match &self.project {
17344 Some(project) => project.clone(),
17345 None => return None,
17346 };
17347
17348 Some(self.perform_format(
17349 project,
17350 FormatTrigger::Manual,
17351 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17352 window,
17353 cx,
17354 ))
17355 }
17356
17357 fn format_selections(
17358 &mut self,
17359 _: &FormatSelections,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) -> Option<Task<Result<()>>> {
17363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17364
17365 let project = match &self.project {
17366 Some(project) => project.clone(),
17367 None => return None,
17368 };
17369
17370 let ranges = self
17371 .selections
17372 .all_adjusted(&self.display_snapshot(cx))
17373 .into_iter()
17374 .map(|selection| selection.range())
17375 .collect_vec();
17376
17377 Some(self.perform_format(
17378 project,
17379 FormatTrigger::Manual,
17380 FormatTarget::Ranges(ranges),
17381 window,
17382 cx,
17383 ))
17384 }
17385
17386 fn perform_format(
17387 &mut self,
17388 project: Entity<Project>,
17389 trigger: FormatTrigger,
17390 target: FormatTarget,
17391 window: &mut Window,
17392 cx: &mut Context<Self>,
17393 ) -> Task<Result<()>> {
17394 let buffer = self.buffer.clone();
17395 let (buffers, target) = match target {
17396 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17397 FormatTarget::Ranges(selection_ranges) => {
17398 let multi_buffer = buffer.read(cx);
17399 let snapshot = multi_buffer.read(cx);
17400 let mut buffers = HashSet::default();
17401 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17402 BTreeMap::new();
17403 for selection_range in selection_ranges {
17404 for (buffer, buffer_range, _) in
17405 snapshot.range_to_buffer_ranges(selection_range)
17406 {
17407 let buffer_id = buffer.remote_id();
17408 let start = buffer.anchor_before(buffer_range.start);
17409 let end = buffer.anchor_after(buffer_range.end);
17410 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17411 buffer_id_to_ranges
17412 .entry(buffer_id)
17413 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17414 .or_insert_with(|| vec![start..end]);
17415 }
17416 }
17417 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17418 }
17419 };
17420
17421 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17422 let selections_prev = transaction_id_prev
17423 .and_then(|transaction_id_prev| {
17424 // default to selections as they were after the last edit, if we have them,
17425 // instead of how they are now.
17426 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17427 // will take you back to where you made the last edit, instead of staying where you scrolled
17428 self.selection_history
17429 .transaction(transaction_id_prev)
17430 .map(|t| t.0.clone())
17431 })
17432 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17433
17434 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17435 let format = project.update(cx, |project, cx| {
17436 project.format(buffers, target, true, trigger, cx)
17437 });
17438
17439 cx.spawn_in(window, async move |editor, cx| {
17440 let transaction = futures::select_biased! {
17441 transaction = format.log_err().fuse() => transaction,
17442 () = timeout => {
17443 log::warn!("timed out waiting for formatting");
17444 None
17445 }
17446 };
17447
17448 buffer
17449 .update(cx, |buffer, cx| {
17450 if let Some(transaction) = transaction
17451 && !buffer.is_singleton()
17452 {
17453 buffer.push_transaction(&transaction.0, cx);
17454 }
17455 cx.notify();
17456 })
17457 .ok();
17458
17459 if let Some(transaction_id_now) =
17460 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17461 {
17462 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17463 if has_new_transaction {
17464 _ = editor.update(cx, |editor, _| {
17465 editor
17466 .selection_history
17467 .insert_transaction(transaction_id_now, selections_prev);
17468 });
17469 }
17470 }
17471
17472 Ok(())
17473 })
17474 }
17475
17476 fn organize_imports(
17477 &mut self,
17478 _: &OrganizeImports,
17479 window: &mut Window,
17480 cx: &mut Context<Self>,
17481 ) -> Option<Task<Result<()>>> {
17482 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17483 let project = match &self.project {
17484 Some(project) => project.clone(),
17485 None => return None,
17486 };
17487 Some(self.perform_code_action_kind(
17488 project,
17489 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17490 window,
17491 cx,
17492 ))
17493 }
17494
17495 fn perform_code_action_kind(
17496 &mut self,
17497 project: Entity<Project>,
17498 kind: CodeActionKind,
17499 window: &mut Window,
17500 cx: &mut Context<Self>,
17501 ) -> Task<Result<()>> {
17502 let buffer = self.buffer.clone();
17503 let buffers = buffer.read(cx).all_buffers();
17504 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17505 let apply_action = project.update(cx, |project, cx| {
17506 project.apply_code_action_kind(buffers, kind, true, cx)
17507 });
17508 cx.spawn_in(window, async move |_, cx| {
17509 let transaction = futures::select_biased! {
17510 () = timeout => {
17511 log::warn!("timed out waiting for executing code action");
17512 None
17513 }
17514 transaction = apply_action.log_err().fuse() => transaction,
17515 };
17516 buffer
17517 .update(cx, |buffer, cx| {
17518 // check if we need this
17519 if let Some(transaction) = transaction
17520 && !buffer.is_singleton()
17521 {
17522 buffer.push_transaction(&transaction.0, cx);
17523 }
17524 cx.notify();
17525 })
17526 .ok();
17527 Ok(())
17528 })
17529 }
17530
17531 pub fn restart_language_server(
17532 &mut self,
17533 _: &RestartLanguageServer,
17534 _: &mut Window,
17535 cx: &mut Context<Self>,
17536 ) {
17537 if let Some(project) = self.project.clone() {
17538 self.buffer.update(cx, |multi_buffer, cx| {
17539 project.update(cx, |project, cx| {
17540 project.restart_language_servers_for_buffers(
17541 multi_buffer.all_buffers().into_iter().collect(),
17542 HashSet::default(),
17543 cx,
17544 );
17545 });
17546 })
17547 }
17548 }
17549
17550 pub fn stop_language_server(
17551 &mut self,
17552 _: &StopLanguageServer,
17553 _: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) {
17556 if let Some(project) = self.project.clone() {
17557 self.buffer.update(cx, |multi_buffer, cx| {
17558 project.update(cx, |project, cx| {
17559 project.stop_language_servers_for_buffers(
17560 multi_buffer.all_buffers().into_iter().collect(),
17561 HashSet::default(),
17562 cx,
17563 );
17564 });
17565 });
17566 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17567 }
17568 }
17569
17570 fn cancel_language_server_work(
17571 workspace: &mut Workspace,
17572 _: &actions::CancelLanguageServerWork,
17573 _: &mut Window,
17574 cx: &mut Context<Workspace>,
17575 ) {
17576 let project = workspace.project();
17577 let buffers = workspace
17578 .active_item(cx)
17579 .and_then(|item| item.act_as::<Editor>(cx))
17580 .map_or(HashSet::default(), |editor| {
17581 editor.read(cx).buffer.read(cx).all_buffers()
17582 });
17583 project.update(cx, |project, cx| {
17584 project.cancel_language_server_work_for_buffers(buffers, cx);
17585 });
17586 }
17587
17588 fn show_character_palette(
17589 &mut self,
17590 _: &ShowCharacterPalette,
17591 window: &mut Window,
17592 _: &mut Context<Self>,
17593 ) {
17594 window.show_character_palette();
17595 }
17596
17597 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17598 if !self.diagnostics_enabled() {
17599 return;
17600 }
17601
17602 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17603 let buffer = self.buffer.read(cx).snapshot(cx);
17604 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17605 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17606 let is_valid = buffer
17607 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17608 .any(|entry| {
17609 entry.diagnostic.is_primary
17610 && !entry.range.is_empty()
17611 && entry.range.start == primary_range_start
17612 && entry.diagnostic.message == active_diagnostics.active_message
17613 });
17614
17615 if !is_valid {
17616 self.dismiss_diagnostics(cx);
17617 }
17618 }
17619 }
17620
17621 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17622 match &self.active_diagnostics {
17623 ActiveDiagnostic::Group(group) => Some(group),
17624 _ => None,
17625 }
17626 }
17627
17628 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17629 if !self.diagnostics_enabled() {
17630 return;
17631 }
17632 self.dismiss_diagnostics(cx);
17633 self.active_diagnostics = ActiveDiagnostic::All;
17634 }
17635
17636 fn activate_diagnostics(
17637 &mut self,
17638 buffer_id: BufferId,
17639 diagnostic: DiagnosticEntryRef<'_, usize>,
17640 window: &mut Window,
17641 cx: &mut Context<Self>,
17642 ) {
17643 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17644 return;
17645 }
17646 self.dismiss_diagnostics(cx);
17647 let snapshot = self.snapshot(window, cx);
17648 let buffer = self.buffer.read(cx).snapshot(cx);
17649 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17650 return;
17651 };
17652
17653 let diagnostic_group = buffer
17654 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17655 .collect::<Vec<_>>();
17656
17657 let blocks =
17658 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17659
17660 let blocks = self.display_map.update(cx, |display_map, cx| {
17661 display_map.insert_blocks(blocks, cx).into_iter().collect()
17662 });
17663 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17664 active_range: buffer.anchor_before(diagnostic.range.start)
17665 ..buffer.anchor_after(diagnostic.range.end),
17666 active_message: diagnostic.diagnostic.message.clone(),
17667 group_id: diagnostic.diagnostic.group_id,
17668 blocks,
17669 });
17670 cx.notify();
17671 }
17672
17673 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17674 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17675 return;
17676 };
17677
17678 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17679 if let ActiveDiagnostic::Group(group) = prev {
17680 self.display_map.update(cx, |display_map, cx| {
17681 display_map.remove_blocks(group.blocks, cx);
17682 });
17683 cx.notify();
17684 }
17685 }
17686
17687 /// Disable inline diagnostics rendering for this editor.
17688 pub fn disable_inline_diagnostics(&mut self) {
17689 self.inline_diagnostics_enabled = false;
17690 self.inline_diagnostics_update = Task::ready(());
17691 self.inline_diagnostics.clear();
17692 }
17693
17694 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17695 self.diagnostics_enabled = false;
17696 self.dismiss_diagnostics(cx);
17697 self.inline_diagnostics_update = Task::ready(());
17698 self.inline_diagnostics.clear();
17699 }
17700
17701 pub fn disable_word_completions(&mut self) {
17702 self.word_completions_enabled = false;
17703 }
17704
17705 pub fn diagnostics_enabled(&self) -> bool {
17706 self.diagnostics_enabled && self.mode.is_full()
17707 }
17708
17709 pub fn inline_diagnostics_enabled(&self) -> bool {
17710 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17711 }
17712
17713 pub fn show_inline_diagnostics(&self) -> bool {
17714 self.show_inline_diagnostics
17715 }
17716
17717 pub fn toggle_inline_diagnostics(
17718 &mut self,
17719 _: &ToggleInlineDiagnostics,
17720 window: &mut Window,
17721 cx: &mut Context<Editor>,
17722 ) {
17723 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17724 self.refresh_inline_diagnostics(false, window, cx);
17725 }
17726
17727 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17728 self.diagnostics_max_severity = severity;
17729 self.display_map.update(cx, |display_map, _| {
17730 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17731 });
17732 }
17733
17734 pub fn toggle_diagnostics(
17735 &mut self,
17736 _: &ToggleDiagnostics,
17737 window: &mut Window,
17738 cx: &mut Context<Editor>,
17739 ) {
17740 if !self.diagnostics_enabled() {
17741 return;
17742 }
17743
17744 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17745 EditorSettings::get_global(cx)
17746 .diagnostics_max_severity
17747 .filter(|severity| severity != &DiagnosticSeverity::Off)
17748 .unwrap_or(DiagnosticSeverity::Hint)
17749 } else {
17750 DiagnosticSeverity::Off
17751 };
17752 self.set_max_diagnostics_severity(new_severity, cx);
17753 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17754 self.active_diagnostics = ActiveDiagnostic::None;
17755 self.inline_diagnostics_update = Task::ready(());
17756 self.inline_diagnostics.clear();
17757 } else {
17758 self.refresh_inline_diagnostics(false, window, cx);
17759 }
17760
17761 cx.notify();
17762 }
17763
17764 pub fn toggle_minimap(
17765 &mut self,
17766 _: &ToggleMinimap,
17767 window: &mut Window,
17768 cx: &mut Context<Editor>,
17769 ) {
17770 if self.supports_minimap(cx) {
17771 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17772 }
17773 }
17774
17775 fn refresh_inline_diagnostics(
17776 &mut self,
17777 debounce: bool,
17778 window: &mut Window,
17779 cx: &mut Context<Self>,
17780 ) {
17781 let max_severity = ProjectSettings::get_global(cx)
17782 .diagnostics
17783 .inline
17784 .max_severity
17785 .unwrap_or(self.diagnostics_max_severity);
17786
17787 if !self.inline_diagnostics_enabled()
17788 || !self.diagnostics_enabled()
17789 || !self.show_inline_diagnostics
17790 || max_severity == DiagnosticSeverity::Off
17791 {
17792 self.inline_diagnostics_update = Task::ready(());
17793 self.inline_diagnostics.clear();
17794 return;
17795 }
17796
17797 let debounce_ms = ProjectSettings::get_global(cx)
17798 .diagnostics
17799 .inline
17800 .update_debounce_ms;
17801 let debounce = if debounce && debounce_ms > 0 {
17802 Some(Duration::from_millis(debounce_ms))
17803 } else {
17804 None
17805 };
17806 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17807 if let Some(debounce) = debounce {
17808 cx.background_executor().timer(debounce).await;
17809 }
17810 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17811 editor
17812 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17813 .ok()
17814 }) else {
17815 return;
17816 };
17817
17818 let new_inline_diagnostics = cx
17819 .background_spawn(async move {
17820 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17821 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17822 let message = diagnostic_entry
17823 .diagnostic
17824 .message
17825 .split_once('\n')
17826 .map(|(line, _)| line)
17827 .map(SharedString::new)
17828 .unwrap_or_else(|| {
17829 SharedString::new(&*diagnostic_entry.diagnostic.message)
17830 });
17831 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17832 let (Ok(i) | Err(i)) = inline_diagnostics
17833 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17834 inline_diagnostics.insert(
17835 i,
17836 (
17837 start_anchor,
17838 InlineDiagnostic {
17839 message,
17840 group_id: diagnostic_entry.diagnostic.group_id,
17841 start: diagnostic_entry.range.start.to_point(&snapshot),
17842 is_primary: diagnostic_entry.diagnostic.is_primary,
17843 severity: diagnostic_entry.diagnostic.severity,
17844 },
17845 ),
17846 );
17847 }
17848 inline_diagnostics
17849 })
17850 .await;
17851
17852 editor
17853 .update(cx, |editor, cx| {
17854 editor.inline_diagnostics = new_inline_diagnostics;
17855 cx.notify();
17856 })
17857 .ok();
17858 });
17859 }
17860
17861 fn pull_diagnostics(
17862 &mut self,
17863 buffer_id: Option<BufferId>,
17864 window: &Window,
17865 cx: &mut Context<Self>,
17866 ) -> Option<()> {
17867 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
17868 return None;
17869 }
17870 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17871 .diagnostics
17872 .lsp_pull_diagnostics;
17873 if !pull_diagnostics_settings.enabled {
17874 return None;
17875 }
17876 let project = self.project()?.downgrade();
17877 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17878 let mut buffers = self.buffer.read(cx).all_buffers();
17879 buffers.retain(|buffer| {
17880 let buffer_id_to_retain = buffer.read(cx).remote_id();
17881 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17882 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17883 });
17884 if buffers.is_empty() {
17885 self.pull_diagnostics_task = Task::ready(());
17886 return None;
17887 }
17888
17889 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17890 cx.background_executor().timer(debounce).await;
17891
17892 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17893 buffers
17894 .into_iter()
17895 .filter_map(|buffer| {
17896 project
17897 .update(cx, |project, cx| {
17898 project.lsp_store().update(cx, |lsp_store, cx| {
17899 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17900 })
17901 })
17902 .ok()
17903 })
17904 .collect::<FuturesUnordered<_>>()
17905 }) else {
17906 return;
17907 };
17908
17909 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17910 match pull_task {
17911 Ok(()) => {
17912 if editor
17913 .update_in(cx, |editor, window, cx| {
17914 editor.update_diagnostics_state(window, cx);
17915 })
17916 .is_err()
17917 {
17918 return;
17919 }
17920 }
17921 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17922 }
17923 }
17924 });
17925
17926 Some(())
17927 }
17928
17929 pub fn set_selections_from_remote(
17930 &mut self,
17931 selections: Vec<Selection<Anchor>>,
17932 pending_selection: Option<Selection<Anchor>>,
17933 window: &mut Window,
17934 cx: &mut Context<Self>,
17935 ) {
17936 let old_cursor_position = self.selections.newest_anchor().head();
17937 self.selections.change_with(cx, |s| {
17938 s.select_anchors(selections);
17939 if let Some(pending_selection) = pending_selection {
17940 s.set_pending(pending_selection, SelectMode::Character);
17941 } else {
17942 s.clear_pending();
17943 }
17944 });
17945 self.selections_did_change(
17946 false,
17947 &old_cursor_position,
17948 SelectionEffects::default(),
17949 window,
17950 cx,
17951 );
17952 }
17953
17954 pub fn transact(
17955 &mut self,
17956 window: &mut Window,
17957 cx: &mut Context<Self>,
17958 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17959 ) -> Option<TransactionId> {
17960 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17961 this.start_transaction_at(Instant::now(), window, cx);
17962 update(this, window, cx);
17963 this.end_transaction_at(Instant::now(), cx)
17964 })
17965 }
17966
17967 pub fn start_transaction_at(
17968 &mut self,
17969 now: Instant,
17970 window: &mut Window,
17971 cx: &mut Context<Self>,
17972 ) -> Option<TransactionId> {
17973 self.end_selection(window, cx);
17974 if let Some(tx_id) = self
17975 .buffer
17976 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17977 {
17978 self.selection_history
17979 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17980 cx.emit(EditorEvent::TransactionBegun {
17981 transaction_id: tx_id,
17982 });
17983 Some(tx_id)
17984 } else {
17985 None
17986 }
17987 }
17988
17989 pub fn end_transaction_at(
17990 &mut self,
17991 now: Instant,
17992 cx: &mut Context<Self>,
17993 ) -> Option<TransactionId> {
17994 if let Some(transaction_id) = self
17995 .buffer
17996 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17997 {
17998 if let Some((_, end_selections)) =
17999 self.selection_history.transaction_mut(transaction_id)
18000 {
18001 *end_selections = Some(self.selections.disjoint_anchors_arc());
18002 } else {
18003 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18004 }
18005
18006 cx.emit(EditorEvent::Edited { transaction_id });
18007 Some(transaction_id)
18008 } else {
18009 None
18010 }
18011 }
18012
18013 pub fn modify_transaction_selection_history(
18014 &mut self,
18015 transaction_id: TransactionId,
18016 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18017 ) -> bool {
18018 self.selection_history
18019 .transaction_mut(transaction_id)
18020 .map(modify)
18021 .is_some()
18022 }
18023
18024 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18025 if self.selection_mark_mode {
18026 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18027 s.move_with(|_, sel| {
18028 sel.collapse_to(sel.head(), SelectionGoal::None);
18029 });
18030 })
18031 }
18032 self.selection_mark_mode = true;
18033 cx.notify();
18034 }
18035
18036 pub fn swap_selection_ends(
18037 &mut self,
18038 _: &actions::SwapSelectionEnds,
18039 window: &mut Window,
18040 cx: &mut Context<Self>,
18041 ) {
18042 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18043 s.move_with(|_, sel| {
18044 if sel.start != sel.end {
18045 sel.reversed = !sel.reversed
18046 }
18047 });
18048 });
18049 self.request_autoscroll(Autoscroll::newest(), cx);
18050 cx.notify();
18051 }
18052
18053 pub fn toggle_focus(
18054 workspace: &mut Workspace,
18055 _: &actions::ToggleFocus,
18056 window: &mut Window,
18057 cx: &mut Context<Workspace>,
18058 ) {
18059 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18060 return;
18061 };
18062 workspace.activate_item(&item, true, true, window, cx);
18063 }
18064
18065 pub fn toggle_fold(
18066 &mut self,
18067 _: &actions::ToggleFold,
18068 window: &mut Window,
18069 cx: &mut Context<Self>,
18070 ) {
18071 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18072 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18073 let selection = self.selections.newest::<Point>(&display_map);
18074
18075 let range = if selection.is_empty() {
18076 let point = selection.head().to_display_point(&display_map);
18077 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18078 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18079 .to_point(&display_map);
18080 start..end
18081 } else {
18082 selection.range()
18083 };
18084 if display_map.folds_in_range(range).next().is_some() {
18085 self.unfold_lines(&Default::default(), window, cx)
18086 } else {
18087 self.fold(&Default::default(), window, cx)
18088 }
18089 } else {
18090 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18091 let buffer_ids: HashSet<_> = self
18092 .selections
18093 .disjoint_anchor_ranges()
18094 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18095 .collect();
18096
18097 let should_unfold = buffer_ids
18098 .iter()
18099 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18100
18101 for buffer_id in buffer_ids {
18102 if should_unfold {
18103 self.unfold_buffer(buffer_id, cx);
18104 } else {
18105 self.fold_buffer(buffer_id, cx);
18106 }
18107 }
18108 }
18109 }
18110
18111 pub fn toggle_fold_recursive(
18112 &mut self,
18113 _: &actions::ToggleFoldRecursive,
18114 window: &mut Window,
18115 cx: &mut Context<Self>,
18116 ) {
18117 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18118
18119 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18120 let range = if selection.is_empty() {
18121 let point = selection.head().to_display_point(&display_map);
18122 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18123 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18124 .to_point(&display_map);
18125 start..end
18126 } else {
18127 selection.range()
18128 };
18129 if display_map.folds_in_range(range).next().is_some() {
18130 self.unfold_recursive(&Default::default(), window, cx)
18131 } else {
18132 self.fold_recursive(&Default::default(), window, cx)
18133 }
18134 }
18135
18136 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18137 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18138 let mut to_fold = Vec::new();
18139 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18140 let selections = self.selections.all_adjusted(&display_map);
18141
18142 for selection in selections {
18143 let range = selection.range().sorted();
18144 let buffer_start_row = range.start.row;
18145
18146 if range.start.row != range.end.row {
18147 let mut found = false;
18148 let mut row = range.start.row;
18149 while row <= range.end.row {
18150 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18151 {
18152 found = true;
18153 row = crease.range().end.row + 1;
18154 to_fold.push(crease);
18155 } else {
18156 row += 1
18157 }
18158 }
18159 if found {
18160 continue;
18161 }
18162 }
18163
18164 for row in (0..=range.start.row).rev() {
18165 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18166 && crease.range().end.row >= buffer_start_row
18167 {
18168 to_fold.push(crease);
18169 if row <= range.start.row {
18170 break;
18171 }
18172 }
18173 }
18174 }
18175
18176 self.fold_creases(to_fold, true, window, cx);
18177 } else {
18178 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18179 let buffer_ids = self
18180 .selections
18181 .disjoint_anchor_ranges()
18182 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18183 .collect::<HashSet<_>>();
18184 for buffer_id in buffer_ids {
18185 self.fold_buffer(buffer_id, cx);
18186 }
18187 }
18188 }
18189
18190 pub fn toggle_fold_all(
18191 &mut self,
18192 _: &actions::ToggleFoldAll,
18193 window: &mut Window,
18194 cx: &mut Context<Self>,
18195 ) {
18196 if self.buffer.read(cx).is_singleton() {
18197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18198 let has_folds = display_map
18199 .folds_in_range(0..display_map.buffer_snapshot().len())
18200 .next()
18201 .is_some();
18202
18203 if has_folds {
18204 self.unfold_all(&actions::UnfoldAll, window, cx);
18205 } else {
18206 self.fold_all(&actions::FoldAll, window, cx);
18207 }
18208 } else {
18209 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18210 let should_unfold = buffer_ids
18211 .iter()
18212 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18213
18214 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18215 editor
18216 .update_in(cx, |editor, _, cx| {
18217 for buffer_id in buffer_ids {
18218 if should_unfold {
18219 editor.unfold_buffer(buffer_id, cx);
18220 } else {
18221 editor.fold_buffer(buffer_id, cx);
18222 }
18223 }
18224 })
18225 .ok();
18226 });
18227 }
18228 }
18229
18230 fn fold_at_level(
18231 &mut self,
18232 fold_at: &FoldAtLevel,
18233 window: &mut Window,
18234 cx: &mut Context<Self>,
18235 ) {
18236 if !self.buffer.read(cx).is_singleton() {
18237 return;
18238 }
18239
18240 let fold_at_level = fold_at.0;
18241 let snapshot = self.buffer.read(cx).snapshot(cx);
18242 let mut to_fold = Vec::new();
18243 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18244
18245 let row_ranges_to_keep: Vec<Range<u32>> = self
18246 .selections
18247 .all::<Point>(&self.display_snapshot(cx))
18248 .into_iter()
18249 .map(|sel| sel.start.row..sel.end.row)
18250 .collect();
18251
18252 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18253 while start_row < end_row {
18254 match self
18255 .snapshot(window, cx)
18256 .crease_for_buffer_row(MultiBufferRow(start_row))
18257 {
18258 Some(crease) => {
18259 let nested_start_row = crease.range().start.row + 1;
18260 let nested_end_row = crease.range().end.row;
18261
18262 if current_level < fold_at_level {
18263 stack.push((nested_start_row, nested_end_row, current_level + 1));
18264 } else if current_level == fold_at_level {
18265 // Fold iff there is no selection completely contained within the fold region
18266 if !row_ranges_to_keep.iter().any(|selection| {
18267 selection.end >= nested_start_row
18268 && selection.start <= nested_end_row
18269 }) {
18270 to_fold.push(crease);
18271 }
18272 }
18273
18274 start_row = nested_end_row + 1;
18275 }
18276 None => start_row += 1,
18277 }
18278 }
18279 }
18280
18281 self.fold_creases(to_fold, true, window, cx);
18282 }
18283
18284 pub fn fold_at_level_1(
18285 &mut self,
18286 _: &actions::FoldAtLevel1,
18287 window: &mut Window,
18288 cx: &mut Context<Self>,
18289 ) {
18290 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18291 }
18292
18293 pub fn fold_at_level_2(
18294 &mut self,
18295 _: &actions::FoldAtLevel2,
18296 window: &mut Window,
18297 cx: &mut Context<Self>,
18298 ) {
18299 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18300 }
18301
18302 pub fn fold_at_level_3(
18303 &mut self,
18304 _: &actions::FoldAtLevel3,
18305 window: &mut Window,
18306 cx: &mut Context<Self>,
18307 ) {
18308 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18309 }
18310
18311 pub fn fold_at_level_4(
18312 &mut self,
18313 _: &actions::FoldAtLevel4,
18314 window: &mut Window,
18315 cx: &mut Context<Self>,
18316 ) {
18317 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18318 }
18319
18320 pub fn fold_at_level_5(
18321 &mut self,
18322 _: &actions::FoldAtLevel5,
18323 window: &mut Window,
18324 cx: &mut Context<Self>,
18325 ) {
18326 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18327 }
18328
18329 pub fn fold_at_level_6(
18330 &mut self,
18331 _: &actions::FoldAtLevel6,
18332 window: &mut Window,
18333 cx: &mut Context<Self>,
18334 ) {
18335 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18336 }
18337
18338 pub fn fold_at_level_7(
18339 &mut self,
18340 _: &actions::FoldAtLevel7,
18341 window: &mut Window,
18342 cx: &mut Context<Self>,
18343 ) {
18344 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18345 }
18346
18347 pub fn fold_at_level_8(
18348 &mut self,
18349 _: &actions::FoldAtLevel8,
18350 window: &mut Window,
18351 cx: &mut Context<Self>,
18352 ) {
18353 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18354 }
18355
18356 pub fn fold_at_level_9(
18357 &mut self,
18358 _: &actions::FoldAtLevel9,
18359 window: &mut Window,
18360 cx: &mut Context<Self>,
18361 ) {
18362 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18363 }
18364
18365 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18366 if self.buffer.read(cx).is_singleton() {
18367 let mut fold_ranges = Vec::new();
18368 let snapshot = self.buffer.read(cx).snapshot(cx);
18369
18370 for row in 0..snapshot.max_row().0 {
18371 if let Some(foldable_range) = self
18372 .snapshot(window, cx)
18373 .crease_for_buffer_row(MultiBufferRow(row))
18374 {
18375 fold_ranges.push(foldable_range);
18376 }
18377 }
18378
18379 self.fold_creases(fold_ranges, true, window, cx);
18380 } else {
18381 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18382 editor
18383 .update_in(cx, |editor, _, cx| {
18384 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18385 editor.fold_buffer(buffer_id, cx);
18386 }
18387 })
18388 .ok();
18389 });
18390 }
18391 }
18392
18393 pub fn fold_function_bodies(
18394 &mut self,
18395 _: &actions::FoldFunctionBodies,
18396 window: &mut Window,
18397 cx: &mut Context<Self>,
18398 ) {
18399 let snapshot = self.buffer.read(cx).snapshot(cx);
18400
18401 let ranges = snapshot
18402 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18403 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18404 .collect::<Vec<_>>();
18405
18406 let creases = ranges
18407 .into_iter()
18408 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18409 .collect();
18410
18411 self.fold_creases(creases, true, window, cx);
18412 }
18413
18414 pub fn fold_recursive(
18415 &mut self,
18416 _: &actions::FoldRecursive,
18417 window: &mut Window,
18418 cx: &mut Context<Self>,
18419 ) {
18420 let mut to_fold = Vec::new();
18421 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18422 let selections = self.selections.all_adjusted(&display_map);
18423
18424 for selection in selections {
18425 let range = selection.range().sorted();
18426 let buffer_start_row = range.start.row;
18427
18428 if range.start.row != range.end.row {
18429 let mut found = false;
18430 for row in range.start.row..=range.end.row {
18431 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18432 found = true;
18433 to_fold.push(crease);
18434 }
18435 }
18436 if found {
18437 continue;
18438 }
18439 }
18440
18441 for row in (0..=range.start.row).rev() {
18442 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18443 if crease.range().end.row >= buffer_start_row {
18444 to_fold.push(crease);
18445 } else {
18446 break;
18447 }
18448 }
18449 }
18450 }
18451
18452 self.fold_creases(to_fold, true, window, cx);
18453 }
18454
18455 pub fn fold_at(
18456 &mut self,
18457 buffer_row: MultiBufferRow,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18462
18463 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18464 let autoscroll = self
18465 .selections
18466 .all::<Point>(&display_map)
18467 .iter()
18468 .any(|selection| crease.range().overlaps(&selection.range()));
18469
18470 self.fold_creases(vec![crease], autoscroll, window, cx);
18471 }
18472 }
18473
18474 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18475 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18476 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18477 let buffer = display_map.buffer_snapshot();
18478 let selections = self.selections.all::<Point>(&display_map);
18479 let ranges = selections
18480 .iter()
18481 .map(|s| {
18482 let range = s.display_range(&display_map).sorted();
18483 let mut start = range.start.to_point(&display_map);
18484 let mut end = range.end.to_point(&display_map);
18485 start.column = 0;
18486 end.column = buffer.line_len(MultiBufferRow(end.row));
18487 start..end
18488 })
18489 .collect::<Vec<_>>();
18490
18491 self.unfold_ranges(&ranges, true, true, cx);
18492 } else {
18493 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18494 let buffer_ids = self
18495 .selections
18496 .disjoint_anchor_ranges()
18497 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18498 .collect::<HashSet<_>>();
18499 for buffer_id in buffer_ids {
18500 self.unfold_buffer(buffer_id, cx);
18501 }
18502 }
18503 }
18504
18505 pub fn unfold_recursive(
18506 &mut self,
18507 _: &UnfoldRecursive,
18508 _window: &mut Window,
18509 cx: &mut Context<Self>,
18510 ) {
18511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18512 let selections = self.selections.all::<Point>(&display_map);
18513 let ranges = selections
18514 .iter()
18515 .map(|s| {
18516 let mut range = s.display_range(&display_map).sorted();
18517 *range.start.column_mut() = 0;
18518 *range.end.column_mut() = display_map.line_len(range.end.row());
18519 let start = range.start.to_point(&display_map);
18520 let end = range.end.to_point(&display_map);
18521 start..end
18522 })
18523 .collect::<Vec<_>>();
18524
18525 self.unfold_ranges(&ranges, true, true, cx);
18526 }
18527
18528 pub fn unfold_at(
18529 &mut self,
18530 buffer_row: MultiBufferRow,
18531 _window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) {
18534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18535
18536 let intersection_range = Point::new(buffer_row.0, 0)
18537 ..Point::new(
18538 buffer_row.0,
18539 display_map.buffer_snapshot().line_len(buffer_row),
18540 );
18541
18542 let autoscroll = self
18543 .selections
18544 .all::<Point>(&display_map)
18545 .iter()
18546 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18547
18548 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18549 }
18550
18551 pub fn unfold_all(
18552 &mut self,
18553 _: &actions::UnfoldAll,
18554 _window: &mut Window,
18555 cx: &mut Context<Self>,
18556 ) {
18557 if self.buffer.read(cx).is_singleton() {
18558 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18559 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18560 } else {
18561 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18562 editor
18563 .update(cx, |editor, cx| {
18564 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18565 editor.unfold_buffer(buffer_id, cx);
18566 }
18567 })
18568 .ok();
18569 });
18570 }
18571 }
18572
18573 pub fn fold_selected_ranges(
18574 &mut self,
18575 _: &FoldSelectedRanges,
18576 window: &mut Window,
18577 cx: &mut Context<Self>,
18578 ) {
18579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18580 let selections = self.selections.all_adjusted(&display_map);
18581 let ranges = selections
18582 .into_iter()
18583 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18584 .collect::<Vec<_>>();
18585 self.fold_creases(ranges, true, window, cx);
18586 }
18587
18588 pub fn fold_ranges<T: ToOffset + Clone>(
18589 &mut self,
18590 ranges: Vec<Range<T>>,
18591 auto_scroll: bool,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18596 let ranges = ranges
18597 .into_iter()
18598 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18599 .collect::<Vec<_>>();
18600 self.fold_creases(ranges, auto_scroll, window, cx);
18601 }
18602
18603 pub fn fold_creases<T: ToOffset + Clone>(
18604 &mut self,
18605 creases: Vec<Crease<T>>,
18606 auto_scroll: bool,
18607 _window: &mut Window,
18608 cx: &mut Context<Self>,
18609 ) {
18610 if creases.is_empty() {
18611 return;
18612 }
18613
18614 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18615
18616 if auto_scroll {
18617 self.request_autoscroll(Autoscroll::fit(), cx);
18618 }
18619
18620 cx.notify();
18621
18622 self.scrollbar_marker_state.dirty = true;
18623 self.folds_did_change(cx);
18624 }
18625
18626 /// Removes any folds whose ranges intersect any of the given ranges.
18627 pub fn unfold_ranges<T: ToOffset + Clone>(
18628 &mut self,
18629 ranges: &[Range<T>],
18630 inclusive: bool,
18631 auto_scroll: bool,
18632 cx: &mut Context<Self>,
18633 ) {
18634 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18635 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18636 });
18637 self.folds_did_change(cx);
18638 }
18639
18640 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18641 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18642 return;
18643 }
18644 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18645 self.display_map.update(cx, |display_map, cx| {
18646 display_map.fold_buffers([buffer_id], cx)
18647 });
18648 cx.emit(EditorEvent::BufferFoldToggled {
18649 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18650 folded: true,
18651 });
18652 cx.notify();
18653 }
18654
18655 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18656 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18657 return;
18658 }
18659 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18660 self.display_map.update(cx, |display_map, cx| {
18661 display_map.unfold_buffers([buffer_id], cx);
18662 });
18663 cx.emit(EditorEvent::BufferFoldToggled {
18664 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18665 folded: false,
18666 });
18667 cx.notify();
18668 }
18669
18670 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18671 self.display_map.read(cx).is_buffer_folded(buffer)
18672 }
18673
18674 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18675 self.display_map.read(cx).folded_buffers()
18676 }
18677
18678 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18679 self.display_map.update(cx, |display_map, cx| {
18680 display_map.disable_header_for_buffer(buffer_id, cx);
18681 });
18682 cx.notify();
18683 }
18684
18685 /// Removes any folds with the given ranges.
18686 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18687 &mut self,
18688 ranges: &[Range<T>],
18689 type_id: TypeId,
18690 auto_scroll: bool,
18691 cx: &mut Context<Self>,
18692 ) {
18693 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18694 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18695 });
18696 self.folds_did_change(cx);
18697 }
18698
18699 fn remove_folds_with<T: ToOffset + Clone>(
18700 &mut self,
18701 ranges: &[Range<T>],
18702 auto_scroll: bool,
18703 cx: &mut Context<Self>,
18704 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18705 ) {
18706 if ranges.is_empty() {
18707 return;
18708 }
18709
18710 let mut buffers_affected = HashSet::default();
18711 let multi_buffer = self.buffer().read(cx);
18712 for range in ranges {
18713 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18714 buffers_affected.insert(buffer.read(cx).remote_id());
18715 };
18716 }
18717
18718 self.display_map.update(cx, update);
18719
18720 if auto_scroll {
18721 self.request_autoscroll(Autoscroll::fit(), cx);
18722 }
18723
18724 cx.notify();
18725 self.scrollbar_marker_state.dirty = true;
18726 self.active_indent_guides_state.dirty = true;
18727 }
18728
18729 pub fn update_renderer_widths(
18730 &mut self,
18731 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18732 cx: &mut Context<Self>,
18733 ) -> bool {
18734 self.display_map
18735 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18736 }
18737
18738 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18739 self.display_map.read(cx).fold_placeholder.clone()
18740 }
18741
18742 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18743 self.buffer.update(cx, |buffer, cx| {
18744 buffer.set_all_diff_hunks_expanded(cx);
18745 });
18746 }
18747
18748 pub fn expand_all_diff_hunks(
18749 &mut self,
18750 _: &ExpandAllDiffHunks,
18751 _window: &mut Window,
18752 cx: &mut Context<Self>,
18753 ) {
18754 self.buffer.update(cx, |buffer, cx| {
18755 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18756 });
18757 }
18758
18759 pub fn collapse_all_diff_hunks(
18760 &mut self,
18761 _: &CollapseAllDiffHunks,
18762 _window: &mut Window,
18763 cx: &mut Context<Self>,
18764 ) {
18765 self.buffer.update(cx, |buffer, cx| {
18766 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18767 });
18768 }
18769
18770 pub fn toggle_selected_diff_hunks(
18771 &mut self,
18772 _: &ToggleSelectedDiffHunks,
18773 _window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 let ranges: Vec<_> = self
18777 .selections
18778 .disjoint_anchors()
18779 .iter()
18780 .map(|s| s.range())
18781 .collect();
18782 self.toggle_diff_hunks_in_ranges(ranges, cx);
18783 }
18784
18785 pub fn diff_hunks_in_ranges<'a>(
18786 &'a self,
18787 ranges: &'a [Range<Anchor>],
18788 buffer: &'a MultiBufferSnapshot,
18789 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18790 ranges.iter().flat_map(move |range| {
18791 let end_excerpt_id = range.end.excerpt_id;
18792 let range = range.to_point(buffer);
18793 let mut peek_end = range.end;
18794 if range.end.row < buffer.max_row().0 {
18795 peek_end = Point::new(range.end.row + 1, 0);
18796 }
18797 buffer
18798 .diff_hunks_in_range(range.start..peek_end)
18799 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18800 })
18801 }
18802
18803 pub fn has_stageable_diff_hunks_in_ranges(
18804 &self,
18805 ranges: &[Range<Anchor>],
18806 snapshot: &MultiBufferSnapshot,
18807 ) -> bool {
18808 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18809 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18810 }
18811
18812 pub fn toggle_staged_selected_diff_hunks(
18813 &mut self,
18814 _: &::git::ToggleStaged,
18815 _: &mut Window,
18816 cx: &mut Context<Self>,
18817 ) {
18818 let snapshot = self.buffer.read(cx).snapshot(cx);
18819 let ranges: Vec<_> = self
18820 .selections
18821 .disjoint_anchors()
18822 .iter()
18823 .map(|s| s.range())
18824 .collect();
18825 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18826 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18827 }
18828
18829 pub fn set_render_diff_hunk_controls(
18830 &mut self,
18831 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18832 cx: &mut Context<Self>,
18833 ) {
18834 self.render_diff_hunk_controls = render_diff_hunk_controls;
18835 cx.notify();
18836 }
18837
18838 pub fn stage_and_next(
18839 &mut self,
18840 _: &::git::StageAndNext,
18841 window: &mut Window,
18842 cx: &mut Context<Self>,
18843 ) {
18844 self.do_stage_or_unstage_and_next(true, window, cx);
18845 }
18846
18847 pub fn unstage_and_next(
18848 &mut self,
18849 _: &::git::UnstageAndNext,
18850 window: &mut Window,
18851 cx: &mut Context<Self>,
18852 ) {
18853 self.do_stage_or_unstage_and_next(false, window, cx);
18854 }
18855
18856 pub fn stage_or_unstage_diff_hunks(
18857 &mut self,
18858 stage: bool,
18859 ranges: Vec<Range<Anchor>>,
18860 cx: &mut Context<Self>,
18861 ) {
18862 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18863 cx.spawn(async move |this, cx| {
18864 task.await?;
18865 this.update(cx, |this, cx| {
18866 let snapshot = this.buffer.read(cx).snapshot(cx);
18867 let chunk_by = this
18868 .diff_hunks_in_ranges(&ranges, &snapshot)
18869 .chunk_by(|hunk| hunk.buffer_id);
18870 for (buffer_id, hunks) in &chunk_by {
18871 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18872 }
18873 })
18874 })
18875 .detach_and_log_err(cx);
18876 }
18877
18878 fn save_buffers_for_ranges_if_needed(
18879 &mut self,
18880 ranges: &[Range<Anchor>],
18881 cx: &mut Context<Editor>,
18882 ) -> Task<Result<()>> {
18883 let multibuffer = self.buffer.read(cx);
18884 let snapshot = multibuffer.read(cx);
18885 let buffer_ids: HashSet<_> = ranges
18886 .iter()
18887 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18888 .collect();
18889 drop(snapshot);
18890
18891 let mut buffers = HashSet::default();
18892 for buffer_id in buffer_ids {
18893 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18894 let buffer = buffer_entity.read(cx);
18895 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18896 {
18897 buffers.insert(buffer_entity);
18898 }
18899 }
18900 }
18901
18902 if let Some(project) = &self.project {
18903 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18904 } else {
18905 Task::ready(Ok(()))
18906 }
18907 }
18908
18909 fn do_stage_or_unstage_and_next(
18910 &mut self,
18911 stage: bool,
18912 window: &mut Window,
18913 cx: &mut Context<Self>,
18914 ) {
18915 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18916
18917 if ranges.iter().any(|range| range.start != range.end) {
18918 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18919 return;
18920 }
18921
18922 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18923 let snapshot = self.snapshot(window, cx);
18924 let position = self
18925 .selections
18926 .newest::<Point>(&snapshot.display_snapshot)
18927 .head();
18928 let mut row = snapshot
18929 .buffer_snapshot()
18930 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18931 .find(|hunk| hunk.row_range.start.0 > position.row)
18932 .map(|hunk| hunk.row_range.start);
18933
18934 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18935 // Outside of the project diff editor, wrap around to the beginning.
18936 if !all_diff_hunks_expanded {
18937 row = row.or_else(|| {
18938 snapshot
18939 .buffer_snapshot()
18940 .diff_hunks_in_range(Point::zero()..position)
18941 .find(|hunk| hunk.row_range.end.0 < position.row)
18942 .map(|hunk| hunk.row_range.start)
18943 });
18944 }
18945
18946 if let Some(row) = row {
18947 let destination = Point::new(row.0, 0);
18948 let autoscroll = Autoscroll::center();
18949
18950 self.unfold_ranges(&[destination..destination], false, false, cx);
18951 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18952 s.select_ranges([destination..destination]);
18953 });
18954 }
18955 }
18956
18957 fn do_stage_or_unstage(
18958 &self,
18959 stage: bool,
18960 buffer_id: BufferId,
18961 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18962 cx: &mut App,
18963 ) -> Option<()> {
18964 let project = self.project()?;
18965 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18966 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18967 let buffer_snapshot = buffer.read(cx).snapshot();
18968 let file_exists = buffer_snapshot
18969 .file()
18970 .is_some_and(|file| file.disk_state().exists());
18971 diff.update(cx, |diff, cx| {
18972 diff.stage_or_unstage_hunks(
18973 stage,
18974 &hunks
18975 .map(|hunk| buffer_diff::DiffHunk {
18976 buffer_range: hunk.buffer_range,
18977 diff_base_byte_range: hunk.diff_base_byte_range,
18978 secondary_status: hunk.secondary_status,
18979 range: Point::zero()..Point::zero(), // unused
18980 })
18981 .collect::<Vec<_>>(),
18982 &buffer_snapshot,
18983 file_exists,
18984 cx,
18985 )
18986 });
18987 None
18988 }
18989
18990 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18991 let ranges: Vec<_> = self
18992 .selections
18993 .disjoint_anchors()
18994 .iter()
18995 .map(|s| s.range())
18996 .collect();
18997 self.buffer
18998 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18999 }
19000
19001 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19002 self.buffer.update(cx, |buffer, cx| {
19003 let ranges = vec![Anchor::min()..Anchor::max()];
19004 if !buffer.all_diff_hunks_expanded()
19005 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19006 {
19007 buffer.collapse_diff_hunks(ranges, cx);
19008 true
19009 } else {
19010 false
19011 }
19012 })
19013 }
19014
19015 fn toggle_diff_hunks_in_ranges(
19016 &mut self,
19017 ranges: Vec<Range<Anchor>>,
19018 cx: &mut Context<Editor>,
19019 ) {
19020 self.buffer.update(cx, |buffer, cx| {
19021 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19022 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19023 })
19024 }
19025
19026 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19027 self.buffer.update(cx, |buffer, cx| {
19028 let snapshot = buffer.snapshot(cx);
19029 let excerpt_id = range.end.excerpt_id;
19030 let point_range = range.to_point(&snapshot);
19031 let expand = !buffer.single_hunk_is_expanded(range, cx);
19032 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19033 })
19034 }
19035
19036 pub(crate) fn apply_all_diff_hunks(
19037 &mut self,
19038 _: &ApplyAllDiffHunks,
19039 window: &mut Window,
19040 cx: &mut Context<Self>,
19041 ) {
19042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19043
19044 let buffers = self.buffer.read(cx).all_buffers();
19045 for branch_buffer in buffers {
19046 branch_buffer.update(cx, |branch_buffer, cx| {
19047 branch_buffer.merge_into_base(Vec::new(), cx);
19048 });
19049 }
19050
19051 if let Some(project) = self.project.clone() {
19052 self.save(
19053 SaveOptions {
19054 format: true,
19055 autosave: false,
19056 },
19057 project,
19058 window,
19059 cx,
19060 )
19061 .detach_and_log_err(cx);
19062 }
19063 }
19064
19065 pub(crate) fn apply_selected_diff_hunks(
19066 &mut self,
19067 _: &ApplyDiffHunk,
19068 window: &mut Window,
19069 cx: &mut Context<Self>,
19070 ) {
19071 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19072 let snapshot = self.snapshot(window, cx);
19073 let hunks = snapshot.hunks_for_ranges(
19074 self.selections
19075 .all(&snapshot.display_snapshot)
19076 .into_iter()
19077 .map(|selection| selection.range()),
19078 );
19079 let mut ranges_by_buffer = HashMap::default();
19080 self.transact(window, cx, |editor, _window, cx| {
19081 for hunk in hunks {
19082 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19083 ranges_by_buffer
19084 .entry(buffer.clone())
19085 .or_insert_with(Vec::new)
19086 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19087 }
19088 }
19089
19090 for (buffer, ranges) in ranges_by_buffer {
19091 buffer.update(cx, |buffer, cx| {
19092 buffer.merge_into_base(ranges, cx);
19093 });
19094 }
19095 });
19096
19097 if let Some(project) = self.project.clone() {
19098 self.save(
19099 SaveOptions {
19100 format: true,
19101 autosave: false,
19102 },
19103 project,
19104 window,
19105 cx,
19106 )
19107 .detach_and_log_err(cx);
19108 }
19109 }
19110
19111 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19112 if hovered != self.gutter_hovered {
19113 self.gutter_hovered = hovered;
19114 cx.notify();
19115 }
19116 }
19117
19118 pub fn insert_blocks(
19119 &mut self,
19120 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19121 autoscroll: Option<Autoscroll>,
19122 cx: &mut Context<Self>,
19123 ) -> Vec<CustomBlockId> {
19124 let blocks = self
19125 .display_map
19126 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19127 if let Some(autoscroll) = autoscroll {
19128 self.request_autoscroll(autoscroll, cx);
19129 }
19130 cx.notify();
19131 blocks
19132 }
19133
19134 pub fn resize_blocks(
19135 &mut self,
19136 heights: HashMap<CustomBlockId, u32>,
19137 autoscroll: Option<Autoscroll>,
19138 cx: &mut Context<Self>,
19139 ) {
19140 self.display_map
19141 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19142 if let Some(autoscroll) = autoscroll {
19143 self.request_autoscroll(autoscroll, cx);
19144 }
19145 cx.notify();
19146 }
19147
19148 pub fn replace_blocks(
19149 &mut self,
19150 renderers: HashMap<CustomBlockId, RenderBlock>,
19151 autoscroll: Option<Autoscroll>,
19152 cx: &mut Context<Self>,
19153 ) {
19154 self.display_map
19155 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19156 if let Some(autoscroll) = autoscroll {
19157 self.request_autoscroll(autoscroll, cx);
19158 }
19159 cx.notify();
19160 }
19161
19162 pub fn remove_blocks(
19163 &mut self,
19164 block_ids: HashSet<CustomBlockId>,
19165 autoscroll: Option<Autoscroll>,
19166 cx: &mut Context<Self>,
19167 ) {
19168 self.display_map.update(cx, |display_map, cx| {
19169 display_map.remove_blocks(block_ids, cx)
19170 });
19171 if let Some(autoscroll) = autoscroll {
19172 self.request_autoscroll(autoscroll, cx);
19173 }
19174 cx.notify();
19175 }
19176
19177 pub fn row_for_block(
19178 &self,
19179 block_id: CustomBlockId,
19180 cx: &mut Context<Self>,
19181 ) -> Option<DisplayRow> {
19182 self.display_map
19183 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19184 }
19185
19186 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19187 self.focused_block = Some(focused_block);
19188 }
19189
19190 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19191 self.focused_block.take()
19192 }
19193
19194 pub fn insert_creases(
19195 &mut self,
19196 creases: impl IntoIterator<Item = Crease<Anchor>>,
19197 cx: &mut Context<Self>,
19198 ) -> Vec<CreaseId> {
19199 self.display_map
19200 .update(cx, |map, cx| map.insert_creases(creases, cx))
19201 }
19202
19203 pub fn remove_creases(
19204 &mut self,
19205 ids: impl IntoIterator<Item = CreaseId>,
19206 cx: &mut Context<Self>,
19207 ) -> Vec<(CreaseId, Range<Anchor>)> {
19208 self.display_map
19209 .update(cx, |map, cx| map.remove_creases(ids, cx))
19210 }
19211
19212 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19213 self.display_map
19214 .update(cx, |map, cx| map.snapshot(cx))
19215 .longest_row()
19216 }
19217
19218 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19219 self.display_map
19220 .update(cx, |map, cx| map.snapshot(cx))
19221 .max_point()
19222 }
19223
19224 pub fn text(&self, cx: &App) -> String {
19225 self.buffer.read(cx).read(cx).text()
19226 }
19227
19228 pub fn is_empty(&self, cx: &App) -> bool {
19229 self.buffer.read(cx).read(cx).is_empty()
19230 }
19231
19232 pub fn text_option(&self, cx: &App) -> Option<String> {
19233 let text = self.text(cx);
19234 let text = text.trim();
19235
19236 if text.is_empty() {
19237 return None;
19238 }
19239
19240 Some(text.to_string())
19241 }
19242
19243 pub fn set_text(
19244 &mut self,
19245 text: impl Into<Arc<str>>,
19246 window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) {
19249 self.transact(window, cx, |this, _, cx| {
19250 this.buffer
19251 .read(cx)
19252 .as_singleton()
19253 .expect("you can only call set_text on editors for singleton buffers")
19254 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19255 });
19256 }
19257
19258 pub fn display_text(&self, cx: &mut App) -> String {
19259 self.display_map
19260 .update(cx, |map, cx| map.snapshot(cx))
19261 .text()
19262 }
19263
19264 fn create_minimap(
19265 &self,
19266 minimap_settings: MinimapSettings,
19267 window: &mut Window,
19268 cx: &mut Context<Self>,
19269 ) -> Option<Entity<Self>> {
19270 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19271 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19272 }
19273
19274 fn initialize_new_minimap(
19275 &self,
19276 minimap_settings: MinimapSettings,
19277 window: &mut Window,
19278 cx: &mut Context<Self>,
19279 ) -> Entity<Self> {
19280 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19281
19282 let mut minimap = Editor::new_internal(
19283 EditorMode::Minimap {
19284 parent: cx.weak_entity(),
19285 },
19286 self.buffer.clone(),
19287 None,
19288 Some(self.display_map.clone()),
19289 window,
19290 cx,
19291 );
19292 minimap.scroll_manager.clone_state(&self.scroll_manager);
19293 minimap.set_text_style_refinement(TextStyleRefinement {
19294 font_size: Some(MINIMAP_FONT_SIZE),
19295 font_weight: Some(MINIMAP_FONT_WEIGHT),
19296 ..Default::default()
19297 });
19298 minimap.update_minimap_configuration(minimap_settings, cx);
19299 cx.new(|_| minimap)
19300 }
19301
19302 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19303 let current_line_highlight = minimap_settings
19304 .current_line_highlight
19305 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19306 self.set_current_line_highlight(Some(current_line_highlight));
19307 }
19308
19309 pub fn minimap(&self) -> Option<&Entity<Self>> {
19310 self.minimap
19311 .as_ref()
19312 .filter(|_| self.minimap_visibility.visible())
19313 }
19314
19315 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19316 let mut wrap_guides = smallvec![];
19317
19318 if self.show_wrap_guides == Some(false) {
19319 return wrap_guides;
19320 }
19321
19322 let settings = self.buffer.read(cx).language_settings(cx);
19323 if settings.show_wrap_guides {
19324 match self.soft_wrap_mode(cx) {
19325 SoftWrap::Column(soft_wrap) => {
19326 wrap_guides.push((soft_wrap as usize, true));
19327 }
19328 SoftWrap::Bounded(soft_wrap) => {
19329 wrap_guides.push((soft_wrap as usize, true));
19330 }
19331 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19332 }
19333 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19334 }
19335
19336 wrap_guides
19337 }
19338
19339 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19340 let settings = self.buffer.read(cx).language_settings(cx);
19341 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19342 match mode {
19343 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19344 SoftWrap::None
19345 }
19346 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19347 language_settings::SoftWrap::PreferredLineLength => {
19348 SoftWrap::Column(settings.preferred_line_length)
19349 }
19350 language_settings::SoftWrap::Bounded => {
19351 SoftWrap::Bounded(settings.preferred_line_length)
19352 }
19353 }
19354 }
19355
19356 pub fn set_soft_wrap_mode(
19357 &mut self,
19358 mode: language_settings::SoftWrap,
19359
19360 cx: &mut Context<Self>,
19361 ) {
19362 self.soft_wrap_mode_override = Some(mode);
19363 cx.notify();
19364 }
19365
19366 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19367 self.hard_wrap = hard_wrap;
19368 cx.notify();
19369 }
19370
19371 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19372 self.text_style_refinement = Some(style);
19373 }
19374
19375 /// called by the Element so we know what style we were most recently rendered with.
19376 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19377 // We intentionally do not inform the display map about the minimap style
19378 // so that wrapping is not recalculated and stays consistent for the editor
19379 // and its linked minimap.
19380 if !self.mode.is_minimap() {
19381 let font = style.text.font();
19382 let font_size = style.text.font_size.to_pixels(window.rem_size());
19383 let display_map = self
19384 .placeholder_display_map
19385 .as_ref()
19386 .filter(|_| self.is_empty(cx))
19387 .unwrap_or(&self.display_map);
19388
19389 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19390 }
19391 self.style = Some(style);
19392 }
19393
19394 pub fn style(&self) -> Option<&EditorStyle> {
19395 self.style.as_ref()
19396 }
19397
19398 // Called by the element. This method is not designed to be called outside of the editor
19399 // element's layout code because it does not notify when rewrapping is computed synchronously.
19400 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19401 if self.is_empty(cx) {
19402 self.placeholder_display_map
19403 .as_ref()
19404 .map_or(false, |display_map| {
19405 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19406 })
19407 } else {
19408 self.display_map
19409 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19410 }
19411 }
19412
19413 pub fn set_soft_wrap(&mut self) {
19414 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19415 }
19416
19417 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19418 if self.soft_wrap_mode_override.is_some() {
19419 self.soft_wrap_mode_override.take();
19420 } else {
19421 let soft_wrap = match self.soft_wrap_mode(cx) {
19422 SoftWrap::GitDiff => return,
19423 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19424 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19425 language_settings::SoftWrap::None
19426 }
19427 };
19428 self.soft_wrap_mode_override = Some(soft_wrap);
19429 }
19430 cx.notify();
19431 }
19432
19433 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19434 let Some(workspace) = self.workspace() else {
19435 return;
19436 };
19437 let fs = workspace.read(cx).app_state().fs.clone();
19438 let current_show = TabBarSettings::get_global(cx).show;
19439 update_settings_file(fs, cx, move |setting, _| {
19440 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19441 });
19442 }
19443
19444 pub fn toggle_indent_guides(
19445 &mut self,
19446 _: &ToggleIndentGuides,
19447 _: &mut Window,
19448 cx: &mut Context<Self>,
19449 ) {
19450 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19451 self.buffer
19452 .read(cx)
19453 .language_settings(cx)
19454 .indent_guides
19455 .enabled
19456 });
19457 self.show_indent_guides = Some(!currently_enabled);
19458 cx.notify();
19459 }
19460
19461 fn should_show_indent_guides(&self) -> Option<bool> {
19462 self.show_indent_guides
19463 }
19464
19465 pub fn toggle_line_numbers(
19466 &mut self,
19467 _: &ToggleLineNumbers,
19468 _: &mut Window,
19469 cx: &mut Context<Self>,
19470 ) {
19471 let mut editor_settings = EditorSettings::get_global(cx).clone();
19472 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19473 EditorSettings::override_global(editor_settings, cx);
19474 }
19475
19476 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19477 if let Some(show_line_numbers) = self.show_line_numbers {
19478 return show_line_numbers;
19479 }
19480 EditorSettings::get_global(cx).gutter.line_numbers
19481 }
19482
19483 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19484 self.use_relative_line_numbers
19485 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19486 }
19487
19488 pub fn toggle_relative_line_numbers(
19489 &mut self,
19490 _: &ToggleRelativeLineNumbers,
19491 _: &mut Window,
19492 cx: &mut Context<Self>,
19493 ) {
19494 let is_relative = self.should_use_relative_line_numbers(cx);
19495 self.set_relative_line_number(Some(!is_relative), cx)
19496 }
19497
19498 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19499 self.use_relative_line_numbers = is_relative;
19500 cx.notify();
19501 }
19502
19503 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19504 self.show_gutter = show_gutter;
19505 cx.notify();
19506 }
19507
19508 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19509 self.show_scrollbars = ScrollbarAxes {
19510 horizontal: show,
19511 vertical: show,
19512 };
19513 cx.notify();
19514 }
19515
19516 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19517 self.show_scrollbars.vertical = show;
19518 cx.notify();
19519 }
19520
19521 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19522 self.show_scrollbars.horizontal = show;
19523 cx.notify();
19524 }
19525
19526 pub fn set_minimap_visibility(
19527 &mut self,
19528 minimap_visibility: MinimapVisibility,
19529 window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 if self.minimap_visibility != minimap_visibility {
19533 if minimap_visibility.visible() && self.minimap.is_none() {
19534 let minimap_settings = EditorSettings::get_global(cx).minimap;
19535 self.minimap =
19536 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19537 }
19538 self.minimap_visibility = minimap_visibility;
19539 cx.notify();
19540 }
19541 }
19542
19543 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19544 self.set_show_scrollbars(false, cx);
19545 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19546 }
19547
19548 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19549 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19550 }
19551
19552 /// Normally the text in full mode and auto height editors is padded on the
19553 /// left side by roughly half a character width for improved hit testing.
19554 ///
19555 /// Use this method to disable this for cases where this is not wanted (e.g.
19556 /// if you want to align the editor text with some other text above or below)
19557 /// or if you want to add this padding to single-line editors.
19558 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19559 self.offset_content = offset_content;
19560 cx.notify();
19561 }
19562
19563 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19564 self.show_line_numbers = Some(show_line_numbers);
19565 cx.notify();
19566 }
19567
19568 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19569 self.disable_expand_excerpt_buttons = true;
19570 cx.notify();
19571 }
19572
19573 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19574 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19575 cx.notify();
19576 }
19577
19578 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19579 self.show_code_actions = Some(show_code_actions);
19580 cx.notify();
19581 }
19582
19583 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19584 self.show_runnables = Some(show_runnables);
19585 cx.notify();
19586 }
19587
19588 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19589 self.show_breakpoints = Some(show_breakpoints);
19590 cx.notify();
19591 }
19592
19593 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19594 if self.display_map.read(cx).masked != masked {
19595 self.display_map.update(cx, |map, _| map.masked = masked);
19596 }
19597 cx.notify()
19598 }
19599
19600 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19601 self.show_wrap_guides = Some(show_wrap_guides);
19602 cx.notify();
19603 }
19604
19605 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19606 self.show_indent_guides = Some(show_indent_guides);
19607 cx.notify();
19608 }
19609
19610 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19611 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19612 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19613 && let Some(dir) = file.abs_path(cx).parent()
19614 {
19615 return Some(dir.to_owned());
19616 }
19617 }
19618
19619 None
19620 }
19621
19622 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19623 self.active_excerpt(cx)?
19624 .1
19625 .read(cx)
19626 .file()
19627 .and_then(|f| f.as_local())
19628 }
19629
19630 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19631 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19632 let buffer = buffer.read(cx);
19633 if let Some(project_path) = buffer.project_path(cx) {
19634 let project = self.project()?.read(cx);
19635 project.absolute_path(&project_path, cx)
19636 } else {
19637 buffer
19638 .file()
19639 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19640 }
19641 })
19642 }
19643
19644 pub fn reveal_in_finder(
19645 &mut self,
19646 _: &RevealInFileManager,
19647 _window: &mut Window,
19648 cx: &mut Context<Self>,
19649 ) {
19650 if let Some(target) = self.target_file(cx) {
19651 cx.reveal_path(&target.abs_path(cx));
19652 }
19653 }
19654
19655 pub fn copy_path(
19656 &mut self,
19657 _: &zed_actions::workspace::CopyPath,
19658 _window: &mut Window,
19659 cx: &mut Context<Self>,
19660 ) {
19661 if let Some(path) = self.target_file_abs_path(cx)
19662 && let Some(path) = path.to_str()
19663 {
19664 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19665 } else {
19666 cx.propagate();
19667 }
19668 }
19669
19670 pub fn copy_relative_path(
19671 &mut self,
19672 _: &zed_actions::workspace::CopyRelativePath,
19673 _window: &mut Window,
19674 cx: &mut Context<Self>,
19675 ) {
19676 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19677 let project = self.project()?.read(cx);
19678 let path = buffer.read(cx).file()?.path();
19679 let path = path.display(project.path_style(cx));
19680 Some(path)
19681 }) {
19682 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19683 } else {
19684 cx.propagate();
19685 }
19686 }
19687
19688 /// Returns the project path for the editor's buffer, if any buffer is
19689 /// opened in the editor.
19690 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19691 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19692 buffer.read(cx).project_path(cx)
19693 } else {
19694 None
19695 }
19696 }
19697
19698 // Returns true if the editor handled a go-to-line request
19699 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19700 maybe!({
19701 let breakpoint_store = self.breakpoint_store.as_ref()?;
19702
19703 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19704 else {
19705 self.clear_row_highlights::<ActiveDebugLine>();
19706 return None;
19707 };
19708
19709 let position = active_stack_frame.position;
19710 let buffer_id = position.buffer_id?;
19711 let snapshot = self
19712 .project
19713 .as_ref()?
19714 .read(cx)
19715 .buffer_for_id(buffer_id, cx)?
19716 .read(cx)
19717 .snapshot();
19718
19719 let mut handled = false;
19720 for (id, ExcerptRange { context, .. }) in
19721 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19722 {
19723 if context.start.cmp(&position, &snapshot).is_ge()
19724 || context.end.cmp(&position, &snapshot).is_lt()
19725 {
19726 continue;
19727 }
19728 let snapshot = self.buffer.read(cx).snapshot(cx);
19729 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19730
19731 handled = true;
19732 self.clear_row_highlights::<ActiveDebugLine>();
19733
19734 self.go_to_line::<ActiveDebugLine>(
19735 multibuffer_anchor,
19736 Some(cx.theme().colors().editor_debugger_active_line_background),
19737 window,
19738 cx,
19739 );
19740
19741 cx.notify();
19742 }
19743
19744 handled.then_some(())
19745 })
19746 .is_some()
19747 }
19748
19749 pub fn copy_file_name_without_extension(
19750 &mut self,
19751 _: &CopyFileNameWithoutExtension,
19752 _: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 if let Some(file) = self.target_file(cx)
19756 && let Some(file_stem) = file.path().file_stem()
19757 {
19758 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19759 }
19760 }
19761
19762 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19763 if let Some(file) = self.target_file(cx)
19764 && let Some(name) = file.path().file_name()
19765 {
19766 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19767 }
19768 }
19769
19770 pub fn toggle_git_blame(
19771 &mut self,
19772 _: &::git::Blame,
19773 window: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19777
19778 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19779 self.start_git_blame(true, window, cx);
19780 }
19781
19782 cx.notify();
19783 }
19784
19785 pub fn toggle_git_blame_inline(
19786 &mut self,
19787 _: &ToggleGitBlameInline,
19788 window: &mut Window,
19789 cx: &mut Context<Self>,
19790 ) {
19791 self.toggle_git_blame_inline_internal(true, window, cx);
19792 cx.notify();
19793 }
19794
19795 pub fn open_git_blame_commit(
19796 &mut self,
19797 _: &OpenGitBlameCommit,
19798 window: &mut Window,
19799 cx: &mut Context<Self>,
19800 ) {
19801 self.open_git_blame_commit_internal(window, cx);
19802 }
19803
19804 fn open_git_blame_commit_internal(
19805 &mut self,
19806 window: &mut Window,
19807 cx: &mut Context<Self>,
19808 ) -> Option<()> {
19809 let blame = self.blame.as_ref()?;
19810 let snapshot = self.snapshot(window, cx);
19811 let cursor = self
19812 .selections
19813 .newest::<Point>(&snapshot.display_snapshot)
19814 .head();
19815 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19816 let (_, blame_entry) = blame
19817 .update(cx, |blame, cx| {
19818 blame
19819 .blame_for_rows(
19820 &[RowInfo {
19821 buffer_id: Some(buffer.remote_id()),
19822 buffer_row: Some(point.row),
19823 ..Default::default()
19824 }],
19825 cx,
19826 )
19827 .next()
19828 })
19829 .flatten()?;
19830 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19831 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19832 let workspace = self.workspace()?.downgrade();
19833 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19834 None
19835 }
19836
19837 pub fn git_blame_inline_enabled(&self) -> bool {
19838 self.git_blame_inline_enabled
19839 }
19840
19841 pub fn toggle_selection_menu(
19842 &mut self,
19843 _: &ToggleSelectionMenu,
19844 _: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 self.show_selection_menu = self
19848 .show_selection_menu
19849 .map(|show_selections_menu| !show_selections_menu)
19850 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19851
19852 cx.notify();
19853 }
19854
19855 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19856 self.show_selection_menu
19857 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19858 }
19859
19860 fn start_git_blame(
19861 &mut self,
19862 user_triggered: bool,
19863 window: &mut Window,
19864 cx: &mut Context<Self>,
19865 ) {
19866 if let Some(project) = self.project() {
19867 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19868 && buffer.read(cx).file().is_none()
19869 {
19870 return;
19871 }
19872
19873 let focused = self.focus_handle(cx).contains_focused(window, cx);
19874
19875 let project = project.clone();
19876 let blame = cx
19877 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19878 self.blame_subscription =
19879 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19880 self.blame = Some(blame);
19881 }
19882 }
19883
19884 fn toggle_git_blame_inline_internal(
19885 &mut self,
19886 user_triggered: bool,
19887 window: &mut Window,
19888 cx: &mut Context<Self>,
19889 ) {
19890 if self.git_blame_inline_enabled {
19891 self.git_blame_inline_enabled = false;
19892 self.show_git_blame_inline = false;
19893 self.show_git_blame_inline_delay_task.take();
19894 } else {
19895 self.git_blame_inline_enabled = true;
19896 self.start_git_blame_inline(user_triggered, window, cx);
19897 }
19898
19899 cx.notify();
19900 }
19901
19902 fn start_git_blame_inline(
19903 &mut self,
19904 user_triggered: bool,
19905 window: &mut Window,
19906 cx: &mut Context<Self>,
19907 ) {
19908 self.start_git_blame(user_triggered, window, cx);
19909
19910 if ProjectSettings::get_global(cx)
19911 .git
19912 .inline_blame_delay()
19913 .is_some()
19914 {
19915 self.start_inline_blame_timer(window, cx);
19916 } else {
19917 self.show_git_blame_inline = true
19918 }
19919 }
19920
19921 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19922 self.blame.as_ref()
19923 }
19924
19925 pub fn show_git_blame_gutter(&self) -> bool {
19926 self.show_git_blame_gutter
19927 }
19928
19929 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19930 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19931 }
19932
19933 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19934 self.show_git_blame_inline
19935 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19936 && !self.newest_selection_head_on_empty_line(cx)
19937 && self.has_blame_entries(cx)
19938 }
19939
19940 fn has_blame_entries(&self, cx: &App) -> bool {
19941 self.blame()
19942 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19943 }
19944
19945 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19946 let cursor_anchor = self.selections.newest_anchor().head();
19947
19948 let snapshot = self.buffer.read(cx).snapshot(cx);
19949 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19950
19951 snapshot.line_len(buffer_row) == 0
19952 }
19953
19954 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19955 let buffer_and_selection = maybe!({
19956 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19957 let selection_range = selection.range();
19958
19959 let multi_buffer = self.buffer().read(cx);
19960 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19961 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19962
19963 let (buffer, range, _) = if selection.reversed {
19964 buffer_ranges.first()
19965 } else {
19966 buffer_ranges.last()
19967 }?;
19968
19969 let selection = text::ToPoint::to_point(&range.start, buffer).row
19970 ..text::ToPoint::to_point(&range.end, buffer).row;
19971 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19972 });
19973
19974 let Some((buffer, selection)) = buffer_and_selection else {
19975 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19976 };
19977
19978 let Some(project) = self.project() else {
19979 return Task::ready(Err(anyhow!("editor does not have project")));
19980 };
19981
19982 project.update(cx, |project, cx| {
19983 project.get_permalink_to_line(&buffer, selection, cx)
19984 })
19985 }
19986
19987 pub fn copy_permalink_to_line(
19988 &mut self,
19989 _: &CopyPermalinkToLine,
19990 window: &mut Window,
19991 cx: &mut Context<Self>,
19992 ) {
19993 let permalink_task = self.get_permalink_to_line(cx);
19994 let workspace = self.workspace();
19995
19996 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19997 Ok(permalink) => {
19998 cx.update(|_, cx| {
19999 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20000 })
20001 .ok();
20002 }
20003 Err(err) => {
20004 let message = format!("Failed to copy permalink: {err}");
20005
20006 anyhow::Result::<()>::Err(err).log_err();
20007
20008 if let Some(workspace) = workspace {
20009 workspace
20010 .update_in(cx, |workspace, _, cx| {
20011 struct CopyPermalinkToLine;
20012
20013 workspace.show_toast(
20014 Toast::new(
20015 NotificationId::unique::<CopyPermalinkToLine>(),
20016 message,
20017 ),
20018 cx,
20019 )
20020 })
20021 .ok();
20022 }
20023 }
20024 })
20025 .detach();
20026 }
20027
20028 pub fn copy_file_location(
20029 &mut self,
20030 _: &CopyFileLocation,
20031 _: &mut Window,
20032 cx: &mut Context<Self>,
20033 ) {
20034 let selection = self
20035 .selections
20036 .newest::<Point>(&self.display_snapshot(cx))
20037 .start
20038 .row
20039 + 1;
20040 if let Some(file) = self.target_file(cx) {
20041 let path = file.path().display(file.path_style(cx));
20042 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20043 }
20044 }
20045
20046 pub fn open_permalink_to_line(
20047 &mut self,
20048 _: &OpenPermalinkToLine,
20049 window: &mut Window,
20050 cx: &mut Context<Self>,
20051 ) {
20052 let permalink_task = self.get_permalink_to_line(cx);
20053 let workspace = self.workspace();
20054
20055 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20056 Ok(permalink) => {
20057 cx.update(|_, cx| {
20058 cx.open_url(permalink.as_ref());
20059 })
20060 .ok();
20061 }
20062 Err(err) => {
20063 let message = format!("Failed to open permalink: {err}");
20064
20065 anyhow::Result::<()>::Err(err).log_err();
20066
20067 if let Some(workspace) = workspace {
20068 workspace
20069 .update(cx, |workspace, cx| {
20070 struct OpenPermalinkToLine;
20071
20072 workspace.show_toast(
20073 Toast::new(
20074 NotificationId::unique::<OpenPermalinkToLine>(),
20075 message,
20076 ),
20077 cx,
20078 )
20079 })
20080 .ok();
20081 }
20082 }
20083 })
20084 .detach();
20085 }
20086
20087 pub fn insert_uuid_v4(
20088 &mut self,
20089 _: &InsertUuidV4,
20090 window: &mut Window,
20091 cx: &mut Context<Self>,
20092 ) {
20093 self.insert_uuid(UuidVersion::V4, window, cx);
20094 }
20095
20096 pub fn insert_uuid_v7(
20097 &mut self,
20098 _: &InsertUuidV7,
20099 window: &mut Window,
20100 cx: &mut Context<Self>,
20101 ) {
20102 self.insert_uuid(UuidVersion::V7, window, cx);
20103 }
20104
20105 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20106 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20107 self.transact(window, cx, |this, window, cx| {
20108 let edits = this
20109 .selections
20110 .all::<Point>(&this.display_snapshot(cx))
20111 .into_iter()
20112 .map(|selection| {
20113 let uuid = match version {
20114 UuidVersion::V4 => uuid::Uuid::new_v4(),
20115 UuidVersion::V7 => uuid::Uuid::now_v7(),
20116 };
20117
20118 (selection.range(), uuid.to_string())
20119 });
20120 this.edit(edits, cx);
20121 this.refresh_edit_prediction(true, false, window, cx);
20122 });
20123 }
20124
20125 pub fn open_selections_in_multibuffer(
20126 &mut self,
20127 _: &OpenSelectionsInMultibuffer,
20128 window: &mut Window,
20129 cx: &mut Context<Self>,
20130 ) {
20131 let multibuffer = self.buffer.read(cx);
20132
20133 let Some(buffer) = multibuffer.as_singleton() else {
20134 return;
20135 };
20136
20137 let Some(workspace) = self.workspace() else {
20138 return;
20139 };
20140
20141 let title = multibuffer.title(cx).to_string();
20142
20143 let locations = self
20144 .selections
20145 .all_anchors(cx)
20146 .iter()
20147 .map(|selection| {
20148 (
20149 buffer.clone(),
20150 (selection.start.text_anchor..selection.end.text_anchor)
20151 .to_point(buffer.read(cx)),
20152 )
20153 })
20154 .into_group_map();
20155
20156 cx.spawn_in(window, async move |_, cx| {
20157 workspace.update_in(cx, |workspace, window, cx| {
20158 Self::open_locations_in_multibuffer(
20159 workspace,
20160 locations,
20161 format!("Selections for '{title}'"),
20162 false,
20163 MultibufferSelectionMode::All,
20164 window,
20165 cx,
20166 );
20167 })
20168 })
20169 .detach();
20170 }
20171
20172 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20173 /// last highlight added will be used.
20174 ///
20175 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20176 pub fn highlight_rows<T: 'static>(
20177 &mut self,
20178 range: Range<Anchor>,
20179 color: Hsla,
20180 options: RowHighlightOptions,
20181 cx: &mut Context<Self>,
20182 ) {
20183 let snapshot = self.buffer().read(cx).snapshot(cx);
20184 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20185 let ix = row_highlights.binary_search_by(|highlight| {
20186 Ordering::Equal
20187 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20188 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20189 });
20190
20191 if let Err(mut ix) = ix {
20192 let index = post_inc(&mut self.highlight_order);
20193
20194 // If this range intersects with the preceding highlight, then merge it with
20195 // the preceding highlight. Otherwise insert a new highlight.
20196 let mut merged = false;
20197 if ix > 0 {
20198 let prev_highlight = &mut row_highlights[ix - 1];
20199 if prev_highlight
20200 .range
20201 .end
20202 .cmp(&range.start, &snapshot)
20203 .is_ge()
20204 {
20205 ix -= 1;
20206 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20207 prev_highlight.range.end = range.end;
20208 }
20209 merged = true;
20210 prev_highlight.index = index;
20211 prev_highlight.color = color;
20212 prev_highlight.options = options;
20213 }
20214 }
20215
20216 if !merged {
20217 row_highlights.insert(
20218 ix,
20219 RowHighlight {
20220 range,
20221 index,
20222 color,
20223 options,
20224 type_id: TypeId::of::<T>(),
20225 },
20226 );
20227 }
20228
20229 // If any of the following highlights intersect with this one, merge them.
20230 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20231 let highlight = &row_highlights[ix];
20232 if next_highlight
20233 .range
20234 .start
20235 .cmp(&highlight.range.end, &snapshot)
20236 .is_le()
20237 {
20238 if next_highlight
20239 .range
20240 .end
20241 .cmp(&highlight.range.end, &snapshot)
20242 .is_gt()
20243 {
20244 row_highlights[ix].range.end = next_highlight.range.end;
20245 }
20246 row_highlights.remove(ix + 1);
20247 } else {
20248 break;
20249 }
20250 }
20251 }
20252 }
20253
20254 /// Remove any highlighted row ranges of the given type that intersect the
20255 /// given ranges.
20256 pub fn remove_highlighted_rows<T: 'static>(
20257 &mut self,
20258 ranges_to_remove: Vec<Range<Anchor>>,
20259 cx: &mut Context<Self>,
20260 ) {
20261 let snapshot = self.buffer().read(cx).snapshot(cx);
20262 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20263 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20264 row_highlights.retain(|highlight| {
20265 while let Some(range_to_remove) = ranges_to_remove.peek() {
20266 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20267 Ordering::Less | Ordering::Equal => {
20268 ranges_to_remove.next();
20269 }
20270 Ordering::Greater => {
20271 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20272 Ordering::Less | Ordering::Equal => {
20273 return false;
20274 }
20275 Ordering::Greater => break,
20276 }
20277 }
20278 }
20279 }
20280
20281 true
20282 })
20283 }
20284
20285 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20286 pub fn clear_row_highlights<T: 'static>(&mut self) {
20287 self.highlighted_rows.remove(&TypeId::of::<T>());
20288 }
20289
20290 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20291 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20292 self.highlighted_rows
20293 .get(&TypeId::of::<T>())
20294 .map_or(&[] as &[_], |vec| vec.as_slice())
20295 .iter()
20296 .map(|highlight| (highlight.range.clone(), highlight.color))
20297 }
20298
20299 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20300 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20301 /// Allows to ignore certain kinds of highlights.
20302 pub fn highlighted_display_rows(
20303 &self,
20304 window: &mut Window,
20305 cx: &mut App,
20306 ) -> BTreeMap<DisplayRow, LineHighlight> {
20307 let snapshot = self.snapshot(window, cx);
20308 let mut used_highlight_orders = HashMap::default();
20309 self.highlighted_rows
20310 .iter()
20311 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20312 .fold(
20313 BTreeMap::<DisplayRow, LineHighlight>::new(),
20314 |mut unique_rows, highlight| {
20315 let start = highlight.range.start.to_display_point(&snapshot);
20316 let end = highlight.range.end.to_display_point(&snapshot);
20317 let start_row = start.row().0;
20318 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20319 && end.column() == 0
20320 {
20321 end.row().0.saturating_sub(1)
20322 } else {
20323 end.row().0
20324 };
20325 for row in start_row..=end_row {
20326 let used_index =
20327 used_highlight_orders.entry(row).or_insert(highlight.index);
20328 if highlight.index >= *used_index {
20329 *used_index = highlight.index;
20330 unique_rows.insert(
20331 DisplayRow(row),
20332 LineHighlight {
20333 include_gutter: highlight.options.include_gutter,
20334 border: None,
20335 background: highlight.color.into(),
20336 type_id: Some(highlight.type_id),
20337 },
20338 );
20339 }
20340 }
20341 unique_rows
20342 },
20343 )
20344 }
20345
20346 pub fn highlighted_display_row_for_autoscroll(
20347 &self,
20348 snapshot: &DisplaySnapshot,
20349 ) -> Option<DisplayRow> {
20350 self.highlighted_rows
20351 .values()
20352 .flat_map(|highlighted_rows| highlighted_rows.iter())
20353 .filter_map(|highlight| {
20354 if highlight.options.autoscroll {
20355 Some(highlight.range.start.to_display_point(snapshot).row())
20356 } else {
20357 None
20358 }
20359 })
20360 .min()
20361 }
20362
20363 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20364 self.highlight_background::<SearchWithinRange>(
20365 ranges,
20366 |colors| colors.colors().editor_document_highlight_read_background,
20367 cx,
20368 )
20369 }
20370
20371 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20372 self.breadcrumb_header = Some(new_header);
20373 }
20374
20375 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20376 self.clear_background_highlights::<SearchWithinRange>(cx);
20377 }
20378
20379 pub fn highlight_background<T: 'static>(
20380 &mut self,
20381 ranges: &[Range<Anchor>],
20382 color_fetcher: fn(&Theme) -> Hsla,
20383 cx: &mut Context<Self>,
20384 ) {
20385 self.background_highlights.insert(
20386 HighlightKey::Type(TypeId::of::<T>()),
20387 (color_fetcher, Arc::from(ranges)),
20388 );
20389 self.scrollbar_marker_state.dirty = true;
20390 cx.notify();
20391 }
20392
20393 pub fn highlight_background_key<T: 'static>(
20394 &mut self,
20395 key: usize,
20396 ranges: &[Range<Anchor>],
20397 color_fetcher: fn(&Theme) -> Hsla,
20398 cx: &mut Context<Self>,
20399 ) {
20400 self.background_highlights.insert(
20401 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20402 (color_fetcher, Arc::from(ranges)),
20403 );
20404 self.scrollbar_marker_state.dirty = true;
20405 cx.notify();
20406 }
20407
20408 pub fn clear_background_highlights<T: 'static>(
20409 &mut self,
20410 cx: &mut Context<Self>,
20411 ) -> Option<BackgroundHighlight> {
20412 let text_highlights = self
20413 .background_highlights
20414 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20415 if !text_highlights.1.is_empty() {
20416 self.scrollbar_marker_state.dirty = true;
20417 cx.notify();
20418 }
20419 Some(text_highlights)
20420 }
20421
20422 pub fn highlight_gutter<T: 'static>(
20423 &mut self,
20424 ranges: impl Into<Vec<Range<Anchor>>>,
20425 color_fetcher: fn(&App) -> Hsla,
20426 cx: &mut Context<Self>,
20427 ) {
20428 self.gutter_highlights
20429 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20430 cx.notify();
20431 }
20432
20433 pub fn clear_gutter_highlights<T: 'static>(
20434 &mut self,
20435 cx: &mut Context<Self>,
20436 ) -> Option<GutterHighlight> {
20437 cx.notify();
20438 self.gutter_highlights.remove(&TypeId::of::<T>())
20439 }
20440
20441 pub fn insert_gutter_highlight<T: 'static>(
20442 &mut self,
20443 range: Range<Anchor>,
20444 color_fetcher: fn(&App) -> Hsla,
20445 cx: &mut Context<Self>,
20446 ) {
20447 let snapshot = self.buffer().read(cx).snapshot(cx);
20448 let mut highlights = self
20449 .gutter_highlights
20450 .remove(&TypeId::of::<T>())
20451 .map(|(_, highlights)| highlights)
20452 .unwrap_or_default();
20453 let ix = highlights.binary_search_by(|highlight| {
20454 Ordering::Equal
20455 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20456 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20457 });
20458 if let Err(ix) = ix {
20459 highlights.insert(ix, range);
20460 }
20461 self.gutter_highlights
20462 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20463 }
20464
20465 pub fn remove_gutter_highlights<T: 'static>(
20466 &mut self,
20467 ranges_to_remove: Vec<Range<Anchor>>,
20468 cx: &mut Context<Self>,
20469 ) {
20470 let snapshot = self.buffer().read(cx).snapshot(cx);
20471 let Some((color_fetcher, mut gutter_highlights)) =
20472 self.gutter_highlights.remove(&TypeId::of::<T>())
20473 else {
20474 return;
20475 };
20476 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20477 gutter_highlights.retain(|highlight| {
20478 while let Some(range_to_remove) = ranges_to_remove.peek() {
20479 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20480 Ordering::Less | Ordering::Equal => {
20481 ranges_to_remove.next();
20482 }
20483 Ordering::Greater => {
20484 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20485 Ordering::Less | Ordering::Equal => {
20486 return false;
20487 }
20488 Ordering::Greater => break,
20489 }
20490 }
20491 }
20492 }
20493
20494 true
20495 });
20496 self.gutter_highlights
20497 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20498 }
20499
20500 #[cfg(feature = "test-support")]
20501 pub fn all_text_highlights(
20502 &self,
20503 window: &mut Window,
20504 cx: &mut Context<Self>,
20505 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20506 let snapshot = self.snapshot(window, cx);
20507 self.display_map.update(cx, |display_map, _| {
20508 display_map
20509 .all_text_highlights()
20510 .map(|highlight| {
20511 let (style, ranges) = highlight.as_ref();
20512 (
20513 *style,
20514 ranges
20515 .iter()
20516 .map(|range| range.clone().to_display_points(&snapshot))
20517 .collect(),
20518 )
20519 })
20520 .collect()
20521 })
20522 }
20523
20524 #[cfg(feature = "test-support")]
20525 pub fn all_text_background_highlights(
20526 &self,
20527 window: &mut Window,
20528 cx: &mut Context<Self>,
20529 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20530 let snapshot = self.snapshot(window, cx);
20531 let buffer = &snapshot.buffer_snapshot();
20532 let start = buffer.anchor_before(0);
20533 let end = buffer.anchor_after(buffer.len());
20534 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20535 }
20536
20537 #[cfg(any(test, feature = "test-support"))]
20538 pub fn sorted_background_highlights_in_range(
20539 &self,
20540 search_range: Range<Anchor>,
20541 display_snapshot: &DisplaySnapshot,
20542 theme: &Theme,
20543 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20544 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20545 res.sort_by(|a, b| {
20546 a.0.start
20547 .cmp(&b.0.start)
20548 .then_with(|| a.0.end.cmp(&b.0.end))
20549 .then_with(|| a.1.cmp(&b.1))
20550 });
20551 res
20552 }
20553
20554 #[cfg(feature = "test-support")]
20555 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20556 let snapshot = self.buffer().read(cx).snapshot(cx);
20557
20558 let highlights = self
20559 .background_highlights
20560 .get(&HighlightKey::Type(TypeId::of::<
20561 items::BufferSearchHighlights,
20562 >()));
20563
20564 if let Some((_color, ranges)) = highlights {
20565 ranges
20566 .iter()
20567 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20568 .collect_vec()
20569 } else {
20570 vec![]
20571 }
20572 }
20573
20574 fn document_highlights_for_position<'a>(
20575 &'a self,
20576 position: Anchor,
20577 buffer: &'a MultiBufferSnapshot,
20578 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20579 let read_highlights = self
20580 .background_highlights
20581 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20582 .map(|h| &h.1);
20583 let write_highlights = self
20584 .background_highlights
20585 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20586 .map(|h| &h.1);
20587 let left_position = position.bias_left(buffer);
20588 let right_position = position.bias_right(buffer);
20589 read_highlights
20590 .into_iter()
20591 .chain(write_highlights)
20592 .flat_map(move |ranges| {
20593 let start_ix = match ranges.binary_search_by(|probe| {
20594 let cmp = probe.end.cmp(&left_position, buffer);
20595 if cmp.is_ge() {
20596 Ordering::Greater
20597 } else {
20598 Ordering::Less
20599 }
20600 }) {
20601 Ok(i) | Err(i) => i,
20602 };
20603
20604 ranges[start_ix..]
20605 .iter()
20606 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20607 })
20608 }
20609
20610 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20611 self.background_highlights
20612 .get(&HighlightKey::Type(TypeId::of::<T>()))
20613 .is_some_and(|(_, highlights)| !highlights.is_empty())
20614 }
20615
20616 /// Returns all background highlights for a given range.
20617 ///
20618 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20619 pub fn background_highlights_in_range(
20620 &self,
20621 search_range: Range<Anchor>,
20622 display_snapshot: &DisplaySnapshot,
20623 theme: &Theme,
20624 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20625 let mut results = Vec::new();
20626 for (color_fetcher, ranges) in self.background_highlights.values() {
20627 let color = color_fetcher(theme);
20628 let start_ix = match ranges.binary_search_by(|probe| {
20629 let cmp = probe
20630 .end
20631 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20632 if cmp.is_gt() {
20633 Ordering::Greater
20634 } else {
20635 Ordering::Less
20636 }
20637 }) {
20638 Ok(i) | Err(i) => i,
20639 };
20640 for range in &ranges[start_ix..] {
20641 if range
20642 .start
20643 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20644 .is_ge()
20645 {
20646 break;
20647 }
20648
20649 let start = range.start.to_display_point(display_snapshot);
20650 let end = range.end.to_display_point(display_snapshot);
20651 results.push((start..end, color))
20652 }
20653 }
20654 results
20655 }
20656
20657 pub fn gutter_highlights_in_range(
20658 &self,
20659 search_range: Range<Anchor>,
20660 display_snapshot: &DisplaySnapshot,
20661 cx: &App,
20662 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20663 let mut results = Vec::new();
20664 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20665 let color = color_fetcher(cx);
20666 let start_ix = match ranges.binary_search_by(|probe| {
20667 let cmp = probe
20668 .end
20669 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20670 if cmp.is_gt() {
20671 Ordering::Greater
20672 } else {
20673 Ordering::Less
20674 }
20675 }) {
20676 Ok(i) | Err(i) => i,
20677 };
20678 for range in &ranges[start_ix..] {
20679 if range
20680 .start
20681 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20682 .is_ge()
20683 {
20684 break;
20685 }
20686
20687 let start = range.start.to_display_point(display_snapshot);
20688 let end = range.end.to_display_point(display_snapshot);
20689 results.push((start..end, color))
20690 }
20691 }
20692 results
20693 }
20694
20695 /// Get the text ranges corresponding to the redaction query
20696 pub fn redacted_ranges(
20697 &self,
20698 search_range: Range<Anchor>,
20699 display_snapshot: &DisplaySnapshot,
20700 cx: &App,
20701 ) -> Vec<Range<DisplayPoint>> {
20702 display_snapshot
20703 .buffer_snapshot()
20704 .redacted_ranges(search_range, |file| {
20705 if let Some(file) = file {
20706 file.is_private()
20707 && EditorSettings::get(
20708 Some(SettingsLocation {
20709 worktree_id: file.worktree_id(cx),
20710 path: file.path().as_ref(),
20711 }),
20712 cx,
20713 )
20714 .redact_private_values
20715 } else {
20716 false
20717 }
20718 })
20719 .map(|range| {
20720 range.start.to_display_point(display_snapshot)
20721 ..range.end.to_display_point(display_snapshot)
20722 })
20723 .collect()
20724 }
20725
20726 pub fn highlight_text_key<T: 'static>(
20727 &mut self,
20728 key: usize,
20729 ranges: Vec<Range<Anchor>>,
20730 style: HighlightStyle,
20731 cx: &mut Context<Self>,
20732 ) {
20733 self.display_map.update(cx, |map, _| {
20734 map.highlight_text(
20735 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20736 ranges,
20737 style,
20738 );
20739 });
20740 cx.notify();
20741 }
20742
20743 pub fn highlight_text<T: 'static>(
20744 &mut self,
20745 ranges: Vec<Range<Anchor>>,
20746 style: HighlightStyle,
20747 cx: &mut Context<Self>,
20748 ) {
20749 self.display_map.update(cx, |map, _| {
20750 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20751 });
20752 cx.notify();
20753 }
20754
20755 pub fn text_highlights<'a, T: 'static>(
20756 &'a self,
20757 cx: &'a App,
20758 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20759 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20760 }
20761
20762 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20763 let cleared = self
20764 .display_map
20765 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20766 if cleared {
20767 cx.notify();
20768 }
20769 }
20770
20771 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20772 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20773 && self.focus_handle.is_focused(window)
20774 }
20775
20776 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20777 self.show_cursor_when_unfocused = is_enabled;
20778 cx.notify();
20779 }
20780
20781 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20782 cx.notify();
20783 }
20784
20785 fn on_debug_session_event(
20786 &mut self,
20787 _session: Entity<Session>,
20788 event: &SessionEvent,
20789 cx: &mut Context<Self>,
20790 ) {
20791 if let SessionEvent::InvalidateInlineValue = event {
20792 self.refresh_inline_values(cx);
20793 }
20794 }
20795
20796 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20797 let Some(project) = self.project.clone() else {
20798 return;
20799 };
20800
20801 if !self.inline_value_cache.enabled {
20802 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20803 self.splice_inlays(&inlays, Vec::new(), cx);
20804 return;
20805 }
20806
20807 let current_execution_position = self
20808 .highlighted_rows
20809 .get(&TypeId::of::<ActiveDebugLine>())
20810 .and_then(|lines| lines.last().map(|line| line.range.end));
20811
20812 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20813 let inline_values = editor
20814 .update(cx, |editor, cx| {
20815 let Some(current_execution_position) = current_execution_position else {
20816 return Some(Task::ready(Ok(Vec::new())));
20817 };
20818
20819 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20820 let snapshot = buffer.snapshot(cx);
20821
20822 let excerpt = snapshot.excerpt_containing(
20823 current_execution_position..current_execution_position,
20824 )?;
20825
20826 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20827 })?;
20828
20829 let range =
20830 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20831
20832 project.inline_values(buffer, range, cx)
20833 })
20834 .ok()
20835 .flatten()?
20836 .await
20837 .context("refreshing debugger inlays")
20838 .log_err()?;
20839
20840 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20841
20842 for (buffer_id, inline_value) in inline_values
20843 .into_iter()
20844 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20845 {
20846 buffer_inline_values
20847 .entry(buffer_id)
20848 .or_default()
20849 .push(inline_value);
20850 }
20851
20852 editor
20853 .update(cx, |editor, cx| {
20854 let snapshot = editor.buffer.read(cx).snapshot(cx);
20855 let mut new_inlays = Vec::default();
20856
20857 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20858 let buffer_id = buffer_snapshot.remote_id();
20859 buffer_inline_values
20860 .get(&buffer_id)
20861 .into_iter()
20862 .flatten()
20863 .for_each(|hint| {
20864 let inlay = Inlay::debugger(
20865 post_inc(&mut editor.next_inlay_id),
20866 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20867 hint.text(),
20868 );
20869 if !inlay.text().chars().contains(&'\n') {
20870 new_inlays.push(inlay);
20871 }
20872 });
20873 }
20874
20875 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20876 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20877
20878 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20879 })
20880 .ok()?;
20881 Some(())
20882 });
20883 }
20884
20885 fn on_buffer_event(
20886 &mut self,
20887 multibuffer: &Entity<MultiBuffer>,
20888 event: &multi_buffer::Event,
20889 window: &mut Window,
20890 cx: &mut Context<Self>,
20891 ) {
20892 match event {
20893 multi_buffer::Event::Edited { edited_buffer } => {
20894 self.scrollbar_marker_state.dirty = true;
20895 self.active_indent_guides_state.dirty = true;
20896 self.refresh_active_diagnostics(cx);
20897 self.refresh_code_actions(window, cx);
20898 self.refresh_selected_text_highlights(true, window, cx);
20899 self.refresh_single_line_folds(window, cx);
20900 self.refresh_matching_bracket_highlights(window, cx);
20901 if self.has_active_edit_prediction() {
20902 self.update_visible_edit_prediction(window, cx);
20903 }
20904
20905 if let Some(buffer) = edited_buffer {
20906 if buffer.read(cx).file().is_none() {
20907 cx.emit(EditorEvent::TitleChanged);
20908 }
20909
20910 if self.project.is_some() {
20911 let buffer_id = buffer.read(cx).remote_id();
20912 self.register_buffer(buffer_id, cx);
20913 self.update_lsp_data(Some(buffer_id), window, cx);
20914 self.refresh_inlay_hints(
20915 InlayHintRefreshReason::BufferEdited(buffer_id),
20916 cx,
20917 );
20918 }
20919 }
20920
20921 cx.emit(EditorEvent::BufferEdited);
20922 cx.emit(SearchEvent::MatchesInvalidated);
20923
20924 let Some(project) = &self.project else { return };
20925 let (telemetry, is_via_ssh) = {
20926 let project = project.read(cx);
20927 let telemetry = project.client().telemetry().clone();
20928 let is_via_ssh = project.is_via_remote_server();
20929 (telemetry, is_via_ssh)
20930 };
20931 telemetry.log_edit_event("editor", is_via_ssh);
20932 }
20933 multi_buffer::Event::ExcerptsAdded {
20934 buffer,
20935 predecessor,
20936 excerpts,
20937 } => {
20938 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20939 let buffer_id = buffer.read(cx).remote_id();
20940 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20941 && let Some(project) = &self.project
20942 {
20943 update_uncommitted_diff_for_buffer(
20944 cx.entity(),
20945 project,
20946 [buffer.clone()],
20947 self.buffer.clone(),
20948 cx,
20949 )
20950 .detach();
20951 }
20952 self.update_lsp_data(Some(buffer_id), window, cx);
20953 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20954 cx.emit(EditorEvent::ExcerptsAdded {
20955 buffer: buffer.clone(),
20956 predecessor: *predecessor,
20957 excerpts: excerpts.clone(),
20958 });
20959 }
20960 multi_buffer::Event::ExcerptsRemoved {
20961 ids,
20962 removed_buffer_ids,
20963 } => {
20964 if let Some(inlay_hints) = &mut self.inlay_hints {
20965 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20966 }
20967 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20968 for buffer_id in removed_buffer_ids {
20969 self.registered_buffers.remove(buffer_id);
20970 }
20971 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20972 cx.emit(EditorEvent::ExcerptsRemoved {
20973 ids: ids.clone(),
20974 removed_buffer_ids: removed_buffer_ids.clone(),
20975 });
20976 }
20977 multi_buffer::Event::ExcerptsEdited {
20978 excerpt_ids,
20979 buffer_ids,
20980 } => {
20981 self.display_map.update(cx, |map, cx| {
20982 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20983 });
20984 cx.emit(EditorEvent::ExcerptsEdited {
20985 ids: excerpt_ids.clone(),
20986 });
20987 }
20988 multi_buffer::Event::ExcerptsExpanded { ids } => {
20989 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20990 self.refresh_document_highlights(cx);
20991 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20992 }
20993 multi_buffer::Event::Reparsed(buffer_id) => {
20994 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20995 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20996
20997 cx.emit(EditorEvent::Reparsed(*buffer_id));
20998 }
20999 multi_buffer::Event::DiffHunksToggled => {
21000 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21001 }
21002 multi_buffer::Event::LanguageChanged(buffer_id) => {
21003 self.registered_buffers.remove(&buffer_id);
21004 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21005 cx.emit(EditorEvent::Reparsed(*buffer_id));
21006 cx.notify();
21007 }
21008 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21009 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21010 multi_buffer::Event::FileHandleChanged
21011 | multi_buffer::Event::Reloaded
21012 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21013 multi_buffer::Event::DiagnosticsUpdated => {
21014 self.update_diagnostics_state(window, cx);
21015 }
21016 _ => {}
21017 };
21018 }
21019
21020 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21021 if !self.diagnostics_enabled() {
21022 return;
21023 }
21024 self.refresh_active_diagnostics(cx);
21025 self.refresh_inline_diagnostics(true, window, cx);
21026 self.scrollbar_marker_state.dirty = true;
21027 cx.notify();
21028 }
21029
21030 pub fn start_temporary_diff_override(&mut self) {
21031 self.load_diff_task.take();
21032 self.temporary_diff_override = true;
21033 }
21034
21035 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21036 self.temporary_diff_override = false;
21037 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21038 self.buffer.update(cx, |buffer, cx| {
21039 buffer.set_all_diff_hunks_collapsed(cx);
21040 });
21041
21042 if let Some(project) = self.project.clone() {
21043 self.load_diff_task = Some(
21044 update_uncommitted_diff_for_buffer(
21045 cx.entity(),
21046 &project,
21047 self.buffer.read(cx).all_buffers(),
21048 self.buffer.clone(),
21049 cx,
21050 )
21051 .shared(),
21052 );
21053 }
21054 }
21055
21056 fn on_display_map_changed(
21057 &mut self,
21058 _: Entity<DisplayMap>,
21059 _: &mut Window,
21060 cx: &mut Context<Self>,
21061 ) {
21062 cx.notify();
21063 }
21064
21065 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21066 if self.diagnostics_enabled() {
21067 let new_severity = EditorSettings::get_global(cx)
21068 .diagnostics_max_severity
21069 .unwrap_or(DiagnosticSeverity::Hint);
21070 self.set_max_diagnostics_severity(new_severity, cx);
21071 }
21072 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21073 self.update_edit_prediction_settings(cx);
21074 self.refresh_edit_prediction(true, false, window, cx);
21075 self.refresh_inline_values(cx);
21076 self.refresh_inlay_hints(
21077 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21078 self.selections.newest_anchor().head(),
21079 &self.buffer.read(cx).snapshot(cx),
21080 cx,
21081 )),
21082 cx,
21083 );
21084
21085 let old_cursor_shape = self.cursor_shape;
21086 let old_show_breadcrumbs = self.show_breadcrumbs;
21087
21088 {
21089 let editor_settings = EditorSettings::get_global(cx);
21090 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21091 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21092 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21093 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21094 }
21095
21096 if old_cursor_shape != self.cursor_shape {
21097 cx.emit(EditorEvent::CursorShapeChanged);
21098 }
21099
21100 if old_show_breadcrumbs != self.show_breadcrumbs {
21101 cx.emit(EditorEvent::BreadcrumbsChanged);
21102 }
21103
21104 let project_settings = ProjectSettings::get_global(cx);
21105 self.serialize_dirty_buffers =
21106 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21107
21108 if self.mode.is_full() {
21109 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21110 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21111 if self.show_inline_diagnostics != show_inline_diagnostics {
21112 self.show_inline_diagnostics = show_inline_diagnostics;
21113 self.refresh_inline_diagnostics(false, window, cx);
21114 }
21115
21116 if self.git_blame_inline_enabled != inline_blame_enabled {
21117 self.toggle_git_blame_inline_internal(false, window, cx);
21118 }
21119
21120 let minimap_settings = EditorSettings::get_global(cx).minimap;
21121 if self.minimap_visibility != MinimapVisibility::Disabled {
21122 if self.minimap_visibility.settings_visibility()
21123 != minimap_settings.minimap_enabled()
21124 {
21125 self.set_minimap_visibility(
21126 MinimapVisibility::for_mode(self.mode(), cx),
21127 window,
21128 cx,
21129 );
21130 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21131 minimap_entity.update(cx, |minimap_editor, cx| {
21132 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21133 })
21134 }
21135 }
21136 }
21137
21138 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21139 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21140 }) {
21141 if !inlay_splice.is_empty() {
21142 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21143 }
21144 self.refresh_colors_for_visible_range(None, window, cx);
21145 }
21146
21147 cx.notify();
21148 }
21149
21150 pub fn set_searchable(&mut self, searchable: bool) {
21151 self.searchable = searchable;
21152 }
21153
21154 pub fn searchable(&self) -> bool {
21155 self.searchable
21156 }
21157
21158 pub fn open_excerpts_in_split(
21159 &mut self,
21160 _: &OpenExcerptsSplit,
21161 window: &mut Window,
21162 cx: &mut Context<Self>,
21163 ) {
21164 self.open_excerpts_common(None, true, window, cx)
21165 }
21166
21167 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21168 self.open_excerpts_common(None, false, window, cx)
21169 }
21170
21171 fn open_excerpts_common(
21172 &mut self,
21173 jump_data: Option<JumpData>,
21174 split: bool,
21175 window: &mut Window,
21176 cx: &mut Context<Self>,
21177 ) {
21178 let Some(workspace) = self.workspace() else {
21179 cx.propagate();
21180 return;
21181 };
21182
21183 if self.buffer.read(cx).is_singleton() {
21184 cx.propagate();
21185 return;
21186 }
21187
21188 let mut new_selections_by_buffer = HashMap::default();
21189 match &jump_data {
21190 Some(JumpData::MultiBufferPoint {
21191 excerpt_id,
21192 position,
21193 anchor,
21194 line_offset_from_top,
21195 }) => {
21196 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21197 if let Some(buffer) = multi_buffer_snapshot
21198 .buffer_id_for_excerpt(*excerpt_id)
21199 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21200 {
21201 let buffer_snapshot = buffer.read(cx).snapshot();
21202 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21203 language::ToPoint::to_point(anchor, &buffer_snapshot)
21204 } else {
21205 buffer_snapshot.clip_point(*position, Bias::Left)
21206 };
21207 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21208 new_selections_by_buffer.insert(
21209 buffer,
21210 (
21211 vec![jump_to_offset..jump_to_offset],
21212 Some(*line_offset_from_top),
21213 ),
21214 );
21215 }
21216 }
21217 Some(JumpData::MultiBufferRow {
21218 row,
21219 line_offset_from_top,
21220 }) => {
21221 let point = MultiBufferPoint::new(row.0, 0);
21222 if let Some((buffer, buffer_point, _)) =
21223 self.buffer.read(cx).point_to_buffer_point(point, cx)
21224 {
21225 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21226 new_selections_by_buffer
21227 .entry(buffer)
21228 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21229 .0
21230 .push(buffer_offset..buffer_offset)
21231 }
21232 }
21233 None => {
21234 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21235 let multi_buffer = self.buffer.read(cx);
21236 for selection in selections {
21237 for (snapshot, range, _, anchor) in multi_buffer
21238 .snapshot(cx)
21239 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21240 {
21241 if let Some(anchor) = anchor {
21242 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21243 else {
21244 continue;
21245 };
21246 let offset = text::ToOffset::to_offset(
21247 &anchor.text_anchor,
21248 &buffer_handle.read(cx).snapshot(),
21249 );
21250 let range = offset..offset;
21251 new_selections_by_buffer
21252 .entry(buffer_handle)
21253 .or_insert((Vec::new(), None))
21254 .0
21255 .push(range)
21256 } else {
21257 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21258 else {
21259 continue;
21260 };
21261 new_selections_by_buffer
21262 .entry(buffer_handle)
21263 .or_insert((Vec::new(), None))
21264 .0
21265 .push(range)
21266 }
21267 }
21268 }
21269 }
21270 }
21271
21272 new_selections_by_buffer
21273 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21274
21275 if new_selections_by_buffer.is_empty() {
21276 return;
21277 }
21278
21279 // We defer the pane interaction because we ourselves are a workspace item
21280 // and activating a new item causes the pane to call a method on us reentrantly,
21281 // which panics if we're on the stack.
21282 window.defer(cx, move |window, cx| {
21283 workspace.update(cx, |workspace, cx| {
21284 let pane = if split {
21285 workspace.adjacent_pane(window, cx)
21286 } else {
21287 workspace.active_pane().clone()
21288 };
21289
21290 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21291 let editor = buffer
21292 .read(cx)
21293 .file()
21294 .is_none()
21295 .then(|| {
21296 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21297 // so `workspace.open_project_item` will never find them, always opening a new editor.
21298 // Instead, we try to activate the existing editor in the pane first.
21299 let (editor, pane_item_index) =
21300 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21301 let editor = item.downcast::<Editor>()?;
21302 let singleton_buffer =
21303 editor.read(cx).buffer().read(cx).as_singleton()?;
21304 if singleton_buffer == buffer {
21305 Some((editor, i))
21306 } else {
21307 None
21308 }
21309 })?;
21310 pane.update(cx, |pane, cx| {
21311 pane.activate_item(pane_item_index, true, true, window, cx)
21312 });
21313 Some(editor)
21314 })
21315 .flatten()
21316 .unwrap_or_else(|| {
21317 workspace.open_project_item::<Self>(
21318 pane.clone(),
21319 buffer,
21320 true,
21321 true,
21322 window,
21323 cx,
21324 )
21325 });
21326
21327 editor.update(cx, |editor, cx| {
21328 let autoscroll = match scroll_offset {
21329 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21330 None => Autoscroll::newest(),
21331 };
21332 let nav_history = editor.nav_history.take();
21333 editor.change_selections(
21334 SelectionEffects::scroll(autoscroll),
21335 window,
21336 cx,
21337 |s| {
21338 s.select_ranges(ranges);
21339 },
21340 );
21341 editor.nav_history = nav_history;
21342 });
21343 }
21344 })
21345 });
21346 }
21347
21348 // For now, don't allow opening excerpts in buffers that aren't backed by
21349 // regular project files.
21350 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21351 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21352 }
21353
21354 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21355 let snapshot = self.buffer.read(cx).read(cx);
21356 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21357 Some(
21358 ranges
21359 .iter()
21360 .map(move |range| {
21361 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21362 })
21363 .collect(),
21364 )
21365 }
21366
21367 fn selection_replacement_ranges(
21368 &self,
21369 range: Range<OffsetUtf16>,
21370 cx: &mut App,
21371 ) -> Vec<Range<OffsetUtf16>> {
21372 let selections = self
21373 .selections
21374 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21375 let newest_selection = selections
21376 .iter()
21377 .max_by_key(|selection| selection.id)
21378 .unwrap();
21379 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21380 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21381 let snapshot = self.buffer.read(cx).read(cx);
21382 selections
21383 .into_iter()
21384 .map(|mut selection| {
21385 selection.start.0 =
21386 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21387 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21388 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21389 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21390 })
21391 .collect()
21392 }
21393
21394 fn report_editor_event(
21395 &self,
21396 reported_event: ReportEditorEvent,
21397 file_extension: Option<String>,
21398 cx: &App,
21399 ) {
21400 if cfg!(any(test, feature = "test-support")) {
21401 return;
21402 }
21403
21404 let Some(project) = &self.project else { return };
21405
21406 // If None, we are in a file without an extension
21407 let file = self
21408 .buffer
21409 .read(cx)
21410 .as_singleton()
21411 .and_then(|b| b.read(cx).file());
21412 let file_extension = file_extension.or(file
21413 .as_ref()
21414 .and_then(|file| Path::new(file.file_name(cx)).extension())
21415 .and_then(|e| e.to_str())
21416 .map(|a| a.to_string()));
21417
21418 let vim_mode = vim_enabled(cx);
21419
21420 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21421 let copilot_enabled = edit_predictions_provider
21422 == language::language_settings::EditPredictionProvider::Copilot;
21423 let copilot_enabled_for_language = self
21424 .buffer
21425 .read(cx)
21426 .language_settings(cx)
21427 .show_edit_predictions;
21428
21429 let project = project.read(cx);
21430 let event_type = reported_event.event_type();
21431
21432 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21433 telemetry::event!(
21434 event_type,
21435 type = if auto_saved {"autosave"} else {"manual"},
21436 file_extension,
21437 vim_mode,
21438 copilot_enabled,
21439 copilot_enabled_for_language,
21440 edit_predictions_provider,
21441 is_via_ssh = project.is_via_remote_server(),
21442 );
21443 } else {
21444 telemetry::event!(
21445 event_type,
21446 file_extension,
21447 vim_mode,
21448 copilot_enabled,
21449 copilot_enabled_for_language,
21450 edit_predictions_provider,
21451 is_via_ssh = project.is_via_remote_server(),
21452 );
21453 };
21454 }
21455
21456 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21457 /// with each line being an array of {text, highlight} objects.
21458 fn copy_highlight_json(
21459 &mut self,
21460 _: &CopyHighlightJson,
21461 window: &mut Window,
21462 cx: &mut Context<Self>,
21463 ) {
21464 #[derive(Serialize)]
21465 struct Chunk<'a> {
21466 text: String,
21467 highlight: Option<&'a str>,
21468 }
21469
21470 let snapshot = self.buffer.read(cx).snapshot(cx);
21471 let range = self
21472 .selected_text_range(false, window, cx)
21473 .and_then(|selection| {
21474 if selection.range.is_empty() {
21475 None
21476 } else {
21477 Some(
21478 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21479 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21480 )
21481 }
21482 })
21483 .unwrap_or_else(|| 0..snapshot.len());
21484
21485 let chunks = snapshot.chunks(range, true);
21486 let mut lines = Vec::new();
21487 let mut line: VecDeque<Chunk> = VecDeque::new();
21488
21489 let Some(style) = self.style.as_ref() else {
21490 return;
21491 };
21492
21493 for chunk in chunks {
21494 let highlight = chunk
21495 .syntax_highlight_id
21496 .and_then(|id| id.name(&style.syntax));
21497 let mut chunk_lines = chunk.text.split('\n').peekable();
21498 while let Some(text) = chunk_lines.next() {
21499 let mut merged_with_last_token = false;
21500 if let Some(last_token) = line.back_mut()
21501 && last_token.highlight == highlight
21502 {
21503 last_token.text.push_str(text);
21504 merged_with_last_token = true;
21505 }
21506
21507 if !merged_with_last_token {
21508 line.push_back(Chunk {
21509 text: text.into(),
21510 highlight,
21511 });
21512 }
21513
21514 if chunk_lines.peek().is_some() {
21515 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21516 line.pop_front();
21517 }
21518 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21519 line.pop_back();
21520 }
21521
21522 lines.push(mem::take(&mut line));
21523 }
21524 }
21525 }
21526
21527 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21528 return;
21529 };
21530 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21531 }
21532
21533 pub fn open_context_menu(
21534 &mut self,
21535 _: &OpenContextMenu,
21536 window: &mut Window,
21537 cx: &mut Context<Self>,
21538 ) {
21539 self.request_autoscroll(Autoscroll::newest(), cx);
21540 let position = self
21541 .selections
21542 .newest_display(&self.display_snapshot(cx))
21543 .start;
21544 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21545 }
21546
21547 pub fn replay_insert_event(
21548 &mut self,
21549 text: &str,
21550 relative_utf16_range: Option<Range<isize>>,
21551 window: &mut Window,
21552 cx: &mut Context<Self>,
21553 ) {
21554 if !self.input_enabled {
21555 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21556 return;
21557 }
21558 if let Some(relative_utf16_range) = relative_utf16_range {
21559 let selections = self
21560 .selections
21561 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21562 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21563 let new_ranges = selections.into_iter().map(|range| {
21564 let start = OffsetUtf16(
21565 range
21566 .head()
21567 .0
21568 .saturating_add_signed(relative_utf16_range.start),
21569 );
21570 let end = OffsetUtf16(
21571 range
21572 .head()
21573 .0
21574 .saturating_add_signed(relative_utf16_range.end),
21575 );
21576 start..end
21577 });
21578 s.select_ranges(new_ranges);
21579 });
21580 }
21581
21582 self.handle_input(text, window, cx);
21583 }
21584
21585 pub fn is_focused(&self, window: &Window) -> bool {
21586 self.focus_handle.is_focused(window)
21587 }
21588
21589 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21590 cx.emit(EditorEvent::Focused);
21591
21592 if let Some(descendant) = self
21593 .last_focused_descendant
21594 .take()
21595 .and_then(|descendant| descendant.upgrade())
21596 {
21597 window.focus(&descendant);
21598 } else {
21599 if let Some(blame) = self.blame.as_ref() {
21600 blame.update(cx, GitBlame::focus)
21601 }
21602
21603 self.blink_manager.update(cx, BlinkManager::enable);
21604 self.show_cursor_names(window, cx);
21605 self.buffer.update(cx, |buffer, cx| {
21606 buffer.finalize_last_transaction(cx);
21607 if self.leader_id.is_none() {
21608 buffer.set_active_selections(
21609 &self.selections.disjoint_anchors_arc(),
21610 self.selections.line_mode(),
21611 self.cursor_shape,
21612 cx,
21613 );
21614 }
21615 });
21616 }
21617 }
21618
21619 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21620 cx.emit(EditorEvent::FocusedIn)
21621 }
21622
21623 fn handle_focus_out(
21624 &mut self,
21625 event: FocusOutEvent,
21626 _window: &mut Window,
21627 cx: &mut Context<Self>,
21628 ) {
21629 if event.blurred != self.focus_handle {
21630 self.last_focused_descendant = Some(event.blurred);
21631 }
21632 self.selection_drag_state = SelectionDragState::None;
21633 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21634 }
21635
21636 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21637 self.blink_manager.update(cx, BlinkManager::disable);
21638 self.buffer
21639 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21640
21641 if let Some(blame) = self.blame.as_ref() {
21642 blame.update(cx, GitBlame::blur)
21643 }
21644 if !self.hover_state.focused(window, cx) {
21645 hide_hover(self, cx);
21646 }
21647 if !self
21648 .context_menu
21649 .borrow()
21650 .as_ref()
21651 .is_some_and(|context_menu| context_menu.focused(window, cx))
21652 {
21653 self.hide_context_menu(window, cx);
21654 }
21655 self.take_active_edit_prediction(cx);
21656 cx.emit(EditorEvent::Blurred);
21657 cx.notify();
21658 }
21659
21660 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21661 let mut pending: String = window
21662 .pending_input_keystrokes()
21663 .into_iter()
21664 .flatten()
21665 .filter_map(|keystroke| {
21666 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21667 keystroke.key_char.clone()
21668 } else {
21669 None
21670 }
21671 })
21672 .collect();
21673
21674 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21675 pending = "".to_string();
21676 }
21677
21678 let existing_pending = self
21679 .text_highlights::<PendingInput>(cx)
21680 .map(|(_, ranges)| ranges.to_vec());
21681 if existing_pending.is_none() && pending.is_empty() {
21682 return;
21683 }
21684 let transaction =
21685 self.transact(window, cx, |this, window, cx| {
21686 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21687 let edits = selections
21688 .iter()
21689 .map(|selection| (selection.end..selection.end, pending.clone()));
21690 this.edit(edits, cx);
21691 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21692 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21693 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21694 }));
21695 });
21696 if let Some(existing_ranges) = existing_pending {
21697 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21698 this.edit(edits, cx);
21699 }
21700 });
21701
21702 let snapshot = self.snapshot(window, cx);
21703 let ranges = self
21704 .selections
21705 .all::<usize>(&snapshot.display_snapshot)
21706 .into_iter()
21707 .map(|selection| {
21708 snapshot.buffer_snapshot().anchor_after(selection.end)
21709 ..snapshot
21710 .buffer_snapshot()
21711 .anchor_before(selection.end + pending.len())
21712 })
21713 .collect();
21714
21715 if pending.is_empty() {
21716 self.clear_highlights::<PendingInput>(cx);
21717 } else {
21718 self.highlight_text::<PendingInput>(
21719 ranges,
21720 HighlightStyle {
21721 underline: Some(UnderlineStyle {
21722 thickness: px(1.),
21723 color: None,
21724 wavy: false,
21725 }),
21726 ..Default::default()
21727 },
21728 cx,
21729 );
21730 }
21731
21732 self.ime_transaction = self.ime_transaction.or(transaction);
21733 if let Some(transaction) = self.ime_transaction {
21734 self.buffer.update(cx, |buffer, cx| {
21735 buffer.group_until_transaction(transaction, cx);
21736 });
21737 }
21738
21739 if self.text_highlights::<PendingInput>(cx).is_none() {
21740 self.ime_transaction.take();
21741 }
21742 }
21743
21744 pub fn register_action_renderer(
21745 &mut self,
21746 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21747 ) -> Subscription {
21748 let id = self.next_editor_action_id.post_inc();
21749 self.editor_actions
21750 .borrow_mut()
21751 .insert(id, Box::new(listener));
21752
21753 let editor_actions = self.editor_actions.clone();
21754 Subscription::new(move || {
21755 editor_actions.borrow_mut().remove(&id);
21756 })
21757 }
21758
21759 pub fn register_action<A: Action>(
21760 &mut self,
21761 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21762 ) -> Subscription {
21763 let id = self.next_editor_action_id.post_inc();
21764 let listener = Arc::new(listener);
21765 self.editor_actions.borrow_mut().insert(
21766 id,
21767 Box::new(move |_, window, _| {
21768 let listener = listener.clone();
21769 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21770 let action = action.downcast_ref().unwrap();
21771 if phase == DispatchPhase::Bubble {
21772 listener(action, window, cx)
21773 }
21774 })
21775 }),
21776 );
21777
21778 let editor_actions = self.editor_actions.clone();
21779 Subscription::new(move || {
21780 editor_actions.borrow_mut().remove(&id);
21781 })
21782 }
21783
21784 pub fn file_header_size(&self) -> u32 {
21785 FILE_HEADER_HEIGHT
21786 }
21787
21788 pub fn restore(
21789 &mut self,
21790 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21791 window: &mut Window,
21792 cx: &mut Context<Self>,
21793 ) {
21794 let workspace = self.workspace();
21795 let project = self.project();
21796 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21797 let mut tasks = Vec::new();
21798 for (buffer_id, changes) in revert_changes {
21799 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21800 buffer.update(cx, |buffer, cx| {
21801 buffer.edit(
21802 changes
21803 .into_iter()
21804 .map(|(range, text)| (range, text.to_string())),
21805 None,
21806 cx,
21807 );
21808 });
21809
21810 if let Some(project) =
21811 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21812 {
21813 project.update(cx, |project, cx| {
21814 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21815 })
21816 }
21817 }
21818 }
21819 tasks
21820 });
21821 cx.spawn_in(window, async move |_, cx| {
21822 for (buffer, task) in save_tasks {
21823 let result = task.await;
21824 if result.is_err() {
21825 let Some(path) = buffer
21826 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21827 .ok()
21828 else {
21829 continue;
21830 };
21831 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21832 let Some(task) = cx
21833 .update_window_entity(workspace, |workspace, window, cx| {
21834 workspace
21835 .open_path_preview(path, None, false, false, false, window, cx)
21836 })
21837 .ok()
21838 else {
21839 continue;
21840 };
21841 task.await.log_err();
21842 }
21843 }
21844 }
21845 })
21846 .detach();
21847 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21848 selections.refresh()
21849 });
21850 }
21851
21852 pub fn to_pixel_point(
21853 &self,
21854 source: multi_buffer::Anchor,
21855 editor_snapshot: &EditorSnapshot,
21856 window: &mut Window,
21857 ) -> Option<gpui::Point<Pixels>> {
21858 let source_point = source.to_display_point(editor_snapshot);
21859 self.display_to_pixel_point(source_point, editor_snapshot, window)
21860 }
21861
21862 pub fn display_to_pixel_point(
21863 &self,
21864 source: DisplayPoint,
21865 editor_snapshot: &EditorSnapshot,
21866 window: &mut Window,
21867 ) -> Option<gpui::Point<Pixels>> {
21868 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21869 let text_layout_details = self.text_layout_details(window);
21870 let scroll_top = text_layout_details
21871 .scroll_anchor
21872 .scroll_position(editor_snapshot)
21873 .y;
21874
21875 if source.row().as_f64() < scroll_top.floor() {
21876 return None;
21877 }
21878 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21879 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21880 Some(gpui::Point::new(source_x, source_y))
21881 }
21882
21883 pub fn has_visible_completions_menu(&self) -> bool {
21884 !self.edit_prediction_preview_is_active()
21885 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21886 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21887 })
21888 }
21889
21890 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21891 if self.mode.is_minimap() {
21892 return;
21893 }
21894 self.addons
21895 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21896 }
21897
21898 pub fn unregister_addon<T: Addon>(&mut self) {
21899 self.addons.remove(&std::any::TypeId::of::<T>());
21900 }
21901
21902 pub fn addon<T: Addon>(&self) -> Option<&T> {
21903 let type_id = std::any::TypeId::of::<T>();
21904 self.addons
21905 .get(&type_id)
21906 .and_then(|item| item.to_any().downcast_ref::<T>())
21907 }
21908
21909 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21910 let type_id = std::any::TypeId::of::<T>();
21911 self.addons
21912 .get_mut(&type_id)
21913 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21914 }
21915
21916 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21917 let text_layout_details = self.text_layout_details(window);
21918 let style = &text_layout_details.editor_style;
21919 let font_id = window.text_system().resolve_font(&style.text.font());
21920 let font_size = style.text.font_size.to_pixels(window.rem_size());
21921 let line_height = style.text.line_height_in_pixels(window.rem_size());
21922 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21923 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21924
21925 CharacterDimensions {
21926 em_width,
21927 em_advance,
21928 line_height,
21929 }
21930 }
21931
21932 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21933 self.load_diff_task.clone()
21934 }
21935
21936 fn read_metadata_from_db(
21937 &mut self,
21938 item_id: u64,
21939 workspace_id: WorkspaceId,
21940 window: &mut Window,
21941 cx: &mut Context<Editor>,
21942 ) {
21943 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21944 && !self.mode.is_minimap()
21945 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21946 {
21947 let buffer_snapshot = OnceCell::new();
21948
21949 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21950 && !folds.is_empty()
21951 {
21952 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21953 self.fold_ranges(
21954 folds
21955 .into_iter()
21956 .map(|(start, end)| {
21957 snapshot.clip_offset(start, Bias::Left)
21958 ..snapshot.clip_offset(end, Bias::Right)
21959 })
21960 .collect(),
21961 false,
21962 window,
21963 cx,
21964 );
21965 }
21966
21967 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21968 && !selections.is_empty()
21969 {
21970 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21971 // skip adding the initial selection to selection history
21972 self.selection_history.mode = SelectionHistoryMode::Skipping;
21973 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21974 s.select_ranges(selections.into_iter().map(|(start, end)| {
21975 snapshot.clip_offset(start, Bias::Left)
21976 ..snapshot.clip_offset(end, Bias::Right)
21977 }));
21978 });
21979 self.selection_history.mode = SelectionHistoryMode::Normal;
21980 };
21981 }
21982
21983 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21984 }
21985
21986 fn update_lsp_data(
21987 &mut self,
21988 for_buffer: Option<BufferId>,
21989 window: &mut Window,
21990 cx: &mut Context<'_, Self>,
21991 ) {
21992 self.pull_diagnostics(for_buffer, window, cx);
21993 self.refresh_colors_for_visible_range(for_buffer, window, cx);
21994 }
21995
21996 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
21997 if self.ignore_lsp_data() {
21998 return;
21999 }
22000 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22001 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22002 }
22003 }
22004
22005 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22006 if !self.registered_buffers.contains_key(&buffer_id)
22007 && let Some(project) = self.project.as_ref()
22008 {
22009 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22010 project.update(cx, |project, cx| {
22011 self.registered_buffers.insert(
22012 buffer_id,
22013 project.register_buffer_with_language_servers(&buffer, cx),
22014 );
22015 });
22016 } else {
22017 self.registered_buffers.remove(&buffer_id);
22018 }
22019 }
22020 }
22021
22022 fn ignore_lsp_data(&self) -> bool {
22023 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22024 // skip any LSP updates for it.
22025 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22026 }
22027}
22028
22029fn edit_for_markdown_paste<'a>(
22030 buffer: &MultiBufferSnapshot,
22031 range: Range<usize>,
22032 to_insert: &'a str,
22033 url: Option<url::Url>,
22034) -> (Range<usize>, Cow<'a, str>) {
22035 if url.is_none() {
22036 return (range, Cow::Borrowed(to_insert));
22037 };
22038
22039 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22040
22041 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22042 Cow::Borrowed(to_insert)
22043 } else {
22044 Cow::Owned(format!("[{old_text}]({to_insert})"))
22045 };
22046 (range, new_text)
22047}
22048
22049fn vim_enabled(cx: &App) -> bool {
22050 vim_mode_setting::VimModeSetting::try_get(cx)
22051 .map(|vim_mode| vim_mode.0)
22052 .unwrap_or(false)
22053}
22054
22055fn process_completion_for_edit(
22056 completion: &Completion,
22057 intent: CompletionIntent,
22058 buffer: &Entity<Buffer>,
22059 cursor_position: &text::Anchor,
22060 cx: &mut Context<Editor>,
22061) -> CompletionEdit {
22062 let buffer = buffer.read(cx);
22063 let buffer_snapshot = buffer.snapshot();
22064 let (snippet, new_text) = if completion.is_snippet() {
22065 let mut snippet_source = completion.new_text.clone();
22066 // Workaround for typescript language server issues so that methods don't expand within
22067 // strings and functions with type expressions. The previous point is used because the query
22068 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22069 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22070 let previous_point = if previous_point.column > 0 {
22071 cursor_position.to_previous_offset(&buffer_snapshot)
22072 } else {
22073 cursor_position.to_offset(&buffer_snapshot)
22074 };
22075 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22076 && scope.prefers_label_for_snippet_in_completion()
22077 && let Some(label) = completion.label()
22078 && matches!(
22079 completion.kind(),
22080 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22081 )
22082 {
22083 snippet_source = label;
22084 }
22085 match Snippet::parse(&snippet_source).log_err() {
22086 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22087 None => (None, completion.new_text.clone()),
22088 }
22089 } else {
22090 (None, completion.new_text.clone())
22091 };
22092
22093 let mut range_to_replace = {
22094 let replace_range = &completion.replace_range;
22095 if let CompletionSource::Lsp {
22096 insert_range: Some(insert_range),
22097 ..
22098 } = &completion.source
22099 {
22100 debug_assert_eq!(
22101 insert_range.start, replace_range.start,
22102 "insert_range and replace_range should start at the same position"
22103 );
22104 debug_assert!(
22105 insert_range
22106 .start
22107 .cmp(cursor_position, &buffer_snapshot)
22108 .is_le(),
22109 "insert_range should start before or at cursor position"
22110 );
22111 debug_assert!(
22112 replace_range
22113 .start
22114 .cmp(cursor_position, &buffer_snapshot)
22115 .is_le(),
22116 "replace_range should start before or at cursor position"
22117 );
22118
22119 let should_replace = match intent {
22120 CompletionIntent::CompleteWithInsert => false,
22121 CompletionIntent::CompleteWithReplace => true,
22122 CompletionIntent::Complete | CompletionIntent::Compose => {
22123 let insert_mode =
22124 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22125 .completions
22126 .lsp_insert_mode;
22127 match insert_mode {
22128 LspInsertMode::Insert => false,
22129 LspInsertMode::Replace => true,
22130 LspInsertMode::ReplaceSubsequence => {
22131 let mut text_to_replace = buffer.chars_for_range(
22132 buffer.anchor_before(replace_range.start)
22133 ..buffer.anchor_after(replace_range.end),
22134 );
22135 let mut current_needle = text_to_replace.next();
22136 for haystack_ch in completion.label.text.chars() {
22137 if let Some(needle_ch) = current_needle
22138 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22139 {
22140 current_needle = text_to_replace.next();
22141 }
22142 }
22143 current_needle.is_none()
22144 }
22145 LspInsertMode::ReplaceSuffix => {
22146 if replace_range
22147 .end
22148 .cmp(cursor_position, &buffer_snapshot)
22149 .is_gt()
22150 {
22151 let range_after_cursor = *cursor_position..replace_range.end;
22152 let text_after_cursor = buffer
22153 .text_for_range(
22154 buffer.anchor_before(range_after_cursor.start)
22155 ..buffer.anchor_after(range_after_cursor.end),
22156 )
22157 .collect::<String>()
22158 .to_ascii_lowercase();
22159 completion
22160 .label
22161 .text
22162 .to_ascii_lowercase()
22163 .ends_with(&text_after_cursor)
22164 } else {
22165 true
22166 }
22167 }
22168 }
22169 }
22170 };
22171
22172 if should_replace {
22173 replace_range.clone()
22174 } else {
22175 insert_range.clone()
22176 }
22177 } else {
22178 replace_range.clone()
22179 }
22180 };
22181
22182 if range_to_replace
22183 .end
22184 .cmp(cursor_position, &buffer_snapshot)
22185 .is_lt()
22186 {
22187 range_to_replace.end = *cursor_position;
22188 }
22189
22190 CompletionEdit {
22191 new_text,
22192 replace_range: range_to_replace.to_offset(buffer),
22193 snippet,
22194 }
22195}
22196
22197struct CompletionEdit {
22198 new_text: String,
22199 replace_range: Range<usize>,
22200 snippet: Option<Snippet>,
22201}
22202
22203fn insert_extra_newline_brackets(
22204 buffer: &MultiBufferSnapshot,
22205 range: Range<usize>,
22206 language: &language::LanguageScope,
22207) -> bool {
22208 let leading_whitespace_len = buffer
22209 .reversed_chars_at(range.start)
22210 .take_while(|c| c.is_whitespace() && *c != '\n')
22211 .map(|c| c.len_utf8())
22212 .sum::<usize>();
22213 let trailing_whitespace_len = buffer
22214 .chars_at(range.end)
22215 .take_while(|c| c.is_whitespace() && *c != '\n')
22216 .map(|c| c.len_utf8())
22217 .sum::<usize>();
22218 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22219
22220 language.brackets().any(|(pair, enabled)| {
22221 let pair_start = pair.start.trim_end();
22222 let pair_end = pair.end.trim_start();
22223
22224 enabled
22225 && pair.newline
22226 && buffer.contains_str_at(range.end, pair_end)
22227 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22228 })
22229}
22230
22231fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22232 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22233 [(buffer, range, _)] => (*buffer, range.clone()),
22234 _ => return false,
22235 };
22236 let pair = {
22237 let mut result: Option<BracketMatch> = None;
22238
22239 for pair in buffer
22240 .all_bracket_ranges(range.clone())
22241 .filter(move |pair| {
22242 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22243 })
22244 {
22245 let len = pair.close_range.end - pair.open_range.start;
22246
22247 if let Some(existing) = &result {
22248 let existing_len = existing.close_range.end - existing.open_range.start;
22249 if len > existing_len {
22250 continue;
22251 }
22252 }
22253
22254 result = Some(pair);
22255 }
22256
22257 result
22258 };
22259 let Some(pair) = pair else {
22260 return false;
22261 };
22262 pair.newline_only
22263 && buffer
22264 .chars_for_range(pair.open_range.end..range.start)
22265 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22266 .all(|c| c.is_whitespace() && c != '\n')
22267}
22268
22269fn update_uncommitted_diff_for_buffer(
22270 editor: Entity<Editor>,
22271 project: &Entity<Project>,
22272 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22273 buffer: Entity<MultiBuffer>,
22274 cx: &mut App,
22275) -> Task<()> {
22276 let mut tasks = Vec::new();
22277 project.update(cx, |project, cx| {
22278 for buffer in buffers {
22279 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22280 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22281 }
22282 }
22283 });
22284 cx.spawn(async move |cx| {
22285 let diffs = future::join_all(tasks).await;
22286 if editor
22287 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22288 .unwrap_or(false)
22289 {
22290 return;
22291 }
22292
22293 buffer
22294 .update(cx, |buffer, cx| {
22295 for diff in diffs.into_iter().flatten() {
22296 buffer.add_diff(diff, cx);
22297 }
22298 })
22299 .ok();
22300 })
22301}
22302
22303fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22304 let tab_size = tab_size.get() as usize;
22305 let mut width = offset;
22306
22307 for ch in text.chars() {
22308 width += if ch == '\t' {
22309 tab_size - (width % tab_size)
22310 } else {
22311 1
22312 };
22313 }
22314
22315 width - offset
22316}
22317
22318#[cfg(test)]
22319mod tests {
22320 use super::*;
22321
22322 #[test]
22323 fn test_string_size_with_expanded_tabs() {
22324 let nz = |val| NonZeroU32::new(val).unwrap();
22325 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22326 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22327 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22328 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22329 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22330 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22331 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22332 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22333 }
22334}
22335
22336/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22337struct WordBreakingTokenizer<'a> {
22338 input: &'a str,
22339}
22340
22341impl<'a> WordBreakingTokenizer<'a> {
22342 fn new(input: &'a str) -> Self {
22343 Self { input }
22344 }
22345}
22346
22347fn is_char_ideographic(ch: char) -> bool {
22348 use unicode_script::Script::*;
22349 use unicode_script::UnicodeScript;
22350 matches!(ch.script(), Han | Tangut | Yi)
22351}
22352
22353fn is_grapheme_ideographic(text: &str) -> bool {
22354 text.chars().any(is_char_ideographic)
22355}
22356
22357fn is_grapheme_whitespace(text: &str) -> bool {
22358 text.chars().any(|x| x.is_whitespace())
22359}
22360
22361fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22362 text.chars()
22363 .next()
22364 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22365}
22366
22367#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22368enum WordBreakToken<'a> {
22369 Word { token: &'a str, grapheme_len: usize },
22370 InlineWhitespace { token: &'a str, grapheme_len: usize },
22371 Newline,
22372}
22373
22374impl<'a> Iterator for WordBreakingTokenizer<'a> {
22375 /// Yields a span, the count of graphemes in the token, and whether it was
22376 /// whitespace. Note that it also breaks at word boundaries.
22377 type Item = WordBreakToken<'a>;
22378
22379 fn next(&mut self) -> Option<Self::Item> {
22380 use unicode_segmentation::UnicodeSegmentation;
22381 if self.input.is_empty() {
22382 return None;
22383 }
22384
22385 let mut iter = self.input.graphemes(true).peekable();
22386 let mut offset = 0;
22387 let mut grapheme_len = 0;
22388 if let Some(first_grapheme) = iter.next() {
22389 let is_newline = first_grapheme == "\n";
22390 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22391 offset += first_grapheme.len();
22392 grapheme_len += 1;
22393 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22394 if let Some(grapheme) = iter.peek().copied()
22395 && should_stay_with_preceding_ideograph(grapheme)
22396 {
22397 offset += grapheme.len();
22398 grapheme_len += 1;
22399 }
22400 } else {
22401 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22402 let mut next_word_bound = words.peek().copied();
22403 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22404 next_word_bound = words.next();
22405 }
22406 while let Some(grapheme) = iter.peek().copied() {
22407 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22408 break;
22409 };
22410 if is_grapheme_whitespace(grapheme) != is_whitespace
22411 || (grapheme == "\n") != is_newline
22412 {
22413 break;
22414 };
22415 offset += grapheme.len();
22416 grapheme_len += 1;
22417 iter.next();
22418 }
22419 }
22420 let token = &self.input[..offset];
22421 self.input = &self.input[offset..];
22422 if token == "\n" {
22423 Some(WordBreakToken::Newline)
22424 } else if is_whitespace {
22425 Some(WordBreakToken::InlineWhitespace {
22426 token,
22427 grapheme_len,
22428 })
22429 } else {
22430 Some(WordBreakToken::Word {
22431 token,
22432 grapheme_len,
22433 })
22434 }
22435 } else {
22436 None
22437 }
22438 }
22439}
22440
22441#[test]
22442fn test_word_breaking_tokenizer() {
22443 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22444 ("", &[]),
22445 (" ", &[whitespace(" ", 2)]),
22446 ("Ʒ", &[word("Ʒ", 1)]),
22447 ("Ǽ", &[word("Ǽ", 1)]),
22448 ("⋑", &[word("⋑", 1)]),
22449 ("⋑⋑", &[word("⋑⋑", 2)]),
22450 (
22451 "原理,进而",
22452 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22453 ),
22454 (
22455 "hello world",
22456 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22457 ),
22458 (
22459 "hello, world",
22460 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22461 ),
22462 (
22463 " hello world",
22464 &[
22465 whitespace(" ", 2),
22466 word("hello", 5),
22467 whitespace(" ", 1),
22468 word("world", 5),
22469 ],
22470 ),
22471 (
22472 "这是什么 \n 钢笔",
22473 &[
22474 word("这", 1),
22475 word("是", 1),
22476 word("什", 1),
22477 word("么", 1),
22478 whitespace(" ", 1),
22479 newline(),
22480 whitespace(" ", 1),
22481 word("钢", 1),
22482 word("笔", 1),
22483 ],
22484 ),
22485 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22486 ];
22487
22488 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22489 WordBreakToken::Word {
22490 token,
22491 grapheme_len,
22492 }
22493 }
22494
22495 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22496 WordBreakToken::InlineWhitespace {
22497 token,
22498 grapheme_len,
22499 }
22500 }
22501
22502 fn newline() -> WordBreakToken<'static> {
22503 WordBreakToken::Newline
22504 }
22505
22506 for (input, result) in tests {
22507 assert_eq!(
22508 WordBreakingTokenizer::new(input)
22509 .collect::<Vec<_>>()
22510 .as_slice(),
22511 *result,
22512 );
22513 }
22514}
22515
22516fn wrap_with_prefix(
22517 first_line_prefix: String,
22518 subsequent_lines_prefix: String,
22519 unwrapped_text: String,
22520 wrap_column: usize,
22521 tab_size: NonZeroU32,
22522 preserve_existing_whitespace: bool,
22523) -> String {
22524 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22525 let subsequent_lines_prefix_len =
22526 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22527 let mut wrapped_text = String::new();
22528 let mut current_line = first_line_prefix;
22529 let mut is_first_line = true;
22530
22531 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22532 let mut current_line_len = first_line_prefix_len;
22533 let mut in_whitespace = false;
22534 for token in tokenizer {
22535 let have_preceding_whitespace = in_whitespace;
22536 match token {
22537 WordBreakToken::Word {
22538 token,
22539 grapheme_len,
22540 } => {
22541 in_whitespace = false;
22542 let current_prefix_len = if is_first_line {
22543 first_line_prefix_len
22544 } else {
22545 subsequent_lines_prefix_len
22546 };
22547 if current_line_len + grapheme_len > wrap_column
22548 && current_line_len != current_prefix_len
22549 {
22550 wrapped_text.push_str(current_line.trim_end());
22551 wrapped_text.push('\n');
22552 is_first_line = false;
22553 current_line = subsequent_lines_prefix.clone();
22554 current_line_len = subsequent_lines_prefix_len;
22555 }
22556 current_line.push_str(token);
22557 current_line_len += grapheme_len;
22558 }
22559 WordBreakToken::InlineWhitespace {
22560 mut token,
22561 mut grapheme_len,
22562 } => {
22563 in_whitespace = true;
22564 if have_preceding_whitespace && !preserve_existing_whitespace {
22565 continue;
22566 }
22567 if !preserve_existing_whitespace {
22568 // Keep a single whitespace grapheme as-is
22569 if let Some(first) =
22570 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22571 {
22572 token = first;
22573 } else {
22574 token = " ";
22575 }
22576 grapheme_len = 1;
22577 }
22578 let current_prefix_len = if is_first_line {
22579 first_line_prefix_len
22580 } else {
22581 subsequent_lines_prefix_len
22582 };
22583 if current_line_len + grapheme_len > wrap_column {
22584 wrapped_text.push_str(current_line.trim_end());
22585 wrapped_text.push('\n');
22586 is_first_line = false;
22587 current_line = subsequent_lines_prefix.clone();
22588 current_line_len = subsequent_lines_prefix_len;
22589 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22590 current_line.push_str(token);
22591 current_line_len += grapheme_len;
22592 }
22593 }
22594 WordBreakToken::Newline => {
22595 in_whitespace = true;
22596 let current_prefix_len = if is_first_line {
22597 first_line_prefix_len
22598 } else {
22599 subsequent_lines_prefix_len
22600 };
22601 if preserve_existing_whitespace {
22602 wrapped_text.push_str(current_line.trim_end());
22603 wrapped_text.push('\n');
22604 is_first_line = false;
22605 current_line = subsequent_lines_prefix.clone();
22606 current_line_len = subsequent_lines_prefix_len;
22607 } else if have_preceding_whitespace {
22608 continue;
22609 } else if current_line_len + 1 > wrap_column
22610 && current_line_len != current_prefix_len
22611 {
22612 wrapped_text.push_str(current_line.trim_end());
22613 wrapped_text.push('\n');
22614 is_first_line = false;
22615 current_line = subsequent_lines_prefix.clone();
22616 current_line_len = subsequent_lines_prefix_len;
22617 } else if current_line_len != current_prefix_len {
22618 current_line.push(' ');
22619 current_line_len += 1;
22620 }
22621 }
22622 }
22623 }
22624
22625 if !current_line.is_empty() {
22626 wrapped_text.push_str(¤t_line);
22627 }
22628 wrapped_text
22629}
22630
22631#[test]
22632fn test_wrap_with_prefix() {
22633 assert_eq!(
22634 wrap_with_prefix(
22635 "# ".to_string(),
22636 "# ".to_string(),
22637 "abcdefg".to_string(),
22638 4,
22639 NonZeroU32::new(4).unwrap(),
22640 false,
22641 ),
22642 "# abcdefg"
22643 );
22644 assert_eq!(
22645 wrap_with_prefix(
22646 "".to_string(),
22647 "".to_string(),
22648 "\thello world".to_string(),
22649 8,
22650 NonZeroU32::new(4).unwrap(),
22651 false,
22652 ),
22653 "hello\nworld"
22654 );
22655 assert_eq!(
22656 wrap_with_prefix(
22657 "// ".to_string(),
22658 "// ".to_string(),
22659 "xx \nyy zz aa bb cc".to_string(),
22660 12,
22661 NonZeroU32::new(4).unwrap(),
22662 false,
22663 ),
22664 "// xx yy zz\n// aa bb cc"
22665 );
22666 assert_eq!(
22667 wrap_with_prefix(
22668 String::new(),
22669 String::new(),
22670 "这是什么 \n 钢笔".to_string(),
22671 3,
22672 NonZeroU32::new(4).unwrap(),
22673 false,
22674 ),
22675 "这是什\n么 钢\n笔"
22676 );
22677 assert_eq!(
22678 wrap_with_prefix(
22679 String::new(),
22680 String::new(),
22681 format!("foo{}bar", '\u{2009}'), // thin space
22682 80,
22683 NonZeroU32::new(4).unwrap(),
22684 false,
22685 ),
22686 format!("foo{}bar", '\u{2009}')
22687 );
22688}
22689
22690pub trait CollaborationHub {
22691 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22692 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22693 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22694}
22695
22696impl CollaborationHub for Entity<Project> {
22697 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22698 self.read(cx).collaborators()
22699 }
22700
22701 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22702 self.read(cx).user_store().read(cx).participant_indices()
22703 }
22704
22705 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22706 let this = self.read(cx);
22707 let user_ids = this.collaborators().values().map(|c| c.user_id);
22708 this.user_store().read(cx).participant_names(user_ids, cx)
22709 }
22710}
22711
22712pub trait SemanticsProvider {
22713 fn hover(
22714 &self,
22715 buffer: &Entity<Buffer>,
22716 position: text::Anchor,
22717 cx: &mut App,
22718 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22719
22720 fn inline_values(
22721 &self,
22722 buffer_handle: Entity<Buffer>,
22723 range: Range<text::Anchor>,
22724 cx: &mut App,
22725 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22726
22727 fn applicable_inlay_chunks(
22728 &self,
22729 buffer: &Entity<Buffer>,
22730 ranges: &[Range<text::Anchor>],
22731 cx: &mut App,
22732 ) -> Vec<Range<BufferRow>>;
22733
22734 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22735
22736 fn inlay_hints(
22737 &self,
22738 invalidate: InvalidationStrategy,
22739 buffer: Entity<Buffer>,
22740 ranges: Vec<Range<text::Anchor>>,
22741 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22742 cx: &mut App,
22743 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22744
22745 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22746
22747 fn document_highlights(
22748 &self,
22749 buffer: &Entity<Buffer>,
22750 position: text::Anchor,
22751 cx: &mut App,
22752 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22753
22754 fn definitions(
22755 &self,
22756 buffer: &Entity<Buffer>,
22757 position: text::Anchor,
22758 kind: GotoDefinitionKind,
22759 cx: &mut App,
22760 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22761
22762 fn range_for_rename(
22763 &self,
22764 buffer: &Entity<Buffer>,
22765 position: text::Anchor,
22766 cx: &mut App,
22767 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22768
22769 fn perform_rename(
22770 &self,
22771 buffer: &Entity<Buffer>,
22772 position: text::Anchor,
22773 new_name: String,
22774 cx: &mut App,
22775 ) -> Option<Task<Result<ProjectTransaction>>>;
22776}
22777
22778pub trait CompletionProvider {
22779 fn completions(
22780 &self,
22781 excerpt_id: ExcerptId,
22782 buffer: &Entity<Buffer>,
22783 buffer_position: text::Anchor,
22784 trigger: CompletionContext,
22785 window: &mut Window,
22786 cx: &mut Context<Editor>,
22787 ) -> Task<Result<Vec<CompletionResponse>>>;
22788
22789 fn resolve_completions(
22790 &self,
22791 _buffer: Entity<Buffer>,
22792 _completion_indices: Vec<usize>,
22793 _completions: Rc<RefCell<Box<[Completion]>>>,
22794 _cx: &mut Context<Editor>,
22795 ) -> Task<Result<bool>> {
22796 Task::ready(Ok(false))
22797 }
22798
22799 fn apply_additional_edits_for_completion(
22800 &self,
22801 _buffer: Entity<Buffer>,
22802 _completions: Rc<RefCell<Box<[Completion]>>>,
22803 _completion_index: usize,
22804 _push_to_history: bool,
22805 _cx: &mut Context<Editor>,
22806 ) -> Task<Result<Option<language::Transaction>>> {
22807 Task::ready(Ok(None))
22808 }
22809
22810 fn is_completion_trigger(
22811 &self,
22812 buffer: &Entity<Buffer>,
22813 position: language::Anchor,
22814 text: &str,
22815 trigger_in_words: bool,
22816 menu_is_open: bool,
22817 cx: &mut Context<Editor>,
22818 ) -> bool;
22819
22820 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22821
22822 fn sort_completions(&self) -> bool {
22823 true
22824 }
22825
22826 fn filter_completions(&self) -> bool {
22827 true
22828 }
22829}
22830
22831pub trait CodeActionProvider {
22832 fn id(&self) -> Arc<str>;
22833
22834 fn code_actions(
22835 &self,
22836 buffer: &Entity<Buffer>,
22837 range: Range<text::Anchor>,
22838 window: &mut Window,
22839 cx: &mut App,
22840 ) -> Task<Result<Vec<CodeAction>>>;
22841
22842 fn apply_code_action(
22843 &self,
22844 buffer_handle: Entity<Buffer>,
22845 action: CodeAction,
22846 excerpt_id: ExcerptId,
22847 push_to_history: bool,
22848 window: &mut Window,
22849 cx: &mut App,
22850 ) -> Task<Result<ProjectTransaction>>;
22851}
22852
22853impl CodeActionProvider for Entity<Project> {
22854 fn id(&self) -> Arc<str> {
22855 "project".into()
22856 }
22857
22858 fn code_actions(
22859 &self,
22860 buffer: &Entity<Buffer>,
22861 range: Range<text::Anchor>,
22862 _window: &mut Window,
22863 cx: &mut App,
22864 ) -> Task<Result<Vec<CodeAction>>> {
22865 self.update(cx, |project, cx| {
22866 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22867 let code_actions = project.code_actions(buffer, range, None, cx);
22868 cx.background_spawn(async move {
22869 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22870 Ok(code_lens_actions
22871 .context("code lens fetch")?
22872 .into_iter()
22873 .flatten()
22874 .chain(
22875 code_actions
22876 .context("code action fetch")?
22877 .into_iter()
22878 .flatten(),
22879 )
22880 .collect())
22881 })
22882 })
22883 }
22884
22885 fn apply_code_action(
22886 &self,
22887 buffer_handle: Entity<Buffer>,
22888 action: CodeAction,
22889 _excerpt_id: ExcerptId,
22890 push_to_history: bool,
22891 _window: &mut Window,
22892 cx: &mut App,
22893 ) -> Task<Result<ProjectTransaction>> {
22894 self.update(cx, |project, cx| {
22895 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22896 })
22897 }
22898}
22899
22900fn snippet_completions(
22901 project: &Project,
22902 buffer: &Entity<Buffer>,
22903 buffer_position: text::Anchor,
22904 cx: &mut App,
22905) -> Task<Result<CompletionResponse>> {
22906 let languages = buffer.read(cx).languages_at(buffer_position);
22907 let snippet_store = project.snippets().read(cx);
22908
22909 let scopes: Vec<_> = languages
22910 .iter()
22911 .filter_map(|language| {
22912 let language_name = language.lsp_id();
22913 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22914
22915 if snippets.is_empty() {
22916 None
22917 } else {
22918 Some((language.default_scope(), snippets))
22919 }
22920 })
22921 .collect();
22922
22923 if scopes.is_empty() {
22924 return Task::ready(Ok(CompletionResponse {
22925 completions: vec![],
22926 display_options: CompletionDisplayOptions::default(),
22927 is_incomplete: false,
22928 }));
22929 }
22930
22931 let snapshot = buffer.read(cx).text_snapshot();
22932 let executor = cx.background_executor().clone();
22933
22934 cx.background_spawn(async move {
22935 let mut is_incomplete = false;
22936 let mut completions: Vec<Completion> = Vec::new();
22937 for (scope, snippets) in scopes.into_iter() {
22938 let classifier =
22939 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22940
22941 const MAX_WORD_PREFIX_LEN: usize = 128;
22942 let last_word: String = snapshot
22943 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22944 .take(MAX_WORD_PREFIX_LEN)
22945 .take_while(|c| classifier.is_word(*c))
22946 .collect::<String>()
22947 .chars()
22948 .rev()
22949 .collect();
22950
22951 if last_word.is_empty() {
22952 return Ok(CompletionResponse {
22953 completions: vec![],
22954 display_options: CompletionDisplayOptions::default(),
22955 is_incomplete: true,
22956 });
22957 }
22958
22959 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22960 let to_lsp = |point: &text::Anchor| {
22961 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22962 point_to_lsp(end)
22963 };
22964 let lsp_end = to_lsp(&buffer_position);
22965
22966 let candidates = snippets
22967 .iter()
22968 .enumerate()
22969 .flat_map(|(ix, snippet)| {
22970 snippet
22971 .prefix
22972 .iter()
22973 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22974 })
22975 .collect::<Vec<StringMatchCandidate>>();
22976
22977 const MAX_RESULTS: usize = 100;
22978 let mut matches = fuzzy::match_strings(
22979 &candidates,
22980 &last_word,
22981 last_word.chars().any(|c| c.is_uppercase()),
22982 true,
22983 MAX_RESULTS,
22984 &Default::default(),
22985 executor.clone(),
22986 )
22987 .await;
22988
22989 if matches.len() >= MAX_RESULTS {
22990 is_incomplete = true;
22991 }
22992
22993 // Remove all candidates where the query's start does not match the start of any word in the candidate
22994 if let Some(query_start) = last_word.chars().next() {
22995 matches.retain(|string_match| {
22996 split_words(&string_match.string).any(|word| {
22997 // Check that the first codepoint of the word as lowercase matches the first
22998 // codepoint of the query as lowercase
22999 word.chars()
23000 .flat_map(|codepoint| codepoint.to_lowercase())
23001 .zip(query_start.to_lowercase())
23002 .all(|(word_cp, query_cp)| word_cp == query_cp)
23003 })
23004 });
23005 }
23006
23007 let matched_strings = matches
23008 .into_iter()
23009 .map(|m| m.string)
23010 .collect::<HashSet<_>>();
23011
23012 completions.extend(snippets.iter().filter_map(|snippet| {
23013 let matching_prefix = snippet
23014 .prefix
23015 .iter()
23016 .find(|prefix| matched_strings.contains(*prefix))?;
23017 let start = as_offset - last_word.len();
23018 let start = snapshot.anchor_before(start);
23019 let range = start..buffer_position;
23020 let lsp_start = to_lsp(&start);
23021 let lsp_range = lsp::Range {
23022 start: lsp_start,
23023 end: lsp_end,
23024 };
23025 Some(Completion {
23026 replace_range: range,
23027 new_text: snippet.body.clone(),
23028 source: CompletionSource::Lsp {
23029 insert_range: None,
23030 server_id: LanguageServerId(usize::MAX),
23031 resolved: true,
23032 lsp_completion: Box::new(lsp::CompletionItem {
23033 label: snippet.prefix.first().unwrap().clone(),
23034 kind: Some(CompletionItemKind::SNIPPET),
23035 label_details: snippet.description.as_ref().map(|description| {
23036 lsp::CompletionItemLabelDetails {
23037 detail: Some(description.clone()),
23038 description: None,
23039 }
23040 }),
23041 insert_text_format: Some(InsertTextFormat::SNIPPET),
23042 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23043 lsp::InsertReplaceEdit {
23044 new_text: snippet.body.clone(),
23045 insert: lsp_range,
23046 replace: lsp_range,
23047 },
23048 )),
23049 filter_text: Some(snippet.body.clone()),
23050 sort_text: Some(char::MAX.to_string()),
23051 ..lsp::CompletionItem::default()
23052 }),
23053 lsp_defaults: None,
23054 },
23055 label: CodeLabel::plain(matching_prefix.clone(), None),
23056 icon_path: None,
23057 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23058 single_line: snippet.name.clone().into(),
23059 plain_text: snippet
23060 .description
23061 .clone()
23062 .map(|description| description.into()),
23063 }),
23064 insert_text_mode: None,
23065 confirm: None,
23066 })
23067 }))
23068 }
23069
23070 Ok(CompletionResponse {
23071 completions,
23072 display_options: CompletionDisplayOptions::default(),
23073 is_incomplete,
23074 })
23075 })
23076}
23077
23078impl CompletionProvider for Entity<Project> {
23079 fn completions(
23080 &self,
23081 _excerpt_id: ExcerptId,
23082 buffer: &Entity<Buffer>,
23083 buffer_position: text::Anchor,
23084 options: CompletionContext,
23085 _window: &mut Window,
23086 cx: &mut Context<Editor>,
23087 ) -> Task<Result<Vec<CompletionResponse>>> {
23088 self.update(cx, |project, cx| {
23089 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23090 let project_completions = project.completions(buffer, buffer_position, options, cx);
23091 cx.background_spawn(async move {
23092 let mut responses = project_completions.await?;
23093 let snippets = snippets.await?;
23094 if !snippets.completions.is_empty() {
23095 responses.push(snippets);
23096 }
23097 Ok(responses)
23098 })
23099 })
23100 }
23101
23102 fn resolve_completions(
23103 &self,
23104 buffer: Entity<Buffer>,
23105 completion_indices: Vec<usize>,
23106 completions: Rc<RefCell<Box<[Completion]>>>,
23107 cx: &mut Context<Editor>,
23108 ) -> Task<Result<bool>> {
23109 self.update(cx, |project, cx| {
23110 project.lsp_store().update(cx, |lsp_store, cx| {
23111 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23112 })
23113 })
23114 }
23115
23116 fn apply_additional_edits_for_completion(
23117 &self,
23118 buffer: Entity<Buffer>,
23119 completions: Rc<RefCell<Box<[Completion]>>>,
23120 completion_index: usize,
23121 push_to_history: bool,
23122 cx: &mut Context<Editor>,
23123 ) -> Task<Result<Option<language::Transaction>>> {
23124 self.update(cx, |project, cx| {
23125 project.lsp_store().update(cx, |lsp_store, cx| {
23126 lsp_store.apply_additional_edits_for_completion(
23127 buffer,
23128 completions,
23129 completion_index,
23130 push_to_history,
23131 cx,
23132 )
23133 })
23134 })
23135 }
23136
23137 fn is_completion_trigger(
23138 &self,
23139 buffer: &Entity<Buffer>,
23140 position: language::Anchor,
23141 text: &str,
23142 trigger_in_words: bool,
23143 menu_is_open: bool,
23144 cx: &mut Context<Editor>,
23145 ) -> bool {
23146 let mut chars = text.chars();
23147 let char = if let Some(char) = chars.next() {
23148 char
23149 } else {
23150 return false;
23151 };
23152 if chars.next().is_some() {
23153 return false;
23154 }
23155
23156 let buffer = buffer.read(cx);
23157 let snapshot = buffer.snapshot();
23158 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23159 return false;
23160 }
23161 let classifier = snapshot
23162 .char_classifier_at(position)
23163 .scope_context(Some(CharScopeContext::Completion));
23164 if trigger_in_words && classifier.is_word(char) {
23165 return true;
23166 }
23167
23168 buffer.completion_triggers().contains(text)
23169 }
23170}
23171
23172impl SemanticsProvider for Entity<Project> {
23173 fn hover(
23174 &self,
23175 buffer: &Entity<Buffer>,
23176 position: text::Anchor,
23177 cx: &mut App,
23178 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23179 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23180 }
23181
23182 fn document_highlights(
23183 &self,
23184 buffer: &Entity<Buffer>,
23185 position: text::Anchor,
23186 cx: &mut App,
23187 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23188 Some(self.update(cx, |project, cx| {
23189 project.document_highlights(buffer, position, cx)
23190 }))
23191 }
23192
23193 fn definitions(
23194 &self,
23195 buffer: &Entity<Buffer>,
23196 position: text::Anchor,
23197 kind: GotoDefinitionKind,
23198 cx: &mut App,
23199 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23200 Some(self.update(cx, |project, cx| match kind {
23201 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23202 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23203 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23204 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23205 }))
23206 }
23207
23208 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23209 self.update(cx, |project, cx| {
23210 if project
23211 .active_debug_session(cx)
23212 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23213 {
23214 return true;
23215 }
23216
23217 buffer.update(cx, |buffer, cx| {
23218 project.any_language_server_supports_inlay_hints(buffer, cx)
23219 })
23220 })
23221 }
23222
23223 fn inline_values(
23224 &self,
23225 buffer_handle: Entity<Buffer>,
23226 range: Range<text::Anchor>,
23227 cx: &mut App,
23228 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23229 self.update(cx, |project, cx| {
23230 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23231
23232 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23233 })
23234 }
23235
23236 fn applicable_inlay_chunks(
23237 &self,
23238 buffer: &Entity<Buffer>,
23239 ranges: &[Range<text::Anchor>],
23240 cx: &mut App,
23241 ) -> Vec<Range<BufferRow>> {
23242 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23243 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23244 })
23245 }
23246
23247 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23248 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23249 lsp_store.invalidate_inlay_hints(for_buffers)
23250 });
23251 }
23252
23253 fn inlay_hints(
23254 &self,
23255 invalidate: InvalidationStrategy,
23256 buffer: Entity<Buffer>,
23257 ranges: Vec<Range<text::Anchor>>,
23258 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23259 cx: &mut App,
23260 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23261 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23262 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23263 }))
23264 }
23265
23266 fn range_for_rename(
23267 &self,
23268 buffer: &Entity<Buffer>,
23269 position: text::Anchor,
23270 cx: &mut App,
23271 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23272 Some(self.update(cx, |project, cx| {
23273 let buffer = buffer.clone();
23274 let task = project.prepare_rename(buffer.clone(), position, cx);
23275 cx.spawn(async move |_, cx| {
23276 Ok(match task.await? {
23277 PrepareRenameResponse::Success(range) => Some(range),
23278 PrepareRenameResponse::InvalidPosition => None,
23279 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23280 // Fallback on using TreeSitter info to determine identifier range
23281 buffer.read_with(cx, |buffer, _| {
23282 let snapshot = buffer.snapshot();
23283 let (range, kind) = snapshot.surrounding_word(position, None);
23284 if kind != Some(CharKind::Word) {
23285 return None;
23286 }
23287 Some(
23288 snapshot.anchor_before(range.start)
23289 ..snapshot.anchor_after(range.end),
23290 )
23291 })?
23292 }
23293 })
23294 })
23295 }))
23296 }
23297
23298 fn perform_rename(
23299 &self,
23300 buffer: &Entity<Buffer>,
23301 position: text::Anchor,
23302 new_name: String,
23303 cx: &mut App,
23304 ) -> Option<Task<Result<ProjectTransaction>>> {
23305 Some(self.update(cx, |project, cx| {
23306 project.perform_rename(buffer.clone(), position, new_name, cx)
23307 }))
23308 }
23309}
23310
23311fn consume_contiguous_rows(
23312 contiguous_row_selections: &mut Vec<Selection<Point>>,
23313 selection: &Selection<Point>,
23314 display_map: &DisplaySnapshot,
23315 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23316) -> (MultiBufferRow, MultiBufferRow) {
23317 contiguous_row_selections.push(selection.clone());
23318 let start_row = starting_row(selection, display_map);
23319 let mut end_row = ending_row(selection, display_map);
23320
23321 while let Some(next_selection) = selections.peek() {
23322 if next_selection.start.row <= end_row.0 {
23323 end_row = ending_row(next_selection, display_map);
23324 contiguous_row_selections.push(selections.next().unwrap().clone());
23325 } else {
23326 break;
23327 }
23328 }
23329 (start_row, end_row)
23330}
23331
23332fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23333 if selection.start.column > 0 {
23334 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23335 } else {
23336 MultiBufferRow(selection.start.row)
23337 }
23338}
23339
23340fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23341 if next_selection.end.column > 0 || next_selection.is_empty() {
23342 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23343 } else {
23344 MultiBufferRow(next_selection.end.row)
23345 }
23346}
23347
23348impl EditorSnapshot {
23349 pub fn remote_selections_in_range<'a>(
23350 &'a self,
23351 range: &'a Range<Anchor>,
23352 collaboration_hub: &dyn CollaborationHub,
23353 cx: &'a App,
23354 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23355 let participant_names = collaboration_hub.user_names(cx);
23356 let participant_indices = collaboration_hub.user_participant_indices(cx);
23357 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23358 let collaborators_by_replica_id = collaborators_by_peer_id
23359 .values()
23360 .map(|collaborator| (collaborator.replica_id, collaborator))
23361 .collect::<HashMap<_, _>>();
23362 self.buffer_snapshot()
23363 .selections_in_range(range, false)
23364 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23365 if replica_id == ReplicaId::AGENT {
23366 Some(RemoteSelection {
23367 replica_id,
23368 selection,
23369 cursor_shape,
23370 line_mode,
23371 collaborator_id: CollaboratorId::Agent,
23372 user_name: Some("Agent".into()),
23373 color: cx.theme().players().agent(),
23374 })
23375 } else {
23376 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23377 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23378 let user_name = participant_names.get(&collaborator.user_id).cloned();
23379 Some(RemoteSelection {
23380 replica_id,
23381 selection,
23382 cursor_shape,
23383 line_mode,
23384 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23385 user_name,
23386 color: if let Some(index) = participant_index {
23387 cx.theme().players().color_for_participant(index.0)
23388 } else {
23389 cx.theme().players().absent()
23390 },
23391 })
23392 }
23393 })
23394 }
23395
23396 pub fn hunks_for_ranges(
23397 &self,
23398 ranges: impl IntoIterator<Item = Range<Point>>,
23399 ) -> Vec<MultiBufferDiffHunk> {
23400 let mut hunks = Vec::new();
23401 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23402 HashMap::default();
23403 for query_range in ranges {
23404 let query_rows =
23405 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23406 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23407 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23408 ) {
23409 // Include deleted hunks that are adjacent to the query range, because
23410 // otherwise they would be missed.
23411 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23412 if hunk.status().is_deleted() {
23413 intersects_range |= hunk.row_range.start == query_rows.end;
23414 intersects_range |= hunk.row_range.end == query_rows.start;
23415 }
23416 if intersects_range {
23417 if !processed_buffer_rows
23418 .entry(hunk.buffer_id)
23419 .or_default()
23420 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23421 {
23422 continue;
23423 }
23424 hunks.push(hunk);
23425 }
23426 }
23427 }
23428
23429 hunks
23430 }
23431
23432 fn display_diff_hunks_for_rows<'a>(
23433 &'a self,
23434 display_rows: Range<DisplayRow>,
23435 folded_buffers: &'a HashSet<BufferId>,
23436 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23437 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23438 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23439
23440 self.buffer_snapshot()
23441 .diff_hunks_in_range(buffer_start..buffer_end)
23442 .filter_map(|hunk| {
23443 if folded_buffers.contains(&hunk.buffer_id) {
23444 return None;
23445 }
23446
23447 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23448 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23449
23450 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23451 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23452
23453 let display_hunk = if hunk_display_start.column() != 0 {
23454 DisplayDiffHunk::Folded {
23455 display_row: hunk_display_start.row(),
23456 }
23457 } else {
23458 let mut end_row = hunk_display_end.row();
23459 if hunk_display_end.column() > 0 {
23460 end_row.0 += 1;
23461 }
23462 let is_created_file = hunk.is_created_file();
23463 DisplayDiffHunk::Unfolded {
23464 status: hunk.status(),
23465 diff_base_byte_range: hunk.diff_base_byte_range,
23466 display_row_range: hunk_display_start.row()..end_row,
23467 multi_buffer_range: Anchor::range_in_buffer(
23468 hunk.excerpt_id,
23469 hunk.buffer_id,
23470 hunk.buffer_range,
23471 ),
23472 is_created_file,
23473 }
23474 };
23475
23476 Some(display_hunk)
23477 })
23478 }
23479
23480 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23481 self.display_snapshot
23482 .buffer_snapshot()
23483 .language_at(position)
23484 }
23485
23486 pub fn is_focused(&self) -> bool {
23487 self.is_focused
23488 }
23489
23490 pub fn placeholder_text(&self) -> Option<String> {
23491 self.placeholder_display_snapshot
23492 .as_ref()
23493 .map(|display_map| display_map.text())
23494 }
23495
23496 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23497 self.scroll_anchor.scroll_position(&self.display_snapshot)
23498 }
23499
23500 fn gutter_dimensions(
23501 &self,
23502 font_id: FontId,
23503 font_size: Pixels,
23504 max_line_number_width: Pixels,
23505 cx: &App,
23506 ) -> Option<GutterDimensions> {
23507 if !self.show_gutter {
23508 return None;
23509 }
23510
23511 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23512 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23513
23514 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23515 matches!(
23516 ProjectSettings::get_global(cx).git.git_gutter,
23517 GitGutterSetting::TrackedFiles
23518 )
23519 });
23520 let gutter_settings = EditorSettings::get_global(cx).gutter;
23521 let show_line_numbers = self
23522 .show_line_numbers
23523 .unwrap_or(gutter_settings.line_numbers);
23524 let line_gutter_width = if show_line_numbers {
23525 // Avoid flicker-like gutter resizes when the line number gains another digit by
23526 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23527 let min_width_for_number_on_gutter =
23528 ch_advance * gutter_settings.min_line_number_digits as f32;
23529 max_line_number_width.max(min_width_for_number_on_gutter)
23530 } else {
23531 0.0.into()
23532 };
23533
23534 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23535 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23536
23537 let git_blame_entries_width =
23538 self.git_blame_gutter_max_author_length
23539 .map(|max_author_length| {
23540 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23541 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23542
23543 /// The number of characters to dedicate to gaps and margins.
23544 const SPACING_WIDTH: usize = 4;
23545
23546 let max_char_count = max_author_length.min(renderer.max_author_length())
23547 + ::git::SHORT_SHA_LENGTH
23548 + MAX_RELATIVE_TIMESTAMP.len()
23549 + SPACING_WIDTH;
23550
23551 ch_advance * max_char_count
23552 });
23553
23554 let is_singleton = self.buffer_snapshot().is_singleton();
23555
23556 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23557 left_padding += if !is_singleton {
23558 ch_width * 4.0
23559 } else if show_runnables || show_breakpoints {
23560 ch_width * 3.0
23561 } else if show_git_gutter && show_line_numbers {
23562 ch_width * 2.0
23563 } else if show_git_gutter || show_line_numbers {
23564 ch_width
23565 } else {
23566 px(0.)
23567 };
23568
23569 let shows_folds = is_singleton && gutter_settings.folds;
23570
23571 let right_padding = if shows_folds && show_line_numbers {
23572 ch_width * 4.0
23573 } else if shows_folds || (!is_singleton && show_line_numbers) {
23574 ch_width * 3.0
23575 } else if show_line_numbers {
23576 ch_width
23577 } else {
23578 px(0.)
23579 };
23580
23581 Some(GutterDimensions {
23582 left_padding,
23583 right_padding,
23584 width: line_gutter_width + left_padding + right_padding,
23585 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23586 git_blame_entries_width,
23587 })
23588 }
23589
23590 pub fn render_crease_toggle(
23591 &self,
23592 buffer_row: MultiBufferRow,
23593 row_contains_cursor: bool,
23594 editor: Entity<Editor>,
23595 window: &mut Window,
23596 cx: &mut App,
23597 ) -> Option<AnyElement> {
23598 let folded = self.is_line_folded(buffer_row);
23599 let mut is_foldable = false;
23600
23601 if let Some(crease) = self
23602 .crease_snapshot
23603 .query_row(buffer_row, self.buffer_snapshot())
23604 {
23605 is_foldable = true;
23606 match crease {
23607 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23608 if let Some(render_toggle) = render_toggle {
23609 let toggle_callback =
23610 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23611 if folded {
23612 editor.update(cx, |editor, cx| {
23613 editor.fold_at(buffer_row, window, cx)
23614 });
23615 } else {
23616 editor.update(cx, |editor, cx| {
23617 editor.unfold_at(buffer_row, window, cx)
23618 });
23619 }
23620 });
23621 return Some((render_toggle)(
23622 buffer_row,
23623 folded,
23624 toggle_callback,
23625 window,
23626 cx,
23627 ));
23628 }
23629 }
23630 }
23631 }
23632
23633 is_foldable |= self.starts_indent(buffer_row);
23634
23635 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23636 Some(
23637 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23638 .toggle_state(folded)
23639 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23640 if folded {
23641 this.unfold_at(buffer_row, window, cx);
23642 } else {
23643 this.fold_at(buffer_row, window, cx);
23644 }
23645 }))
23646 .into_any_element(),
23647 )
23648 } else {
23649 None
23650 }
23651 }
23652
23653 pub fn render_crease_trailer(
23654 &self,
23655 buffer_row: MultiBufferRow,
23656 window: &mut Window,
23657 cx: &mut App,
23658 ) -> Option<AnyElement> {
23659 let folded = self.is_line_folded(buffer_row);
23660 if let Crease::Inline { render_trailer, .. } = self
23661 .crease_snapshot
23662 .query_row(buffer_row, self.buffer_snapshot())?
23663 {
23664 let render_trailer = render_trailer.as_ref()?;
23665 Some(render_trailer(buffer_row, folded, window, cx))
23666 } else {
23667 None
23668 }
23669 }
23670}
23671
23672impl Deref for EditorSnapshot {
23673 type Target = DisplaySnapshot;
23674
23675 fn deref(&self) -> &Self::Target {
23676 &self.display_snapshot
23677 }
23678}
23679
23680#[derive(Clone, Debug, PartialEq, Eq)]
23681pub enum EditorEvent {
23682 InputIgnored {
23683 text: Arc<str>,
23684 },
23685 InputHandled {
23686 utf16_range_to_replace: Option<Range<isize>>,
23687 text: Arc<str>,
23688 },
23689 ExcerptsAdded {
23690 buffer: Entity<Buffer>,
23691 predecessor: ExcerptId,
23692 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23693 },
23694 ExcerptsRemoved {
23695 ids: Vec<ExcerptId>,
23696 removed_buffer_ids: Vec<BufferId>,
23697 },
23698 BufferFoldToggled {
23699 ids: Vec<ExcerptId>,
23700 folded: bool,
23701 },
23702 ExcerptsEdited {
23703 ids: Vec<ExcerptId>,
23704 },
23705 ExcerptsExpanded {
23706 ids: Vec<ExcerptId>,
23707 },
23708 BufferEdited,
23709 Edited {
23710 transaction_id: clock::Lamport,
23711 },
23712 Reparsed(BufferId),
23713 Focused,
23714 FocusedIn,
23715 Blurred,
23716 DirtyChanged,
23717 Saved,
23718 TitleChanged,
23719 SelectionsChanged {
23720 local: bool,
23721 },
23722 ScrollPositionChanged {
23723 local: bool,
23724 autoscroll: bool,
23725 },
23726 TransactionUndone {
23727 transaction_id: clock::Lamport,
23728 },
23729 TransactionBegun {
23730 transaction_id: clock::Lamport,
23731 },
23732 CursorShapeChanged,
23733 BreadcrumbsChanged,
23734 PushedToNavHistory {
23735 anchor: Anchor,
23736 is_deactivate: bool,
23737 },
23738}
23739
23740impl EventEmitter<EditorEvent> for Editor {}
23741
23742impl Focusable for Editor {
23743 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23744 self.focus_handle.clone()
23745 }
23746}
23747
23748impl Render for Editor {
23749 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23750 let settings = ThemeSettings::get_global(cx);
23751
23752 let mut text_style = match self.mode {
23753 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23754 color: cx.theme().colors().editor_foreground,
23755 font_family: settings.ui_font.family.clone(),
23756 font_features: settings.ui_font.features.clone(),
23757 font_fallbacks: settings.ui_font.fallbacks.clone(),
23758 font_size: rems(0.875).into(),
23759 font_weight: settings.ui_font.weight,
23760 line_height: relative(settings.buffer_line_height.value()),
23761 ..Default::default()
23762 },
23763 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23764 color: cx.theme().colors().editor_foreground,
23765 font_family: settings.buffer_font.family.clone(),
23766 font_features: settings.buffer_font.features.clone(),
23767 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23768 font_size: settings.buffer_font_size(cx).into(),
23769 font_weight: settings.buffer_font.weight,
23770 line_height: relative(settings.buffer_line_height.value()),
23771 ..Default::default()
23772 },
23773 };
23774 if let Some(text_style_refinement) = &self.text_style_refinement {
23775 text_style.refine(text_style_refinement)
23776 }
23777
23778 let background = match self.mode {
23779 EditorMode::SingleLine => cx.theme().system().transparent,
23780 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23781 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23782 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23783 };
23784
23785 EditorElement::new(
23786 &cx.entity(),
23787 EditorStyle {
23788 background,
23789 border: cx.theme().colors().border,
23790 local_player: cx.theme().players().local(),
23791 text: text_style,
23792 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23793 syntax: cx.theme().syntax().clone(),
23794 status: cx.theme().status().clone(),
23795 inlay_hints_style: make_inlay_hints_style(cx),
23796 edit_prediction_styles: make_suggestion_styles(cx),
23797 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23798 show_underlines: self.diagnostics_enabled(),
23799 },
23800 )
23801 }
23802}
23803
23804impl EntityInputHandler for Editor {
23805 fn text_for_range(
23806 &mut self,
23807 range_utf16: Range<usize>,
23808 adjusted_range: &mut Option<Range<usize>>,
23809 _: &mut Window,
23810 cx: &mut Context<Self>,
23811 ) -> Option<String> {
23812 let snapshot = self.buffer.read(cx).read(cx);
23813 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23814 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23815 if (start.0..end.0) != range_utf16 {
23816 adjusted_range.replace(start.0..end.0);
23817 }
23818 Some(snapshot.text_for_range(start..end).collect())
23819 }
23820
23821 fn selected_text_range(
23822 &mut self,
23823 ignore_disabled_input: bool,
23824 _: &mut Window,
23825 cx: &mut Context<Self>,
23826 ) -> Option<UTF16Selection> {
23827 // Prevent the IME menu from appearing when holding down an alphabetic key
23828 // while input is disabled.
23829 if !ignore_disabled_input && !self.input_enabled {
23830 return None;
23831 }
23832
23833 let selection = self
23834 .selections
23835 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23836 let range = selection.range();
23837
23838 Some(UTF16Selection {
23839 range: range.start.0..range.end.0,
23840 reversed: selection.reversed,
23841 })
23842 }
23843
23844 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23845 let snapshot = self.buffer.read(cx).read(cx);
23846 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23847 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23848 }
23849
23850 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23851 self.clear_highlights::<InputComposition>(cx);
23852 self.ime_transaction.take();
23853 }
23854
23855 fn replace_text_in_range(
23856 &mut self,
23857 range_utf16: Option<Range<usize>>,
23858 text: &str,
23859 window: &mut Window,
23860 cx: &mut Context<Self>,
23861 ) {
23862 if !self.input_enabled {
23863 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23864 return;
23865 }
23866
23867 self.transact(window, cx, |this, window, cx| {
23868 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23869 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23870 Some(this.selection_replacement_ranges(range_utf16, cx))
23871 } else {
23872 this.marked_text_ranges(cx)
23873 };
23874
23875 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23876 let newest_selection_id = this.selections.newest_anchor().id;
23877 this.selections
23878 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23879 .iter()
23880 .zip(ranges_to_replace.iter())
23881 .find_map(|(selection, range)| {
23882 if selection.id == newest_selection_id {
23883 Some(
23884 (range.start.0 as isize - selection.head().0 as isize)
23885 ..(range.end.0 as isize - selection.head().0 as isize),
23886 )
23887 } else {
23888 None
23889 }
23890 })
23891 });
23892
23893 cx.emit(EditorEvent::InputHandled {
23894 utf16_range_to_replace: range_to_replace,
23895 text: text.into(),
23896 });
23897
23898 if let Some(new_selected_ranges) = new_selected_ranges {
23899 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23900 selections.select_ranges(new_selected_ranges)
23901 });
23902 this.backspace(&Default::default(), window, cx);
23903 }
23904
23905 this.handle_input(text, window, cx);
23906 });
23907
23908 if let Some(transaction) = self.ime_transaction {
23909 self.buffer.update(cx, |buffer, cx| {
23910 buffer.group_until_transaction(transaction, cx);
23911 });
23912 }
23913
23914 self.unmark_text(window, cx);
23915 }
23916
23917 fn replace_and_mark_text_in_range(
23918 &mut self,
23919 range_utf16: Option<Range<usize>>,
23920 text: &str,
23921 new_selected_range_utf16: Option<Range<usize>>,
23922 window: &mut Window,
23923 cx: &mut Context<Self>,
23924 ) {
23925 if !self.input_enabled {
23926 return;
23927 }
23928
23929 let transaction = self.transact(window, cx, |this, window, cx| {
23930 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23931 let snapshot = this.buffer.read(cx).read(cx);
23932 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23933 for marked_range in &mut marked_ranges {
23934 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23935 marked_range.start.0 += relative_range_utf16.start;
23936 marked_range.start =
23937 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23938 marked_range.end =
23939 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23940 }
23941 }
23942 Some(marked_ranges)
23943 } else if let Some(range_utf16) = range_utf16 {
23944 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23945 Some(this.selection_replacement_ranges(range_utf16, cx))
23946 } else {
23947 None
23948 };
23949
23950 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23951 let newest_selection_id = this.selections.newest_anchor().id;
23952 this.selections
23953 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23954 .iter()
23955 .zip(ranges_to_replace.iter())
23956 .find_map(|(selection, range)| {
23957 if selection.id == newest_selection_id {
23958 Some(
23959 (range.start.0 as isize - selection.head().0 as isize)
23960 ..(range.end.0 as isize - selection.head().0 as isize),
23961 )
23962 } else {
23963 None
23964 }
23965 })
23966 });
23967
23968 cx.emit(EditorEvent::InputHandled {
23969 utf16_range_to_replace: range_to_replace,
23970 text: text.into(),
23971 });
23972
23973 if let Some(ranges) = ranges_to_replace {
23974 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23975 s.select_ranges(ranges)
23976 });
23977 }
23978
23979 let marked_ranges = {
23980 let snapshot = this.buffer.read(cx).read(cx);
23981 this.selections
23982 .disjoint_anchors_arc()
23983 .iter()
23984 .map(|selection| {
23985 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23986 })
23987 .collect::<Vec<_>>()
23988 };
23989
23990 if text.is_empty() {
23991 this.unmark_text(window, cx);
23992 } else {
23993 this.highlight_text::<InputComposition>(
23994 marked_ranges.clone(),
23995 HighlightStyle {
23996 underline: Some(UnderlineStyle {
23997 thickness: px(1.),
23998 color: None,
23999 wavy: false,
24000 }),
24001 ..Default::default()
24002 },
24003 cx,
24004 );
24005 }
24006
24007 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24008 let use_autoclose = this.use_autoclose;
24009 let use_auto_surround = this.use_auto_surround;
24010 this.set_use_autoclose(false);
24011 this.set_use_auto_surround(false);
24012 this.handle_input(text, window, cx);
24013 this.set_use_autoclose(use_autoclose);
24014 this.set_use_auto_surround(use_auto_surround);
24015
24016 if let Some(new_selected_range) = new_selected_range_utf16 {
24017 let snapshot = this.buffer.read(cx).read(cx);
24018 let new_selected_ranges = marked_ranges
24019 .into_iter()
24020 .map(|marked_range| {
24021 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24022 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24023 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24024 snapshot.clip_offset_utf16(new_start, Bias::Left)
24025 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24026 })
24027 .collect::<Vec<_>>();
24028
24029 drop(snapshot);
24030 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24031 selections.select_ranges(new_selected_ranges)
24032 });
24033 }
24034 });
24035
24036 self.ime_transaction = self.ime_transaction.or(transaction);
24037 if let Some(transaction) = self.ime_transaction {
24038 self.buffer.update(cx, |buffer, cx| {
24039 buffer.group_until_transaction(transaction, cx);
24040 });
24041 }
24042
24043 if self.text_highlights::<InputComposition>(cx).is_none() {
24044 self.ime_transaction.take();
24045 }
24046 }
24047
24048 fn bounds_for_range(
24049 &mut self,
24050 range_utf16: Range<usize>,
24051 element_bounds: gpui::Bounds<Pixels>,
24052 window: &mut Window,
24053 cx: &mut Context<Self>,
24054 ) -> Option<gpui::Bounds<Pixels>> {
24055 let text_layout_details = self.text_layout_details(window);
24056 let CharacterDimensions {
24057 em_width,
24058 em_advance,
24059 line_height,
24060 } = self.character_dimensions(window);
24061
24062 let snapshot = self.snapshot(window, cx);
24063 let scroll_position = snapshot.scroll_position();
24064 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24065
24066 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24067 let x = Pixels::from(
24068 ScrollOffset::from(
24069 snapshot.x_for_display_point(start, &text_layout_details)
24070 + self.gutter_dimensions.full_width(),
24071 ) - scroll_left,
24072 );
24073 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24074
24075 Some(Bounds {
24076 origin: element_bounds.origin + point(x, y),
24077 size: size(em_width, line_height),
24078 })
24079 }
24080
24081 fn character_index_for_point(
24082 &mut self,
24083 point: gpui::Point<Pixels>,
24084 _window: &mut Window,
24085 _cx: &mut Context<Self>,
24086 ) -> Option<usize> {
24087 let position_map = self.last_position_map.as_ref()?;
24088 if !position_map.text_hitbox.contains(&point) {
24089 return None;
24090 }
24091 let display_point = position_map.point_for_position(point).previous_valid;
24092 let anchor = position_map
24093 .snapshot
24094 .display_point_to_anchor(display_point, Bias::Left);
24095 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24096 Some(utf16_offset.0)
24097 }
24098}
24099
24100trait SelectionExt {
24101 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24102 fn spanned_rows(
24103 &self,
24104 include_end_if_at_line_start: bool,
24105 map: &DisplaySnapshot,
24106 ) -> Range<MultiBufferRow>;
24107}
24108
24109impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24110 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24111 let start = self
24112 .start
24113 .to_point(map.buffer_snapshot())
24114 .to_display_point(map);
24115 let end = self
24116 .end
24117 .to_point(map.buffer_snapshot())
24118 .to_display_point(map);
24119 if self.reversed {
24120 end..start
24121 } else {
24122 start..end
24123 }
24124 }
24125
24126 fn spanned_rows(
24127 &self,
24128 include_end_if_at_line_start: bool,
24129 map: &DisplaySnapshot,
24130 ) -> Range<MultiBufferRow> {
24131 let start = self.start.to_point(map.buffer_snapshot());
24132 let mut end = self.end.to_point(map.buffer_snapshot());
24133 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24134 end.row -= 1;
24135 }
24136
24137 let buffer_start = map.prev_line_boundary(start).0;
24138 let buffer_end = map.next_line_boundary(end).0;
24139 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24140 }
24141}
24142
24143impl<T: InvalidationRegion> InvalidationStack<T> {
24144 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24145 where
24146 S: Clone + ToOffset,
24147 {
24148 while let Some(region) = self.last() {
24149 let all_selections_inside_invalidation_ranges =
24150 if selections.len() == region.ranges().len() {
24151 selections
24152 .iter()
24153 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24154 .all(|(selection, invalidation_range)| {
24155 let head = selection.head().to_offset(buffer);
24156 invalidation_range.start <= head && invalidation_range.end >= head
24157 })
24158 } else {
24159 false
24160 };
24161
24162 if all_selections_inside_invalidation_ranges {
24163 break;
24164 } else {
24165 self.pop();
24166 }
24167 }
24168 }
24169}
24170
24171impl<T> Default for InvalidationStack<T> {
24172 fn default() -> Self {
24173 Self(Default::default())
24174 }
24175}
24176
24177impl<T> Deref for InvalidationStack<T> {
24178 type Target = Vec<T>;
24179
24180 fn deref(&self) -> &Self::Target {
24181 &self.0
24182 }
24183}
24184
24185impl<T> DerefMut for InvalidationStack<T> {
24186 fn deref_mut(&mut self) -> &mut Self::Target {
24187 &mut self.0
24188 }
24189}
24190
24191impl InvalidationRegion for SnippetState {
24192 fn ranges(&self) -> &[Range<Anchor>] {
24193 &self.ranges[self.active_index]
24194 }
24195}
24196
24197fn edit_prediction_edit_text(
24198 current_snapshot: &BufferSnapshot,
24199 edits: &[(Range<Anchor>, String)],
24200 edit_preview: &EditPreview,
24201 include_deletions: bool,
24202 cx: &App,
24203) -> HighlightedText {
24204 let edits = edits
24205 .iter()
24206 .map(|(anchor, text)| {
24207 (
24208 anchor.start.text_anchor..anchor.end.text_anchor,
24209 text.clone(),
24210 )
24211 })
24212 .collect::<Vec<_>>();
24213
24214 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24215}
24216
24217fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24218 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24219 // Just show the raw edit text with basic styling
24220 let mut text = String::new();
24221 let mut highlights = Vec::new();
24222
24223 let insertion_highlight_style = HighlightStyle {
24224 color: Some(cx.theme().colors().text),
24225 ..Default::default()
24226 };
24227
24228 for (_, edit_text) in edits {
24229 let start_offset = text.len();
24230 text.push_str(edit_text);
24231 let end_offset = text.len();
24232
24233 if start_offset < end_offset {
24234 highlights.push((start_offset..end_offset, insertion_highlight_style));
24235 }
24236 }
24237
24238 HighlightedText {
24239 text: text.into(),
24240 highlights,
24241 }
24242}
24243
24244pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24245 match severity {
24246 lsp::DiagnosticSeverity::ERROR => colors.error,
24247 lsp::DiagnosticSeverity::WARNING => colors.warning,
24248 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24249 lsp::DiagnosticSeverity::HINT => colors.info,
24250 _ => colors.ignored,
24251 }
24252}
24253
24254pub fn styled_runs_for_code_label<'a>(
24255 label: &'a CodeLabel,
24256 syntax_theme: &'a theme::SyntaxTheme,
24257) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24258 let fade_out = HighlightStyle {
24259 fade_out: Some(0.35),
24260 ..Default::default()
24261 };
24262
24263 let mut prev_end = label.filter_range.end;
24264 label
24265 .runs
24266 .iter()
24267 .enumerate()
24268 .flat_map(move |(ix, (range, highlight_id))| {
24269 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24270 style
24271 } else {
24272 return Default::default();
24273 };
24274 let muted_style = style.highlight(fade_out);
24275
24276 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24277 if range.start >= label.filter_range.end {
24278 if range.start > prev_end {
24279 runs.push((prev_end..range.start, fade_out));
24280 }
24281 runs.push((range.clone(), muted_style));
24282 } else if range.end <= label.filter_range.end {
24283 runs.push((range.clone(), style));
24284 } else {
24285 runs.push((range.start..label.filter_range.end, style));
24286 runs.push((label.filter_range.end..range.end, muted_style));
24287 }
24288 prev_end = cmp::max(prev_end, range.end);
24289
24290 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24291 runs.push((prev_end..label.text.len(), fade_out));
24292 }
24293
24294 runs
24295 })
24296}
24297
24298pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24299 let mut prev_index = 0;
24300 let mut prev_codepoint: Option<char> = None;
24301 text.char_indices()
24302 .chain([(text.len(), '\0')])
24303 .filter_map(move |(index, codepoint)| {
24304 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24305 let is_boundary = index == text.len()
24306 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24307 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24308 if is_boundary {
24309 let chunk = &text[prev_index..index];
24310 prev_index = index;
24311 Some(chunk)
24312 } else {
24313 None
24314 }
24315 })
24316}
24317
24318pub trait RangeToAnchorExt: Sized {
24319 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24320
24321 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24322 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24323 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24324 }
24325}
24326
24327impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24328 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24329 let start_offset = self.start.to_offset(snapshot);
24330 let end_offset = self.end.to_offset(snapshot);
24331 if start_offset == end_offset {
24332 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24333 } else {
24334 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24335 }
24336 }
24337}
24338
24339pub trait RowExt {
24340 fn as_f64(&self) -> f64;
24341
24342 fn next_row(&self) -> Self;
24343
24344 fn previous_row(&self) -> Self;
24345
24346 fn minus(&self, other: Self) -> u32;
24347}
24348
24349impl RowExt for DisplayRow {
24350 fn as_f64(&self) -> f64 {
24351 self.0 as _
24352 }
24353
24354 fn next_row(&self) -> Self {
24355 Self(self.0 + 1)
24356 }
24357
24358 fn previous_row(&self) -> Self {
24359 Self(self.0.saturating_sub(1))
24360 }
24361
24362 fn minus(&self, other: Self) -> u32 {
24363 self.0 - other.0
24364 }
24365}
24366
24367impl RowExt for MultiBufferRow {
24368 fn as_f64(&self) -> f64 {
24369 self.0 as _
24370 }
24371
24372 fn next_row(&self) -> Self {
24373 Self(self.0 + 1)
24374 }
24375
24376 fn previous_row(&self) -> Self {
24377 Self(self.0.saturating_sub(1))
24378 }
24379
24380 fn minus(&self, other: Self) -> u32 {
24381 self.0 - other.0
24382 }
24383}
24384
24385trait RowRangeExt {
24386 type Row;
24387
24388 fn len(&self) -> usize;
24389
24390 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24391}
24392
24393impl RowRangeExt for Range<MultiBufferRow> {
24394 type Row = MultiBufferRow;
24395
24396 fn len(&self) -> usize {
24397 (self.end.0 - self.start.0) as usize
24398 }
24399
24400 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24401 (self.start.0..self.end.0).map(MultiBufferRow)
24402 }
24403}
24404
24405impl RowRangeExt for Range<DisplayRow> {
24406 type Row = DisplayRow;
24407
24408 fn len(&self) -> usize {
24409 (self.end.0 - self.start.0) as usize
24410 }
24411
24412 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24413 (self.start.0..self.end.0).map(DisplayRow)
24414 }
24415}
24416
24417/// If select range has more than one line, we
24418/// just point the cursor to range.start.
24419fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24420 if range.start.row == range.end.row {
24421 range
24422 } else {
24423 range.start..range.start
24424 }
24425}
24426pub struct KillRing(ClipboardItem);
24427impl Global for KillRing {}
24428
24429const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24430
24431enum BreakpointPromptEditAction {
24432 Log,
24433 Condition,
24434 HitCondition,
24435}
24436
24437struct BreakpointPromptEditor {
24438 pub(crate) prompt: Entity<Editor>,
24439 editor: WeakEntity<Editor>,
24440 breakpoint_anchor: Anchor,
24441 breakpoint: Breakpoint,
24442 edit_action: BreakpointPromptEditAction,
24443 block_ids: HashSet<CustomBlockId>,
24444 editor_margins: Arc<Mutex<EditorMargins>>,
24445 _subscriptions: Vec<Subscription>,
24446}
24447
24448impl BreakpointPromptEditor {
24449 const MAX_LINES: u8 = 4;
24450
24451 fn new(
24452 editor: WeakEntity<Editor>,
24453 breakpoint_anchor: Anchor,
24454 breakpoint: Breakpoint,
24455 edit_action: BreakpointPromptEditAction,
24456 window: &mut Window,
24457 cx: &mut Context<Self>,
24458 ) -> Self {
24459 let base_text = match edit_action {
24460 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24461 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24462 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24463 }
24464 .map(|msg| msg.to_string())
24465 .unwrap_or_default();
24466
24467 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24468 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24469
24470 let prompt = cx.new(|cx| {
24471 let mut prompt = Editor::new(
24472 EditorMode::AutoHeight {
24473 min_lines: 1,
24474 max_lines: Some(Self::MAX_LINES as usize),
24475 },
24476 buffer,
24477 None,
24478 window,
24479 cx,
24480 );
24481 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24482 prompt.set_show_cursor_when_unfocused(false, cx);
24483 prompt.set_placeholder_text(
24484 match edit_action {
24485 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24486 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24487 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24488 },
24489 window,
24490 cx,
24491 );
24492
24493 prompt
24494 });
24495
24496 Self {
24497 prompt,
24498 editor,
24499 breakpoint_anchor,
24500 breakpoint,
24501 edit_action,
24502 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24503 block_ids: Default::default(),
24504 _subscriptions: vec![],
24505 }
24506 }
24507
24508 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24509 self.block_ids.extend(block_ids)
24510 }
24511
24512 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24513 if let Some(editor) = self.editor.upgrade() {
24514 let message = self
24515 .prompt
24516 .read(cx)
24517 .buffer
24518 .read(cx)
24519 .as_singleton()
24520 .expect("A multi buffer in breakpoint prompt isn't possible")
24521 .read(cx)
24522 .as_rope()
24523 .to_string();
24524
24525 editor.update(cx, |editor, cx| {
24526 editor.edit_breakpoint_at_anchor(
24527 self.breakpoint_anchor,
24528 self.breakpoint.clone(),
24529 match self.edit_action {
24530 BreakpointPromptEditAction::Log => {
24531 BreakpointEditAction::EditLogMessage(message.into())
24532 }
24533 BreakpointPromptEditAction::Condition => {
24534 BreakpointEditAction::EditCondition(message.into())
24535 }
24536 BreakpointPromptEditAction::HitCondition => {
24537 BreakpointEditAction::EditHitCondition(message.into())
24538 }
24539 },
24540 cx,
24541 );
24542
24543 editor.remove_blocks(self.block_ids.clone(), None, cx);
24544 cx.focus_self(window);
24545 });
24546 }
24547 }
24548
24549 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24550 self.editor
24551 .update(cx, |editor, cx| {
24552 editor.remove_blocks(self.block_ids.clone(), None, cx);
24553 window.focus(&editor.focus_handle);
24554 })
24555 .log_err();
24556 }
24557
24558 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24559 let settings = ThemeSettings::get_global(cx);
24560 let text_style = TextStyle {
24561 color: if self.prompt.read(cx).read_only(cx) {
24562 cx.theme().colors().text_disabled
24563 } else {
24564 cx.theme().colors().text
24565 },
24566 font_family: settings.buffer_font.family.clone(),
24567 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24568 font_size: settings.buffer_font_size(cx).into(),
24569 font_weight: settings.buffer_font.weight,
24570 line_height: relative(settings.buffer_line_height.value()),
24571 ..Default::default()
24572 };
24573 EditorElement::new(
24574 &self.prompt,
24575 EditorStyle {
24576 background: cx.theme().colors().editor_background,
24577 local_player: cx.theme().players().local(),
24578 text: text_style,
24579 ..Default::default()
24580 },
24581 )
24582 }
24583}
24584
24585impl Render for BreakpointPromptEditor {
24586 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24587 let editor_margins = *self.editor_margins.lock();
24588 let gutter_dimensions = editor_margins.gutter;
24589 h_flex()
24590 .key_context("Editor")
24591 .bg(cx.theme().colors().editor_background)
24592 .border_y_1()
24593 .border_color(cx.theme().status().info_border)
24594 .size_full()
24595 .py(window.line_height() / 2.5)
24596 .on_action(cx.listener(Self::confirm))
24597 .on_action(cx.listener(Self::cancel))
24598 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24599 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24600 }
24601}
24602
24603impl Focusable for BreakpointPromptEditor {
24604 fn focus_handle(&self, cx: &App) -> FocusHandle {
24605 self.prompt.focus_handle(cx)
24606 }
24607}
24608
24609fn all_edits_insertions_or_deletions(
24610 edits: &Vec<(Range<Anchor>, String)>,
24611 snapshot: &MultiBufferSnapshot,
24612) -> bool {
24613 let mut all_insertions = true;
24614 let mut all_deletions = true;
24615
24616 for (range, new_text) in edits.iter() {
24617 let range_is_empty = range.to_offset(snapshot).is_empty();
24618 let text_is_empty = new_text.is_empty();
24619
24620 if range_is_empty != text_is_empty {
24621 if range_is_empty {
24622 all_deletions = false;
24623 } else {
24624 all_insertions = false;
24625 }
24626 } else {
24627 return false;
24628 }
24629
24630 if !all_insertions && !all_deletions {
24631 return false;
24632 }
24633 }
24634 all_insertions || all_deletions
24635}
24636
24637struct MissingEditPredictionKeybindingTooltip;
24638
24639impl Render for MissingEditPredictionKeybindingTooltip {
24640 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24641 ui::tooltip_container(cx, |container, cx| {
24642 container
24643 .flex_shrink_0()
24644 .max_w_80()
24645 .min_h(rems_from_px(124.))
24646 .justify_between()
24647 .child(
24648 v_flex()
24649 .flex_1()
24650 .text_ui_sm(cx)
24651 .child(Label::new("Conflict with Accept Keybinding"))
24652 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24653 )
24654 .child(
24655 h_flex()
24656 .pb_1()
24657 .gap_1()
24658 .items_end()
24659 .w_full()
24660 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24661 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24662 }))
24663 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24664 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24665 })),
24666 )
24667 })
24668 }
24669}
24670
24671#[derive(Debug, Clone, Copy, PartialEq)]
24672pub struct LineHighlight {
24673 pub background: Background,
24674 pub border: Option<gpui::Hsla>,
24675 pub include_gutter: bool,
24676 pub type_id: Option<TypeId>,
24677}
24678
24679struct LineManipulationResult {
24680 pub new_text: String,
24681 pub line_count_before: usize,
24682 pub line_count_after: usize,
24683}
24684
24685fn render_diff_hunk_controls(
24686 row: u32,
24687 status: &DiffHunkStatus,
24688 hunk_range: Range<Anchor>,
24689 is_created_file: bool,
24690 line_height: Pixels,
24691 editor: &Entity<Editor>,
24692 _window: &mut Window,
24693 cx: &mut App,
24694) -> AnyElement {
24695 h_flex()
24696 .h(line_height)
24697 .mr_1()
24698 .gap_1()
24699 .px_0p5()
24700 .pb_1()
24701 .border_x_1()
24702 .border_b_1()
24703 .border_color(cx.theme().colors().border_variant)
24704 .rounded_b_lg()
24705 .bg(cx.theme().colors().editor_background)
24706 .gap_1()
24707 .block_mouse_except_scroll()
24708 .shadow_md()
24709 .child(if status.has_secondary_hunk() {
24710 Button::new(("stage", row as u64), "Stage")
24711 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24712 .tooltip({
24713 let focus_handle = editor.focus_handle(cx);
24714 move |_window, cx| {
24715 Tooltip::for_action_in(
24716 "Stage Hunk",
24717 &::git::ToggleStaged,
24718 &focus_handle,
24719 cx,
24720 )
24721 }
24722 })
24723 .on_click({
24724 let editor = editor.clone();
24725 move |_event, _window, cx| {
24726 editor.update(cx, |editor, cx| {
24727 editor.stage_or_unstage_diff_hunks(
24728 true,
24729 vec![hunk_range.start..hunk_range.start],
24730 cx,
24731 );
24732 });
24733 }
24734 })
24735 } else {
24736 Button::new(("unstage", row as u64), "Unstage")
24737 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24738 .tooltip({
24739 let focus_handle = editor.focus_handle(cx);
24740 move |_window, cx| {
24741 Tooltip::for_action_in(
24742 "Unstage Hunk",
24743 &::git::ToggleStaged,
24744 &focus_handle,
24745 cx,
24746 )
24747 }
24748 })
24749 .on_click({
24750 let editor = editor.clone();
24751 move |_event, _window, cx| {
24752 editor.update(cx, |editor, cx| {
24753 editor.stage_or_unstage_diff_hunks(
24754 false,
24755 vec![hunk_range.start..hunk_range.start],
24756 cx,
24757 );
24758 });
24759 }
24760 })
24761 })
24762 .child(
24763 Button::new(("restore", row as u64), "Restore")
24764 .tooltip({
24765 let focus_handle = editor.focus_handle(cx);
24766 move |_window, cx| {
24767 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24768 }
24769 })
24770 .on_click({
24771 let editor = editor.clone();
24772 move |_event, window, cx| {
24773 editor.update(cx, |editor, cx| {
24774 let snapshot = editor.snapshot(window, cx);
24775 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24776 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24777 });
24778 }
24779 })
24780 .disabled(is_created_file),
24781 )
24782 .when(
24783 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24784 |el| {
24785 el.child(
24786 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24787 .shape(IconButtonShape::Square)
24788 .icon_size(IconSize::Small)
24789 // .disabled(!has_multiple_hunks)
24790 .tooltip({
24791 let focus_handle = editor.focus_handle(cx);
24792 move |_window, cx| {
24793 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24794 }
24795 })
24796 .on_click({
24797 let editor = editor.clone();
24798 move |_event, window, cx| {
24799 editor.update(cx, |editor, cx| {
24800 let snapshot = editor.snapshot(window, cx);
24801 let position =
24802 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24803 editor.go_to_hunk_before_or_after_position(
24804 &snapshot,
24805 position,
24806 Direction::Next,
24807 window,
24808 cx,
24809 );
24810 editor.expand_selected_diff_hunks(cx);
24811 });
24812 }
24813 }),
24814 )
24815 .child(
24816 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24817 .shape(IconButtonShape::Square)
24818 .icon_size(IconSize::Small)
24819 // .disabled(!has_multiple_hunks)
24820 .tooltip({
24821 let focus_handle = editor.focus_handle(cx);
24822 move |_window, cx| {
24823 Tooltip::for_action_in(
24824 "Previous Hunk",
24825 &GoToPreviousHunk,
24826 &focus_handle,
24827 cx,
24828 )
24829 }
24830 })
24831 .on_click({
24832 let editor = editor.clone();
24833 move |_event, window, cx| {
24834 editor.update(cx, |editor, cx| {
24835 let snapshot = editor.snapshot(window, cx);
24836 let point =
24837 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24838 editor.go_to_hunk_before_or_after_position(
24839 &snapshot,
24840 point,
24841 Direction::Prev,
24842 window,
24843 cx,
24844 );
24845 editor.expand_selected_diff_hunks(cx);
24846 });
24847 }
24848 }),
24849 )
24850 },
24851 )
24852 .into_any_element()
24853}
24854
24855pub fn multibuffer_context_lines(cx: &App) -> u32 {
24856 EditorSettings::try_get(cx)
24857 .map(|settings| settings.excerpt_context_lines)
24858 .unwrap_or(2)
24859 .min(32)
24860}